chore: fix lint:format skipping assets and tests (#32662)

This commit is contained in:
Michael Geers 2026-08-09 13:01:19 +02:00 • committed by GitHub
parent 99a9c88948
commit 28e41bdd09
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 52 additions and 51 deletions

View file

@ -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",

View file

@ -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">

View file

@ -215,5 +215,4 @@ export default {
.w-min-200 {
min-width: min(200px, 100%);
}
</style>

View file

@ -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();

View file

@ -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",

View file

@ -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();

View file

@ -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 }) => {