From 826936c6a4a7160b07224f0fa94b6d2d941bc6dc Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Mon, 24 Aug 2026 11:21:24 +0300 Subject: [PATCH] fix: harden frontend dependency and env hygiene --- .env | 1 - .gitignore | 4 +++ package-lock.json | 8 ++--- package.json | 5 ++-- scripts/repository-hygiene-check.mjs | 44 ++++++++++++++++++++++++++++ 5 files changed, 55 insertions(+), 7 deletions(-) delete mode 100644 .env create mode 100644 scripts/repository-hygiene-check.mjs diff --git a/.env b/.env deleted file mode 100644 index 99cbcd5..0000000 --- a/.env +++ /dev/null @@ -1 +0,0 @@ -VITE_API_BASE_URL=http://localhost:3000 diff --git a/.gitignore b/.gitignore index 785a308..49a7066 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,10 @@ dist-ssr coverage *.local +# Local runtime configuration +.env* +!.env.example + # Editor directories and files .vscode/* !.vscode/extensions.json diff --git a/package-lock.json b/package-lock.json index eb88005..d106d1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "react-dom": "19.2.8", "react-hook-form": "7.85.0", "react-i18next": "16.3.5", - "react-router": "7.9.6", + "react-router": "7.18.2", "zod": "3.25.76" }, "devDependencies": { @@ -5816,9 +5816,9 @@ } }, "node_modules/react-router": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.6.tgz", - "integrity": "sha512-Y1tUp8clYRXpfPITyuifmSoE2vncSME18uVLgaqyxh9H35JWpIfzHo+9y3Fzh5odk/jxPW29IgLgzcdwxGqyNA==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.2.tgz", + "integrity": "sha512-aUVMjFm3GAPTTZL7oYr5E7ETiqfQCHRLH+B+5afnICvf0r7kkK4eR6SMuwbSTJw/7t+12khT/Kahij49fqOCIg==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", diff --git a/package.json b/package.json index a47c695..5c9c1af 100644 --- a/package.json +++ b/package.json @@ -17,9 +17,10 @@ "openapi:generate": "openapi-typescript src/shared/api/spec/openapi.json -o src/shared/api/generated/schema.d.ts", "openapi:sync": "node scripts/openapi-sync.mjs", "openapi:check": "node scripts/openapi-check.mjs", + "hygiene:check": "node scripts/repository-hygiene-check.mjs", "knip": "knip", "deps": "depcruise src --config dependency-cruiser.cjs --output-type err-long", - "verify": "npm run openapi:check && npm run typecheck && npm run lint && npm run test:run && npm run test:coverage && npm run knip && npm run deps && npm run build", + "verify": "npm run openapi:check && npm run hygiene:check && npm run typecheck && npm run lint && npm run test:run && npm run test:coverage && npm run knip && npm run deps && npm run build", "prepare": "husky", "preview": "vite preview" }, @@ -37,7 +38,7 @@ "react-dom": "19.2.8", "react-hook-form": "7.85.0", "react-i18next": "16.3.5", - "react-router": "7.9.6", + "react-router": "7.18.2", "zod": "3.25.76" }, "devDependencies": { diff --git a/scripts/repository-hygiene-check.mjs b/scripts/repository-hygiene-check.mjs new file mode 100644 index 0000000..ac06ac9 --- /dev/null +++ b/scripts/repository-hygiene-check.mjs @@ -0,0 +1,44 @@ +import { execFileSync } from "node:child_process"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); + +function git(args) { + return execFileSync("git", args, { cwd: root, encoding: "utf8" }).trim(); +} + +function isIgnored(file) { + try { + execFileSync("git", ["check-ignore", "--no-index", "-q", file], { + cwd: root, + stdio: "ignore", + }); + return true; + } catch (error) { + if (error.status === 1) return false; + throw error; + } +} + +function assert(condition, message) { + if (!condition) throw new Error(message); +} + +const trackedEnvironmentFiles = git(["ls-files", "--", ".env", ".env.*"]) + .split("\n") + .filter(Boolean) + .sort(); + +assert( + JSON.stringify(trackedEnvironmentFiles) === JSON.stringify([".env.example"]), + `Only .env.example may be tracked; found: ${trackedEnvironmentFiles.join(", ") || "none"}`, +); + +for (const file of [".env", ".env.local", ".env.development", ".env.test"]) { + assert(isIgnored(file), `Local environment file must be ignored: ${file}`); +} + +assert(!isIgnored(".env.example"), ".env.example must remain versionable"); + +console.log("Frontend repository hygiene verified");