commit-sheriff 1.5.0 → 1.6.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 +37 -14
- package/bin/commit-sheriff.js +70 -4
- package/package.json +1 -1
- package/templates/eslint.config.module-boundaries.mjs +6 -0
- package/templates/pre-commit +21 -6
package/README.md
CHANGED
|
@@ -56,11 +56,10 @@ Different repos tend to drift into different commit-message and branch-naming co
|
|
|
56
56
|
npm install --save-dev commit-sheriff husky
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
`lint-staged`, `eslint`, and `prettier`
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
```
|
|
59
|
+
`npx commit-sheriff init` installs `lint-staged`, `eslint`, and `prettier` for you automatically
|
|
60
|
+
(via `npm install --save-dev`) if they're missing, so the pre-commit lint/format step is actually
|
|
61
|
+
functional right away — you don't need to install them yourself first. This only happens as part
|
|
62
|
+
of `init` (see below), not from installing `commit-sheriff` itself.
|
|
64
63
|
|
|
65
64
|
## Usage
|
|
66
65
|
|
|
@@ -77,10 +76,19 @@ Run this once per repo, from the repo root (where `package.json` lives). This wo
|
|
|
77
76
|
3. Creates a default `.commitsheriffrc.json` file at the repo root **only if one doesn't already exist** (and only if there's no legacy `package.json` → `commitGuard` block either — see [Configuration](#configuration-commitsheriffrcjson)) — re-running `init` never overwrites your customized config.
|
|
78
77
|
4. Adds a default `lint-staged` block to `package.json` **only if one doesn't already exist**.
|
|
79
78
|
5. Adds a `"prepare": "husky"` npm script if missing, so hooks are (re)installed automatically after `npm install`.
|
|
80
|
-
6.
|
|
79
|
+
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.
|
|
81
81
|
|
|
82
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).
|
|
83
83
|
|
|
84
|
+
**Why the auto-install matters**: the pre-commit hook only runs `lint-staged` if it can actually
|
|
85
|
+
find the package — if it's missing, older versions of this hook **silently skipped linting
|
|
86
|
+
entirely**, so a broken/incomplete setup looked identical to a working one until someone noticed
|
|
87
|
+
bad code slipping through. The hook itself is now hardened too: if `package.json` has a
|
|
88
|
+
`lint-staged` config block but the package isn't actually installed (e.g. `node_modules` got
|
|
89
|
+
wiped, or someone added the config by hand), the commit now **fails loudly** with instructions,
|
|
90
|
+
instead of passing silently.
|
|
91
|
+
|
|
84
92
|
## Commit message format
|
|
85
93
|
|
|
86
94
|
```
|
|
@@ -227,7 +235,7 @@ Allowed `type` words in **commit messages**, following [Conventional Commits](ht
|
|
|
227
235
|
In addition to the branch name check, `pre-commit` conditionally runs (skipped when the project doesn't have the relevant tool):
|
|
228
236
|
|
|
229
237
|
- `npm run type-check` — if a `type-check` script exists in `package.json`
|
|
230
|
-
- `npx lint-staged` — if `lint-staged`
|
|
238
|
+
- `npx lint-staged` — if `package.json` has a `lint-staged` config block. If the block exists but the `lint-staged` package itself isn't actually installed, the commit **fails with instructions** rather than silently skipping the check (this used to silently no-op — see [What `init` does](#what-init-does)).
|
|
231
239
|
- `npx vitest related --run --passWithNoTests <staged .ts/.tsx files>` — if `vitest` is listed as a dependency and staged `.ts`/`.tsx` files exist (bounded to 60s via `timeout`/`gtimeout` when available)
|
|
232
240
|
|
|
233
241
|
Any failure here aborts the commit.
|
|
@@ -239,13 +247,19 @@ The default config added by `init` (only if you don't already have one):
|
|
|
239
247
|
```json
|
|
240
248
|
{
|
|
241
249
|
"lint-staged": {
|
|
242
|
-
"*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"],
|
|
250
|
+
"*.{js,jsx,ts,tsx}": ["eslint --fix --max-warnings=0", "prettier --write"],
|
|
243
251
|
"*.{json,md,css,scss,html}": ["prettier --write"]
|
|
244
252
|
}
|
|
245
253
|
}
|
|
246
254
|
```
|
|
247
255
|
|
|
248
|
-
|
|
256
|
+
`--max-warnings=0` is there on purpose: ESLint only fails (non-zero exit) on errors by default, so
|
|
257
|
+
a rule set to `"warn"` (e.g. many "recommended" configs' default severity for unused imports) would
|
|
258
|
+
otherwise print in the terminal but still let the commit through looking clean.
|
|
259
|
+
|
|
260
|
+
Adjust freely — `commit-sheriff` doesn't overwrite it once present. That also means this default
|
|
261
|
+
only lands on **new** setups; if your project already has a `lint-staged` block from before this
|
|
262
|
+
change, add `--max-warnings=0` to it by hand to get the same behavior.
|
|
249
263
|
|
|
250
264
|
## Advanced: ESLint module-boundary template (TypeScript/React)
|
|
251
265
|
|
|
@@ -292,15 +306,24 @@ just fill in `modules` in `.commitsheriffrc.json` and it picks it up automatical
|
|
|
292
306
|
This template assumes a `src/app/<module>/...` folder layout with `<module>.public.ts` barrel
|
|
293
307
|
files; adjust the paths inside the generated config if your structure differs.
|
|
294
308
|
|
|
295
|
-
It
|
|
309
|
+
It installs whatever it needs automatically (via `npm install --save-dev`, same as `init` does for
|
|
310
|
+
`lint-staged`/`eslint`/`prettier`), skipping anything already present:
|
|
296
311
|
|
|
297
312
|
```bash
|
|
298
|
-
npm install --save-dev typescript @typescript-eslint/parser
|
|
299
|
-
@eslint/js @eslint/eslintrc eslint-plugin-sonarjs
|
|
300
|
-
eslint-
|
|
301
|
-
eslint-plugin-
|
|
313
|
+
npm install --save-dev eslint@^9 typescript @typescript-eslint/parser \
|
|
314
|
+
@typescript-eslint/eslint-plugin @eslint/js @eslint/eslintrc eslint-plugin-sonarjs \
|
|
315
|
+
eslint-plugin-import eslint-plugin-prettier eslint-config-prettier eslint-plugin-react \
|
|
316
|
+
eslint-plugin-react-hooks eslint-plugin-jsx-a11y eslint-plugin-testing-library \
|
|
317
|
+
@vitest/eslint-plugin eslint-plugin-jest prettier
|
|
302
318
|
```
|
|
303
319
|
|
|
320
|
+
`eslint` is pinned to `^9` on purpose rather than left to resolve to whatever `latest` is — plugins
|
|
321
|
+
like `eslint-plugin-react` have historically taken a while to support each new ESLint major, so an
|
|
322
|
+
unpinned install can grab a version the rest of the toolchain doesn't work with yet.
|
|
323
|
+
|
|
324
|
+
If the install fails (offline, registry issue, etc.), it prints this exact command so you can run
|
|
325
|
+
it yourself.
|
|
326
|
+
|
|
304
327
|
#### Why flat config?
|
|
305
328
|
|
|
306
329
|
ESLint 9+ looks for `eslint.config.*` first and, if one is found, **ignores `.eslintrc.js`
|
package/bin/commit-sheriff.js
CHANGED
|
@@ -38,7 +38,10 @@ const DEFAULT_COMMIT_GUARD = {
|
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
const DEFAULT_LINT_STAGED = {
|
|
41
|
-
|
|
41
|
+
// --max-warnings=0: without this, ESLint only fails (non-zero exit) on errors — a warning
|
|
42
|
+
// (e.g. many "recommended" configs' default severity for unused imports) prints in the
|
|
43
|
+
// terminal but still lets `eslint --fix` exit 0, so the commit goes through looking clean.
|
|
44
|
+
"*.{js,jsx,ts,tsx}": ["eslint --fix --max-warnings=0", "prettier --write"],
|
|
42
45
|
"*.{json,md,css,scss,html}": ["prettier --write"],
|
|
43
46
|
};
|
|
44
47
|
|
|
@@ -91,6 +94,46 @@ function ensureConfigFile() {
|
|
|
91
94
|
console.log(`✔ ${CONFIG_FILE_NAME} yazıldı`);
|
|
92
95
|
}
|
|
93
96
|
|
|
97
|
+
// Installs whatever in `packages` isn't already a listed dependency/devDependency, so the hooks
|
|
98
|
+
// this tool wires up are actually functional right after `init`/`add-eslint-react` — not just
|
|
99
|
+
// documented as a copy-paste command the user has to remember to run separately. Falls back to
|
|
100
|
+
// printing the manual command if the install itself fails (offline, registry auth issue, etc.),
|
|
101
|
+
// so the user isn't left silently thinking it worked.
|
|
102
|
+
// Strips a trailing "@version" from an install spec to get the plain package name for looking it
|
|
103
|
+
// up in package.json — careful with scoped packages ("@eslint/js" has no version, but
|
|
104
|
+
// "@vitest/eslint-plugin@^1" does; only the *last* "@" is ever the version separator).
|
|
105
|
+
function packageNameOf(spec) {
|
|
106
|
+
const atIndex = spec.lastIndexOf("@");
|
|
107
|
+
return atIndex > 0 ? spec.slice(0, atIndex) : spec;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function ensureDevDeps(packages) {
|
|
111
|
+
const { pkg } = readPackageJson();
|
|
112
|
+
const deps = Object.assign({}, pkg.dependencies, pkg.devDependencies);
|
|
113
|
+
const missing = packages.filter((spec) => !deps[packageNameOf(spec)]);
|
|
114
|
+
if (!missing.length) {
|
|
115
|
+
console.log("• Lazımi devDependencies artıq quraşdırılıb, toxunulmadı");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
console.log(`→ Quraşdırılır: ${missing.join(" ")}`);
|
|
119
|
+
try {
|
|
120
|
+
// --legacy-peer-deps: some ESLint plugins lag behind the newest ESLint major in their
|
|
121
|
+
// declared peerDependencies (e.g. eslint-plugin-import still capping at ^9 while npm already
|
|
122
|
+
// resolves plain "eslint" to 10.x), which makes npm's default strict peer resolution abort
|
|
123
|
+
// the whole install with ERESOLVE even though the versions work fine together in practice.
|
|
124
|
+
execSync(`npm install --save-dev --legacy-peer-deps ${missing.join(" ")}`, {
|
|
125
|
+
cwd,
|
|
126
|
+
stdio: "inherit",
|
|
127
|
+
});
|
|
128
|
+
console.log("✔ devDependencies quraşdırıldı");
|
|
129
|
+
} catch {
|
|
130
|
+
console.log(
|
|
131
|
+
"\n Avtomatik quraşdırma alınmadı (şəbəkə/npm xətası ola bilər) — özünüz işlədin:\n" +
|
|
132
|
+
` npm install --save-dev ${missing.join(" ")}\n`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
94
137
|
function mergeConfig() {
|
|
95
138
|
const { pkgPath, pkg } = readPackageJson();
|
|
96
139
|
let changed = false;
|
|
@@ -114,6 +157,18 @@ function mergeConfig() {
|
|
|
114
157
|
if (changed) {
|
|
115
158
|
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
116
159
|
}
|
|
160
|
+
|
|
161
|
+
// Without this, the pre-commit hook's `lint-staged` step silently no-ops when the package
|
|
162
|
+
// itself isn't installed (it only checks whether *some* config/listing exists), so lint/format
|
|
163
|
+
// checks would never actually run on commit even though everything *looks* set up. `eslint`
|
|
164
|
+
// and `prettier` are included too since the default lint-staged config above already assumes
|
|
165
|
+
// both are runnable — otherwise the very first commit would fail on "command not found" instead
|
|
166
|
+
// of an actual lint/format problem. `eslint` is pinned to ^9 (not left to resolve to whatever
|
|
167
|
+
// "latest" is) because the plugin ecosystem this tool depends on (eslint-plugin-react, etc.)
|
|
168
|
+
// regularly lags behind ESLint's newest major for months — installing an unpinned "latest" has
|
|
169
|
+
// broken real installs (e.g. eslint-plugin-react crashing under ESLint 10's changed rule-context
|
|
170
|
+
// API) within days of a new major landing on npm.
|
|
171
|
+
ensureDevDeps(["lint-staged", "eslint@^9", "prettier"]);
|
|
117
172
|
}
|
|
118
173
|
|
|
119
174
|
function isReactTypescriptProject() {
|
|
@@ -145,6 +200,9 @@ function init() {
|
|
|
145
200
|
}
|
|
146
201
|
|
|
147
202
|
const ESLINT_REACT_DEV_DEPS = [
|
|
203
|
+
// Pinned (not left to resolve to "latest") — see the comment on the eslint@^9 install in
|
|
204
|
+
// mergeConfig() for why: the plugin ecosystem here regularly lags behind ESLint's newest major.
|
|
205
|
+
"eslint@^9",
|
|
148
206
|
"typescript",
|
|
149
207
|
"@typescript-eslint/parser",
|
|
150
208
|
"@typescript-eslint/eslint-plugin",
|
|
@@ -218,10 +276,18 @@ function addEslintReact() {
|
|
|
218
276
|
"Diqqət: eslint.config.* (və ya yuxarıdakı side-file) və .prettierrc.js hər dəfə\n" +
|
|
219
277
|
"şablonun ən son versiyası ilə üzərinə yazılır (commit-msg/pre-commit hook-ları kimi)\n" +
|
|
220
278
|
"— özünüzə uyğun etdiyiniz dəyişiklikləri qorumaq istəsəniz, əvvəlcə fərqli adla\n" +
|
|
221
|
-
"backup götürün.\n
|
|
222
|
-
"Lazımi devDependencies (özünüz quraşdırın, layihənizin real versiyalarına uyğun):\n" +
|
|
223
|
-
` npm install --save-dev ${ESLINT_REACT_DEV_DEPS.join(" ")}\n`,
|
|
279
|
+
"backup götürün.\n",
|
|
224
280
|
);
|
|
281
|
+
ensureDevDeps(ESLINT_REACT_DEV_DEPS);
|
|
282
|
+
if (flatConfigAlreadyExists) {
|
|
283
|
+
console.log(
|
|
284
|
+
`\nUnutmayın: ${FLAT_CONFIG_SIDE_FILE} avtomatik import olunmur — onu öz\n` +
|
|
285
|
+
"eslint.config.* faylınıza əlavə etməsəniz, bu qaydalar heç vaxt işə düşməyəcək\n" +
|
|
286
|
+
"(yuxarıdakı nümunəyə baxın). Həmçinin, əgər layihənizdə hələ də köhnə .eslintrc.js\n" +
|
|
287
|
+
"varsa, onu silin — flat config varkən ESLint onu oxumur, üstəlik özü də indi\n" +
|
|
288
|
+
"adi .js fayl kimi lint xətaları törədə bilər.\n",
|
|
289
|
+
);
|
|
290
|
+
}
|
|
225
291
|
}
|
|
226
292
|
|
|
227
293
|
if (command === "init") {
|
package/package.json
CHANGED
|
@@ -129,6 +129,12 @@ const moduleBoundaryConfig = [
|
|
|
129
129
|
"**/*.d.ts",
|
|
130
130
|
"**/*.config.js",
|
|
131
131
|
"**/*.config.mjs",
|
|
132
|
+
// Root-level CommonJS config dotfiles (this template's own .prettierrc.js among them) aren't
|
|
133
|
+
// part of the TS project's `include`, so `parserOptions.project: true` below fails to parse
|
|
134
|
+
// them with a "TSConfig does not include this file" error unless they're excluded here.
|
|
135
|
+
".eslintrc.js",
|
|
136
|
+
".prettierrc.js",
|
|
137
|
+
".prettierrc.cjs",
|
|
132
138
|
],
|
|
133
139
|
},
|
|
134
140
|
// FlatCompat translates the same legacy-style `extends`/`env`/`parserOptions`/`plugins`/`rules`
|
package/templates/pre-commit
CHANGED
|
@@ -66,13 +66,28 @@ if [ "$HAS_TYPE_CHECK" = "1" ]; then
|
|
|
66
66
|
npm run type-check || exit 1
|
|
67
67
|
fi
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
# Two separate signals on purpose: whether the project *wants* lint-staged (has a config block
|
|
70
|
+
# in package.json — commit-sheriff's own `init` writes one by default) vs. whether the package is
|
|
71
|
+
# *actually installed* right now. If a config exists but the package isn't resolvable, that's not
|
|
72
|
+
# "this project doesn't use lint-staged" — it's a broken setup (e.g. someone ran `npm prune`,
|
|
73
|
+
# skipped `npm install`, or deleted node_modules) and used to fail silently, letting broken commits
|
|
74
|
+
# through with zero lint/format enforcement. Now it fails loudly instead.
|
|
75
|
+
HAS_LINT_CONFIG=$(node -e "
|
|
76
|
+
process.stdout.write(require('./package.json')['lint-staged'] ? '1' : '0');
|
|
73
77
|
" 2>/dev/null)
|
|
74
|
-
if [ "$
|
|
75
|
-
|
|
78
|
+
if [ "$HAS_LINT_CONFIG" = "1" ]; then
|
|
79
|
+
if node -e "require.resolve('lint-staged')" 2>/dev/null; then
|
|
80
|
+
npx lint-staged || exit 1
|
|
81
|
+
else
|
|
82
|
+
echo ""
|
|
83
|
+
echo " package.json-da 'lint-staged' konfiqurasiyası var, amma paketin özü"
|
|
84
|
+
echo " node_modules-də tapılmadı — lint/format yoxlanışı keçirilə bilmədi."
|
|
85
|
+
echo ""
|
|
86
|
+
echo " Düzəltmək üçün:"
|
|
87
|
+
echo " npm install --save-dev lint-staged"
|
|
88
|
+
echo ""
|
|
89
|
+
exit 1
|
|
90
|
+
fi
|
|
76
91
|
fi
|
|
77
92
|
|
|
78
93
|
# ── Related tests (only if vitest is installed) ─────────────────────────────
|