commit-sheriff 1.6.2 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -28
- package/bin/commit-sheriff.js +120 -46
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,11 +5,15 @@ Shared [Husky](https://typicode.github.io/husky/) git hooks for any project —
|
|
|
5
5
|
## Quick start
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm
|
|
8
|
+
npm install --save-dev commit-sheriff husky
|
|
9
9
|
npx commit-sheriff init
|
|
10
|
-
npx commit-sheriff add-eslint-react # optional —
|
|
10
|
+
npx commit-sheriff add-eslint-react # optional — only needed if init didn't auto-detect react+typescript
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
This is the full flow end to end. The `Install`, `Usage`, and `What init does` sections below repeat
|
|
14
|
+
these same commands with more context/explanation — you don't need to run anything extra from
|
|
15
|
+
there, they're not additional steps.
|
|
16
|
+
|
|
13
17
|
## Table of contents
|
|
14
18
|
|
|
15
19
|
- [Quick start](#quick-start)
|
|
@@ -77,9 +81,9 @@ Run this once per repo, from the repo root (where `package.json` lives). This wo
|
|
|
77
81
|
4. Adds a default `lint-staged` block to `package.json` **only if one doesn't already exist**.
|
|
78
82
|
5. Adds a `"prepare": "husky"` npm script if missing, so hooks are (re)installed automatically after `npm install`.
|
|
79
83
|
6. Installs `lint-staged`, `eslint`, and `prettier` as devDependencies (via `npm install --save-dev`) if any of them are missing, so the pre-commit lint/format step actually runs instead of silently no-oping.
|
|
80
|
-
7. If `react` and `typescript` are both present in your `dependencies`/`devDependencies`, also runs [`add-eslint-react`](#advanced-eslint-module-boundary-template-typescriptreact) automatically, which
|
|
84
|
+
7. If `react` and `typescript` are both present in your `dependencies`/`devDependencies`, also runs [`add-eslint-react`](#advanced-eslint-module-boundary-template-typescriptreact) automatically, which **overwrites** `eslint.config.mjs` and `.prettierrc.js` with the latest templates (folding in `next/core-web-vitals`/`next/typescript` automatically if `next` is detected) and installs the extra plugins it needs — no manual merge step. Otherwise it's skipped and you can add it manually later.
|
|
81
85
|
|
|
82
|
-
Re-running `npx commit-sheriff init` later (e.g. after updating the package) is safe for your project-specific settings: it refreshes the hook scripts and (on react+typescript projects) the ESLint/Prettier templates to the latest version, but leaves your `.commitsheriffrc.json` / `lint-staged` config alone.
|
|
86
|
+
Re-running `npx commit-sheriff init` later (e.g. after updating the package) is safe for your project-specific settings: it refreshes the hook scripts and (on react+typescript projects) the ESLint/Prettier templates to the latest version, but leaves your `.commitsheriffrc.json` / `lint-staged` config alone. `eslint.config.mjs`, `eslint.config.commit-sheriff.mjs`, and `.prettierrc.js` are **always overwritten** on every re-run — back them up under a different name first if you've hand-edited them.
|
|
83
87
|
|
|
84
88
|
**Why the auto-install matters**: the pre-commit hook only runs `lint-staged` if it can actually
|
|
85
89
|
find the package — if it's missing, older versions of this hook **silently skipped linting
|
|
@@ -272,29 +276,40 @@ npx commit-sheriff add-eslint-react
|
|
|
272
276
|
```
|
|
273
277
|
|
|
274
278
|
This ships as a **flat config** (`eslint.config.*`, ESLint 9+ format), not the legacy
|
|
275
|
-
`.eslintrc.js` format — see [why](#why-flat-config) below.
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
`
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
279
|
+
`.eslintrc.js` format — see [why](#why-flat-config) below. It always writes two files, no manual
|
|
280
|
+
merge step required, ever:
|
|
281
|
+
|
|
282
|
+
- `eslint.config.commit-sheriff.mjs` — the actual ruleset (module-boundary rules + all the
|
|
283
|
+
recommended plugins: TypeScript, React, hooks, a11y, sonarjs, import, testing-library, vitest,
|
|
284
|
+
jest, prettier).
|
|
285
|
+
- `eslint.config.mjs` — the entry point ESLint actually reads, generated fresh every run to import
|
|
286
|
+
the file above. **If you already had an `eslint.config.mjs` (e.g. Next.js's own
|
|
287
|
+
`create-next-app`-generated one), it is fully overwritten** — not merged, not left alone. If
|
|
288
|
+
`next` is detected in your `dependencies`, the generated entry point folds in Next's own ESLint
|
|
289
|
+
rules automatically so you don't lose Next-specific linting; `eslint-config-next` is
|
|
290
|
+
auto-installed too if it's missing. Any other hand-written customizations in your previous
|
|
291
|
+
`eslint.config.mjs` are not preserved — back it up under a different name first if you need them.
|
|
292
|
+
|
|
293
|
+
`eslint-config-next` changed shape between major versions, so the generated entry point checks
|
|
294
|
+
the actually-installed version and picks the matching pattern automatically:
|
|
295
|
+
- **v16+** (current): ships a native flat-config array
|
|
296
|
+
(`import nextCoreWebVitals from "eslint-config-next/core-web-vitals"`, spread directly). Mixing
|
|
297
|
+
this with our own module-boundary plugins (which resolve the same plugin _names_ — `react`,
|
|
298
|
+
`jsx-a11y`, `import`, etc. — through a separate loader) would otherwise throw `Cannot redefine
|
|
299
|
+
plugin ...`; the generated config strips those redundant re-registrations automatically so both
|
|
300
|
+
rule sets apply cleanly.
|
|
301
|
+
- **v15 and earlier**: still ships the legacy eslintrc-style object, so the generated config uses
|
|
302
|
+
`FlatCompat.extends("next/core-web-vitals", "next/typescript")` instead — the same bridge
|
|
303
|
+
`create-next-app@15` itself generates.
|
|
304
|
+
|
|
305
|
+
(An earlier version of this tool hardcoded the v15-style pattern unconditionally, which crashed
|
|
306
|
+
with `TypeError: Converting circular structure to JSON` on projects using `eslint-config-next@16+`
|
|
307
|
+
— fixed by detecting the installed version instead of assuming one.)
|
|
308
|
+
|
|
309
|
+
`.prettierrc.js` is written/refreshed the same way — **always overwritten** with the latest
|
|
310
|
+
template (same behavior as the `commit-msg`/`pre-commit` hooks). This trade-off is intentional:
|
|
311
|
+
zero manual steps for the common case, at the cost of not preserving bespoke prior ESLint/Prettier
|
|
312
|
+
setups.
|
|
298
313
|
|
|
299
314
|
The module list for the boundary rule isn't hardcoded — it's read straight from the `modules`
|
|
300
315
|
array in your root `.commitsheriffrc.json` (the same list the commit-msg/branch-name hooks use
|
|
@@ -338,7 +353,7 @@ entirely** — there's no fallback. Since frameworks like Next.js already scaffo
|
|
|
338
353
|
actually run (this was a real bug: an unused `useState` import went completely unflagged because
|
|
339
354
|
the module-boundary config was silently dead). The flat config here uses `FlatCompat` (the same
|
|
340
355
|
official migration helper Next.js's own generated config uses internally) so the exact same rule
|
|
341
|
-
set works natively under ESLint 9+,
|
|
356
|
+
set works natively under ESLint 9+, whether or not the project already had its own flat config.
|
|
342
357
|
|
|
343
358
|
A legacy `.eslintrc.js` version of this template (`eslintrc.module-boundaries.js`) still ships
|
|
344
359
|
inside the package for reference/manual use on pure ESLint 8 projects with no flat config support
|
package/bin/commit-sheriff.js
CHANGED
|
@@ -229,17 +229,12 @@ const ESLINT_REACT_DEV_DEPS = [
|
|
|
229
229
|
"prettier",
|
|
230
230
|
];
|
|
231
231
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
const FLAT_CONFIG_SIDE_FILE = "eslint.config.commit-sheriff.mjs";
|
|
239
|
-
|
|
240
|
-
function hasFlatEslintConfig() {
|
|
241
|
-
return FLAT_CONFIG_CANDIDATES.some((name) => fs.existsSync(path.join(cwd, name)));
|
|
242
|
-
}
|
|
232
|
+
// The module-boundary + all-recommended-plugins ruleset lives in this file, copied verbatim from
|
|
233
|
+
// the template every run. `eslint.config.mjs` itself (the file ESLint actually reads) is then
|
|
234
|
+
// generated fresh below to import it — no manual merge step for the user, ever. This intentionally
|
|
235
|
+
// means a fully hand-written `eslint.config.mjs` gets replaced: simplicity/zero-manual-steps was
|
|
236
|
+
// chosen over preserving bespoke prior setups, matching how `.prettierrc.js` already worked.
|
|
237
|
+
const RULES_FILE = "eslint.config.commit-sheriff.mjs";
|
|
243
238
|
|
|
244
239
|
function copyTemplateOverwrite(templateName, destName) {
|
|
245
240
|
const src = path.join(__dirname, "..", "templates", templateName);
|
|
@@ -250,50 +245,129 @@ function copyTemplateOverwrite(templateName, destName) {
|
|
|
250
245
|
return dest;
|
|
251
246
|
}
|
|
252
247
|
|
|
253
|
-
function
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
248
|
+
function writeFileOverwrite(destName, content) {
|
|
249
|
+
const dest = path.join(cwd, destName);
|
|
250
|
+
const existed = fs.existsSync(dest);
|
|
251
|
+
fs.writeFileSync(dest, content);
|
|
252
|
+
console.log(existed ? `✔ ${destName} yeniləndi (üzərinə yazıldı)` : `✔ ${destName} yazıldı`);
|
|
253
|
+
return dest;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// eslint-config-next changed shape across majors: up through v15 it ships an eslintrc-style
|
|
257
|
+
// object (`module.exports = { extends: [...] }`) meant to be pulled in via FlatCompat's legacy
|
|
258
|
+
// `compat.extends("next/core-web-vitals", "next/typescript")` bridge (exactly what
|
|
259
|
+
// `create-next-app` itself generates for v15 projects). From v16 on it ships a native flat-config
|
|
260
|
+
// array as the default export of `eslint-config-next/core-web-vitals` / `/typescript` instead —
|
|
261
|
+
// running the *old* FlatCompat-string pattern against a v16 install crashes with
|
|
262
|
+
// "TypeError: Converting circular structure to JSON" deep inside `@eslint/eslintrc`'s config
|
|
263
|
+
// validator (confirmed by reproducing it in a scratch project — the crash reproduces with only
|
|
264
|
+
// Next's own extends, no module-boundary rules involved at all). `create-next-app@latest` itself
|
|
265
|
+
// already generates the new native-import form for v16, so we mirror that here rather than the
|
|
266
|
+
// older FlatCompat form once eslint-config-next resolves to 16+.
|
|
267
|
+
function installedVersion(pkgName) {
|
|
268
|
+
try {
|
|
269
|
+
return require(path.join(cwd, "node_modules", pkgName, "package.json")).version;
|
|
270
|
+
} catch {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function buildEslintEntryConfig(hasNext) {
|
|
276
|
+
if (!hasNext) {
|
|
277
|
+
return (
|
|
278
|
+
`import moduleBoundaries from "./${RULES_FILE}";\n\n` +
|
|
279
|
+
`export default [...moduleBoundaries];\n`
|
|
272
280
|
);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const version = installedVersion("eslint-config-next");
|
|
284
|
+
const major = version ? parseInt(version.split(".")[0], 10) : null;
|
|
285
|
+
// Default to the modern (16+) form when the version can't be determined (e.g. install failed
|
|
286
|
+
// offline) — that's what any newly-scaffolded Next project will have going forward.
|
|
287
|
+
const useNativeFlatExport = major === null || major >= 16;
|
|
288
|
+
|
|
289
|
+
if (useNativeFlatExport) {
|
|
290
|
+
// eslint-config-next's native flat array already registers plugins like "react",
|
|
291
|
+
// "react-hooks", "import", "jsx-a11y" and "@typescript-eslint". Our own module-boundary
|
|
292
|
+
// template is built through @eslint/eslintrc's FlatCompat, which independently resolves
|
|
293
|
+
// those same plugin *names* into separate object instances — even though they load the same
|
|
294
|
+
// underlying package, ESLint's flat config throws "Cannot redefine plugin ..." when a plugin
|
|
295
|
+
// name is registered twice with two different object references (confirmed by reproducing it
|
|
296
|
+
// in a scratch Next 16 project). Only one registration per plugin name is actually needed, so
|
|
297
|
+
// any config entry from our own template that re-declares a plugin Next.js already registered
|
|
298
|
+
// has that redundant "plugins" key stripped below — its "rules" keep working against the
|
|
299
|
+
// plugin instance Next.js already loaded.
|
|
300
|
+
return (
|
|
301
|
+
`import nextCoreWebVitals from "eslint-config-next/core-web-vitals";\n` +
|
|
302
|
+
`import nextTypescript from "eslint-config-next/typescript";\n` +
|
|
303
|
+
`import moduleBoundaries from "./${RULES_FILE}";\n\n` +
|
|
304
|
+
`const nextConfigs = [...nextCoreWebVitals, ...nextTypescript];\n` +
|
|
305
|
+
`const pluginsNextAlreadyRegisters = new Set(\n` +
|
|
306
|
+
` nextConfigs.flatMap((entry) => (entry.plugins ? Object.keys(entry.plugins) : [])),\n` +
|
|
307
|
+
`);\n` +
|
|
308
|
+
`const dedupedModuleBoundaries = moduleBoundaries.map((entry) => {\n` +
|
|
309
|
+
` if (!entry.plugins) return entry;\n` +
|
|
310
|
+
` const ownPlugins = Object.fromEntries(\n` +
|
|
311
|
+
` Object.entries(entry.plugins).filter(([name]) => !pluginsNextAlreadyRegisters.has(name)),\n` +
|
|
312
|
+
` );\n` +
|
|
313
|
+
` const { plugins, ...rest } = entry;\n` +
|
|
314
|
+
` return Object.keys(ownPlugins).length ? { ...rest, plugins: ownPlugins } : rest;\n` +
|
|
315
|
+
`});\n\n` +
|
|
316
|
+
`export default [...nextConfigs, ...dedupedModuleBoundaries];\n`
|
|
277
317
|
);
|
|
278
318
|
}
|
|
319
|
+
|
|
320
|
+
return (
|
|
321
|
+
`import { fileURLToPath } from "node:url";\n` +
|
|
322
|
+
`import path from "node:path";\n` +
|
|
323
|
+
`import { FlatCompat } from "@eslint/eslintrc";\n` +
|
|
324
|
+
`import moduleBoundaries from "./${RULES_FILE}";\n\n` +
|
|
325
|
+
`const compat = new FlatCompat({\n` +
|
|
326
|
+
` baseDirectory: path.dirname(fileURLToPath(import.meta.url)),\n` +
|
|
327
|
+
`});\n\n` +
|
|
328
|
+
`export default [\n` +
|
|
329
|
+
` ...compat.extends("next/core-web-vitals", "next/typescript"),\n` +
|
|
330
|
+
` ...moduleBoundaries,\n` +
|
|
331
|
+
`];\n`
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function addEslintReact() {
|
|
336
|
+
const { pkg } = readPackageJson();
|
|
337
|
+
const deps = Object.assign({}, pkg.dependencies, pkg.devDependencies);
|
|
338
|
+
const hasNext = Boolean(deps.next);
|
|
339
|
+
|
|
340
|
+
copyTemplateOverwrite("eslint.config.module-boundaries.mjs", RULES_FILE);
|
|
341
|
+
|
|
342
|
+
// eslint-config-next must actually be installed (and its version readable) *before* generating
|
|
343
|
+
// the entry config below, so the version check above sees real, resolved data instead of "not
|
|
344
|
+
// installed yet".
|
|
345
|
+
const extraDeps = hasNext && !deps["eslint-config-next"] ? ["eslint-config-next"] : [];
|
|
346
|
+
ensureDevDeps(ESLINT_REACT_DEV_DEPS.concat(extraDeps));
|
|
347
|
+
|
|
348
|
+
writeFileOverwrite("eslint.config.mjs", buildEslintEntryConfig(hasNext));
|
|
279
349
|
copyTemplateOverwrite("prettier.module-boundaries.js", ".prettierrc.js");
|
|
350
|
+
|
|
280
351
|
console.log(
|
|
281
|
-
"
|
|
352
|
+
"\nBu şablon TypeScript/React + modul-sərhəd (module-boundary) qaydaları üçündür.\n" +
|
|
282
353
|
`Modul siyahısı ${CONFIG_FILE_NAME}.modules-dən avtomatik oxunur (boşdursa nümunə\n` +
|
|
283
354
|
"siyahı istifadə olunur — şablon faylı içindəki şərhi oxuyun).\n\n" +
|
|
284
|
-
"Diqqət: eslint.config
|
|
285
|
-
|
|
286
|
-
"
|
|
287
|
-
"
|
|
355
|
+
"Diqqət: eslint.config.mjs, " +
|
|
356
|
+
RULES_FILE +
|
|
357
|
+
" və .prettierrc.js hər dəfə\n" +
|
|
358
|
+
"şablonun ən son versiyası ilə üzərinə yazılır (commit-msg/pre-commit hook-ları kimi) —\n" +
|
|
359
|
+
"manual dəyişiklik lazım deyil, amma özünüzə uyğun etdiyiniz dəyişiklikləri qorumaq\n" +
|
|
360
|
+
"istəsəniz, əvvəlcə fərqli adla backup götürün.\n" +
|
|
361
|
+
(hasNext
|
|
362
|
+
? "\nNext.js aşkarlandı → next/core-web-vitals və next/typescript qaydaları da\n" +
|
|
363
|
+
"avtomatik daxil edildi.\n"
|
|
364
|
+
: ""),
|
|
288
365
|
);
|
|
289
|
-
|
|
290
|
-
if (
|
|
366
|
+
|
|
367
|
+
if (fs.existsSync(path.join(cwd, ".eslintrc.js"))) {
|
|
291
368
|
console.log(
|
|
292
|
-
|
|
293
|
-
"
|
|
294
|
-
"(yuxarıdakı nümunəyə baxın). Həmçinin, əgər layihənizdə hələ də köhnə .eslintrc.js\n" +
|
|
295
|
-
"varsa, onu silin — flat config varkən ESLint onu oxumur, üstəlik özü də indi\n" +
|
|
296
|
-
"adi .js fayl kimi lint xətaları törədə bilər.\n",
|
|
369
|
+
"\nDiqqət: layihənizdə hələ də köhnə .eslintrc.js var — silin. Flat config varkən\n" +
|
|
370
|
+
"ESLint onu oxumur, üstəlik özü də indi adi .js fayl kimi lint xətaları törədə bilər.\n",
|
|
297
371
|
);
|
|
298
372
|
}
|
|
299
373
|
}
|