@williamthorsen/nmr 0.39.0 โ 0.41.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/.meta/changelog.json +145 -4
- package/.readyup/kits/default.js +47 -32
- package/.readyup/manifest.json +12 -7
- package/CHANGELOG.md +44 -0
- package/README.md +36 -75
- package/agents/README.md +8 -8
- package/agents/guidance/rulebooks/nmr.md +8 -8
- package/bin/ensure-prepublish-hooks.js +1 -1
- package/bin/nmr-clean.js +1 -1
- package/bin/nmr-compile.js +1 -1
- package/bin/nmr-fmt.js +1 -1
- package/bin/nmr-report-catalog.js +1 -1
- package/bin/nmr-report-overrides.js +1 -1
- package/bin/nmr-taze.js +1 -1
- package/bin/nmr.js +1 -1
- package/dist/esm/UserError.d.ts +1 -2
- package/dist/esm/check-cache.d.ts +59 -61
- package/dist/esm/commands/build-output.d.ts +35 -36
- package/dist/esm/commands/build.d.ts +13 -14
- package/dist/esm/commands/build.js +3 -3
- package/dist/esm/commands/clean.d.ts +6 -6
- package/dist/esm/commands/ensure-prepublish-hooks.d.ts +5 -5
- package/dist/esm/commands/fmt.d.ts +8 -8
- package/dist/esm/commands/fmt.js +1 -1
- package/dist/esm/commands/report-catalog.d.ts +5 -7
- package/dist/esm/commands/report-catalog.js +1 -1
- package/dist/esm/commands/report-overrides.d.ts +0 -2
- package/dist/esm/commands/report-overrides.js +1 -1
- package/dist/esm/commands/taze.d.ts +9 -8
- package/dist/esm/config.d.ts +5 -5
- package/dist/esm/config.js +3 -3
- package/dist/esm/context.d.ts +1 -2
- package/dist/esm/default-scripts.d.ts +5 -6
- package/dist/esm/defineConfig.d.ts +1 -1
- package/dist/esm/git-ignored-paths.d.ts +9 -0
- package/dist/esm/git-ignored-paths.js +10 -0
- package/dist/esm/glyphs.d.ts +3 -3
- package/dist/esm/help.d.ts +2 -2
- package/dist/esm/helpers/clampToBytes.d.ts +4 -5
- package/dist/esm/helpers/config-path.d.ts +0 -2
- package/dist/esm/helpers/createBoundedBuffer.d.ts +2 -2
- package/dist/esm/helpers/deriveExcerpt.d.ts +4 -5
- package/dist/esm/helpers/duration.d.ts +8 -9
- package/dist/esm/helpers/filter-selection.d.ts +19 -19
- package/dist/esm/helpers/findClosestName.d.ts +1 -1
- package/dist/esm/helpers/hook-name.d.ts +2 -5
- package/dist/esm/helpers/package-json.d.ts +9 -8
- package/dist/esm/helpers/readStringValues.d.ts +2 -3
- package/dist/esm/helpers/reportClosing.d.ts +8 -9
- package/dist/esm/helpers/transcript.d.ts +13 -13
- package/dist/esm/helpers/type-guards.d.ts +1 -0
- package/dist/esm/output-style.d.ts +12 -13
- package/dist/esm/prettier.d.ts +2 -2
- package/dist/esm/recording.d.ts +20 -15
- package/dist/esm/recording.js +7 -7
- package/dist/esm/replay-assembly.d.ts +10 -10
- package/dist/esm/report-format.d.ts +14 -19
- package/dist/esm/reportCliFailure.d.ts +2 -3
- package/dist/esm/resolve-scripts.d.ts +4 -5
- package/dist/esm/resolveBinStyles.d.ts +4 -5
- package/dist/esm/resolver.d.ts +14 -18
- package/dist/esm/runCli.d.ts +6 -6
- package/dist/esm/runCli.js +104 -27
- package/dist/esm/runner.d.ts +19 -19
- package/dist/esm/steps.d.ts +45 -42
- package/dist/esm/steps.js +6 -6
- package/dist/esm/taze.d.ts +1 -1
- package/dist/esm/tests.d.ts +18 -13
- package/dist/esm/tests.js +2 -2
- package/dist/esm/tiers.d.ts +27 -23
- package/dist/esm/tiers.js +6 -2
- package/dist/esm/types.d.ts +5 -5
- package/dist/esm/verbosity.d.ts +22 -20
- package/dist/esm/verdict.d.ts +33 -33
- package/dist/esm/verdict.js +1 -1
- package/dist/esm/vitest-source-resolution.d.ts +11 -11
- package/dist/esm/vitest-source-resolution.js +2 -2
- package/dist/esm/vitest.d.ts +25 -24
- package/dist/esm/vitest.js +16 -8
- package/dist/esm/workspace.d.ts +4 -4
- package/docs/check-cache.md +57 -57
- package/docs/prettier.md +10 -10
- package/docs/reporting.md +46 -46
- package/docs/scripts.md +29 -29
- package/docs/upgrades.md +18 -18
- package/docs/utilities.md +13 -13
- package/docs/vitest.md +64 -62
- package/docs/workspace.md +19 -19
- package/package.json +6 -5
package/.meta/changelog.json
CHANGED
|
@@ -1,4 +1,144 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.41.0",
|
|
4
|
+
"date": "2026-10-02",
|
|
5
|
+
"sections": [
|
|
6
|
+
{
|
|
7
|
+
"title": "๐ Features",
|
|
8
|
+
"audience": "all",
|
|
9
|
+
"items": [
|
|
10
|
+
{
|
|
11
|
+
"description": "Adds a hint to `nmr` that, when a command fails, names any of nmr's own flags written after the command name and shows the corrected invocation, such as `nmr -q check`. (#956)",
|
|
12
|
+
"hash": "21e1759f8eb8257d81bc5bf1e533dbd496d99017",
|
|
13
|
+
"entry": 1
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"version": "0.40.0",
|
|
21
|
+
"date": "2026-10-01",
|
|
22
|
+
"sections": [
|
|
23
|
+
{
|
|
24
|
+
"title": "๐ Features",
|
|
25
|
+
"audience": "all",
|
|
26
|
+
"items": [
|
|
27
|
+
{
|
|
28
|
+
"description": "Makes `checkTestFileConventions` and the readyup kit's tier and placement checks skip untracked paths that git ignores, so that local build output such as a stale `.netlify/` directory no longer fails the check. (#898)",
|
|
29
|
+
"hash": "15132d5e797b734f1fc78bce7abc5d82c7e81974",
|
|
30
|
+
"entry": 1
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"description": "Excludes untracked paths that git ignores from collection in `defineVitestConfig` and `defineRootVitestConfig`, so that Vitest does not run a test file that the conventions check skips. (#898)",
|
|
34
|
+
"hash": "15132d5e797b734f1fc78bce7abc5d82c7e81974",
|
|
35
|
+
"entry": 2
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"description": "Rewords the `nmr --log` refusal for a run that the check-result cache does not cover to `the check-result cache does not apply here (NMR_DEBUG=1 reports why)`, and renames the matching `docs/check-cache.md` heading to \"When the cache does not apply\", which changes its anchor. (#923)",
|
|
39
|
+
"hash": "71d8c3dfb45aca872ca897ee589aae6351a1f516",
|
|
40
|
+
"entry": 1
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"title": "๐ Bug fixes",
|
|
46
|
+
"audience": "all",
|
|
47
|
+
"items": [
|
|
48
|
+
{
|
|
49
|
+
"description": "Rewords the messages printed by `nmr`, its bin wrappers, and its readyup kit, which used figures such as \"sits under\" and \"holds\", em-dashes, and \"no\" as a determiner, so that a diagnosis such as \"-F/--filter did not match any workspace\" states its condition directly. (#932)",
|
|
50
|
+
"hash": "570f1103437d22b81b556f2d2b2e02f1fc32942c",
|
|
51
|
+
"entry": 1
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"description": "Fixes the issue that the `nmr` rulebook recommended `-q` without stating that nmr reads its own flags only before the command name, which led agents to run `nmr ci -q` and fail when `nmr-fmt` rejected the flag. (#948)",
|
|
55
|
+
"hash": "b7cf3b3dfcd901db11546f153ec6de61580e6a69",
|
|
56
|
+
"entry": 1
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"title": "โป๏ธ Refactoring",
|
|
62
|
+
"audience": "dev",
|
|
63
|
+
"items": [
|
|
64
|
+
{
|
|
65
|
+
"description": "Replaces every `.sort()` call that suppressed `unicorn/no-array-sort` with `toSorted()` and drops the spread copy wherever the source was already an array. (#925)",
|
|
66
|
+
"hash": "4680fb4e865ffce0bd135fac20d49b766b156992",
|
|
67
|
+
"entry": 1
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"title": "๐งช Tests",
|
|
73
|
+
"audience": "dev",
|
|
74
|
+
"items": [
|
|
75
|
+
{
|
|
76
|
+
"description": "Stubs `GIT_CEILING_DIRECTORIES` to the fixture's parent in every test that expects git to find no repository, so that a repository above the temp directory can no longer change the test's result. (#923)",
|
|
77
|
+
"hash": "71d8c3dfb45aca872ca897ee589aae6351a1f516",
|
|
78
|
+
"entry": 2
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"description": "Moves the `everyBinTargetIsACommittedWrapper` tests from `kit-bin-checks.unit.test.ts` to `kit-bin-checks.tool.test.ts`, because they spawn git through readyup's `listTrackedFiles`. (#923)",
|
|
82
|
+
"hash": "71d8c3dfb45aca872ca897ee589aae6351a1f516",
|
|
83
|
+
"entry": 3
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"title": "โ๏ธ Tooling",
|
|
89
|
+
"audience": "dev",
|
|
90
|
+
"items": [
|
|
91
|
+
{
|
|
92
|
+
"description": "Adds a check to `bootstrap.tool.test.ts` that fails when a package's `prepare` runs the build bootstrap without either depending on nmr-core or passing `--conditions nmr-source`, which would make `pnpm install` fail on a fresh clone. (#903)",
|
|
93
|
+
"hash": "b00b17d277197c1b18133da2e5258cf52719946a",
|
|
94
|
+
"entry": 3
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"title": "๐ฆ Dependencies",
|
|
100
|
+
"audience": "dev",
|
|
101
|
+
"items": [
|
|
102
|
+
{
|
|
103
|
+
"description": "Adds `esbuild` 0.28.2 to the pnpm catalog and declares it as a devDependency of `nmr`, `release-kit`, and `v11y-check`, because `readyup` declares it only as an optional peer and `rdy compile` failed in those packages once a regenerated lockfile stopped installing it. (#944)",
|
|
104
|
+
"hash": "6dffd5df5bc4209dc225d08390ef2b67fe381f1b",
|
|
105
|
+
"entry": 2
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"title": "๐ Documentation",
|
|
111
|
+
"audience": "dev",
|
|
112
|
+
"items": [
|
|
113
|
+
{
|
|
114
|
+
"description": "Revises the comments in `packages/nmr/src` and `packages/nmr/.readyup/kits/default.ts` against the comment-discipline rules, correcting comments that misstated the code, such as the Levenshtein distance quoted in `runCli.ts` and a `root-tests-config` kit check that does not exist. (#919)",
|
|
115
|
+
"hash": "84fe905d4142059425d82eb8d10821b687f522c2",
|
|
116
|
+
"entry": 1
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"description": "Corrects the truncation example in `packages/nmr/docs/reporting.md`, replacing 90 seconds, which renders as `1m 30s` whether it is truncated or rounded, with 119.9 seconds, which truncates to `1m 59s`. (#919)",
|
|
120
|
+
"hash": "84fe905d4142059425d82eb8d10821b687f522c2",
|
|
121
|
+
"entry": 2
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"description": "Rewrites the sentences in the README, the guides under `docs/`, the agent rulebook, and the code comments of `@williamthorsen/nmr` that broke the repository's writing rules on plain speech, em-dashes, second-person address, and relative clauses. (#932)",
|
|
125
|
+
"hash": "570f1103437d22b81b556f2d2b2e02f1fc32942c",
|
|
126
|
+
"entry": 2
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"description": "Replaces \"where\" in two doc comments in `packages/nmr/src/check-cache.ts` and in the fixture comment in two tier-budget tests, because each use introduced a consequence rather than a place. (#945)",
|
|
130
|
+
"hash": "6f2c93ee0c73810ebd4cb4ab76618c00235edc42",
|
|
131
|
+
"entry": 1
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"description": "Writes `-q` as `nmr -q <command>` in `docs/reporting.md` and in the README's verbosity paragraph. (#948)",
|
|
135
|
+
"hash": "b7cf3b3dfcd901db11546f153ec6de61580e6a69",
|
|
136
|
+
"entry": 2
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
},
|
|
2
142
|
{
|
|
3
143
|
"version": "0.39.0",
|
|
4
144
|
"date": "2026-09-22",
|
|
@@ -229,7 +369,8 @@
|
|
|
229
369
|
"items": [
|
|
230
370
|
{
|
|
231
371
|
"description": "Keep global git ignore rules and attributes out of test subprocesses (#773)",
|
|
232
|
-
"body": "Fixes an issue where a test that spawns `git` read ignore rules and attributes from outside the repository, so a suite could pass or fail on whatever a developer or their machine happened to have configured. The setup file that `defineVitestConfig` loads by default now isolates both, alongside the git configuration already covered.\
|
|
372
|
+
"body": "Fixes an issue where a test that spawns `git` read ignore rules and attributes from outside the repository, so a suite could pass or fail on whatever a developer or their machine happened to have configured. The setup file that `defineVitestConfig` loads by default now isolates both, alongside the git configuration already covered.\n\nMigration: Where a test needs its own `core.excludesFile` or `core.attributesFile`, pass it on the invocation as `git -c core.excludesFile=<path>`. An environment-injected key now outranks repository-local config.",
|
|
373
|
+
"migration": "Where a test needs its own `core.excludesFile` or `core.attributesFile`, pass it on the invocation as `git -c core.excludesFile=<path>`. An environment-injected key now outranks repository-local config.",
|
|
233
374
|
"hash": "59b454ff3344a7c00d766f041a5862faa1ee6f1f"
|
|
234
375
|
}
|
|
235
376
|
]
|
|
@@ -466,7 +607,7 @@
|
|
|
466
607
|
"items": [
|
|
467
608
|
{
|
|
468
609
|
"description": "Carry the test-tier convention in the ambient rulebook (#689)",
|
|
469
|
-
"body": "Adds the test-tier convention to `nmr.md`, the ambient rulebook `codeassembly sync` deploys to each consuming repo's agent guidance. The guidance describes the `<subject>[.<aspect>].<tier>.test.ts` grammar and the four tiers, and warns that tests outside a `__tests__` directory are ignored. Tests not explicitly attributed to a tier are flagged by the ReadyUp kit bundled with `nmr`.\
|
|
610
|
+
"body": "Adds the test-tier convention to `nmr.md`, the ambient rulebook `codeassembly sync` deploys to each consuming repo's agent guidance. The guidance describes the `<subject>[.<aspect>].<tier>.test.ts` grammar and the four tiers, and warns that tests outside a `__tests__` directory are ignored. Tests not explicitly attributed to a tier are flagged by the ReadyUp kit bundled with `nmr`.\n\nThe previous guidance is removed from `AGENTS.md`.",
|
|
470
611
|
"hash": "a1fa0e0eb19362f0506ce4f98cd527b205baf1e7"
|
|
471
612
|
},
|
|
472
613
|
{
|
|
@@ -503,7 +644,7 @@
|
|
|
503
644
|
},
|
|
504
645
|
{
|
|
505
646
|
"description": "Adopt `isError` and consolidate errno narrowing on a shared helper (#697)",
|
|
506
|
-
"body": "Replaces every hand-rolled `instanceof Error` narrowing with `hasErrnoCode`, a new predicate exported from `@williamthorsen/nmr-core`, or with `isError` from `@williamthorsen/toolbelt.errors`. Previously the errno question was answered by inline copies alongside two private helpers of differing shape; both helpers are removed.\
|
|
647
|
+
"body": "Replaces every hand-rolled `instanceof Error` narrowing with `hasErrnoCode`, a new predicate exported from `@williamthorsen/nmr-core`, or with `isError` from `@williamthorsen/toolbelt.errors`. Previously the errno question was answered by inline copies alongside two private helpers of differing shape; both helpers are removed.\n\nSeparately, `eslint.config.ts` now bans `instanceof Error` in every position rather than in a ternary alone.",
|
|
507
648
|
"hash": "c5e8e8a5e006b451e8c5aeea75d35772926d503c"
|
|
508
649
|
}
|
|
509
650
|
]
|
|
@@ -581,7 +722,7 @@
|
|
|
581
722
|
"items": [
|
|
582
723
|
{
|
|
583
724
|
"description": "Derive the shelled-nmr remedy from the declaration site and reject a malformed package.json (#658)",
|
|
584
|
-
"body": "Fixes a shelled-nmr warning that named `.config/nmr.config.ts` as the remedy for every step it reported, including the `package.json` entries that tier cannot resolve. The line now names the relevant declaration and remedy. Shelled nmr can now be detected not only in a step's first token but anywhere in the step.\
|
|
725
|
+
"body": "Fixes a shelled-nmr warning that named `.config/nmr.config.ts` as the remedy for every step it reported, including the `package.json` entries that tier cannot resolve. The line now names the relevant declaration and remedy. Shelled nmr can now be detected not only in a step's first token but anywhere in the step.\n\nSeparately, a configuration mistake now reports as a plain message rather than a Node stack trace.",
|
|
585
726
|
"hash": "74ea98c361cf13e213d257952c582a4ebd631d9a"
|
|
586
727
|
}
|
|
587
728
|
]
|
package/.readyup/kits/default.js
CHANGED
|
@@ -64,7 +64,7 @@ var workspaceScripts = {
|
|
|
64
64
|
"test:unit": "pnpm exec vitest --project unit",
|
|
65
65
|
"test:watch": `pnpm exec vitest ${GATE_PROJECTS} --watch`,
|
|
66
66
|
typecheck: "tsgo --noEmit",
|
|
67
|
-
// The command is a string because neither half names an nmr command:
|
|
67
|
+
// The command is a string because neither half names an nmr command: Both are binaries.
|
|
68
68
|
upgrade: "nmr-report-catalog && nmr-taze",
|
|
69
69
|
"view-coverage": "open coverage/index.html"
|
|
70
70
|
};
|
|
@@ -75,8 +75,8 @@ var rootScripts = {
|
|
|
75
75
|
build: ["-R build"],
|
|
76
76
|
check: [TYPECHECK_STEP, "fmt:check", "lint:check", "test"],
|
|
77
77
|
"check:strict": [TYPECHECK_STEP, "fmt:check", "lint:strict", "test"],
|
|
78
|
-
// Excludes the audit, which in CI has a workflow of its own. The
|
|
79
|
-
//
|
|
78
|
+
// Excludes the audit, which in CI has a workflow of its own. The narrowed check runs against the build, so
|
|
79
|
+
// the build declines the arguments rather than being narrowed by them.
|
|
80
80
|
ci: [{ run: "build", shouldDeclineArguments: true }, "check:strict"],
|
|
81
81
|
clean: "nmr-clean",
|
|
82
82
|
fix: ["lint", "fmt"],
|
|
@@ -86,8 +86,8 @@ var rootScripts = {
|
|
|
86
86
|
lint: "eslint --fix .",
|
|
87
87
|
"lint:check": "eslint .",
|
|
88
88
|
"lint:strict": "strict-lint",
|
|
89
|
-
// The audit
|
|
90
|
-
// dependency tree,
|
|
89
|
+
// The audit takes seconds and `ci` takes minutes, so the cheap gate fails first. The audit reads the
|
|
90
|
+
// dependency tree, and an argument narrowing the code under test does not say anything about that tree.
|
|
91
91
|
prepush: [{ run: "audit", shouldDeclineArguments: true }, "ci"],
|
|
92
92
|
"report-overrides": "nmr-report-overrides",
|
|
93
93
|
"root:check": [ROOT_TYPECHECK_STEP, "fmt:check", "root:lint:check", "root:test"],
|
|
@@ -99,7 +99,7 @@ var rootScripts = {
|
|
|
99
99
|
"root:test:tool": "vitest --config ./vitest.root.config.ts --project tool",
|
|
100
100
|
"root:test:unit": "vitest --config ./vitest.root.config.ts --project unit",
|
|
101
101
|
"root:typecheck": "tsgo --noEmit",
|
|
102
|
-
//
|
|
102
|
+
// Includes the override report for the same reason `upgrade` does: Both end in the tool that rewrites a
|
|
103
103
|
// `pnpm.overrides` block, so both need the reporter's rejection ahead of them.
|
|
104
104
|
"root:upgrade": "nmr-report-overrides && nmr-taze",
|
|
105
105
|
test: ["root:test", "-R test"],
|
|
@@ -109,9 +109,9 @@ var rootScripts = {
|
|
|
109
109
|
"test:unit": ["root:test:unit", "-R test:unit"],
|
|
110
110
|
"test:watch": `vitest ${GATE_PROJECTS} --watch`,
|
|
111
111
|
// Neither step is narrowable, so `nmr typecheck <file>` is rejected rather than checking that file under
|
|
112
|
-
// default options at the root and
|
|
112
|
+
// default options at the root and searching for it in every package.
|
|
113
113
|
typecheck: [ROOT_TYPECHECK_STEP, { run: "-R typecheck", shouldDeclineArguments: true }],
|
|
114
|
-
// The command is a string because neither half names an nmr command:
|
|
114
|
+
// The command is a string because neither half names an nmr command: Both are binaries, and a composite
|
|
115
115
|
// element can name only a command.
|
|
116
116
|
upgrade: "nmr-report-overrides && nmr-taze --recursive"
|
|
117
117
|
};
|
|
@@ -124,6 +124,20 @@ function getDefaultRootScripts() {
|
|
|
124
124
|
// src/tiers.ts
|
|
125
125
|
import { readdirSync } from "node:fs";
|
|
126
126
|
import path from "node:path";
|
|
127
|
+
|
|
128
|
+
// src/git-ignored-paths.ts
|
|
129
|
+
import { spawnSync } from "node:child_process";
|
|
130
|
+
function listGitIgnoredPaths(dir) {
|
|
131
|
+
const result = spawnSync(
|
|
132
|
+
"git",
|
|
133
|
+
["ls-files", "-z", "--others", "--ignored", "--exclude-standard", "--directory", "--no-empty-directory"],
|
|
134
|
+
{ cwd: dir, encoding: "utf8" }
|
|
135
|
+
);
|
|
136
|
+
if (result.error !== void 0 || result.status !== 0) return [];
|
|
137
|
+
return result.stdout.split("\0").filter((entry) => entry !== "").toSorted();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/tiers.ts
|
|
127
141
|
var TEST_DIR = "__tests__";
|
|
128
142
|
var TEST_EXTENSIONS = "{ts,tsx}";
|
|
129
143
|
var TEST_GLOB_PREFIX = `**/${TEST_DIR}/**`;
|
|
@@ -149,9 +163,9 @@ function collectTestFiles(dir, relativeDir, isInTestDir, context) {
|
|
|
149
163
|
for (const entry of entries) {
|
|
150
164
|
const relativePath = relativeDir === "" ? entry.name : `${relativeDir}/${entry.name}`;
|
|
151
165
|
if (entry.isDirectory()) {
|
|
152
|
-
if (context.pruned.has(entry.name)) continue;
|
|
166
|
+
if (context.pruned.has(entry.name) || context.ignoredPaths.has(`${relativePath}/`)) continue;
|
|
153
167
|
collectTestFiles(path.join(dir, entry.name), relativePath, isInTestDir || entry.name === TEST_DIR, context);
|
|
154
|
-
} else if (isInTestDir !== context.misplaced && TEST_FILE_PATTERN.test(entry.name)) {
|
|
168
|
+
} else if (isInTestDir !== context.misplaced && TEST_FILE_PATTERN.test(entry.name) && !context.ignoredPaths.has(relativePath)) {
|
|
155
169
|
context.foundPaths.push(relativePath);
|
|
156
170
|
}
|
|
157
171
|
}
|
|
@@ -159,6 +173,7 @@ function collectTestFiles(dir, relativeDir, isInTestDir, context) {
|
|
|
159
173
|
function walkTestFiles(rootDir, { excludedBasenames = [] }, misplaced) {
|
|
160
174
|
const context = {
|
|
161
175
|
foundPaths: [],
|
|
176
|
+
ignoredPaths: new Set(listGitIgnoredPaths(rootDir)),
|
|
162
177
|
misplaced,
|
|
163
178
|
pruned: /* @__PURE__ */ new Set([...TEST_COLLECTION_EXCLUDE, ...excludedBasenames])
|
|
164
179
|
};
|
|
@@ -210,21 +225,21 @@ var default_default = defineRdyKit({
|
|
|
210
225
|
severity: "warn",
|
|
211
226
|
quiet: true,
|
|
212
227
|
check: toolVersionsHasNoPnpm,
|
|
213
|
-
fix: "Remove pnpm from .tool-versions
|
|
228
|
+
fix: "Remove pnpm from .tool-versions, and manage it through the packageManager field"
|
|
214
229
|
},
|
|
215
230
|
{
|
|
216
231
|
name: "no package.json declares a pnpm field",
|
|
217
232
|
severity: "error",
|
|
218
233
|
quiet: true,
|
|
219
234
|
check: () => noPnpmFieldInPackageJson(),
|
|
220
|
-
fix: "Move these settings into pnpm-workspace.yaml, quoting each version under `overrides`, or run `pnpx codemod run pnpm-v10-to-v11`. pnpm 11
|
|
235
|
+
fix: "Move these settings into pnpm-workspace.yaml, quoting each version under `overrides`, or run `pnpx codemod run pnpm-v10-to-v11`. pnpm 11 does not read any key from the `pnpm` field, so an override left there pins nothing while an upgrade run with `--write` goes on rewriting it"
|
|
221
236
|
},
|
|
222
237
|
{
|
|
223
238
|
name: ".config/nmr.config.ts uses defineConfig",
|
|
224
239
|
severity: "recommend",
|
|
225
240
|
skip: () => !fileExists(".config/nmr.config.ts") ? "no nmr config file" : false,
|
|
226
241
|
check: () => fileContains(".config/nmr.config.ts", /defineConfig/),
|
|
227
|
-
fix: "Wrap
|
|
242
|
+
fix: "Wrap the config export with defineConfig() from @williamthorsen/nmr/config for type safety"
|
|
228
243
|
},
|
|
229
244
|
// `error` because falling short produces wrong results rather than a failure. Names and fixes are
|
|
230
245
|
// getters because their version constants are declared below the kit.
|
|
@@ -236,7 +251,7 @@ var default_default = defineRdyKit({
|
|
|
236
251
|
skip: () => !hasDevDependency("eslint") ? "eslint not installed" : false,
|
|
237
252
|
check: hasSupportedEslintVersion,
|
|
238
253
|
get fix() {
|
|
239
|
-
return `pnpm add --save-dev eslint@^${MIN_ESLINT_VERSION}
|
|
254
|
+
return `pnpm add --save-dev eslint@^${MIN_ESLINT_VERSION}. Earlier releases resolve config from the working directory, so nmr's root lint and lint:check would apply the root config to every package`;
|
|
240
255
|
}
|
|
241
256
|
},
|
|
242
257
|
{
|
|
@@ -247,7 +262,7 @@ var default_default = defineRdyKit({
|
|
|
247
262
|
skip: () => !hasDevDependency("@williamthorsen/strict-lint") ? "strict-lint not installed" : false,
|
|
248
263
|
check: hasSupportedStrictLintVersion,
|
|
249
264
|
get fix() {
|
|
250
|
-
return `pnpm add --save-dev @williamthorsen/strict-lint@^${MIN_STRICT_LINT_VERSION}
|
|
265
|
+
return `pnpm add --save-dev @williamthorsen/strict-lint@^${MIN_STRICT_LINT_VERSION}. Earlier releases pin ESLint to one config and resolve ceilings from the working directory, so nmr's root lint:strict would report the wrong rules for every package`;
|
|
251
266
|
}
|
|
252
267
|
},
|
|
253
268
|
// -- Root script cleanup -------------------------------------------------
|
|
@@ -256,7 +271,7 @@ var default_default = defineRdyKit({
|
|
|
256
271
|
severity: "warn",
|
|
257
272
|
quiet: true,
|
|
258
273
|
check: noRedundantRootScripts,
|
|
259
|
-
fix: "Remove scripts from root package.json that nmr provides as built-in root scripts
|
|
274
|
+
fix: "Remove scripts from root package.json that nmr provides as built-in root scripts, and invoke them through nmr directly"
|
|
260
275
|
},
|
|
261
276
|
// -- Git hooks -----------------------------------------------------------
|
|
262
277
|
{
|
|
@@ -278,13 +293,13 @@ var default_default = defineRdyKit({
|
|
|
278
293
|
name: "every bin target is a committed wrapper",
|
|
279
294
|
severity: "error",
|
|
280
295
|
check: () => everyBinTargetIsACommittedWrapper(),
|
|
281
|
-
fix: `Point each listed entry at a committed wrapper under bin/ that loads the build output at runtime. pnpm links a workspace package's bins during the install's link phase, which runs before anything is built, so a target that is not committed does not exist when pnpm
|
|
296
|
+
fix: `Point each listed entry at a committed wrapper under bin/ that loads the build output at runtime. pnpm links a workspace package's bins during the install's link phase, which runs before anything is built, so a target that is not committed does not exist when pnpm tries to link it. pnpm never retries, which leaves the link missing for the life of the node_modules tree`
|
|
282
297
|
},
|
|
283
298
|
{
|
|
284
299
|
name: "every bin wrapper's build-output target is covered by files",
|
|
285
300
|
severity: "warn",
|
|
286
301
|
check: () => everyBinWrapperTargetIsCoveredByFiles(),
|
|
287
|
-
fix: "Add the build output directory to `files` in each listed package. npm and pnpm publish the bin target itself whatever `files` says, so the wrapper
|
|
302
|
+
fix: "Add the build output directory to `files` in each listed package. npm and pnpm publish the bin target itself whatever `files` says, so the wrapper is published pointing at build output missing from the tarball"
|
|
288
303
|
},
|
|
289
304
|
// -- Vitest projects -----------------------------------------------------
|
|
290
305
|
{
|
|
@@ -298,7 +313,7 @@ var default_default = defineRdyKit({
|
|
|
298
313
|
name: "every vitest.config builds on @williamthorsen/nmr/vitest",
|
|
299
314
|
severity: "error",
|
|
300
315
|
check: () => vitestConfigBuildsOnSharedConfig(),
|
|
301
|
-
fix: "Replace each listed config with: import { defineVitestConfig } from '@williamthorsen/nmr/vitest'; export default defineVitestConfig(); -- a config that does not call the factory
|
|
316
|
+
fix: "Replace each listed config with: import { defineVitestConfig } from '@williamthorsen/nmr/vitest'; export default defineVitestConfig(); -- a config that does not call the factory does not declare any projects, so every tier-selecting test command fails against it. Pass the repo's own settings to the factory as layers to keep them"
|
|
302
317
|
},
|
|
303
318
|
{
|
|
304
319
|
name: "vitest.root.config.ts builds on @williamthorsen/nmr/vitest",
|
|
@@ -310,27 +325,27 @@ var default_default = defineRdyKit({
|
|
|
310
325
|
name: "every workspace with a Vite config has a Vitest config",
|
|
311
326
|
severity: "error",
|
|
312
327
|
check: () => everyViteConfigHasVitestConfig(),
|
|
313
|
-
fix: "Add a vitest.config.ts calling defineVitestConfig() from @williamthorsen/nmr/vitest beside each listed vite.config -- Vitest stops its config search at the first directory
|
|
328
|
+
fix: "Add a vitest.config.ts calling defineVitestConfig() from @williamthorsen/nmr/vitest beside each listed vite.config -- Vitest stops its config search at the first directory containing either name, so the Vite config otherwise wins and the projects model is never reached"
|
|
314
329
|
},
|
|
315
330
|
{
|
|
316
331
|
name: "the test suite gates the test-file conventions",
|
|
317
332
|
severity: "warn",
|
|
318
333
|
check: () => testSuiteGatesTestFileConventions(),
|
|
319
|
-
fix: "Add a test of the repo's own under a __tests__ directory that declares the check: import { checkTestFileConventions } from '@williamthorsen/nmr/tests'; checkTestFileConventions(); -- passing `excludedBasenames` the directory names that the repo passes to `testCollectionExclude`. Without it
|
|
334
|
+
fix: "Add a test of the repo's own under a __tests__ directory that declares the check: import { checkTestFileConventions } from '@williamthorsen/nmr/tests'; checkTestFileConventions(); -- passing `excludedBasenames` the directory names that the repo passes to `testCollectionExclude`. Without it, the repo's test run does not report an untiered or misplaced test file, and this kit reports them in its place against only nmr's built-in exclusions and the paths that git ignores"
|
|
320
335
|
},
|
|
321
336
|
{
|
|
322
337
|
name: "every test file names its isolation tier",
|
|
323
338
|
severity: "error",
|
|
324
339
|
skip: () => describeConventionsGuardSkip(),
|
|
325
340
|
check: () => everyTestFileNamesItsTier(),
|
|
326
|
-
fix: `Rename each to <subject>[.<aspect>].<tier>.test.ts, naming one of ${TIER_NAMES.join(", ")}. Use tool for a test that reaches a program the environment
|
|
341
|
+
fix: `Rename each to <subject>[.<aspect>].<tier>.test.ts, naming one of ${TIER_NAMES.join(", ")}. Use tool for a test that reaches a program supplied by the environment, which is the tier for a retired .int. or .integration. file. Only the segment before .test. selects a project, so an untiered file runs under the residual unit project and reports success`
|
|
327
342
|
},
|
|
328
343
|
{
|
|
329
|
-
name: "every test file
|
|
344
|
+
name: "every test file is under a __tests__ directory",
|
|
330
345
|
severity: "error",
|
|
331
346
|
skip: () => describeConventionsGuardSkip(),
|
|
332
347
|
check: () => everyTestFileSitsUnderTestsDir(),
|
|
333
|
-
fix: "Move each into a __tests__ directory, the only place from which the shared Vitest config collects.
|
|
348
|
+
fix: "Move each into a __tests__ directory, the only place from which the shared Vitest config collects. Vitest does not collect a file outside one, so it runs nowhere and reports nothing. For a file that is not a test, declare checkTestFileConventions with its directory in `excludedBasenames` instead, which this check then defers to"
|
|
334
349
|
},
|
|
335
350
|
{
|
|
336
351
|
name: "no package re-exports the ancestor Vitest config",
|
|
@@ -351,14 +366,14 @@ var default_default = defineRdyKit({
|
|
|
351
366
|
name: "taze.config.ts builds on @williamthorsen/nmr/taze",
|
|
352
367
|
severity: "warn",
|
|
353
368
|
check: () => tazeConfigBuildsOnSharedConfig(),
|
|
354
|
-
fix: "Replace the taze config with: import { defineConfig } from '@williamthorsen/nmr/taze'; export default defineConfig();
|
|
369
|
+
fix: "Replace the taze config with: import { defineConfig } from '@williamthorsen/nmr/taze'; export default defineConfig(); -- nmr's upgrade policy applies to a repo only through this file, so without it `nmr upgrade` does not report a dependency pinned to an exact version"
|
|
355
370
|
},
|
|
356
371
|
{
|
|
357
372
|
name: "taze config declares no option taze discards",
|
|
358
373
|
severity: "warn",
|
|
359
374
|
quiet: true,
|
|
360
375
|
check: () => tazeConfigAvoidsClobberedOptions(),
|
|
361
|
-
fix: "Set these through the upgrade script instead, as rootScripts: { upgrade: 'nmr-report-overrides && nmr-taze --recursive --request-timeout 90000' } in .config/nmr.config.ts, keeping the rest of the default script
|
|
376
|
+
fix: "Set these through the upgrade script instead, as rootScripts: { upgrade: 'nmr-report-overrides && nmr-taze --recursive --request-timeout 90000' } in .config/nmr.config.ts, keeping the rest of the default script. taze's CLI writes a default for each of them over whatever the config file declares, so taze never uses the file's value (antfu-collective/taze#317). nmr already forwards a 30-second request timeout"
|
|
362
377
|
},
|
|
363
378
|
// -- Audit dependency --------------------------------------------------------
|
|
364
379
|
{
|
|
@@ -373,7 +388,7 @@ var default_default = defineRdyKit({
|
|
|
373
388
|
severity: "error",
|
|
374
389
|
quiet: true,
|
|
375
390
|
check: () => !fileExists("scripts/run-workspace-script.ts"),
|
|
376
|
-
fix: "Delete scripts/run-workspace-script.ts
|
|
391
|
+
fix: "Delete scripts/run-workspace-script.ts, because nmr replaces this custom script runner"
|
|
377
392
|
},
|
|
378
393
|
{
|
|
379
394
|
name: 'no workspace packages reference run-workspace-script or "pnpm run ws"',
|
|
@@ -481,9 +496,9 @@ function prettierConfigBuildsOnSharedConfig(cwd = process.cwd()) {
|
|
|
481
496
|
}
|
|
482
497
|
function describeMissingPrettierConfig(cwd) {
|
|
483
498
|
const inertConfigs = findFiles(INERT_PRETTIER_CONFIGS, cwd);
|
|
484
|
-
if (inertConfigs.length > 0) return `
|
|
499
|
+
if (inertConfigs.length > 0) return `does not contain code that calls the factory: ${inertConfigs.join(", ")}`;
|
|
485
500
|
if (hasPrettierConfigKey(cwd)) {
|
|
486
|
-
return '
|
|
501
|
+
return 'does not contain code that calls the factory: the "prettier" key in package.json';
|
|
487
502
|
}
|
|
488
503
|
return ".prettierrc.js is missing";
|
|
489
504
|
}
|
|
@@ -602,7 +617,7 @@ function formatPaths(paths) {
|
|
|
602
617
|
${paths.map((path2) => ` ${path2}`).join("\n")}`;
|
|
603
618
|
}
|
|
604
619
|
function getMinVersion() {
|
|
605
|
-
const pickedFields = { "version": "0.
|
|
620
|
+
const pickedFields = { "version": "0.41.0" };
|
|
606
621
|
if (typeof pickedFields["version"] !== "string") {
|
|
607
622
|
throw new TypeError("nmr/package.json: 'version' must be a string");
|
|
608
623
|
}
|
|
@@ -754,14 +769,14 @@ function tazeConfigAvoidsClobberedOptions(cwd = process.cwd()) {
|
|
|
754
769
|
}
|
|
755
770
|
function describeMissingTazeConfig(cwd) {
|
|
756
771
|
const inertConfigs = findFiles(INERT_TAZE_CONFIGS, cwd);
|
|
757
|
-
if (inertConfigs.length > 0) return `
|
|
772
|
+
if (inertConfigs.length > 0) return `does not contain code that calls the factory: ${inertConfigs.join(", ")}`;
|
|
758
773
|
return "taze.config.ts is missing";
|
|
759
774
|
}
|
|
760
775
|
function testSuiteGatesTestFileConventions(cwd = process.cwd()) {
|
|
761
776
|
if (findConventionsGuard(cwd) !== void 0) return true;
|
|
762
777
|
return {
|
|
763
778
|
ok: false,
|
|
764
|
-
detail: `
|
|
779
|
+
detail: `the test files under __tests__ do not import checkTestFileConventions from ${SHARED_TESTS_MODULE}`
|
|
765
780
|
};
|
|
766
781
|
}
|
|
767
782
|
function describeConventionsGuardSkip() {
|
package/.readyup/manifest.json
CHANGED
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
"esbuildVersion": "0.28.2",
|
|
12
12
|
"inputs": [
|
|
13
13
|
{
|
|
14
|
-
"hash": "
|
|
14
|
+
"hash": "5f7bd316",
|
|
15
15
|
"kind": "module",
|
|
16
16
|
"path": "kits/default.ts"
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
|
-
"hash": "
|
|
19
|
+
"hash": "00e9a998",
|
|
20
20
|
"kind": "inline",
|
|
21
21
|
"path": "../package.json",
|
|
22
22
|
"paths": [
|
|
@@ -24,17 +24,22 @@
|
|
|
24
24
|
]
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
|
-
"hash": "
|
|
27
|
+
"hash": "9336f4c0",
|
|
28
28
|
"kind": "module",
|
|
29
29
|
"path": "../src/default-scripts.ts"
|
|
30
30
|
},
|
|
31
31
|
{
|
|
32
|
-
"hash": "
|
|
32
|
+
"hash": "d4828172",
|
|
33
|
+
"kind": "module",
|
|
34
|
+
"path": "../src/git-ignored-paths.ts"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"hash": "23646439",
|
|
33
38
|
"kind": "module",
|
|
34
39
|
"path": "../src/resolve-scripts.ts"
|
|
35
40
|
},
|
|
36
41
|
{
|
|
37
|
-
"hash": "
|
|
42
|
+
"hash": "19dec003",
|
|
38
43
|
"kind": "module",
|
|
39
44
|
"path": "../src/tiers.ts"
|
|
40
45
|
}
|
|
@@ -43,8 +48,8 @@
|
|
|
43
48
|
"path": "kits/default.js",
|
|
44
49
|
"readyupVersion": "0.38.0",
|
|
45
50
|
"source": "kits/default.ts",
|
|
46
|
-
"sourceHash": "
|
|
47
|
-
"targetHash": "
|
|
51
|
+
"sourceHash": "5f7bd316",
|
|
52
|
+
"targetHash": "69936c4f"
|
|
48
53
|
}
|
|
49
54
|
]
|
|
50
55
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,50 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.41.0 โ 2026-10-02
|
|
6
|
+
|
|
7
|
+
### ๐ Features
|
|
8
|
+
|
|
9
|
+
- Adds a hint to `nmr` that, when a command fails, names any of nmr's own flags written after the command name and shows the corrected invocation, such as `nmr -q check`. (#956)
|
|
10
|
+
|
|
11
|
+
## 0.40.0 โ 2026-10-01
|
|
12
|
+
|
|
13
|
+
### ๐ Features
|
|
14
|
+
|
|
15
|
+
- Makes `checkTestFileConventions` and the readyup kit's tier and placement checks skip untracked paths that git ignores, so that local build output such as a stale `.netlify/` directory no longer fails the check. (#898)
|
|
16
|
+
- Excludes untracked paths that git ignores from collection in `defineVitestConfig` and `defineRootVitestConfig`, so that Vitest does not run a test file that the conventions check skips. (#898)
|
|
17
|
+
- Rewords the `nmr --log` refusal for a run that the check-result cache does not cover to `the check-result cache does not apply here (NMR_DEBUG=1 reports why)`, and renames the matching `docs/check-cache.md` heading to "When the cache does not apply", which changes its anchor. (#923)
|
|
18
|
+
|
|
19
|
+
### ๐ Bug fixes
|
|
20
|
+
|
|
21
|
+
- Rewords the messages printed by `nmr`, its bin wrappers, and its readyup kit, which used figures such as "sits under" and "holds", em-dashes, and "no" as a determiner, so that a diagnosis such as "-F/--filter did not match any workspace" states its condition directly. (#932)
|
|
22
|
+
- Fixes the issue that the `nmr` rulebook recommended `-q` without stating that nmr reads its own flags only before the command name, which led agents to run `nmr ci -q` and fail when `nmr-fmt` rejected the flag. (#948)
|
|
23
|
+
|
|
24
|
+
### โป๏ธ Refactoring
|
|
25
|
+
|
|
26
|
+
- Replaces every `.sort()` call that suppressed `unicorn/no-array-sort` with `toSorted()` and drops the spread copy wherever the source was already an array. (#925)
|
|
27
|
+
|
|
28
|
+
### ๐งช Tests
|
|
29
|
+
|
|
30
|
+
- Stubs `GIT_CEILING_DIRECTORIES` to the fixture's parent in every test that expects git to find no repository, so that a repository above the temp directory can no longer change the test's result. (#923)
|
|
31
|
+
- Moves the `everyBinTargetIsACommittedWrapper` tests from `kit-bin-checks.unit.test.ts` to `kit-bin-checks.tool.test.ts`, because they spawn git through readyup's `listTrackedFiles`. (#923)
|
|
32
|
+
|
|
33
|
+
### โ๏ธ Tooling
|
|
34
|
+
|
|
35
|
+
- Adds a check to `bootstrap.tool.test.ts` that fails when a package's `prepare` runs the build bootstrap without either depending on nmr-core or passing `--conditions nmr-source`, which would make `pnpm install` fail on a fresh clone. (#903)
|
|
36
|
+
|
|
37
|
+
### ๐ฆ Dependencies
|
|
38
|
+
|
|
39
|
+
- Adds `esbuild` 0.28.2 to the pnpm catalog and declares it as a devDependency of `nmr`, `release-kit`, and `v11y-check`, because `readyup` declares it only as an optional peer and `rdy compile` failed in those packages once a regenerated lockfile stopped installing it. (#944)
|
|
40
|
+
|
|
41
|
+
### ๐ Documentation
|
|
42
|
+
|
|
43
|
+
- Revises the comments in `packages/nmr/src` and `packages/nmr/.readyup/kits/default.ts` against the comment-discipline rules, correcting comments that misstated the code, such as the Levenshtein distance quoted in `runCli.ts` and a `root-tests-config` kit check that does not exist. (#919)
|
|
44
|
+
- Corrects the truncation example in `packages/nmr/docs/reporting.md`, replacing 90 seconds, which renders as `1m 30s` whether it is truncated or rounded, with 119.9 seconds, which truncates to `1m 59s`. (#919)
|
|
45
|
+
- Rewrites the sentences in the README, the guides under `docs/`, the agent rulebook, and the code comments of `@williamthorsen/nmr` that broke the repository's writing rules on plain speech, em-dashes, second-person address, and relative clauses. (#932)
|
|
46
|
+
- Replaces "where" in two doc comments in `packages/nmr/src/check-cache.ts` and in the fixture comment in two tier-budget tests, because each use introduced a consequence rather than a place. (#945)
|
|
47
|
+
- Writes `-q` as `nmr -q <command>` in `docs/reporting.md` and in the README's verbosity paragraph. (#948)
|
|
48
|
+
|
|
5
49
|
## 0.39.0 โ 2026-09-22
|
|
6
50
|
|
|
7
51
|
### ๐ Features
|