lintlane 2.5.0 → 3.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Expo2Dev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -8,7 +8,8 @@ checks your CI will run — and see exactly what would fail — before you push.
8
8
  **What you get**
9
9
 
10
10
  - A shared Biome preset (`base`, `strict`, `next`) with auto-detection.
11
- - A pre-commit hook that formats and lints staged files.
11
+ - A pre-commit hook that formats and lints exactly what you staged, and never
12
+ stages anything you didn't.
12
13
  - Conventional Commits, enforced and guided: a step-by-step prompt, a
13
14
  commit-message check, a pre-push check and a CI check.
14
15
  - A GitHub Actions workflow running `biome ci`.
@@ -21,6 +22,7 @@ checks your CI will run — and see exactly what would fail — before you push.
21
22
  - [Commands at a glance](#commands-at-a-glance)
22
23
  - [Requirements](#requirements)
23
24
  - [`init`](#init--one-time-project-setup)
25
+ - [The pre-commit hook](#the-pre-commit-hook)
24
26
  - [`check`, `check:fix`, `ci`](#check-checkfix-ci--run-biome)
25
27
  - [Commit workflow](#commit-workflow)
26
28
  - [`commit`](#commit--guided-conventional-commit)
@@ -31,6 +33,7 @@ checks your CI will run — and see exactly what would fail — before you push.
31
33
  - [Dashboard (`ui`)](#dashboard-ui)
32
34
  - [Presets](#presets)
33
35
  - [Manual setup (without the CLI)](#manual-setup-without-the-cli)
36
+ - [Limitations](#limitations)
34
37
  - [Troubleshooting](#troubleshooting)
35
38
  - [Reference](#reference)
36
39
  - [Developing lintlane](#developing-lintlane)
@@ -39,22 +42,24 @@ checks your CI will run — and see exactly what would fail — before you push.
39
42
 
40
43
  ```sh
41
44
  # 1. In your React project (a git repository with a package.json):
42
- bunx lintlane init
45
+ npx lintlane init
43
46
 
44
47
  # 2. Fix what Biome can fix, see what's left:
45
- bunx lintlane check:fix
46
- bunx lintlane check
48
+ npx lintlane check:fix
49
+ npx lintlane check
47
50
 
48
51
  # 3. Open the dashboard to review issues and run your CI checks locally:
49
- bunx lintlane ui
52
+ npx lintlane ui
50
53
  ```
51
54
 
52
55
  From then on, `git commit` is guided and checked, and every push is checked
53
56
  before it leaves your machine.
54
57
 
55
- > The first `bunx lintlane init` downloads the package. After that it's
56
- > installed in your project, so `bunx lintlane …` (or `npx lintlane …`,
57
- > `bun run lintlane …`) runs your local copy without touching the network.
58
+ > Use your package manager's runner for that first command: `npx lintlane init`,
59
+ > `pnpm dlx lintlane init`, `yarn dlx lintlane init` or `bunx lintlane init`. It
60
+ > runs on Node.js, so you don't need Bun. After `init`, lintlane is installed in
61
+ > your project and `npx lintlane …` (or `pnpm exec lintlane …`, `yarn lintlane …`,
62
+ > `bunx lintlane …`) runs your local copy without touching the network.
58
63
 
59
64
  ## Commands at a glance
60
65
 
@@ -70,41 +75,41 @@ before it leaves your machine.
70
75
  | `lintlane -h` / `commit -h` / `check-commit -h` | Show help. |
71
76
 
72
77
  Every command except `init` runs from the project root. Examples below use
73
- `bunx lintlane …`; if you've installed it, `bun run lintlane …`,
74
- `npx lintlane …` or `node_modules/.bin/lintlane …` work too.
78
+ `npx lintlane …`; once lintlane is installed, `pnpm exec lintlane …`,
79
+ `yarn lintlane …`, `bunx lintlane …` and `node_modules/.bin/lintlane …` work too.
75
80
 
76
81
  ## Requirements
77
82
 
78
- - **Bun 1.2 or newer** to run the CLI (`init`, `check`, `check:fix`, `ci`,
79
- `commit`, `check-commit`, `ui`). The CLI is a Bun script.
80
- - **Your project does not need to use Bun.** `init` detects npm, pnpm, Yarn or
81
- Bun and uses that package manager.
82
- - **Git hooks only need Node** (18 or newer): they're plain Node scripts with no
83
- extra dependencies, so teammates don't need Bun to commit or push.
83
+ - **Node.js 22 or newer.** The CLI and the git hooks are plain Node.js scripts
84
+ with no runtime dependencies. **Bun is not required**, and neither is any
85
+ particular package manager: `npx`, `pnpm dlx`, `yarn dlx` and `bunx` all work
86
+ (Bun users can also run lintlane under the Bun runtime).
87
+ - **Your project can use any package manager.** `init` detects npm, pnpm, Yarn
88
+ or Bun and installs with that one (see [Package manager detection](#package-manager-detection)).
84
89
  - **Biome 2.2 up to (not including) 3.** It's a peer dependency; `init` installs it.
85
- - **Git**, for the commit workflow.
90
+ - **Git**, for the hooks and the commit workflow.
86
91
  - **macOS or Linux** for the guided prompt on a plain `git commit` (it reads the
87
92
  terminal through `/dev/tty`). Elsewhere that prompt is skipped; messages are
88
- still validated, and `lintlane commit` still works. Not tested on Windows.
93
+ still validated, and `lintlane commit` still works. Windows isn't tested yet.
89
94
 
90
95
  ## `init` — one-time project setup
91
96
 
92
97
  ```sh
93
- bunx lintlane init
98
+ npx lintlane init
94
99
  ```
95
100
 
96
101
  `init` installs `@biomejs/biome`, `lintlane` and (unless `--no-hooks`)
97
102
  `husky`, then writes:
98
103
 
99
- | File / change | Purpose |
100
- | ----------------------------- | ----------------------------------------------------------------- |
101
- | `biome.json` | Extends the preset. If one exists, it only tells you what to add. |
102
- | `package.json` scripts | `check`, `check:fix`, and `prepare` (runs husky). |
103
- | `.husky/pre-commit` | Biome on staged files, auto-fixing what it can. |
104
- | `.husky/commit-msg` | Rejects messages that aren't Conventional Commits. |
105
- | `.husky/prepare-commit-msg` | Guided prompt when you run a plain `git commit`. |
106
- | `.husky/pre-push` | Checks every commit about to be pushed. |
107
- | `.github/workflows/biome.yml` | `biome ci`, plus a commit-message check on pull requests. |
104
+ | File / change | Purpose |
105
+ | ----------------------------- | ------------------------------------------------------------------------------------------------------------- |
106
+ | `biome.json` | Extends the preset. If one exists, it only tells you what to add. |
107
+ | `package.json` scripts | `check`, `check:fix`, and `prepare` (runs husky). |
108
+ | `.husky/pre-commit` | Biome on the staged files, auto-fixing what it can (safely: see [the pre-commit hook](#the-pre-commit-hook)). |
109
+ | `.husky/commit-msg` | Rejects messages that aren't Conventional Commits. |
110
+ | `.husky/prepare-commit-msg` | Guided prompt when you run a plain `git commit`. |
111
+ | `.husky/pre-push` | Checks every commit about to be pushed. |
112
+ | `.github/workflows/biome.yml` | `biome ci`, plus a commit-message check on pull requests. |
108
113
 
109
114
  ### Flags
110
115
 
@@ -118,12 +123,12 @@ bunx lintlane init
118
123
  | `-h`, `--help` | Show usage. |
119
124
 
120
125
  ```sh
121
- bunx lintlane init # everything, preset auto-detected
122
- bunx lintlane init --preset strict # force a preset
123
- bunx lintlane init --no-commit-lint # lint hook only, no commit rules
124
- bunx lintlane init --no-hooks --no-ci # just biome.json and scripts
125
- bunx lintlane init --skip-install # you install packages yourself
126
- bunx lintlane init --preset next --no-ci
126
+ npx lintlane init # everything, preset auto-detected
127
+ npx lintlane init --preset strict # force a preset
128
+ npx lintlane init --no-commit-lint # lint hook only, no commit rules
129
+ npx lintlane init --no-hooks --no-ci # just biome.json and scripts
130
+ npx lintlane init --skip-install # you install packages yourself
131
+ npx lintlane init --preset next --no-ci
127
132
  ```
128
133
 
129
134
  ### Preset auto-detection
@@ -145,10 +150,25 @@ changes which preset is used. `init` says what it detected either way.
145
150
 
146
151
  ### Package manager detection
147
152
 
148
- `init` reads the `packageManager` field in `package.json`, otherwise whichever
149
- lockfile is present (`bun.lock`, `package-lock.json`, `pnpm-lock.yaml`,
150
- `yarn.lock`), and falls back to Bun. It uses that package manager's install
151
- command and writes matching CI setup steps.
153
+ `init` picks the package manager in this order, and tells you which signal it used:
154
+
155
+ 1. the `packageManager` field in `package.json` (for example `pnpm@9.1.0`);
156
+ 2. a lockfile: `bun.lock` / `bun.lockb`, `package-lock.json` / `npm-shrinkwrap.json`,
157
+ `pnpm-lock.yaml` or `yarn.lock`;
158
+ 3. the tool you launched lintlane with (`npx`, `pnpm dlx`, `yarn dlx`, `bunx`);
159
+ 4. otherwise **npm**, which ships with Node.js.
160
+
161
+ It then uses that package manager's install command (exact dev dependencies) and
162
+ writes matching setup steps into the CI workflow. If the package manager isn't
163
+ installed, `init` stops with a clear message before writing anything; you can
164
+ re-run with `--skip-install` and install the packages yourself.
165
+
166
+ | Package manager | Install command `init` runs |
167
+ | --------------- | ------------------------------------------------ |
168
+ | npm | `npm install --save-dev --save-exact <packages>` |
169
+ | pnpm | `pnpm add --save-dev --save-exact <packages>` |
170
+ | Yarn | `yarn add --dev --exact <packages>` |
171
+ | Bun | `bun add --dev --exact <packages>` |
152
172
 
153
173
  ### Safe to re-run
154
174
 
@@ -157,19 +177,25 @@ workflow file; it prints a `!` warning for whatever already exists and writes
157
177
  only what's missing. That makes it the way to **upgrade**: update the package,
158
178
  then run `init` again to pick up hooks added in newer versions.
159
179
 
180
+ One exception, for safety: an untouched copy of the **old pre-commit hook**
181
+ (the one that ended with `git update-index --again`, written by lintlane up to
182
+ 2.5.1) is replaced with the current one, because the old hook could stage changes
183
+ you hadn't staged. If you edited that hook yourself, `init` leaves it alone and
184
+ warns you to replace it.
185
+
160
186
  ## `check`, `check:fix`, `ci` — run Biome
161
187
 
162
188
  These wrap the Biome binary installed in your project, so you never need
163
189
  Biome's own flags:
164
190
 
165
191
  ```sh
166
- bunx lintlane check # biome check . (read-only)
167
- bunx lintlane check:fix # biome check --write . (applies safe fixes)
168
- bunx lintlane ci # biome ci --reporter=github . (CI-formatted output)
192
+ npx lintlane check # biome check . (read-only)
193
+ npx lintlane check:fix # biome check --write . (applies safe fixes)
194
+ npx lintlane ci # biome ci --reporter=github . (CI-formatted output)
169
195
 
170
- bunx lintlane check src/ # limit to paths
171
- bunx lintlane check:fix src/components/Button.tsx
172
- bunx lintlane ci src/ tests/
196
+ npx lintlane check src/ # limit to paths
197
+ npx lintlane check:fix src/components/Button.tsx
198
+ npx lintlane ci src/ tests/
173
199
  ```
174
200
 
175
201
  The exit code is Biome's, so they work in scripts and CI. If Biome isn't
@@ -177,13 +203,53 @@ installed yet they say so and point you to `init`.
177
203
 
178
204
  `init` also adds plain `check` and `check:fix` scripts to `package.json` that
179
205
  call `biome` directly, so `npm run check` (or `pnpm`/`yarn`/`bun run check`)
180
- works without Bun:
206
+ works without lintlane's own command:
181
207
 
182
208
  ```sh
183
209
  npm run check
184
210
  npm run check:fix
185
211
  ```
186
212
 
213
+ ## The pre-commit hook
214
+
215
+ `.husky/pre-commit` runs `node_modules/lintlane/bin/pre-commit.mjs`, which runs
216
+ `biome check --write` on the files you staged and puts the result back into your
217
+ commit. It is built so that **it can never stage, discard or rewrite anything you
218
+ didn't stage**:
219
+
220
+ - Only the staged files are touched. Unstaged edits to other files and untracked
221
+ files are never read, formatted or staged.
222
+ - A **partially staged** file (you staged some hunks and kept editing) is handled
223
+ correctly: Biome sees exactly the staged content, the fix is written to the
224
+ index, and Biome's changes are merged back into your working copy with a
225
+ three-way merge, so your unstaged edits survive byte for byte. If your unstaged
226
+ edit overlaps a formatting change, your working copy is left exactly as you
227
+ wrote it (the staged content is still formatted), and you get a notice.
228
+ - Nothing is ever staged with `git add`, `git add -A` or `git update-index --again`.
229
+ Fixed content goes into the index entry of each staged path only.
230
+ - If Biome reports an error it can't fix, the commit is blocked **and every file is
231
+ put back exactly as it was**: the index, the working tree and HEAD are
232
+ unchanged. It is all-or-nothing, so a fixable file isn't modified when another
233
+ staged file fails.
234
+ - Deleted and renamed files, file names with spaces, non-ASCII characters or a
235
+ leading dash, executable bits, symlinks, files Biome ignores and file types it
236
+ doesn't know are all handled.
237
+ - Ctrl+C or a termination signal during the run restores your working files
238
+ before exiting. Your working copy of a partially staged file is saved under
239
+ `.git/lintlane-backup` while the hook runs.
240
+
241
+ If the hook is killed in a way that can't be caught (`kill -9`, a power cut), the
242
+ next commit stops with a message instead of continuing, and you recover with:
243
+
244
+ ```sh
245
+ node node_modules/lintlane/bin/pre-commit.mjs --restore
246
+ ```
247
+
248
+ Why not `lint-staged`? It solves the same problem, but it adds a dependency to
249
+ every project, needs its own configuration, and hides your unstaged changes with
250
+ `git stash` while tasks run. This hook has no dependencies, uses the same Biome
251
+ command and ignore rules as `lintlane check`, and never stashes.
252
+
187
253
  ## Commit workflow
188
254
 
189
255
  `init` sets up [Conventional Commits](https://www.conventionalcommits.org)
@@ -217,7 +283,7 @@ BREAKING CHANGE: what breaks and how to migrate
217
283
 
218
284
  ```sh
219
285
  git add <files>
220
- bunx lintlane commit # alias: bunx lintlane c
286
+ npx lintlane commit # alias: npx lintlane c
221
287
  ```
222
288
 
223
289
  Asks seven questions — **type, scope, subject, body, breaking change, footer,
@@ -237,19 +303,19 @@ period, an over-long header) are explained and asked again.
237
303
  | `-- <git args>` | Everything after `--` is passed to `git commit`. |
238
304
 
239
305
  ```sh
240
- bunx lintlane commit # ask, then commit the staged changes
241
- bunx lintlane commit -- -a # stage all tracked changes, then commit
242
- bunx lintlane commit -- -a -S # …and sign the commit
243
- bunx lintlane commit -- -s # add a Signed-off-by line
244
- bunx lintlane commit -- -n # skip git hooks
245
- bunx lintlane commit --dry-run # just print the message
246
- bunx lintlane commit --dry-run --write-message-to-file msg.txt
247
- bunx lintlane commit -l 72 # stricter header limit for this commit
306
+ npx lintlane commit # ask, then commit the staged changes
307
+ npx lintlane commit -- -a # stage all tracked changes, then commit
308
+ npx lintlane commit -- -a -S # …and sign the commit
309
+ npx lintlane commit -- -s # add a Signed-off-by line
310
+ npx lintlane commit -- -n # skip git hooks
311
+ npx lintlane commit --dry-run # just print the message
312
+ npx lintlane commit --dry-run --write-message-to-file msg.txt
313
+ npx lintlane commit -l 72 # stricter header limit for this commit
248
314
  ```
249
315
 
250
316
  **Retrying after a failed commit.** If a hook (for example Biome in pre-commit)
251
317
  rejects the commit after you've answered the questions, the message is kept.
252
- Fix the problem, stage the fix, then run `bunx lintlane commit --retry` — no
318
+ Fix the problem, stage the fix, then run `npx lintlane commit --retry` — no
253
319
  re-typing. The saved message is cleared once a commit succeeds. Set
254
320
  `retryAfterFailure` (see [Settings](#settings)) to reuse it automatically.
255
321
 
@@ -258,11 +324,11 @@ re-typing. The saved message is cleared once a commit succeeds. Set
258
324
  Give it exactly one thing to check:
259
325
 
260
326
  ```sh
261
- bunx lintlane check-commit --message "feat: add login" # a message
262
- echo "feat: add login" | bunx lintlane check-commit # …or from stdin
263
- bunx lintlane check-commit --commit-msg-file .git/COMMIT_EDITMSG
264
- bunx lintlane check-commit --rev-range origin/main..HEAD # every commit in a range
265
- bunx lintlane check-commit -d # shorthand for <default branch>..HEAD
327
+ npx lintlane check-commit --message "feat: add login" # a message
328
+ echo "feat: add login" | npx lintlane check-commit # …or from stdin
329
+ npx lintlane check-commit --commit-msg-file .git/COMMIT_EDITMSG
330
+ npx lintlane check-commit --rev-range origin/main..HEAD # every commit in a range
331
+ npx lintlane check-commit -d # shorthand for <default branch>..HEAD
266
332
  ```
267
333
 
268
334
  | Flag | Effect |
@@ -370,9 +436,9 @@ it to one you already have:
370
436
  would fail before you push:
371
437
 
372
438
  ```sh
373
- bunx lintlane ui # opens http://127.0.0.1:4321
374
- bunx lintlane ui --port 5000 # use another port
375
- bunx lintlane ui --no-open # print the URL without opening a browser
439
+ npx lintlane ui # opens http://127.0.0.1:4321
440
+ npx lintlane ui --port 5000 # use another port
441
+ npx lintlane ui --no-open # print the URL without opening a browser
376
442
  ```
377
443
 
378
444
  | Flag | Effect |
@@ -459,8 +525,8 @@ add your own ignores:
459
525
  To migrate an existing config into Biome first:
460
526
 
461
527
  ```sh
462
- bunx biome migrate eslint --write
463
- bunx biome migrate prettier --write
528
+ npx biome migrate eslint --write
529
+ npx biome migrate prettier --write
464
530
  ```
465
531
 
466
532
  ## Manual setup (without the CLI)
@@ -491,8 +557,8 @@ Add scripts:
491
557
  **Git hooks**
492
558
 
493
559
  ```sh
494
- bun add -d husky
495
- bunx husky init
560
+ npm install --save-dev husky # or: pnpm add -D husky / yarn add -D husky / bun add -d husky
561
+ npx husky init
496
562
  ```
497
563
 
498
564
  Put these in `.husky/`:
@@ -500,10 +566,16 @@ Put these in `.husky/`:
500
566
  `.husky/pre-commit`
501
567
 
502
568
  ```sh
503
- ./node_modules/.bin/biome check --write --staged --no-errors-on-unmatched --files-ignore-unknown=true
504
- git update-index --again
569
+ if [ -f node_modules/lintlane/bin/pre-commit.mjs ]; then
570
+ node node_modules/lintlane/bin/pre-commit.mjs
571
+ else
572
+ echo "lintlane: node_modules/lintlane not found; skipping staged-file checks. Run your package manager's install." >&2
573
+ fi
505
574
  ```
506
575
 
576
+ Don't write your own `biome check --write --staged` followed by `git add` or
577
+ `git update-index --again`: that stages unstaged edits in partially staged files.
578
+
507
579
  `.husky/commit-msg`
508
580
 
509
581
  ```sh
@@ -527,12 +599,30 @@ node node_modules/lintlane/bin/pre-push-check.mjs "$1" "$2"
527
599
 
528
600
  Commits with errors Biome can't fix are blocked until you resolve them.
529
601
 
602
+ ## Limitations
603
+
604
+ - **Windows is untested.** Subprocesses are started through `.cmd` shims there, but
605
+ none of it has been run on Windows. The guided prompt on `git commit` needs
606
+ `/dev/tty` and is skipped on Windows (`lintlane commit` works).
607
+ - **Yarn:** command generation is covered by tests, but a real Yarn install isn't
608
+ part of the automated end-to-end run here, so Yarn is the least exercised.
609
+ - **Line-ending and content filters.** The pre-commit hook compares your working
610
+ files with the raw staged content. If `core.autocrlf`, `.gitattributes` eol rules
611
+ or a filter makes them differ byte for byte, the file is treated as partially
612
+ staged: it's formatted in the index and your working copy is left as it was.
613
+ - **Merges.** During a merge commit, the hook also checks the files the merge staged.
614
+ - **Monorepos.** The hook runs from the repository root and uses the repository's
615
+ `node_modules`; a `package.json` in a subfolder isn't supported yet.
616
+ - **Concurrent edits.** If you save a file in your editor _while_ the hook is
617
+ running, that file is left as saved and the hook says so.
618
+
530
619
  ## Troubleshooting
531
620
 
532
- **`bunx lintlane …` downloads the package each time, or says "command not found".**
533
- Outside a project that has it installed, `bunx` fetches `lintlane` from npm.
534
- Run `bunx lintlane init` in the project first (or install `lintlane` as a dev
535
- dependency), after which `bunx lintlane …` uses the local copy.
621
+ **`npx lintlane …` downloads the package each time, or says "command not found".**
622
+ Outside a project that has it installed, `npx` (or `pnpm dlx`, `yarn dlx`, `bunx`)
623
+ fetches `lintlane` from npm.
624
+ Run `npx lintlane init` in the project first (or install `lintlane` as a dev
625
+ dependency), after which `npx lintlane …` uses the local copy.
536
626
 
537
627
  **Hooks don't run.** Run your package manager's install (the `prepare` script
538
628
  runs husky), or run `node_modules/.bin/husky` once. Check
@@ -541,14 +631,29 @@ runs husky), or run `node_modules/.bin/husky` once. Check
541
631
  **The guided prompt doesn't appear on `git commit`.** It's skipped when `CI` or
542
632
  `LINTLANE_NO_PROMPT` is set, when you pass `-m`/`-F` or amend, when there's no
543
633
  terminal (GUI clients, some editors) and on systems without `/dev/tty`. Run
544
- `bunx lintlane commit` for the same steps. The message is validated regardless.
634
+ `npx lintlane commit` for the same steps. The message is validated regardless.
545
635
 
546
636
  **A commit was rejected and I lost my message.** Use
547
- `bunx lintlane commit --retry` if you committed through `lintlane commit`.
637
+ `npx lintlane commit --retry` if you committed through `lintlane commit`.
548
638
 
549
639
  **The pre-commit hook blocks my commit.** Biome found an error it can't fix.
550
- Run `bunx lintlane check` to see it, or open `bunx lintlane ui`. Fix it and
551
- commit again.
640
+ Run `npx lintlane check` to see it, or open `npx lintlane ui`. Fix it and
641
+ commit again. Your files were left exactly as they were.
642
+
643
+ **"An earlier lintlane run was interrupted".** The hook was killed before it could
644
+ put your working files back. Run
645
+ `node node_modules/lintlane/bin/pre-commit.mjs --restore`, then commit again.
646
+
647
+ **My unstaged edit was reformatted / not reformatted.** The hook only formats what
648
+ you staged. When it formats a partially staged file it merges those changes into
649
+ your working copy without touching your unstaged edit. If your edit overlaps the
650
+ formatting change, your file is left exactly as you wrote it. Stage the file
651
+ fully, or run `npx lintlane check:fix`, to format it.
652
+
653
+ **`npx lintlane init` fails with "pnpm was not found" (or yarn, bun).** The project
654
+ says it uses that package manager (a lockfile or `packageManager`) but it isn't
655
+ installed. Install it, or run `npx lintlane init --skip-install` and install
656
+ `@biomejs/biome`, `lintlane` and `husky` yourself.
552
657
 
553
658
  **Push blocked: "commit message(s) don't follow Conventional Commits".** Reword
554
659
  the listed commits with `git rebase -i` (or `git commit --amend` for the latest),
@@ -568,11 +673,12 @@ scan them.
568
673
 
569
674
  **Environment variables**
570
675
 
571
- | Variable | Effect |
572
- | -------------------- | --------------------------------------------------------------------------------- |
573
- | `LINTLANE_NO_PROMPT` | Set to any value to skip the guided prompt on `git commit`. |
574
- | `CI` | When set, the guided prompt is skipped. The CI tab sets it for the steps it runs. |
575
- | `NO_COLOR` | Turns off colors in the prompt. |
676
+ | Variable | Effect |
677
+ | ----------------------- | ------------------------------------------------------------------------------------------------------ |
678
+ | `LINTLANE_NO_PROMPT` | Set to any value to skip the guided prompt on `git commit`. |
679
+ | `CI` | When set, the guided prompt is skipped. The CI tab sets it for the steps it runs. |
680
+ | `NO_COLOR` | Turns off colors in the prompt. |
681
+ | `LINTLANE_INSTALL_SPEC` | What `init` installs instead of `lintlane@<version>` (a tarball or folder), for testing a local build. |
576
682
 
577
683
  **Exit codes**
578
684
 
@@ -585,30 +691,50 @@ scan them.
585
691
 
586
692
  **Files lintlane may create or use**
587
693
 
588
- | Path | What |
589
- | ------------------------------------------------------------------- | ---------------------------------------------- |
590
- | `biome.json` | Biome config extending a preset. |
591
- | `.husky/pre-commit`, `commit-msg`, `prepare-commit-msg`, `pre-push` | Git hooks. |
592
- | `.github/workflows/biome.yml` | CI workflow. |
593
- | `.git/lintlane-commit-msg` | Your last message, kept after a failed commit. |
694
+ | Path | What |
695
+ | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
696
+ | `biome.json` | Biome config extending a preset. |
697
+ | `.husky/pre-commit`, `commit-msg`, `prepare-commit-msg`, `pre-push` | Git hooks. |
698
+ | `.github/workflows/biome.yml` | CI workflow. |
699
+ | `.git/lintlane-commit-msg` | Your last message, kept after a failed commit. |
700
+ | `.git/lintlane-backup/` | Your working copies, only while the pre-commit hook runs (or after it was killed). |
594
701
 
595
702
  ## Developing lintlane
596
703
 
597
- To try a local build in a test app before publishing:
704
+ Requires Node.js 22 or newer. There are no runtime dependencies; the dev
705
+ dependency is Biome.
706
+
707
+ ```sh
708
+ npm test # unit + integration tests (about 15 seconds, no network)
709
+ npm run test:e2e # real npm / pnpm / bun installs against the registry (needs network)
710
+ npm run check # lint and format this repository
711
+ ```
712
+
713
+ The tests use Node's built-in test runner and real temporary git repositories:
714
+
715
+ | File | What it covers |
716
+ | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
717
+ | `test/pre-commit.test.mjs` | The pre-commit hook: partial staging, failures, odd file names, interruption. |
718
+ | `test/package-managers.test.mjs` | Package-manager detection and install/workflow command generation (pure). |
719
+ | `test/cli.test.mjs` | Running on Node without Bun, packaging, `init` for new and existing projects, generated hooks. Package managers are **mocked** here. |
720
+ | `test/ui.test.mjs` | The dashboard: startup, token auth, every API route, Stop killing a run. |
721
+ | `test/e2e.test.mjs` | **Real** package managers, the packed tarball and real husky. Skipped unless `LINTLANE_E2E=1`; a package manager that isn't installed is reported as skipped. |
722
+
723
+ To try a local build in a throwaway app before publishing:
598
724
 
599
725
  ```sh
600
726
  # In the lintlane repo:
601
- npm pack --pack-destination /tmp/lintlane-pack # builds lintlane-<version>.tgz
727
+ npm pack --pack-destination /tmp/lintlane-pack # builds lintlane-<version>.tgz
602
728
 
603
- # In a throwaway app (a git repo with a package.json):
604
- bun add --dev --exact @biomejs/biome husky /tmp/lintlane-pack/lintlane-<version>.tgz
605
- node_modules/.bin/lintlane init --skip-install # the package isn't on npm yet
606
- node_modules/.bin/husky
729
+ # In a throwaway app (a git repo with a package.json), from its directory:
730
+ LINTLANE_INSTALL_SPEC=/tmp/lintlane-pack/lintlane-<version>.tgz \
731
+ npx --package=/tmp/lintlane-pack/lintlane-<version>.tgz lintlane init
607
732
  ```
608
733
 
609
- If you rebuild without changing the version, Bun may reuse its cached tarball:
610
- run `bun pm cache rm`, delete `node_modules/lintlane`, and reinstall (or
611
- bump the version first). `npm pack --dry-run` lists what would be published.
734
+ `init` then installs your tarball, Biome and husky with the app's package manager
735
+ and does everything a real run does. `npm pack --dry-run` lists what would be
736
+ published. If you rebuild without changing the version, a package manager may
737
+ reuse its cached tarball: bump the version, or clear the cache and reinstall.
612
738
 
613
739
  ## License
614
740