commit-sheriff 1.7.1 → 1.7.3
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 +32 -49
- package/bin/commit-sheriff.js +35 -37
- package/package.json +1 -1
- package/templates/pre-commit +8 -4
package/README.md
CHANGED
|
@@ -251,19 +251,19 @@ The default config added by `init` (only if you don't already have one):
|
|
|
251
251
|
```json
|
|
252
252
|
{
|
|
253
253
|
"lint-staged": {
|
|
254
|
-
"*.{js,jsx,ts,tsx}": ["eslint --fix
|
|
254
|
+
"*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"],
|
|
255
255
|
"*.{json,md,css,scss,html}": ["prettier --write"]
|
|
256
256
|
}
|
|
257
257
|
}
|
|
258
258
|
```
|
|
259
259
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
260
|
+
Warnings are printed but never block the commit — only actual errors (ESLint's non-zero exit) do.
|
|
261
|
+
That's the default ESLint behavior (`--fix` alone exits 0 on warning-only results), so a rule set to
|
|
262
|
+
`"warn"` (e.g. an unused import under some "recommended" configs) shows up in the terminal as a
|
|
263
|
+
heads-up but doesn't stop you from committing.
|
|
263
264
|
|
|
264
|
-
Adjust freely — `commit-sheriff` doesn't overwrite it once present.
|
|
265
|
-
|
|
266
|
-
change, add `--max-warnings=0` to it by hand to get the same behavior.
|
|
265
|
+
Adjust freely — `commit-sheriff` doesn't overwrite it once present. If you want warnings to block
|
|
266
|
+
the commit too, add `--max-warnings=0` to the `eslint --fix` command by hand.
|
|
267
267
|
|
|
268
268
|
## Advanced: ESLint module-boundary template (TypeScript/React)
|
|
269
269
|
|
|
@@ -276,40 +276,25 @@ npx commit-sheriff add-eslint-react
|
|
|
276
276
|
```
|
|
277
277
|
|
|
278
278
|
This ships as a **flat config** (`eslint.config.*`, ESLint 9+ format), not the legacy
|
|
279
|
-
`.eslintrc.js` format — see [why](#why-flat-config) below.
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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.
|
|
279
|
+
`.eslintrc.js` format — see [why](#why-flat-config) below.
|
|
280
|
+
|
|
281
|
+
It writes/refreshes three files every run, **always overwriting** whatever was there before (same
|
|
282
|
+
as the `commit-msg`/`pre-commit` hooks) — no manual merge step, ever. If you've hand-edited any of
|
|
283
|
+
these, back them up under a different name first:
|
|
284
|
+
|
|
285
|
+
| File | What it is |
|
|
286
|
+
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
287
|
+
| `eslint.config.commit-sheriff.mjs` | The actual ruleset: module-boundary rules + all the recommended plugins (TypeScript, React, hooks, a11y, sonarjs, import, testing-library, vitest, jest, prettier). |
|
|
288
|
+
| `eslint.config.mjs` | The entry point ESLint actually reads. Just imports the file above — or, if `next` is detected in your dependencies, also folds in Next.js's own ESLint rules so you don't lose Next-specific linting (`eslint-config-next` is auto-installed if missing). |
|
|
289
|
+
| `.prettierrc.js` | Matching Prettier config for the same ruleset. |
|
|
290
|
+
|
|
291
|
+
If your project already had its own `eslint.config.mjs` (e.g. Next.js's own
|
|
292
|
+
`create-next-app`-generated one), it gets replaced by the one above, not merged with it.
|
|
293
|
+
|
|
294
|
+
Next.js support is version-aware: `eslint-config-next` changed its internal shape between major
|
|
295
|
+
versions (a native flat-config array from v16 on, the older eslintrc-style object before that), and
|
|
296
|
+
the generated `eslint.config.mjs` detects which one is actually installed and uses the matching
|
|
297
|
+
pattern automatically — you don't need to think about this at all, it's handled for you.
|
|
313
298
|
|
|
314
299
|
The module list for the boundary rule isn't hardcoded — it's read straight from the `modules`
|
|
315
300
|
array in your root `.commitsheriffrc.json` (the same list the commit-msg/branch-name hooks use
|
|
@@ -348,16 +333,14 @@ it yourself.
|
|
|
348
333
|
#### Why flat config?
|
|
349
334
|
|
|
350
335
|
ESLint 9+ looks for `eslint.config.*` first and, if one is found, **ignores `.eslintrc.js`
|
|
351
|
-
entirely** — there's no fallback.
|
|
352
|
-
`eslint.config.mjs
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
official migration helper Next.js's own generated config uses internally) so the exact same rule
|
|
356
|
-
set works natively under ESLint 9+, whether or not the project already had its own flat config.
|
|
336
|
+
entirely** — there's no fallback. A legacy-only `.eslintrc.js` template sitting next to a
|
|
337
|
+
framework-generated `eslint.config.mjs` (Next.js scaffolds one by default) would simply never run —
|
|
338
|
+
this used to be a real bug here, where an unused import went completely unflagged because the
|
|
339
|
+
module-boundary rules were silently dead. Flat config avoids that trap entirely.
|
|
357
340
|
|
|
358
341
|
A legacy `.eslintrc.js` version of this template (`eslintrc.module-boundaries.js`) still ships
|
|
359
|
-
inside the package for reference/manual use on pure ESLint 8 projects with no flat config support
|
|
360
|
-
|
|
342
|
+
inside the package for reference/manual use on pure ESLint 8 projects with no flat config support,
|
|
343
|
+
but the CLI no longer writes it by default.
|
|
361
344
|
|
|
362
345
|
## Examples
|
|
363
346
|
|
package/bin/commit-sheriff.js
CHANGED
|
@@ -38,10 +38,9 @@ const DEFAULT_COMMIT_GUARD = {
|
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
const DEFAULT_LINT_STAGED = {
|
|
41
|
-
// --max-warnings=0
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
"*.{js,jsx,ts,tsx}": ["eslint --fix --max-warnings=0", "prettier --write"],
|
|
41
|
+
// No --max-warnings=0 here on purpose: warnings should be visible (ESLint prints them to the
|
|
42
|
+
// terminal either way) but must NOT block the commit — only actual errors (non-zero exit) do.
|
|
43
|
+
"*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"],
|
|
45
44
|
"*.{json,md,css,scss,html}": ["prettier --write"],
|
|
46
45
|
};
|
|
47
46
|
|
|
@@ -50,7 +49,7 @@ const CONFIG_FILE_NAME = ".commitsheriffrc.json";
|
|
|
50
49
|
function readPackageJson() {
|
|
51
50
|
const pkgPath = path.join(cwd, "package.json");
|
|
52
51
|
if (!fs.existsSync(pkgPath)) {
|
|
53
|
-
console.error(" package.json
|
|
52
|
+
console.error(" package.json not found. Run `npm init` first.");
|
|
54
53
|
process.exit(1);
|
|
55
54
|
}
|
|
56
55
|
return { pkgPath, pkg: JSON.parse(fs.readFileSync(pkgPath, "utf8")) };
|
|
@@ -60,7 +59,7 @@ function ensureHusky() {
|
|
|
60
59
|
const huskyDir = path.join(cwd, ".husky");
|
|
61
60
|
const shimPath = path.join(huskyDir, "_", "husky.sh");
|
|
62
61
|
if (!fs.existsSync(shimPath)) {
|
|
63
|
-
console.log("→ husky
|
|
62
|
+
console.log("→ setting up husky (npx husky init)...");
|
|
64
63
|
execSync("npx husky init", { cwd, stdio: "inherit" });
|
|
65
64
|
}
|
|
66
65
|
}
|
|
@@ -71,7 +70,7 @@ function copyHook(name) {
|
|
|
71
70
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
72
71
|
fs.copyFileSync(src, dest);
|
|
73
72
|
fs.chmodSync(dest, 0o755);
|
|
74
|
-
console.log(`✔ .husky/${name}
|
|
73
|
+
console.log(`✔ .husky/${name} written`);
|
|
75
74
|
}
|
|
76
75
|
|
|
77
76
|
function ensureConfigFile() {
|
|
@@ -79,19 +78,19 @@ function ensureConfigFile() {
|
|
|
79
78
|
const { pkg } = readPackageJson();
|
|
80
79
|
|
|
81
80
|
if (fs.existsSync(configPath)) {
|
|
82
|
-
console.log(`• ${CONFIG_FILE_NAME}
|
|
81
|
+
console.log(`• ${CONFIG_FILE_NAME} already exists, left untouched`);
|
|
83
82
|
return;
|
|
84
83
|
}
|
|
85
84
|
|
|
86
85
|
if (pkg.commitGuard) {
|
|
87
86
|
console.log(
|
|
88
|
-
`•
|
|
87
|
+
`• Found a legacy-style 'commitGuard' block in package.json — ${CONFIG_FILE_NAME} not created, the old configuration keeps working.`,
|
|
89
88
|
);
|
|
90
89
|
return;
|
|
91
90
|
}
|
|
92
91
|
|
|
93
92
|
fs.writeFileSync(configPath, JSON.stringify(DEFAULT_COMMIT_GUARD, null, 2) + "\n");
|
|
94
|
-
console.log(`✔ ${CONFIG_FILE_NAME}
|
|
93
|
+
console.log(`✔ ${CONFIG_FILE_NAME} written`);
|
|
95
94
|
}
|
|
96
95
|
|
|
97
96
|
// Installs whatever in `packages` isn't already a listed dependency/devDependency, so the hooks
|
|
@@ -112,10 +111,10 @@ function ensureDevDeps(packages) {
|
|
|
112
111
|
const deps = Object.assign({}, pkg.dependencies, pkg.devDependencies);
|
|
113
112
|
const missing = packages.filter((spec) => !deps[packageNameOf(spec)]);
|
|
114
113
|
if (!missing.length) {
|
|
115
|
-
console.log("•
|
|
114
|
+
console.log("• Required devDependencies are already installed, nothing to do");
|
|
116
115
|
return;
|
|
117
116
|
}
|
|
118
|
-
console.log(`→
|
|
117
|
+
console.log(`→ Installing: ${missing.join(" ")}`);
|
|
119
118
|
try {
|
|
120
119
|
// --legacy-peer-deps: some ESLint plugins lag behind the newest ESLint major in their
|
|
121
120
|
// declared peerDependencies (e.g. eslint-plugin-import still capping at ^9 while npm already
|
|
@@ -125,10 +124,10 @@ function ensureDevDeps(packages) {
|
|
|
125
124
|
cwd,
|
|
126
125
|
stdio: "inherit",
|
|
127
126
|
});
|
|
128
|
-
console.log("✔ devDependencies
|
|
127
|
+
console.log("✔ devDependencies installed");
|
|
129
128
|
} catch {
|
|
130
129
|
console.log(
|
|
131
|
-
"\n
|
|
130
|
+
"\n Automatic install failed (could be a network/npm error) — run it yourself:\n" +
|
|
132
131
|
` npm install --save-dev ${missing.join(" ")}\n`,
|
|
133
132
|
);
|
|
134
133
|
}
|
|
@@ -143,9 +142,9 @@ function mergeConfig() {
|
|
|
143
142
|
if (!pkg["lint-staged"]) {
|
|
144
143
|
pkg["lint-staged"] = DEFAULT_LINT_STAGED;
|
|
145
144
|
changed = true;
|
|
146
|
-
console.log("✔ package.json → lint-staged
|
|
145
|
+
console.log("✔ package.json → lint-staged configuration added");
|
|
147
146
|
} else {
|
|
148
|
-
console.log("• package.json
|
|
147
|
+
console.log("• package.json already has a lint-staged block, left untouched");
|
|
149
148
|
}
|
|
150
149
|
|
|
151
150
|
if (!pkg.scripts) pkg.scripts = {};
|
|
@@ -185,18 +184,16 @@ function init() {
|
|
|
185
184
|
|
|
186
185
|
if (isReactTypescriptProject()) {
|
|
187
186
|
console.log(
|
|
188
|
-
"\n• react + typescript
|
|
187
|
+
"\n• react + typescript detected → also adding the TS/React module-boundary ESLint template:",
|
|
189
188
|
);
|
|
190
189
|
addEslintReact();
|
|
191
190
|
} else {
|
|
192
191
|
console.log(
|
|
193
|
-
"\n(
|
|
192
|
+
"\n(Project not detected as TS/React — you can add it manually with `npx commit-sheriff add-eslint-react` if needed.)",
|
|
194
193
|
);
|
|
195
194
|
}
|
|
196
195
|
|
|
197
|
-
console.log(
|
|
198
|
-
`\nHazırdır. ${CONFIG_FILE_NAME} faylında 'project' və lazım olsa 'modules' dəyərlərini tənzimləyin.\n`,
|
|
199
|
-
);
|
|
196
|
+
console.log(`\nDone. Adjust 'project' and, if needed, 'modules' in ${CONFIG_FILE_NAME}.\n`);
|
|
200
197
|
}
|
|
201
198
|
|
|
202
199
|
const ESLINT_REACT_DEV_DEPS = [
|
|
@@ -241,7 +238,7 @@ function copyTemplateOverwrite(templateName, destName) {
|
|
|
241
238
|
const dest = path.join(cwd, destName);
|
|
242
239
|
const existed = fs.existsSync(dest);
|
|
243
240
|
fs.copyFileSync(src, dest);
|
|
244
|
-
console.log(existed ? `✔ ${destName}
|
|
241
|
+
console.log(existed ? `✔ ${destName} updated (overwritten)` : `✔ ${destName} written`);
|
|
245
242
|
return dest;
|
|
246
243
|
}
|
|
247
244
|
|
|
@@ -249,7 +246,7 @@ function writeFileOverwrite(destName, content) {
|
|
|
249
246
|
const dest = path.join(cwd, destName);
|
|
250
247
|
const existed = fs.existsSync(dest);
|
|
251
248
|
fs.writeFileSync(dest, content);
|
|
252
|
-
console.log(existed ? `✔ ${destName}
|
|
249
|
+
console.log(existed ? `✔ ${destName} updated (overwritten)` : `✔ ${destName} written`);
|
|
253
250
|
return dest;
|
|
254
251
|
}
|
|
255
252
|
|
|
@@ -349,25 +346,26 @@ function addEslintReact() {
|
|
|
349
346
|
copyTemplateOverwrite("prettier.module-boundaries.js", ".prettierrc.js");
|
|
350
347
|
|
|
351
348
|
console.log(
|
|
352
|
-
"\
|
|
353
|
-
`
|
|
354
|
-
"
|
|
355
|
-
"
|
|
349
|
+
"\nThis template is for TypeScript/React + module-boundary rules.\n" +
|
|
350
|
+
`The module list is read automatically from ${CONFIG_FILE_NAME}'s "modules" (falls back\n` +
|
|
351
|
+
"to an illustrative default if empty — read the comment inside the template file).\n\n" +
|
|
352
|
+
"Note: eslint.config.mjs, " +
|
|
356
353
|
RULES_FILE +
|
|
357
|
-
"
|
|
358
|
-
"
|
|
359
|
-
"
|
|
360
|
-
"
|
|
354
|
+
", and .prettierrc.js are overwritten\n" +
|
|
355
|
+
"with the latest template every run (same as the commit-msg/pre-commit hooks) — no manual\n" +
|
|
356
|
+
"merge step needed, but if you want to keep your own customizations, back them up under a\n" +
|
|
357
|
+
"different name first.\n" +
|
|
361
358
|
(hasNext
|
|
362
|
-
? "\nNext.js
|
|
363
|
-
"
|
|
359
|
+
? "\nNext.js detected → next/core-web-vitals and next/typescript rules are folded in\n" +
|
|
360
|
+
"automatically too.\n"
|
|
364
361
|
: ""),
|
|
365
362
|
);
|
|
366
363
|
|
|
367
364
|
if (fs.existsSync(path.join(cwd, ".eslintrc.js"))) {
|
|
368
365
|
console.log(
|
|
369
|
-
"\
|
|
370
|
-
"ESLint
|
|
366
|
+
"\nNote: your project still has a legacy .eslintrc.js — delete it. With a flat config\n" +
|
|
367
|
+
"present, ESLint never reads it, and it can itself start producing lint errors as a\n" +
|
|
368
|
+
"plain .js file.\n",
|
|
371
369
|
);
|
|
372
370
|
}
|
|
373
371
|
}
|
|
@@ -377,9 +375,9 @@ if (command === "init") {
|
|
|
377
375
|
} else if (command === "add-eslint-react") {
|
|
378
376
|
addEslintReact();
|
|
379
377
|
} else {
|
|
380
|
-
console.log("
|
|
378
|
+
console.log("Usage: npx commit-sheriff init");
|
|
381
379
|
console.log(
|
|
382
|
-
"
|
|
380
|
+
" : npx commit-sheriff add-eslint-react (optional, TS/React module-boundary ESLint template)",
|
|
383
381
|
);
|
|
384
382
|
process.exit(command ? 1 : 0);
|
|
385
383
|
}
|
package/package.json
CHANGED
package/templates/pre-commit
CHANGED
|
@@ -77,13 +77,17 @@ HAS_LINT_CONFIG=$(node -e "
|
|
|
77
77
|
" 2>/dev/null)
|
|
78
78
|
if [ "$HAS_LINT_CONFIG" = "1" ]; then
|
|
79
79
|
if node -e "require.resolve('lint-staged')" 2>/dev/null; then
|
|
80
|
-
|
|
80
|
+
# --verbose: lint-staged only prints a task's output when it *fails* by default, so a
|
|
81
|
+
# warning-only ESLint result (exit 0, commit still allowed through) would otherwise show
|
|
82
|
+
# nothing at all. --verbose keeps it printing on success too, so warnings stay visible as
|
|
83
|
+
# a heads-up without blocking the commit.
|
|
84
|
+
npx lint-staged --verbose || exit 1
|
|
81
85
|
else
|
|
82
86
|
echo ""
|
|
83
|
-
echo " package.json
|
|
84
|
-
echo " node_modules
|
|
87
|
+
echo " package.json has a 'lint-staged' configuration, but the package itself"
|
|
88
|
+
echo " was not found in node_modules — the lint/format check could not run."
|
|
85
89
|
echo ""
|
|
86
|
-
echo "
|
|
90
|
+
echo " To fix:"
|
|
87
91
|
echo " npm install --save-dev lint-staged"
|
|
88
92
|
echo ""
|
|
89
93
|
exit 1
|