From ee0124b97b2d6d6a632cc1ceda14116a0b032907 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Aug 2026 11:45:28 +0000 Subject: [PATCH] Lint config files with ESLint and Prettier Config files were largely excluded from linting: ESLint only ran on `src/` and JSON files, so no `.mjs`/`.mts`/`.ts` config file was ever checked, and the Prettier globs missed `.mjs`/`.mts` entirely as well as every file in the repository root. As a result `site/eslint.config.mjs` and `site-configs/site-configs.ts` were unformatted, and the `next.config.*` override in `site/eslint.config.mjs` was dead configuration. - Each package's `lint:eslint` now also covers the package-root `*.{ts,mts,js,mjs,cjs}` files, and `lint:prettier` adds `mts`/`mjs`/`cjs` - Each `eslint.config.mjs` disables the TypeScript project service for those config files (they are not part of any tsconfig) and allows them to import devDependencies - Root `lint:root`/`lint:fix:root` now cover repository-root files and `site-configs/`, which the previous glob excluded, plus `ts`/`mts`/`mjs`/`cjs` - `lint-staged` mirrors the new patterns and runs ESLint on config files - Fixed the findings this surfaced, and the `lint:create-app` script, which ran the API lint instead of create-app's COM-239 --- AGENTS.md | 4 +++- admin/codegen.ts | 2 +- admin/eslint.config.mjs | 28 +++++++++++++++++++++++++++- admin/package.json | 4 ++-- api/eslint.config.mjs | 28 ++++++++++++++++++++++++++-- api/package.json | 4 ++-- create-app/eslint.config.mjs | 15 +++++++++++++++ create-app/package.json | 4 ++-- lint-staged.config.js | 13 ++++++++----- package.json | 6 +++--- site-configs/site-configs.ts | 6 +++--- site/cache-handler.ts | 10 +++++++--- site/codegen.ts | 2 +- site/eslint.config.mjs | 25 +++++++++++++++++++++---- site/package.json | 4 ++-- site/server.ts | 2 ++ 16 files changed, 125 insertions(+), 32 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 39d4f6e8e..3e24d2f51 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -69,7 +69,7 @@ npm --prefix admin run lint:fix # Auto-fix Admin npm --prefix site run lint:fix # Auto-fix Site (includes stylelint) ``` -These commands auto-fix import sorting, remove unused imports, and apply Prettier formatting. The root `lint:fix` also formats config files outside the packages. +These commands auto-fix import sorting, remove unused imports, and apply Prettier formatting. The root `lint:fix` also formats files outside the packages (repository-root config files, `site-configs/`, `.github/`, `.digitalocean/`, ...). ### Testing (API only currently) @@ -153,6 +153,8 @@ The `site-configs/` directory manages site configurations, compiled into environ - stylelint for Site SCSS/CSS - knip for unused exports detection +Each package's `lint:eslint` covers `src/`, all JSON files, and the package-root `.ts`/`.mts`/`.js`/`.mjs`/`.cjs` files (`next.config.mjs`, `vite.config.mts`, `eslint.config.mjs`, ...). Because those config files are not part of the TypeScript project, each `eslint.config.mjs` disables the project service for them and allows importing devDependencies. + ### Environment Files - `.env` - Development defaults diff --git a/admin/codegen.ts b/admin/codegen.ts index eeb989b9f..a5ac72bc4 100644 --- a/admin/codegen.ts +++ b/admin/codegen.ts @@ -1,4 +1,4 @@ -import { type CodegenConfig } from "@graphql-codegen/cli"; +import type { CodegenConfig } from "@graphql-codegen/cli"; import { readFileSync } from "fs"; import { buildSchema } from "graphql"; diff --git a/admin/eslint.config.mjs b/admin/eslint.config.mjs index dc9b55c51..07929a915 100644 --- a/admin/eslint.config.mjs +++ b/admin/eslint.config.mjs @@ -2,8 +2,34 @@ import eslintConfigReact from "@dextinity/eslint-config/react.js"; import { defineConfig, globalIgnores } from "eslint/config"; const config = defineConfig([ - globalIgnores(["schema.json", "src/fragmentTypes.json", "dist/**", "src/**/*.generated.ts", "src/**/generated/**", "block-meta.json", "**/package-lock.json", "lang/**", "lang-compiled/**", "lang-extracted/**"]), + globalIgnores([ + "schema.json", + "src/fragmentTypes.json", + "dist/**", + "src/**/*.generated.ts", + "src/**/generated/**", + "block-meta.json", + "**/package-lock.json", + "lang/**", + "lang-compiled/**", + "lang-extracted/**", + ]), ...eslintConfigReact, + { + // Config files in the package root are dev-only tooling and not part of the TypeScript project, + // so they can't be parsed by the project service and may import devDependencies. + files: ["*.mjs", "*.cjs", "*.mts", "*.config.ts", "*.config.js"], + languageOptions: { + parserOptions: { + projectService: false, + project: null, + programs: null, + }, + }, + rules: { + "import/no-extraneous-dependencies": ["error", { devDependencies: true }], + }, + }, ]); export default config; diff --git a/admin/package.json b/admin/package.json index e0eed6c76..444ffed3c 100644 --- a/admin/package.json +++ b/admin/package.json @@ -18,12 +18,12 @@ "prelint": "npm run intl:compile && run-p gql:types generate-block-types", "lint": "npm run admin-generator && run-p -l lint:{eslint,knip,prettier,tsc} intl:extract", "lint:ci": "npm run lint", - "lint:eslint": "eslint --max-warnings 0 src/ '**/*.json' --no-warn-ignored", + "lint:eslint": "eslint --max-warnings 0 src/ '*.{ts,mts,js,mjs,cjs}' '**/*.json' --no-warn-ignored", "lint:fix": "run-p lint:fix:{eslint,prettier}", "lint:fix:eslint": "npm run lint:eslint -- --fix", "lint:fix:prettier": "npm run lint:prettier -- --write", "lint:knip": "knip --exports --files --dependencies --tags=-knipignore", - "lint:prettier": "npx prettier --check '*.{ts,js,json,md,yml,yaml}'", + "lint:prettier": "npx prettier --check '*.{ts,mts,js,mjs,cjs,json,md,yml,yaml}'", "lint:tsc": "tsc --project .", "preview": "npm run build && vite preview", "serve": "node server", diff --git a/api/eslint.config.mjs b/api/eslint.config.mjs index 967057958..917c2eba9 100644 --- a/api/eslint.config.mjs +++ b/api/eslint.config.mjs @@ -2,8 +2,31 @@ import eslintConfigNestJs from "@dextinity/eslint-config/nestjs.js"; import { defineConfig, globalIgnores } from "eslint/config"; const config = defineConfig([ - globalIgnores(["src/db/migrations/**", "dist/**", "src/**/*.generated.ts", "src/**/generated", "block-meta.json", "package-lock.json", "uploads/**"]), + globalIgnores([ + "src/db/migrations/**", + "dist/**", + "src/**/*.generated.ts", + "src/**/generated", + "block-meta.json", + "package-lock.json", + "uploads/**", + ]), ...eslintConfigNestJs, + { + // Config files in the package root are dev-only tooling and not part of the TypeScript project, + // so they can't be parsed by the project service and may import devDependencies. + files: ["*.mjs", "*.cjs", "*.mts", "*.config.ts", "*.config.js"], + languageOptions: { + parserOptions: { + projectService: false, + project: null, + programs: null, + }, + }, + rules: { + "import/no-extraneous-dependencies": ["error", { devDependencies: true }], + }, + }, { rules: { "no-restricted-imports": [ @@ -12,7 +35,8 @@ const config = defineConfig([ paths: [ { name: "@faker-js/faker", - message: "Import faker from '@src/db/fixtures/faker' instead, which lazily loads @faker-js/faker so it isn't pulled into memory on every API startup.", + message: + "Import faker from '@src/db/fixtures/faker' instead, which lazily loads @faker-js/faker so it isn't pulled into memory on every API startup.", }, ], }, diff --git a/api/package.json b/api/package.json index 5edae2c3f..09cc5f953 100644 --- a/api/package.json +++ b/api/package.json @@ -17,13 +17,13 @@ "lint": "npm run api-generator && run-p -l lint:{eslint,knip,prettier,tsc,api-can-start}", "lint:api-can-start": "MIKRO_ORM_NO_CONNECT=true PRIVATE_SITE_CONFIGS=$(echo '[]' | base64) dotenv -e ../.env -- ts-node --transpile-only -r tsconfig-paths/register src/console.ts --help", "lint:ci": "npm run lint && npm run lint:generated-files-not-modified", - "lint:eslint": "eslint --max-warnings 0 src/ '**/*.json' --no-warn-ignored", + "lint:eslint": "eslint --max-warnings 0 src/ '*.{ts,mts,js,mjs,cjs}' '**/*.json' --no-warn-ignored", "lint:fix": "run-p lint:fix:{eslint,prettier}", "lint:fix:eslint": "npm run lint:eslint -- --fix", "lint:fix:prettier": "npm run lint:prettier -- --write", "lint:generated-files-not-modified": "git diff --exit-code HEAD -- schema.gql block-meta.json", "lint:knip": "knip --exports --files --dependencies --tags=-knipignore", - "lint:prettier": "npx prettier --check '*.{ts,js,json,md,yml,yaml}'", + "lint:prettier": "npx prettier --check '*.{ts,mts,js,mjs,cjs,json,md,yml,yaml}'", "lint:tsc": "tsc --project ./tsconfig.lint.json", "mikro-orm": "dotenv -e .env.secrets -e .env.local -e .env -e .env.site-configs -- mikro-orm", "repl": "npm run start -- --watch --entryFile repl", diff --git a/create-app/eslint.config.mjs b/create-app/eslint.config.mjs index fc701c827..ac40a4ba1 100644 --- a/create-app/eslint.config.mjs +++ b/create-app/eslint.config.mjs @@ -4,6 +4,21 @@ import { defineConfig, globalIgnores } from "eslint/config"; const config = defineConfig([ globalIgnores(["src/db/migrations/**", "dist/**", "src/**/*.generated.ts", "package-lock.json"]), ...eslintConfigNestJs, + { + // Config files in the package root are dev-only tooling and not part of the TypeScript project, + // so they can't be parsed by the project service and may import devDependencies. + files: ["*.mjs", "*.cjs", "*.mts", "*.config.ts", "*.config.js"], + languageOptions: { + parserOptions: { + projectService: false, + project: null, + programs: null, + }, + }, + rules: { + "import/no-extraneous-dependencies": ["error", { devDependencies: true }], + }, + }, { rules: { "@dextinity/no-other-module-relative-import": "off", diff --git a/create-app/package.json b/create-app/package.json index a4e820bb6..1c1c19f72 100644 --- a/create-app/package.json +++ b/create-app/package.json @@ -23,9 +23,9 @@ "build": "tsc --project tsconfig.json", "clean": "rimraf lib", "lint": "run-p -l lint:{eslint,knip,prettier,tsc}", - "lint:eslint": "eslint --max-warnings 0 src/ '**/*.json' --no-warn-ignored", + "lint:eslint": "eslint --max-warnings 0 src/ '*.{ts,mts,js,mjs,cjs}' '**/*.json' --no-warn-ignored", "lint:knip": "knip --exports --files --dependencies --tags=-knipignore", - "lint:prettier": "npx prettier --check '*.{ts,js,json,md,yml,yaml}'", + "lint:prettier": "npx prettier --check '*.{ts,mts,js,mjs,cjs,json,md,yml,yaml}'", "lint:tsc": "tsc --project ./tsconfig.json", "prepublishOnly": "npm run lint && npm run build", "prestart": "npm run clean", diff --git a/lint-staged.config.js b/lint-staged.config.js index 41672af30..e11ebcc92 100644 --- a/lint-staged.config.js +++ b/lint-staged.config.js @@ -1,16 +1,19 @@ module.exports = { "admin/src/**/*.{ts,tsx,js,jsx,json,css,md}": () => ["npm --prefix admin run lint:eslint", "npm --prefix admin run lint:knip"], "admin/**/*.{ts,tsx}": () => ["npm --prefix admin run lint:tsc", "npm --prefix admin run intl:extract"], - "admin/*.{ts,js,json,md,yml,yaml}": () => "npm --prefix admin run lint:prettier", + "admin/*.{ts,mts,js,mjs,cjs,json,md,yml,yaml}": () => ["npm --prefix admin run lint:prettier", "npm --prefix admin run lint:eslint"], "api/src/**/*.{ts,tsx,js,jsx,json,css,md}": () => ["npm --prefix api run lint:eslint", "npm --prefix api run lint:knip"], "api/**/*.{ts,tsx}": () => "npm --prefix api run lint:tsc", - "api/*.{ts,js,json,md,yml,yaml}": () => "npm --prefix api run lint:prettier", + "api/*.{ts,mts,js,mjs,cjs,json,md,yml,yaml}": () => ["npm --prefix api run lint:prettier", "npm --prefix api run lint:eslint"], "site/src/**/*.{ts,tsx,js,jsx,json,css,md}": () => ["npm --prefix site run lint:eslint", "npm --prefix site run lint:knip"], "site/**/*.{ts,tsx}": () => ["npm --prefix site run lint:tsc", "npm --prefix site run intl:extract"], - "site/*.{ts,js,json,md,yml,yaml}": () => "npm --prefix site run lint:prettier", + "site/*.{ts,mts,js,mjs,cjs,json,md,yml,yaml}": () => ["npm --prefix site run lint:prettier", "npm --prefix site run lint:eslint"], "site/src/**/*.{css,scss}": () => "npm --prefix site run lint:style", "create-app/src/**/*.{ts,tsx,js,jsx,json,css,scss,md}": () => "npm --prefix create-app run lint:eslint", "create-app/**/*.{ts,tsx}": () => "npm --prefix create-app run lint:tsc", - "create-app/*.{ts,js,json,md,yml,yaml}": () => "npm --prefix create-app run lint:prettier", - "./**/*.{js,json,md,yml,yaml}": () => "npm run lint:root", + "create-app/*.{ts,mts,js,mjs,cjs,json,md,yml,yaml}": () => [ + "npm --prefix create-app run lint:prettier", + "npm --prefix create-app run lint:eslint", + ], + "./**/*.{ts,mts,js,mjs,cjs,json,md,yml,yaml}": () => "npm run lint:root", }; diff --git a/package.json b/package.json index d4935be09..37e866ab1 100644 --- a/package.json +++ b/package.json @@ -11,17 +11,17 @@ "lint:admin": "npm --prefix admin run lint", "lint:api": "npm --prefix api run lint", "lint:ci": "npm run lint:root", - "lint:create-app": "npm --prefix api run lint", + "lint:create-app": "npm --prefix create-app run lint", "lint:fix": "run-p -l lint:fix:{admin,api,root,site}", "lint:fix:admin": "npm --prefix admin run lint:fix", "lint:fix:api": "npm --prefix api run lint:fix", - "lint:fix:root": "npx prettier --write './!(admin|api|site|create-app)/**/*.{js,json,md,yml,yaml}'", + "lint:fix:root": "npx prettier --write '*.{ts,mts,js,mjs,cjs,json,md,yml,yaml}' './{site-configs,!(admin|api|site|create-app)}/**/*.{ts,mts,js,mjs,cjs,json,md,yml,yaml}'", "lint:fix:site": "npm --prefix site run lint:fix", "lint:knip": "run-p -l lint:knip:{admin,api,site}", "lint:knip:admin": "npm --prefix admin run lint:knip", "lint:knip:api": "npm --prefix api run lint:knip", "lint:knip:site": "npm --prefix site run lint:knip", - "lint:root": "npx prettier --check './!(admin|api|site|create-app)/**/*.{js,json,md,yml,yaml}'", + "lint:root": "npx prettier --check '*.{ts,mts,js,mjs,cjs,json,md,yml,yaml}' './{site-configs,!(admin|api|site|create-app)}/**/*.{ts,mts,js,mjs,cjs,json,md,yml,yaml}'", "lint:site": "npm --prefix site run lint", "test:ci": "run-p test:ci:*", "test:ci:api": "npm --prefix api run test:ci", diff --git a/site-configs/site-configs.ts b/site-configs/site-configs.ts index 272b6c2e2..8197b66c9 100644 --- a/site-configs/site-configs.ts +++ b/site-configs/site-configs.ts @@ -8,14 +8,14 @@ export type GetSiteConfig = (env: Environment) => SiteConfig; const isValidEnvironment = (env: string): env is Environment => { return ["local", "dev", "test", "staging", "prod"].includes(env); -} +}; // Called by `npx @dextinity/cli inject-site-configs` const getSiteConfigs = async (env: string): Promise => { - if(!isValidEnvironment(env)) { + if (!isValidEnvironment(env)) { throw new Error(`Invalid environment: ${env}`); } - + const imports = [main, secondary]; return imports.map((getSiteConfig) => { return getSiteConfig(env); diff --git a/site/cache-handler.ts b/site/cache-handler.ts index e33ae2630..e18493c3f 100644 --- a/site/cache-handler.ts +++ b/site/cache-handler.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ import { Redis } from "ioredis"; import { LRUCache } from "lru-cache"; import type { CacheHandler as NextCacheHandler, CacheHandlerValue } from "next/dist/server/lib/incremental-cache"; @@ -42,7 +41,10 @@ let isFallbackInUse = false; function parseBodyForGqlError(body: string) { try { const decodedBody = Buffer.from(body, "base64").toString("utf-8"); - if (!decodedBody.startsWith("{")) return null; // Not a JSON response, ignore + // Not a JSON response, ignore + if (!decodedBody.startsWith("{")) { + return null; + } return JSON.parse(decodedBody); } catch (error) { console.error("CacheHandler.parseBodyForGqlError error", error); @@ -117,7 +119,9 @@ export default class CacheHandler { } async revalidateTag(tags: string | string[]): Promise { - if (tags.length === 0) return; + if (tags.length === 0) { + return; + } console.warn("CacheHandler.revalidateTag", tags); } } diff --git a/site/codegen.ts b/site/codegen.ts index 31f32be62..1bca00b42 100644 --- a/site/codegen.ts +++ b/site/codegen.ts @@ -1,4 +1,4 @@ -import { type CodegenConfig } from "@graphql-codegen/cli"; +import type { CodegenConfig } from "@graphql-codegen/cli"; import { readFileSync } from "fs"; import { buildSchema } from "graphql"; diff --git a/site/eslint.config.mjs b/site/eslint.config.mjs index 3f0255c59..40c00739f 100644 --- a/site/eslint.config.mjs +++ b/site/eslint.config.mjs @@ -4,7 +4,17 @@ import { defineConfig, globalIgnores } from "eslint/config"; const docsLink = "https://cms-docs.dextinity.com/docs/faqs/environment-variables-in-site"; const config = defineConfig([ - globalIgnores(["**/**/*.generated.ts", "dist/**", "lang/**", "lang-compiled/**", "lang-extracted/**", ".next/**", "public/**", "block-meta.json", "package-lock.json"]), + globalIgnores([ + "**/**/*.generated.ts", + "dist/**", + "lang/**", + "lang-compiled/**", + "lang-extracted/**", + ".next/**", + "public/**", + "block-meta.json", + "package-lock.json", + ]), ...eslintConfigNextJs, { rules: { @@ -19,7 +29,9 @@ const config = defineConfig([ }, }, { - files: ["next.config.*"], + // Config files in the package root are dev-only tooling and not part of the TypeScript project, + // so they can't be parsed by the project service and may import devDependencies. + files: ["*.mjs", "*.cjs", "*.mts", "*.config.ts", "*.config.js"], languageOptions: { parserOptions: { projectService: false, @@ -27,12 +39,17 @@ const config = defineConfig([ programs: null, }, }, + rules: { + "import/no-extraneous-dependencies": ["error", { devDependencies: true }], + }, + }, + { + files: ["next.config.*"], rules: { "no-restricted-syntax": [ "error", { - selector: - "MemberExpression[object.type='MemberExpression'][object.object.name='process'][object.property.name='env']", + selector: "MemberExpression[object.type='MemberExpression'][object.object.name='process'][object.property.name='env']", message: `Usage of process.env in next.config is not allowed. Use site configs or runtime configuration instead. See ${docsLink}`, }, ], diff --git a/site/package.json b/site/package.json index fdb7147a7..73863a25d 100644 --- a/site/package.json +++ b/site/package.json @@ -16,13 +16,13 @@ "prelint": "npm run intl:compile && run-p gql:types generate-block-types css:types", "lint": "run-p -l lint:{eslint,knip,prettier,style,tsc} intl:extract", "lint:ci": "npm run lint", - "lint:eslint": "eslint --max-warnings 0 src/ '**/*.json' --no-warn-ignored", + "lint:eslint": "eslint --max-warnings 0 src/ '*.{ts,mts,js,mjs,cjs}' '**/*.json' --no-warn-ignored", "lint:fix": "run-p lint:fix:{eslint,prettier,style}", "lint:fix:eslint": "npm run lint:eslint -- --fix", "lint:fix:prettier": "npm run lint:prettier -- --write", "lint:fix:style": "npm run lint:style -- --fix", "lint:knip": "knip --exports --files --dependencies --tags=-knipignore", - "lint:prettier": "npx prettier --check '*.{ts,js,json,md,yml,yaml,css,scss}'", + "lint:prettier": "npx prettier --check '*.{ts,mts,js,mjs,cjs,json,md,yml,yaml,css,scss}'", "lint:style": "npx stylelint '**/*.{css,scss}'", "lint:tsc": "npx next typegen && tsc --project .", "serve": "NODE_ENV=production node dist/server.js", diff --git a/site/server.ts b/site/server.ts index ffd7ebd91..393f27d25 100644 --- a/site/server.ts +++ b/site/server.ts @@ -13,6 +13,8 @@ const handle = app.getRequestHandler(); app.prepare().then(() => { if (process.env.TRACING_ENABLED === "1") { + // Loaded lazily via require so tracing is only initialized when enabled. + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./tracing"); } createServer(async (req, res) => {