commit-sheriff 1.6.2 → 1.7.0

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 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 i commit-sheriff
8
+ npm install --save-dev commit-sheriff husky
9
9
  npx commit-sheriff init
10
- npx commit-sheriff add-eslint-react # optional — TypeScript/React + module-boundary ESLint config
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 writes the ESLint flat-config module-boundary template (as `eslint.config.mjs` if you have none yet, or as a separate `eslint.config.commit-sheriff.mjs` if you already have one), **overwrites** `.prettierrc.js` with the latest template, and installs the extra plugins it needs. Otherwise it's skipped and you can add it manually later.
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. If you've hand-edited `.prettierrc.js`, or `eslint.config.mjs` in the no-existing-config case, back them up first — those get overwritten on every re-run (the side-file case, `eslint.config.commit-sheriff.mjs`, is also overwritten each time, but your own `eslint.config.mjs` is never touched there).
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,25 @@ 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. Where it lands depends on what your
276
- project already has:
277
-
278
- - **No `eslint.config.*` yet** written directly as `eslint.config.mjs` (your main config).
279
- - **You already have one** (e.g. Next.js's own `create-next-app`-generated `eslint.config.mjs`)
280
- left untouched; the module-boundary rules go into a separate
281
- `eslint.config.commit-sheriff.mjs` instead, printed with a snippet to import + spread it into
282
- your existing config:
283
-
284
- ```js
285
- import moduleBoundaries from "./eslint.config.commit-sheriff.mjs";
286
- export default [
287
- ...compat.extends("next/core-web-vitals", "next/typescript"),
288
- ...moduleBoundaries,
289
- ];
290
- ```
291
-
292
- Either way, `.prettierrc.js` is also written/refreshed, **always overwriting** whatever is
293
- already there at that filename (same behavior as the `commit-msg`/`pre-commit` hooks — re-running
294
- `add-eslint-react` refreshes it to the latest version of the template). If you've customized it
295
- by hand, back it up under a different name first — your edits will be replaced. The same applies
296
- to `eslint.config.mjs` when it's the one written directly (the no-existing-config case above); the
297
- side-file case never touches your own config.
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
289
+ `next/core-web-vitals`/`next/typescript` automatically (via the same `FlatCompat` pattern Next's
290
+ own generated config uses) so you don't lose Next-specific linting; `eslint-config-next` is
291
+ auto-installed too if it's missing. Any other hand-written customizations in your previous
292
+ `eslint.config.mjs` are not preserved — back it up under a different name first if you need them.
293
+
294
+ `.prettierrc.js` is written/refreshed the same way — **always overwritten** with the latest
295
+ template (same behavior as the `commit-msg`/`pre-commit` hooks). This trade-off is intentional:
296
+ zero manual steps for the common case, at the cost of not preserving bespoke prior ESLint/Prettier
297
+ setups.
298
298
 
299
299
  The module list for the boundary rule isn't hardcoded — it's read straight from the `modules`
300
300
  array in your root `.commitsheriffrc.json` (the same list the commit-msg/branch-name hooks use
@@ -338,7 +338,7 @@ entirely** — there's no fallback. Since frameworks like Next.js already scaffo
338
338
  actually run (this was a real bug: an unused `useState` import went completely unflagged because
339
339
  the module-boundary config was silently dead). The flat config here uses `FlatCompat` (the same
340
340
  official migration helper Next.js's own generated config uses internally) so the exact same rule
341
- set works natively under ESLint 9+, in both the standalone and merge-into-existing-config cases.
341
+ set works natively under ESLint 9+, whether or not the project already had its own flat config.
342
342
 
343
343
  A legacy `.eslintrc.js` version of this template (`eslintrc.module-boundaries.js`) still ships
344
344
  inside the package for reference/manual use on pure ESLint 8 projects with no flat config support
@@ -229,17 +229,12 @@ const ESLINT_REACT_DEV_DEPS = [
229
229
  "prettier",
230
230
  ];
231
231
 
232
- const FLAT_CONFIG_CANDIDATES = [
233
- "eslint.config.js",
234
- "eslint.config.mjs",
235
- "eslint.config.cjs",
236
- "eslint.config.ts",
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,71 @@ function copyTemplateOverwrite(templateName, destName) {
250
245
  return dest;
251
246
  }
252
247
 
253
- function addEslintReact() {
254
- // ESLint 9+ prioritizes flat config (`eslint.config.*`) and completely ignores a legacy
255
- // `.eslintrc.js` when one is present — no fallback. Frameworks like Next.js already scaffold
256
- // their own `eslint.config.mjs`, so if we wrote only `.eslintrc.js` here it would silently
257
- // never run for the majority of modern projects. We therefore ship the flat-config template
258
- // as the primary artifact, and decide where it lands based on what the project already has.
259
- const flatConfigAlreadyExists = hasFlatEslintConfig();
260
-
261
- if (flatConfigAlreadyExists) {
262
- copyTemplateOverwrite("eslint.config.module-boundaries.mjs", FLAT_CONFIG_SIDE_FILE);
263
- console.log(
264
- `\n• Layihədə artıq öz eslint.config.* faylınız var — ona toxunulmadı.\n` +
265
- ` Modul-sərhəd qaydaları ayrıca ${FLAT_CONFIG_SIDE_FILE} faylına yazıldı.\n` +
266
- ` Özünüzün eslint.config.* faylınıza əlavə edin, məsələn:\n\n` +
267
- ` import moduleBoundaries from "./${FLAT_CONFIG_SIDE_FILE}";\n` +
268
- ` export default [\n` +
269
- ` ...compat.extends("next/core-web-vitals", "next/typescript"),\n` +
270
- ` ...moduleBoundaries,\n` +
271
- ` ];\n`,
272
- );
273
- } else {
274
- copyTemplateOverwrite("eslint.config.module-boundaries.mjs", "eslint.config.mjs");
275
- console.log(
276
- "\n• Layihədə eslint.config.* yox idi — eslint.config.mjs olaraq birbaşa yazıldı.\n",
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
+ function buildEslintEntryConfig(hasNext) {
257
+ if (!hasNext) {
258
+ return (
259
+ `import moduleBoundaries from "./${RULES_FILE}";\n\n` +
260
+ `export default [...moduleBoundaries];\n`
277
261
  );
278
262
  }
263
+ // Next.js scaffolds its own eslint.config.mjs with `next/core-web-vitals`/`next/typescript` —
264
+ // those are folded in here (via the same FlatCompat pattern Next's own generated config uses)
265
+ // so overwriting the file doesn't silently drop Next-specific linting.
266
+ return (
267
+ `import { fileURLToPath } from "node:url";\n` +
268
+ `import path from "node:path";\n` +
269
+ `import { FlatCompat } from "@eslint/eslintrc";\n` +
270
+ `import moduleBoundaries from "./${RULES_FILE}";\n\n` +
271
+ `const compat = new FlatCompat({\n` +
272
+ ` baseDirectory: path.dirname(fileURLToPath(import.meta.url)),\n` +
273
+ `});\n\n` +
274
+ `export default [\n` +
275
+ ` ...compat.extends("next/core-web-vitals", "next/typescript"),\n` +
276
+ ` ...moduleBoundaries,\n` +
277
+ `];\n`
278
+ );
279
+ }
280
+
281
+ function addEslintReact() {
282
+ const { pkg } = readPackageJson();
283
+ const deps = Object.assign({}, pkg.dependencies, pkg.devDependencies);
284
+ const hasNext = Boolean(deps.next);
285
+
286
+ copyTemplateOverwrite("eslint.config.module-boundaries.mjs", RULES_FILE);
287
+ writeFileOverwrite("eslint.config.mjs", buildEslintEntryConfig(hasNext));
279
288
  copyTemplateOverwrite("prettier.module-boundaries.js", ".prettierrc.js");
289
+
280
290
  console.log(
281
- "Bu şablon TypeScript/React + modul-sərhəd (module-boundary) qaydaları üçündür.\n" +
291
+ "\nBu şablon TypeScript/React + modul-sərhəd (module-boundary) qaydaları üçündür.\n" +
282
292
  `Modul siyahısı ${CONFIG_FILE_NAME}.modules-dən avtomatik oxunur (boşdursa nümunə\n` +
283
293
  "siyahı istifadə olunur — şablon faylı içindəki şərhi oxuyun).\n\n" +
284
- "Diqqət: eslint.config.* (və ya yuxarıdakı side-file) və .prettierrc.js hər dəfə\n" +
285
- "şablonun ən son versiyası ilə üzərinə yazılır (commit-msg/pre-commit hook-ları kimi)\n" +
286
- " özünüzə uyğun etdiyiniz dəyişiklikləri qorumaq istəsəniz, əvvəlcə fərqli adla\n" +
287
- "backup götürün.\n",
294
+ "Diqqət: eslint.config.mjs, " +
295
+ RULES_FILE +
296
+ " .prettierrc.js hər dəfə\n" +
297
+ "şablonun ən son versiyası ilə üzərinə yazılır (commit-msg/pre-commit hook-ları kimi) —\n" +
298
+ "manual dəyişiklik lazım deyil, amma özünüzə uyğun etdiyiniz dəyişiklikləri qorumaq\n" +
299
+ "istəsəniz, əvvəlcə fərqli adla backup götürün.\n" +
300
+ (hasNext
301
+ ? "\nNext.js aşkarlandı → next/core-web-vitals və next/typescript qaydaları da\n" +
302
+ "avtomatik daxil edildi.\n"
303
+ : ""),
288
304
  );
289
- ensureDevDeps(ESLINT_REACT_DEV_DEPS);
290
- if (flatConfigAlreadyExists) {
305
+
306
+ const extraDeps = hasNext && !deps["eslint-config-next"] ? ["eslint-config-next"] : [];
307
+ ensureDevDeps(ESLINT_REACT_DEV_DEPS.concat(extraDeps));
308
+
309
+ if (fs.existsSync(path.join(cwd, ".eslintrc.js"))) {
291
310
  console.log(
292
- `\nUnutmayın: ${FLAT_CONFIG_SIDE_FILE} avtomatik import olunmuronu öz\n` +
293
- "eslint.config.* faylınıza əlavə etməsəniz, bu qaydalar heç vaxt işə düşməyəcək\n" +
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",
311
+ "\nDiqqət: layihənizdə hələ köhnə .eslintrc.js var silin. Flat config varkən\n" +
312
+ "ESLint onu oxumur, üstəlik özü indi adi .js fayl kimi lint xətaları törədə bilər.\n",
297
313
  );
298
314
  }
299
315
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commit-sheriff",
3
- "version": "1.6.2",
3
+ "version": "1.7.0",
4
4
  "description": "Shared husky commit-msg / pre-commit hooks enforcing ticket-based commit messages and branch naming",
5
5
  "main": "index.js",
6
6
  "bin": {