chore: fix lint:format skipping assets and tests (#32662)
This commit is contained in:
parent
99a9c88948
commit
28e41bdd09
7 changed files with 52 additions and 51 deletions
|
|
@ -52,14 +52,7 @@ import BatteryIcon from "../Energyflow/BatteryIcon.vue";
|
|||
import Item from "./Item.vue";
|
||||
import MoreItem from "./MoreItem.vue";
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import type {
|
||||
FatalError,
|
||||
Sponsor,
|
||||
EvOpt,
|
||||
AuthProviders,
|
||||
Battery,
|
||||
Vehicle,
|
||||
} from "@/types/evcc";
|
||||
import type { FatalError, Sponsor, EvOpt, AuthProviders, Battery, Vehicle } from "@/types/evcc";
|
||||
|
||||
export default defineComponent({
|
||||
name: "BottomTabBar",
|
||||
|
|
|
|||
|
|
@ -31,7 +31,10 @@
|
|||
<div v-else class="row">
|
||||
<div class="col-12 col-lg-6 mb-3">
|
||||
<div class="row">
|
||||
<label :for="formId('continuous')" class="col-form-label col-5 col-lg-12">
|
||||
<label
|
||||
:for="formId('continuous')"
|
||||
class="col-form-label col-5 col-lg-12"
|
||||
>
|
||||
{{ $t("main.chargingPlan.optimization.label") }}
|
||||
</label>
|
||||
<div class="col-7 col-lg-12">
|
||||
|
|
@ -56,7 +59,10 @@
|
|||
</div>
|
||||
<div class="col-12 col-lg-6 mb-3">
|
||||
<div class="row">
|
||||
<label :for="formId('precondition')" class="col-form-label col-5 col-lg-12">
|
||||
<label
|
||||
:for="formId('precondition')"
|
||||
class="col-form-label col-5 col-lg-12"
|
||||
>
|
||||
{{ $t("main.chargingPlan.precondition.label") }}
|
||||
</label>
|
||||
<div class="col-7 col-lg-12">
|
||||
|
|
|
|||
|
|
@ -215,5 +215,4 @@ export default {
|
|||
.w-min-200 {
|
||||
min-width: min(200px, 100%);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -11,16 +11,16 @@ const __dirname = dirname(__filename);
|
|||
function validateFilePath(filePath: string): string {
|
||||
const resolvedPath = resolve(filePath);
|
||||
const resolvedBaseDir = resolve(__dirname);
|
||||
|
||||
|
||||
if (!resolvedPath.startsWith(resolvedBaseDir)) {
|
||||
throw new Error(`Path traversal attempt detected: ${filePath}`);
|
||||
}
|
||||
|
||||
|
||||
// Ensure it's a JSON file
|
||||
if (extname(resolvedPath) !== ".json") {
|
||||
throw new Error(`Invalid file type: ${filePath}`);
|
||||
}
|
||||
|
||||
|
||||
return resolvedPath;
|
||||
}
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ function validateFilePath(filePath: string): string {
|
|||
function extractPlaceholders(text: string): string[] {
|
||||
const matches = text.match(/\{[^}]+\}/g);
|
||||
if (!matches) return [];
|
||||
|
||||
|
||||
// Remove duplicates and sort
|
||||
const unique = [...new Set(matches)];
|
||||
return unique.sort();
|
||||
|
|
@ -36,15 +36,18 @@ function extractPlaceholders(text: string): string[] {
|
|||
|
||||
// Flatten nested translation object into dot-notation keys
|
||||
function flattenObject(obj: any, prefix = ""): Record<string, string> {
|
||||
return Object.entries(obj).reduce((acc, [key, val]) => {
|
||||
const path = prefix ? `${prefix}.${key}` : key;
|
||||
if (val && typeof val === "object") {
|
||||
Object.assign(acc, flattenObject(val, path));
|
||||
} else if (typeof val === "string") {
|
||||
acc[path] = val;
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, string>);
|
||||
return Object.entries(obj).reduce(
|
||||
(acc, [key, val]) => {
|
||||
const path = prefix ? `${prefix}.${key}` : key;
|
||||
if (val && typeof val === "object") {
|
||||
Object.assign(acc, flattenObject(val, path));
|
||||
} else if (typeof val === "string") {
|
||||
acc[path] = val;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string>
|
||||
);
|
||||
}
|
||||
|
||||
// Compare placeholder arrays using Set equality for better performance
|
||||
|
|
@ -69,63 +72,62 @@ function loadTranslationFile(filePath: string): Record<string, string> {
|
|||
// Get all translation files in the directory safely
|
||||
function getTranslationFiles(): string[] {
|
||||
const files = readdirSync(__dirname)
|
||||
.filter(file => {
|
||||
.filter((file) => {
|
||||
// Only allow valid filename characters and JSON extension
|
||||
return /^[a-zA-Z0-9_-]+\.json$/.test(file);
|
||||
})
|
||||
.map(file => validateFilePath(join(__dirname, file)));
|
||||
|
||||
.map((file) => validateFilePath(join(__dirname, file)));
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
// Main validation function
|
||||
function validateTranslations(): void {
|
||||
console.log("🔍 Checking translation placeholder consistency...");
|
||||
|
||||
|
||||
const sourceFile = validateFilePath(join(__dirname, "en.json"));
|
||||
|
||||
|
||||
if (!existsSync(sourceFile)) {
|
||||
console.error(`❌ Source file not found: ${sourceFile}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
const sourceFlat = loadTranslationFile(sourceFile);
|
||||
const translationFiles = getTranslationFiles()
|
||||
.filter(file => basename(file) !== "en.json");
|
||||
|
||||
const translationFiles = getTranslationFiles().filter((file) => basename(file) !== "en.json");
|
||||
|
||||
if (translationFiles.length === 0) {
|
||||
console.log("ℹ️ No translation files to check.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const allErrors: Array<{file: string; key: string; expected: string[]; found: string[]}> = [];
|
||||
|
||||
|
||||
const allErrors: Array<{ file: string; key: string; expected: string[]; found: string[] }> = [];
|
||||
|
||||
for (const file of translationFiles) {
|
||||
const lang = basename(file, ".json");
|
||||
const targetFlat = loadTranslationFile(file);
|
||||
|
||||
|
||||
for (const [key, sourceStr] of Object.entries(sourceFlat)) {
|
||||
const sourcePlaceholders = extractPlaceholders(sourceStr);
|
||||
|
||||
|
||||
// Only check if source has placeholders
|
||||
if (sourcePlaceholders.length === 0) continue;
|
||||
|
||||
|
||||
// Skip if translation doesn't exist at all (fallback to English is fine)
|
||||
if (!(key in targetFlat)) continue;
|
||||
|
||||
|
||||
const targetPlaceholders = extractPlaceholders(targetFlat[key]);
|
||||
|
||||
|
||||
if (!placeholdersMatch(sourcePlaceholders, targetPlaceholders)) {
|
||||
allErrors.push({
|
||||
file: lang,
|
||||
key,
|
||||
expected: sourcePlaceholders,
|
||||
found: targetPlaceholders
|
||||
found: targetPlaceholders,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Group errors by file and output
|
||||
const errorsByFile = new Map<string, typeof allErrors>();
|
||||
for (const error of allErrors) {
|
||||
|
|
@ -134,7 +136,7 @@ function validateTranslations(): void {
|
|||
}
|
||||
errorsByFile.get(error.file)!.push(error);
|
||||
}
|
||||
|
||||
|
||||
for (const [lang, errors] of errorsByFile) {
|
||||
console.log(`\n📄 i18n/${lang}.json (${errors.length} errors)`);
|
||||
for (const error of errors) {
|
||||
|
|
@ -143,7 +145,7 @@ function validateTranslations(): void {
|
|||
console.log(` found: ${error.found.join(", ") || "(none)"}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (allErrors.length > 0) {
|
||||
console.log(`\n💡 ${allErrors.length} placeholder errors found.`);
|
||||
process.exit(1);
|
||||
|
|
@ -154,4 +156,4 @@ function validateTranslations(): void {
|
|||
}
|
||||
|
||||
// Run the validation
|
||||
validateTranslations();
|
||||
validateTranslations();
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
"build": "vp build",
|
||||
"test": "cross-env TZ=Europe/Berlin NODE_OPTIONS=--no-experimental-webstorage vp test",
|
||||
"lint": "vp run lint:format && vp run lint:lint && vp run lint:tsc && vp run lint:i18n",
|
||||
"lint:format": "vp fmt assets tests **/*.{yaml,sh} i18n/*.json --write",
|
||||
"lint:format": "vp fmt assets tests i18n --write && vp fmt '**/*.{yaml,sh}' --write",
|
||||
"lint:lint": "vp lint --fix --max-warnings=0",
|
||||
"lint:tsc": "vue-tsc --noEmit",
|
||||
"lint:i18n": "tsx i18n/check.ts",
|
||||
|
|
|
|||
|
|
@ -127,7 +127,10 @@ maxconsumptionpower:
|
|||
await expectModalHidden(hemsModal);
|
||||
|
||||
// enable experimental
|
||||
await page.getByTestId("generalconfig-experimental").getByRole("button", { name: "edit" }).click();
|
||||
await page
|
||||
.getByTestId("generalconfig-experimental")
|
||||
.getByRole("button", { name: "edit" })
|
||||
.click();
|
||||
const experimentalModal = page.getByTestId("experimental-modal");
|
||||
await expectModalVisible(experimentalModal);
|
||||
await experimentalModal.getByLabel("Enable experimental features.").click();
|
||||
|
|
|
|||
|
|
@ -725,9 +725,7 @@ test.describe("plan strategy", async () => {
|
|||
await expect(modal.getByTestId("plan-strategy")).toContainText("cleanest slots");
|
||||
|
||||
await optimization.selectOption("continuous");
|
||||
await expect(modal.getByTestId("plan-strategy")).toContainText(
|
||||
"cleanest uninterrupted series"
|
||||
);
|
||||
await expect(modal.getByTestId("plan-strategy")).toContainText("cleanest uninterrupted series");
|
||||
});
|
||||
|
||||
test("visible and functional on mobile", async ({ page }) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue