@williamthorsen/nmr 0.48.0 → 0.49.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 +158 -0
- package/.readyup/kits/default.js +1527 -1460
- package/.readyup/manifest.json +96 -11
- package/CHANGELOG.md +41 -0
- package/README.md +36 -8
- package/agents/guidance/rulebooks/nmr.md +2 -2
- package/dist/esm/check-cache-build-output.d.ts +24 -0
- package/dist/esm/check-cache-build-output.js +49 -0
- package/dist/esm/check-cache-delegation.d.ts +27 -0
- package/dist/esm/check-cache-delegation.js +92 -0
- package/dist/esm/check-cache-entries.d.ts +109 -0
- package/dist/esm/check-cache-entries.js +87 -0
- package/dist/esm/check-cache-gate.d.ts +64 -0
- package/dist/esm/check-cache-gate.js +226 -0
- package/dist/esm/check-cache-key.d.ts +47 -0
- package/dist/esm/check-cache-key.js +77 -0
- package/dist/esm/check-cache-record.d.ts +69 -0
- package/dist/esm/check-cache-record.js +167 -0
- package/dist/esm/check-cache.d.ts +8 -176
- package/dist/esm/check-cache.js +9 -214
- package/dist/esm/child-env.d.ts +57 -0
- package/dist/esm/child-env.js +59 -0
- package/dist/esm/child-process.d.ts +55 -0
- package/dist/esm/child-process.js +112 -0
- package/dist/esm/commands/build.d.ts +0 -16
- package/dist/esm/commands/build.js +9 -214
- package/dist/esm/commands/clean.js +3 -2
- package/dist/esm/commands/compiler.d.ts +18 -0
- package/dist/esm/commands/compiler.js +49 -0
- package/dist/esm/commands/fmt.d.ts +0 -39
- package/dist/esm/commands/fmt.js +4 -81
- package/dist/esm/commands/format-targets.d.ts +39 -0
- package/dist/esm/commands/format-targets.js +83 -0
- package/dist/esm/commands/rewrite-specifiers.d.ts +17 -0
- package/dist/esm/commands/rewrite-specifiers.js +122 -0
- package/dist/esm/commands/tsconfig-chain.d.ts +8 -0
- package/dist/esm/commands/tsconfig-chain.js +63 -0
- package/dist/esm/config-check-cache.d.ts +17 -0
- package/dist/esm/config-check-cache.js +63 -0
- package/dist/esm/config-validation.d.ts +20 -0
- package/dist/esm/config-validation.js +236 -0
- package/dist/esm/config.js +4 -286
- package/dist/esm/crossing-diagnostics.d.ts +39 -0
- package/dist/esm/crossing-diagnostics.js +132 -0
- package/dist/esm/default-scripts.js +1 -1
- package/dist/esm/delegate-host.d.ts +43 -0
- package/dist/esm/delegate-host.js +178 -0
- package/dist/esm/delegation-target.d.ts +52 -0
- package/dist/esm/delegation-target.js +71 -0
- package/dist/esm/fan-out.d.ts +57 -0
- package/dist/esm/fan-out.js +146 -0
- package/dist/esm/glyphs.d.ts +1 -1
- package/dist/esm/help.js +9 -3
- package/dist/esm/helpers/deriveExcerpt.d.ts +9 -0
- package/dist/esm/helpers/deriveExcerpt.js +1 -1
- package/dist/esm/helpers/renderQuotedList.d.ts +2 -0
- package/dist/esm/helpers/renderQuotedList.js +9 -0
- package/dist/esm/hooks.d.ts +17 -0
- package/dist/esm/hooks.js +23 -0
- package/dist/esm/invocation-args.d.ts +40 -0
- package/dist/esm/invocation-args.js +178 -0
- package/dist/esm/invocation.d.ts +67 -0
- package/dist/esm/invocation.js +3 -0
- package/dist/esm/machine-lock.d.ts +2 -0
- package/dist/esm/machine-lock.js +1 -1
- package/dist/esm/output-channel.d.ts +23 -0
- package/dist/esm/output-channel.js +14 -0
- package/dist/esm/pass-manifest.d.ts +53 -0
- package/dist/esm/pass-manifest.js +89 -0
- package/dist/esm/presentation.d.ts +47 -0
- package/dist/esm/presentation.js +32 -0
- package/dist/esm/recording.d.ts +1 -1
- package/dist/esm/recording.js +2 -2
- package/dist/esm/remote-host-session.d.ts +62 -0
- package/dist/esm/remote-host-session.js +277 -0
- package/dist/esm/remote-probe-host.d.ts +22 -0
- package/dist/esm/remote-probe-host.js +50 -0
- package/dist/esm/remote-probe.d.ts +31 -0
- package/dist/esm/remote-probe.js +78 -0
- package/dist/esm/remote-run.d.ts +28 -73
- package/dist/esm/remote-run.js +103 -165
- package/dist/esm/remote-session.d.ts +40 -0
- package/dist/esm/remote-session.js +151 -0
- package/dist/esm/remote-snapshot.d.ts +21 -0
- package/dist/esm/remote-snapshot.js +80 -0
- package/dist/esm/replay-assembly.d.ts +2 -2
- package/dist/esm/replay-assembly.js +1 -1
- package/dist/esm/run-chain.d.ts +21 -0
- package/dist/esm/run-chain.js +108 -0
- package/dist/esm/runCli.d.ts +1 -9
- package/dist/esm/runCli.js +39 -1002
- package/dist/esm/runner.d.ts +1 -54
- package/dist/esm/runner.js +2 -125
- package/dist/esm/shell-syntax.d.ts +30 -0
- package/dist/esm/shell-syntax.js +120 -0
- package/dist/esm/steps.d.ts +0 -2
- package/dist/esm/steps.js +3 -119
- package/dist/esm/types.d.ts +5 -0
- package/dist/esm/user-config.d.ts +34 -0
- package/dist/esm/user-config.js +93 -0
- package/dist/esm/verbosity.d.ts +6 -0
- package/dist/esm/verbosity.js +7 -7
- package/dist/esm/verdict.d.ts +1 -1
- package/dist/esm/verdict.js +1 -1
- package/docs/check-cache.md +28 -12
- package/docs/delegate.md +96 -16
- package/docs/reporting.md +6 -1
- package/docs/scripts.md +2 -1
- package/package.json +3 -3
- /package/dist/esm/{prettier-default-config.d.ts → prettier-default.config.d.ts} +0 -0
- /package/dist/esm/{prettier-default-config.js → prettier-default.config.js} +0 -0
package/.readyup/manifest.json
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"checklist": "nmr",
|
|
25
25
|
"checks": [
|
|
26
26
|
"@williamthorsen/nmr in devDependencies",
|
|
27
|
-
"@williamthorsen/nmr >= 0.
|
|
27
|
+
"@williamthorsen/nmr >= 0.49.0",
|
|
28
28
|
"pnpm-workspace.yaml exists",
|
|
29
29
|
"catalogued dependencies use catalog:",
|
|
30
30
|
"package.json has packageManager field",
|
|
@@ -75,6 +75,16 @@
|
|
|
75
75
|
"kind": "module",
|
|
76
76
|
"path": "../../nmr-core/dist/esm/cache-store.js"
|
|
77
77
|
},
|
|
78
|
+
{
|
|
79
|
+
"hash": "81f1406b",
|
|
80
|
+
"kind": "module",
|
|
81
|
+
"path": "../../nmr-core/dist/esm/collectChangedPaths.js"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"hash": "200f69f1",
|
|
85
|
+
"kind": "module",
|
|
86
|
+
"path": "../../nmr-core/dist/esm/gitObjects.js"
|
|
87
|
+
},
|
|
78
88
|
{
|
|
79
89
|
"hash": "54384dca",
|
|
80
90
|
"kind": "module",
|
|
@@ -86,7 +96,7 @@
|
|
|
86
96
|
"path": "../../nmr-core/dist/esm/hasErrnoCode.js"
|
|
87
97
|
},
|
|
88
98
|
{
|
|
89
|
-
"hash": "
|
|
99
|
+
"hash": "1eb2b90c",
|
|
90
100
|
"kind": "module",
|
|
91
101
|
"path": "../../nmr-core/dist/esm/hashWorkingTree.js"
|
|
92
102
|
},
|
|
@@ -105,6 +115,11 @@
|
|
|
105
115
|
"kind": "module",
|
|
106
116
|
"path": "../../nmr-core/dist/esm/parseArgs.js"
|
|
107
117
|
},
|
|
118
|
+
{
|
|
119
|
+
"hash": "7915ba49",
|
|
120
|
+
"kind": "module",
|
|
121
|
+
"path": "../../nmr-core/dist/esm/runGit.js"
|
|
122
|
+
},
|
|
108
123
|
{
|
|
109
124
|
"hash": "14547f78",
|
|
110
125
|
"kind": "module",
|
|
@@ -126,12 +141,67 @@
|
|
|
126
141
|
"path": "../../nmr-core/dist/esm/writeFileWithCheck.js"
|
|
127
142
|
},
|
|
128
143
|
{
|
|
129
|
-
"hash": "
|
|
144
|
+
"hash": "9fd61bf4",
|
|
130
145
|
"kind": "module",
|
|
131
146
|
"path": "kits/default.ts"
|
|
132
147
|
},
|
|
133
148
|
{
|
|
134
|
-
"hash": "
|
|
149
|
+
"hash": "fbdddbc3",
|
|
150
|
+
"kind": "module",
|
|
151
|
+
"path": "lib/bin-targets.ts"
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"hash": "56e92527",
|
|
155
|
+
"kind": "module",
|
|
156
|
+
"path": "lib/catalog.ts"
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"hash": "369c92bb",
|
|
160
|
+
"kind": "module",
|
|
161
|
+
"path": "lib/github.ts"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"hash": "4f0d7859",
|
|
165
|
+
"kind": "module",
|
|
166
|
+
"path": "lib/manifest-fields.ts"
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"hash": "e3de73f8",
|
|
170
|
+
"kind": "module",
|
|
171
|
+
"path": "lib/repo-files.ts"
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"hash": "2a90a1d3",
|
|
175
|
+
"kind": "module",
|
|
176
|
+
"path": "lib/root-scripts.ts"
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"hash": "3d661ab8",
|
|
180
|
+
"kind": "module",
|
|
181
|
+
"path": "lib/self-hosted-runner.ts"
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"hash": "f8f79929",
|
|
185
|
+
"kind": "module",
|
|
186
|
+
"path": "lib/setup.ts"
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"hash": "400757e6",
|
|
190
|
+
"kind": "module",
|
|
191
|
+
"path": "lib/shared-configs.ts"
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"hash": "b3bdf8ee",
|
|
195
|
+
"kind": "module",
|
|
196
|
+
"path": "lib/vitest.ts"
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"hash": "00130733",
|
|
200
|
+
"kind": "module",
|
|
201
|
+
"path": "lib/workspace-build.ts"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"hash": "08c09ade",
|
|
135
205
|
"kind": "inline",
|
|
136
206
|
"path": "../package.json",
|
|
137
207
|
"paths": [
|
|
@@ -139,12 +209,22 @@
|
|
|
139
209
|
]
|
|
140
210
|
},
|
|
141
211
|
{
|
|
142
|
-
"hash": "
|
|
212
|
+
"hash": "19b57f2e",
|
|
213
|
+
"kind": "module",
|
|
214
|
+
"path": "../src/config-check-cache.ts"
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
"hash": "2cfe8c2e",
|
|
218
|
+
"kind": "module",
|
|
219
|
+
"path": "../src/config-validation.ts"
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"hash": "5b589c7e",
|
|
143
223
|
"kind": "module",
|
|
144
224
|
"path": "../src/config.ts"
|
|
145
225
|
},
|
|
146
226
|
{
|
|
147
|
-
"hash": "
|
|
227
|
+
"hash": "d2448f72",
|
|
148
228
|
"kind": "module",
|
|
149
229
|
"path": "../src/default-scripts.ts"
|
|
150
230
|
},
|
|
@@ -199,7 +279,12 @@
|
|
|
199
279
|
"path": "../src/resolver.ts"
|
|
200
280
|
},
|
|
201
281
|
{
|
|
202
|
-
"hash": "
|
|
282
|
+
"hash": "bb18a285",
|
|
283
|
+
"kind": "module",
|
|
284
|
+
"path": "../src/shell-syntax.ts"
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
"hash": "a1b70d65",
|
|
203
288
|
"kind": "module",
|
|
204
289
|
"path": "../src/steps.ts"
|
|
205
290
|
},
|
|
@@ -214,7 +299,7 @@
|
|
|
214
299
|
"path": "../src/UserError.ts"
|
|
215
300
|
},
|
|
216
301
|
{
|
|
217
|
-
"hash": "
|
|
302
|
+
"hash": "ecf8a873",
|
|
218
303
|
"kind": "module",
|
|
219
304
|
"path": "../src/verbosity.ts"
|
|
220
305
|
},
|
|
@@ -231,10 +316,10 @@
|
|
|
231
316
|
],
|
|
232
317
|
"name": "default",
|
|
233
318
|
"path": "kits/default.js",
|
|
234
|
-
"readyupVersion": "0.
|
|
319
|
+
"readyupVersion": "0.42.0",
|
|
235
320
|
"source": "kits/default.ts",
|
|
236
|
-
"sourceHash": "
|
|
237
|
-
"targetHash": "
|
|
321
|
+
"sourceHash": "9fd61bf4",
|
|
322
|
+
"targetHash": "b0939edd"
|
|
238
323
|
}
|
|
239
324
|
]
|
|
240
325
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,47 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.49.0 — 2026-10-18
|
|
6
|
+
|
|
7
|
+
### 🎉 Features
|
|
8
|
+
|
|
9
|
+
- Makes a delegation wait while another delegation from the same worktree holds the host checkout, printing `nmr: waiting for <command> in <dir> (pid <pid>) to finish on <host>` and leaving the wait out of the verdict's duration. (#1079)
|
|
10
|
+
- Extends Ctrl-C, which previously ended only the run step on the host, to end the delegation during the wait for the checkout, the check-out step, and the install step as well. (#1079)
|
|
11
|
+
- Makes `nmr --delegate` fail at the check-out step with `could not check out on <host>: node is not on the PATH that the interactive login shell sets there` when the host's interactive login shell does not put Node on `PATH`, and documents that Node on the host must satisfy nmr's `engines` range (`>=24.16.0`). (#1079)
|
|
12
|
+
- Makes `nmr --delegate` and `NMR_DELEGATE` look up this machine's check-result cache before contacting the host, so that a tree with a pass recorded here is reported as `passed … ago on this tree` and an unknown command is reported here, without either one reaching the host. (#1080)
|
|
13
|
+
- Adds a record in this machine's check-result cache for each pass that a delegation host reports, so that a later delegation to the same host recalls it as `passed … ago on <host>` and `nmr --log` prints the host's transcript under a header that names the host. (#1080)
|
|
14
|
+
- Makes `nmr --delegate` record the passes that the host reports for a delegated composite's constituents, so that `nmr --delegate <host> check` after `nmr --delegate <host> ci` on the same tree recalls the pass without contacting the host. (#1082)
|
|
15
|
+
- Makes `nmr --delegate` record the excerpts that the host reports with each delegated pass, so that a recall of a delegated command, such as `ci`, replays them instead of nothing. (#1082)
|
|
16
|
+
- Lets `--delegate` and `NMR_DELEGATE` take a comma-separated list of hosts in order of preference, from which nmr chooses the first host on which the delegation would not wait for a lock, otherwise the first host that answered nmr's lock check, and never the delegating machine. (#1083)
|
|
17
|
+
- Extends the check-result cache to recall a pass earned on any host in the list, without checking any host's locks. (#1083)
|
|
18
|
+
- 🚨 **Breaking:** Makes `--delegate` a boolean flag, under which `nmr` runs the command on a host that `NMR_DELEGATE` or the user config names, and adds `--delegate-to <hosts>` to name the hosts for one run. (#1084)
|
|
19
|
+
- Adds a machine-level user config, `~/.config/nmr/config.ts`, in which `delegate.hosts` names this machine's hosts in order of preference and `delegate.when` selects the runs that `nmr` delegates to them without a flag: runs from a coding agent (`'agent'`) or every run (`'always'`), except when `CI` is set and for a `-F` or `-R` invocation. (#1084)
|
|
20
|
+
- Adds the repo config key `delegate`, whose only accepted value, `false`, makes `nmr` run every check on the machine that has the working tree, overriding `NMR_DELEGATE` and the user config but not a delegation flag. (#1084)
|
|
21
|
+
- Adds a stderr line, printed by `nmr` when a delegation decided by the user config cannot reach a host, that names the config file and the remedies in order: network access to the hosts, then `nmr --no-delegate <command>`. (#1084)
|
|
22
|
+
- Updates the `nmr` rulebook to direct agents not to pass `--delegate` or `--delegate-to`, since the user config decides delegation, and to ask for network access to a host before running `nmr --no-delegate`. (#1084)
|
|
23
|
+
|
|
24
|
+
### 🐛 Bug fixes
|
|
25
|
+
|
|
26
|
+
- Fixes the issue that the snapshot sent to the host by `nmr --delegate` omitted a file edited in the same second as git's last write of the index. (#1078)
|
|
27
|
+
- Fixes the issue that `NMR_NO_CACHE`, `NMR_DEBUG`, and `NMR_RUN_ID` set on this machine did not reach the delegation host, which left the host's cache in use under `NMR_NO_CACHE=1`. (#1080)
|
|
28
|
+
|
|
29
|
+
### ⚡ Performance
|
|
30
|
+
|
|
31
|
+
- Keys the check cache on the git tree id that `git add --all` would record for the working tree, which lets `nmr` skip a check after the checked tree is committed, and invalidates every pass recorded by earlier versions once. (#1078)
|
|
32
|
+
|
|
33
|
+
### ♻️ Refactoring
|
|
34
|
+
|
|
35
|
+
- Splits the `nmr` source modules that exceeded the 300-line `max-lines` limit, such as `check-cache.ts` and `commands/build.ts`, into one module per concern, and moves each module's tests with its code. (#1061)
|
|
36
|
+
- Splits `config.ts`, `steps.ts`, and the default readyup kit `.readyup/kits/default.ts` to bring each under the 300-line `max-lines` limit, moving field-shape validation into `config-validation.ts`, the `checkCache` name check into `config-check-cache.ts`, shell tokenizing and quoting into `shell-syntax.ts`, and each kit section into a module under `.readyup/lib/`. (#1063)
|
|
37
|
+
- Moves the argument parser, the invocation types, pnpm fan-out, the delegate host, chain assembly, hook wrapping, crossing diagnostics, the check-cache gate and its recording, the child environment, and presentation out of `runCli.ts` into modules of their own, leaving `runCli.ts` with the run orchestration and bringing it under the 300-line `max-lines` limit. (#1064)
|
|
38
|
+
- Rewrites the code that the new and widened rules in `eslint-plugin-unicorn` 78 report, mostly by changing `flatMap` callbacks to return a single value rather than a one-element array. (#1072)
|
|
39
|
+
- Renames nmr's default Prettier config from `prettier-default-config.ts` to `prettier-default.config.ts`, so that the upstream exemption of tool-loaded `*.config.*` files from `unicorn/no-top-level-side-effects` covers it, and updates `nmr-fmt` to resolve the new name. (#1072)
|
|
40
|
+
|
|
41
|
+
### 🧪 Tests
|
|
42
|
+
|
|
43
|
+
- Splits the `nmr` test files that exceeded the 500-line `max-lines` limit for tests into files by aspect, and moves the fixtures and fake-spawn helpers that the new files share into `test-utils/`. (#1061)
|
|
44
|
+
- Splits `runCli.unit.test.ts` into four files named for the aspect that each covers and moves the pre/post hooks suite out of `cli.tool.test.ts` into `cli.hooks.tool.test.ts`, with the workspace-tree factory, the in-process runners, and the bin harness moved into shared files under `test-utils/`. (#1064)
|
|
45
|
+
|
|
5
46
|
## 0.48.0 — 2026-10-17
|
|
6
47
|
|
|
7
48
|
### 🎉 Features
|
package/README.md
CHANGED
|
@@ -5,13 +5,33 @@
|
|
|
5
5
|
Context-aware script runner for pnpm monorepos. Includes an `nmr` (node-monorepo run) binary that provides centralized, consistent script execution across workspace packages and the monorepo root.
|
|
6
6
|
|
|
7
7
|
<!-- section:release-notes -->
|
|
8
|
-
## Release notes — v0.
|
|
8
|
+
## Release notes — v0.49.0 (2026-10-18)
|
|
9
9
|
|
|
10
10
|
### 🎉 Features
|
|
11
11
|
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
12
|
+
- Makes a delegation wait while another delegation from the same worktree holds the host checkout, printing `nmr: waiting for <command> in <dir> (pid <pid>) to finish on <host>` and leaving the wait out of the verdict's duration. (#1079)
|
|
13
|
+
- Extends Ctrl-C, which previously ended only the run step on the host, to end the delegation during the wait for the checkout, the check-out step, and the install step as well. (#1079)
|
|
14
|
+
- Makes `nmr --delegate` fail at the check-out step with `could not check out on <host>: node is not on the PATH that the interactive login shell sets there` when the host's interactive login shell does not put Node on `PATH`, and documents that Node on the host must satisfy nmr's `engines` range (`>=24.16.0`). (#1079)
|
|
15
|
+
- Makes `nmr --delegate` and `NMR_DELEGATE` look up this machine's check-result cache before contacting the host, so that a tree with a pass recorded here is reported as `passed … ago on this tree` and an unknown command is reported here, without either one reaching the host. (#1080)
|
|
16
|
+
- Adds a record in this machine's check-result cache for each pass that a delegation host reports, so that a later delegation to the same host recalls it as `passed … ago on <host>` and `nmr --log` prints the host's transcript under a header that names the host. (#1080)
|
|
17
|
+
- Makes `nmr --delegate` record the passes that the host reports for a delegated composite's constituents, so that `nmr --delegate <host> check` after `nmr --delegate <host> ci` on the same tree recalls the pass without contacting the host. (#1082)
|
|
18
|
+
- Makes `nmr --delegate` record the excerpts that the host reports with each delegated pass, so that a recall of a delegated command, such as `ci`, replays them instead of nothing. (#1082)
|
|
19
|
+
- Lets `--delegate` and `NMR_DELEGATE` take a comma-separated list of hosts in order of preference, from which nmr chooses the first host on which the delegation would not wait for a lock, otherwise the first host that answered nmr's lock check, and never the delegating machine. (#1083)
|
|
20
|
+
- Extends the check-result cache to recall a pass earned on any host in the list, without checking any host's locks. (#1083)
|
|
21
|
+
- 🚨 **Breaking:** Makes `--delegate` a boolean flag, under which `nmr` runs the command on a host that `NMR_DELEGATE` or the user config names, and adds `--delegate-to <hosts>` to name the hosts for one run. (#1084)
|
|
22
|
+
- Adds a machine-level user config, `~/.config/nmr/config.ts`, in which `delegate.hosts` names this machine's hosts in order of preference and `delegate.when` selects the runs that `nmr` delegates to them without a flag: runs from a coding agent (`'agent'`) or every run (`'always'`), except when `CI` is set and for a `-F` or `-R` invocation. (#1084)
|
|
23
|
+
- Adds the repo config key `delegate`, whose only accepted value, `false`, makes `nmr` run every check on the machine that has the working tree, overriding `NMR_DELEGATE` and the user config but not a delegation flag. (#1084)
|
|
24
|
+
- Adds a stderr line, printed by `nmr` when a delegation decided by the user config cannot reach a host, that names the config file and the remedies in order: network access to the hosts, then `nmr --no-delegate <command>`. (#1084)
|
|
25
|
+
- Updates the `nmr` rulebook to direct agents not to pass `--delegate` or `--delegate-to`, since the user config decides delegation, and to ask for network access to a host before running `nmr --no-delegate`. (#1084)
|
|
26
|
+
|
|
27
|
+
### 🐛 Bug fixes
|
|
28
|
+
|
|
29
|
+
- Fixes the issue that the snapshot sent to the host by `nmr --delegate` omitted a file edited in the same second as git's last write of the index. (#1078)
|
|
30
|
+
- Fixes the issue that `NMR_NO_CACHE`, `NMR_DEBUG`, and `NMR_RUN_ID` set on this machine did not reach the delegation host, which left the host's cache in use under `NMR_NO_CACHE=1`. (#1080)
|
|
31
|
+
|
|
32
|
+
### ⚡ Performance
|
|
33
|
+
|
|
34
|
+
- Keys the check cache on the git tree id that `git add --all` would record for the working tree, which lets `nmr` skip a check after the checked tree is committed, and invalidates every pass recorded by earlier versions once. (#1078)
|
|
15
35
|
<!-- /section:release-notes -->
|
|
16
36
|
|
|
17
37
|
## Installation
|
|
@@ -195,14 +215,22 @@ The lock lives in `/tmp/nmr-<uid>/`, or in `NMR_LOCK_DIR`; a sandboxed session n
|
|
|
195
215
|
|
|
196
216
|
## Delegating to another machine
|
|
197
217
|
|
|
198
|
-
|
|
218
|
+
nmr runs a check on another machine over SSH, against this working tree, and exits with its status. A user config on each machine, `~/.config/nmr/config.ts`, names the hosts in order of preference, and with `when` delegates agents' runs, or every run, without being asked:
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
export default {
|
|
222
|
+
delegate: { hosts: ['imac2', 'imac3'], when: 'agent' },
|
|
223
|
+
};
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
`--delegate-to` names the hosts for one run, and nmr runs on the first one that is not busy:
|
|
199
227
|
|
|
200
228
|
```console
|
|
201
|
-
$ nmr --delegate imac2 ci
|
|
229
|
+
$ nmr --delegate-to imac2 ci
|
|
202
230
|
✅ my-repo: ci: passed on imac2 in 1m 48s
|
|
203
231
|
```
|
|
204
232
|
|
|
205
|
-
[Delegating to another machine](docs/delegate.md) covers which commands can be delegated, what the host runs, how failures are reported, and setting up the host.
|
|
233
|
+
A delegation consults the [check-result cache](#check-result-cache) first, so a tree that already passed is not sent to the host; a pass that a host earned does not satisfy a run here. A repo whose checks must run on the machine that has the tree declares `delegate: false` in its config. [Delegating to another machine](docs/delegate.md) covers the user config, `--delegate`, `NMR_DELEGATE`, the rules that decide where a command runs, which commands can be delegated, how nmr chooses among several hosts, how the cache is consulted, what the host runs, how failures are reported, and setting up the host.
|
|
206
234
|
|
|
207
235
|
## Shared configs and helpers
|
|
208
236
|
|
|
@@ -308,7 +336,7 @@ rdy list --from npm:@williamthorsen/nmr # what nmr publishes
|
|
|
308
336
|
- [What nmr reports](docs/reporting.md): Verdicts, `--json`, output style, and verbosity
|
|
309
337
|
- [Check-result cache](docs/check-cache.md): What is skipped, what a hit requires, and replaying, reading, and bypassing recorded runs
|
|
310
338
|
- [Machine lock](docs/machine-lock.md): Which commands wait for one another, where the lock lives, and bypassing it
|
|
311
|
-
- [Delegating to another machine](docs/delegate.md): `--delegate`, `NMR_DELEGATE`, what the host runs, and setting it up
|
|
339
|
+
- [Delegating to another machine](docs/delegate.md): The user config, `--delegate`, `--delegate-to`, `NMR_DELEGATE`, where a command runs, choosing among several hosts, what the host runs, and setting it up
|
|
312
340
|
- [Dependency upgrades](docs/upgrades.md): `nmr upgrade`, the upgrade policy, `report-catalog`, `report-overrides`, and `nmr-taze`
|
|
313
341
|
- [Standalone utilities](docs/utilities.md): `nmr-clean`, `nmr-compile`, `nmr-fmt`, and `ensure-prepublish-hooks`
|
|
314
342
|
- [Shared Vitest config](docs/vitest.md): Test tiers, test selections, the conventions check, and customization
|
|
@@ -15,5 +15,5 @@ delivery: ambient
|
|
|
15
15
|
- Every command that nmr runs reports one line -- `PASS <scope>: <command>: passed in 12.4s`, `FAIL … failed in 1.2s (exit 1)`, `SKIP … passed 4m ago on this tree`, or `NOOP … skipped, the override is empty` -- and a composite reports alongside every command into which it expands. Those are the words that a pipe and a CI log get; a terminal gets ✅, ❌, ⏩, and ⚪ in their place, and `--output-style rich` or `NMR_OUTPUT_STYLE=rich` forces the emoji on any stream. The lines print in every verbosity, so `nmr -q <command>` is the low-noise way to run rather than a silent one: It withholds the output of the commands that nmr runs and keeps nmr's own verdicts, which is how a pass, a cached skip, and a `""`/`":"` override stay distinguishable. nmr reads its own flags only before the command name and passes everything after the name to the commands that run: `nmr ci -q` hands `-q` to `nmr-fmt`, which rejects it. Expect a run to be quiet already without the flag: nmr recognizes a known agent harness in the environment and takes quiet from that, as it does from `NMR_COMMAND_VERBOSITY` or the repo's `output.commandVerbosity`. A command whose output you need in full is one to run with `NMR_COMMAND_VERBOSITY=full`, which outranks both.
|
|
16
16
|
- Every test file lives under a `__tests__` directory and names its isolation tier last: `<subject>[.<aspect>].<tier>.test.ts`, where any earlier segment is free-form documentation. A tier is the furthest thing that the test reaches -- `tool` (a process that the test starts, or a program other than the package's own code that the test makes do its work, including an embedded engine, which the package installs and the test runs in-process, such as PGlite), `localhost` (a service already running on this machine), `remote` (a machine that isn't this one), or `unit`, the residual claiming everything else. A file outside `__tests__`, or one whose tier segment is missing or misspelt, fails the test run in a repo that declares nmr's exported conventions check, which reports both. In a repo that has not, neither fails anything -- the first is collected by nothing and the second runs under `unit` -- and `rdy run --sources` reports both and warns that the check is missing.
|
|
17
17
|
- A heavy command (`check`, `ci`, and the `test` commands but `test:watch`) may print `nmr: waiting for <command> in <dir> (pid <pid>) to finish` and wait: nmr runs one at a time on the machine, across every repository. The run is not stuck, and it continues once the holder ends. Do not kill it, and do not rerun it with `--no-lock` to get past the wait, which defeats the memory protection that the lock exists for. A warning that nmr is `running without the machine lock` names a directory that the sandbox cannot write.
|
|
18
|
-
- A verdict reading `passed on <host>` or `failed on <host>` reports a command that ran on
|
|
19
|
-
- The rest is in `node_modules/@williamthorsen/nmr/docs/`: `scripts.md` for pre/post hooks (every `nmr X` auto-wraps as `nmr X:pre && nmr X && nmr X:post`) and script overrides and their `""`/`":"` skip values; `upgrades.md` for `nmr upgrade` ceilings (report-only until `nmr upgrade --write`); `utilities.md` for the `nmr-compile` build (the repo does not define a build script of its own); `vitest.md` for the test tiers' timeout budgets, the `tiers` seam that raises one, and which script reaches which tier; `machine-lock.md` for which commands take the machine lock, where it lives, and its bypass; `delegate.md` for which commands
|
|
18
|
+
- Delegation is configured per machine, in nmr's user config (`~/.config/nmr/config.ts`), so a command may run on another machine without being asked: Never pass `--delegate` or `--delegate-to`. A verdict reading `passed on <host>` or `failed on <host>` reports a command that ran on that machine; the output above it is the host's. A delegation consults this machine's check-result cache first: `passed … ago on this tree` recalls a pass earned here, and `passed … ago on <host>` one that the host earned, without contacting it. A failure that names the user config, or exit 255 with `could not prepare <host>` inside a sandbox, means that the sandbox refused `ssh`: Ask for network access to the host first, and run the command here with `nmr --no-delegate <command>` only when that is refused.
|
|
19
|
+
- The rest is in `node_modules/@williamthorsen/nmr/docs/`: `scripts.md` for pre/post hooks (every `nmr X` auto-wraps as `nmr X:pre && nmr X && nmr X:post`) and script overrides and their `""`/`":"` skip values; `upgrades.md` for `nmr upgrade` ceilings (report-only until `nmr upgrade --write`); `utilities.md` for the `nmr-compile` build (the repo does not define a build script of its own); `vitest.md` for the test tiers' timeout budgets, the `tiers` seam that raises one, and which script reaches which tier; `machine-lock.md` for which commands take the machine lock, where it lives, and its bypass; `delegate.md` for the user config, the rules that decide where a command runs, which commands can run on another machine, what the host runs, and its setup; and `check-cache.md` for the check-result cache's key, the retention key to which a replayed excerpt is additionally held, the retention ceiling within which `--log` prints, its `checkCache` config, and every condition that disables it.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { NmrConfig } from './types.js';
|
|
2
|
+
/** What nmr's own build has left on disk across the workspace. */
|
|
3
|
+
export interface BuildOutputState {
|
|
4
|
+
/** Covered packages whose output is absent. */
|
|
5
|
+
missingPackages: string[];
|
|
6
|
+
/** The digest of the inputs from which each covered package's output was built, keyed by package name. */
|
|
7
|
+
digests: Record<string, string>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Names a covered package whose output differs between two observations of it, or `undefined` when every
|
|
11
|
+
* package agrees. A package that has appeared or disappeared between them counts as a disagreement, because
|
|
12
|
+
* the output that the earlier observation describes is not the output that the later one found.
|
|
13
|
+
*/
|
|
14
|
+
export declare function findStaleBuildOutput(earlierDigests: Record<string, string>, laterDigests: Record<string, string>): string | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Reads the state of the build output that nmr's own build covers. Build output is git-ignored, so the tree id
|
|
17
|
+
* says nothing about it: a `ci` whose `build` constituent is cached would otherwise skip on a tree whose `dist`
|
|
18
|
+
* had been deleted, or whose `dist` was compiled from a different tree, and hand back a green exit over a
|
|
19
|
+
* repository that cannot run.
|
|
20
|
+
*
|
|
21
|
+
* Because a package whose `build` or `compile` is overridden emits to a location that this function does not
|
|
22
|
+
* know about, it is left out rather than made a permanent miss.
|
|
23
|
+
*/
|
|
24
|
+
export declare function readBuildOutputState(monorepoRoot: string, config: NmrConfig): Promise<BuildOutputState>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { hasBuildOutput, readBuildDigest } from "./commands/build-output.js";
|
|
3
|
+
import { loadWorkspaceConfig } from "./config.js";
|
|
4
|
+
import { getDefaultWorkspaceScripts } from "./resolve-scripts.js";
|
|
5
|
+
import { buildWorkspaceRegistry, resolveScript } from "./resolver.js";
|
|
6
|
+
import { renderChain } from "./steps.js";
|
|
7
|
+
import { getWorkspacePackageDirs } from "./workspace.js";
|
|
8
|
+
const BUILT_IN_COMPILE = 'nmr-compile';
|
|
9
|
+
export function findStaleBuildOutput(earlierDigests, laterDigests) {
|
|
10
|
+
const names = [...new Set([...Object.keys(earlierDigests), ...Object.keys(laterDigests)])].toSorted();
|
|
11
|
+
return names.find((name) => earlierDigests[name] !== laterDigests[name]);
|
|
12
|
+
}
|
|
13
|
+
export async function readBuildOutputState(monorepoRoot, config) {
|
|
14
|
+
const state = { missingPackages: [], digests: {} };
|
|
15
|
+
let packageDirs;
|
|
16
|
+
try {
|
|
17
|
+
packageDirs = getWorkspacePackageDirs(monorepoRoot);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return state;
|
|
21
|
+
}
|
|
22
|
+
const registry = buildWorkspaceRegistry(config);
|
|
23
|
+
if (JSON.stringify(registry['build']) !== JSON.stringify(getDefaultWorkspaceScripts()['build'])) {
|
|
24
|
+
return state;
|
|
25
|
+
}
|
|
26
|
+
for (const packageDir of packageDirs) {
|
|
27
|
+
if (!isProbeSubject(packageDir, registry)) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const { build } = await loadWorkspaceConfig(packageDir);
|
|
31
|
+
const options = build?.extraIgnorePatterns === undefined ? {} : { extraIgnorePatterns: build.extraIgnorePatterns };
|
|
32
|
+
const name = path.relative(monorepoRoot, packageDir);
|
|
33
|
+
if (await hasBuildOutput(packageDir, options)) {
|
|
34
|
+
state.digests[name] = (await readBuildDigest(packageDir)) ?? '';
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
state.missingPackages.push(name);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return state;
|
|
41
|
+
}
|
|
42
|
+
function isProbeSubject(packageDir, registry) {
|
|
43
|
+
const build = resolveScript('build', registry, packageDir, false);
|
|
44
|
+
if (build === undefined || build.origin.tier === 'package') {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
const compile = resolveScript('compile', registry, packageDir, false);
|
|
48
|
+
return compile !== undefined && renderChain(compile.steps) === BUILT_IN_COMPILE;
|
|
49
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type TreeSnapshot } from './check-cache.js';
|
|
2
|
+
import type { BuildOutputState } from './check-cache-build-output.js';
|
|
3
|
+
import type { HostRunResult } from './delegate-host.js';
|
|
4
|
+
import { type RunPlan } from './invocation.js';
|
|
5
|
+
import type { OutputChannels } from './output-channel.js';
|
|
6
|
+
/** What the check-result cache gates a run on: the key, the tree, and the build output that the run saw. */
|
|
7
|
+
export interface Gate {
|
|
8
|
+
buildOutputBefore: BuildOutputState;
|
|
9
|
+
/** The channels on which the invocation's output runs, which every retention key that it records folds. */
|
|
10
|
+
channels: OutputChannels;
|
|
11
|
+
key: string;
|
|
12
|
+
retentionKey: string;
|
|
13
|
+
snapshot: TreeSnapshot;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Records the passes that the host earned, provided that the tree pushed to it is the tree on which the gate keyed,
|
|
17
|
+
* and that the working tree did not move while the host ran: a pass over any other tree says nothing about this one.
|
|
18
|
+
*
|
|
19
|
+
* Each valid entry of the host's pass manifest is recorded as a pass that the host earned at the entry's scope. It is
|
|
20
|
+
* keyed on this machine's inputs and the host's command string, so that an entry whose string differs from what would
|
|
21
|
+
* run here is never recalled, and its excerpts are keyed on this invocation's presentation, for which the host styled
|
|
22
|
+
* them. The invocation's own entry supplies only the retention of the invocation's pass, which is recorded last, so
|
|
23
|
+
* that a reader never sees a composite's pass ahead of its constituents' passes.
|
|
24
|
+
*/
|
|
25
|
+
export declare function recordDelegatedPass(plan: RunPlan, gate: Gate, hostRun: Extract<HostRunResult, {
|
|
26
|
+
kind: 'ran';
|
|
27
|
+
}>): Promise<void>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { resolveTreeSnapshot, writeDebugNote } from "./check-cache.js";
|
|
3
|
+
import { computeCacheKey, computeRetentionKey } from "./check-cache-key.js";
|
|
4
|
+
import { recordHostPass } from "./check-cache-record.js";
|
|
5
|
+
import { VERSION } from "./invocation.js";
|
|
6
|
+
import { formatManifestScope, validatePassManifest } from "./pass-manifest.js";
|
|
7
|
+
export async function recordDelegatedPass(plan, gate, hostRun) {
|
|
8
|
+
const { anchorDir, command, context, env, stderr } = plan.invocation;
|
|
9
|
+
const { config, monorepoRoot } = context;
|
|
10
|
+
if (hostRun.treeId !== gate.snapshot.treeId) {
|
|
11
|
+
writeDebugNote(`not recording ${command}: the pushed tree is not the tree that the gate keyed on`, env, stderr);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const currentRead = resolveTreeSnapshot({ monorepoRoot, env: {} });
|
|
15
|
+
if (!currentRead.ok) {
|
|
16
|
+
writeDebugNote(`not recording ${command}: ${currentRead.reason}`, env, stderr);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (currentRead.snapshot.treeId !== gate.snapshot.treeId) {
|
|
20
|
+
writeDebugNote(`not recording ${command}: the tree changed while it ran`, env, stderr);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const entries = validatePassManifest({
|
|
24
|
+
checkCache: config.checkCache,
|
|
25
|
+
entries: hostRun.passes,
|
|
26
|
+
env,
|
|
27
|
+
monorepoRoot,
|
|
28
|
+
stderr,
|
|
29
|
+
treeId: gate.snapshot.treeId,
|
|
30
|
+
workspacePackageDirs: context.workspacePackageDirs,
|
|
31
|
+
});
|
|
32
|
+
const ownScope = formatManifestScope(monorepoRoot, anchorDir);
|
|
33
|
+
let ownReplay;
|
|
34
|
+
for (const entry of entries) {
|
|
35
|
+
if (entry.scope === ownScope && entry.command === command) {
|
|
36
|
+
if (entry.commandString === plan.commandString) {
|
|
37
|
+
ownReplay = entry.replay;
|
|
38
|
+
}
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
await recordLearnedPass(plan, gate, hostRun.host, entry);
|
|
42
|
+
}
|
|
43
|
+
await recordHostPass({
|
|
44
|
+
commandString: plan.commandString,
|
|
45
|
+
config,
|
|
46
|
+
durationMs: hostRun.durationMs,
|
|
47
|
+
env,
|
|
48
|
+
host: hostRun.host,
|
|
49
|
+
key: gate.key,
|
|
50
|
+
monorepoRoot,
|
|
51
|
+
ref: { anchorDir, command, monorepoRoot },
|
|
52
|
+
retainedOutput: hostRun.retainedOutput,
|
|
53
|
+
retention: composeRetention(gate.retentionKey, ownReplay, plan.runId),
|
|
54
|
+
snapshot: gate.snapshot,
|
|
55
|
+
stderr,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function composeRetention(key, replay, runId) {
|
|
59
|
+
return replay === undefined ? undefined : { key, replay, runId };
|
|
60
|
+
}
|
|
61
|
+
async function recordLearnedPass(plan, gate, host, entry) {
|
|
62
|
+
const { context, env, stderr } = plan.invocation;
|
|
63
|
+
const { monorepoRoot } = context;
|
|
64
|
+
const anchorDir = path.resolve(monorepoRoot, entry.scope);
|
|
65
|
+
const keyed = computeCacheKey({
|
|
66
|
+
anchorDir,
|
|
67
|
+
command: entry.command,
|
|
68
|
+
commandString: entry.commandString,
|
|
69
|
+
env,
|
|
70
|
+
monorepoRoot,
|
|
71
|
+
nmrVersion: VERSION,
|
|
72
|
+
snapshot: gate.snapshot,
|
|
73
|
+
});
|
|
74
|
+
if (!keyed.ok) {
|
|
75
|
+
writeDebugNote(`not recording ${entry.command} on ${host}: ${keyed.reason}`, env, stderr);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
await recordHostPass({
|
|
79
|
+
commandString: entry.commandString,
|
|
80
|
+
config: context.config,
|
|
81
|
+
durationMs: entry.durationMs,
|
|
82
|
+
env,
|
|
83
|
+
host,
|
|
84
|
+
key: keyed.key,
|
|
85
|
+
monorepoRoot,
|
|
86
|
+
ref: { anchorDir, command: entry.command, monorepoRoot },
|
|
87
|
+
retainedOutput: undefined,
|
|
88
|
+
retention: composeRetention(computeRetentionKey({ channels: gate.channels, env, passKey: keyed.key }), entry.replay, plan.runId),
|
|
89
|
+
snapshot: gate.snapshot,
|
|
90
|
+
stderr,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { Writable } from 'node:stream';
|
|
2
|
+
/** A recorded pass: what ran, on which tree, and how long it took. */
|
|
3
|
+
export interface CheckCacheEntry {
|
|
4
|
+
/** The full cache key under which the pass was recorded; a hit is this key matching the key computed now. */
|
|
5
|
+
key: string;
|
|
6
|
+
treeHash: string;
|
|
7
|
+
headSha: string;
|
|
8
|
+
commandString: string;
|
|
9
|
+
nmrVersion: string;
|
|
10
|
+
nodeVersion: string;
|
|
11
|
+
durationMs: number;
|
|
12
|
+
/** ISO-8601 instant the pass completed. */
|
|
13
|
+
recordedAt: string;
|
|
14
|
+
/**
|
|
15
|
+
* The build digest that each covered package's output had when the pass was recorded. Build output is
|
|
16
|
+
* git-ignored, so the tree id cannot describe it; comparing these separates output built from this tree from
|
|
17
|
+
* output that another tree left behind.
|
|
18
|
+
*/
|
|
19
|
+
buildDigests: Record<string, string>;
|
|
20
|
+
/** The host that earned a delegated pass, absent on a pass earned on this machine. */
|
|
21
|
+
host?: string;
|
|
22
|
+
/** What a skip replays in place of the run that it recalls, absent on a pass that retained nothing. */
|
|
23
|
+
retention?: Retention;
|
|
24
|
+
}
|
|
25
|
+
/** What locates one command's recorded pass at one scope, and the transcript beside it. */
|
|
26
|
+
export interface EntryRef {
|
|
27
|
+
anchorDir: string;
|
|
28
|
+
command: string;
|
|
29
|
+
monorepoRoot: string;
|
|
30
|
+
}
|
|
31
|
+
/** One command's excerpt, attributed to the scope and the command that produced it. */
|
|
32
|
+
export interface ReplayLine {
|
|
33
|
+
command: string;
|
|
34
|
+
excerpt: string;
|
|
35
|
+
scope: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* What a recalled pass replays, the key certifying the excerpts describe this environment's output, and the
|
|
39
|
+
* run that last vouched for them.
|
|
40
|
+
*
|
|
41
|
+
* A list rather than one excerpt: a composite's entry contains its constituents' lines, and a nested one's are
|
|
42
|
+
* spliced into its parent's, after which the attribution cannot be re-derived from the entry that contains them.
|
|
43
|
+
*
|
|
44
|
+
* The witness admits a constituent's line into the assembly that its parent records: a run writes it when it
|
|
45
|
+
* records an excerpt, and restamps it when it recalls one and replays it.
|
|
46
|
+
*/
|
|
47
|
+
export interface Retention {
|
|
48
|
+
key: string;
|
|
49
|
+
replay: ReplayLine[];
|
|
50
|
+
runId: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Restamps a recalled entry's retention with the run that is replaying it, so that an excerpt certified by this
|
|
54
|
+
* run can join the assembly recorded by a composite above it.
|
|
55
|
+
*
|
|
56
|
+
* A recall certifies as surely as a recording does. Because the pass key matched, the excerpt describes this
|
|
57
|
+
* tree; because the caller calls this only when the retention key matched too, the excerpt also describes this
|
|
58
|
+
* presentation environment. Everything else the entry records stands -- the instant and the duration belong to
|
|
59
|
+
* the run that earned the pass, and a recall must not make it read as later or longer than it was.
|
|
60
|
+
*
|
|
61
|
+
* A cache that cannot be written is not worth failing a green run over: `certifyRetention` reports that failure
|
|
62
|
+
* in the debug note.
|
|
63
|
+
*/
|
|
64
|
+
export declare function certifyRetention(options: {
|
|
65
|
+
anchorDir: string;
|
|
66
|
+
command: string;
|
|
67
|
+
entry: CheckCacheEntry;
|
|
68
|
+
env: NodeJS.ProcessEnv;
|
|
69
|
+
monorepoRoot: string;
|
|
70
|
+
runId: string;
|
|
71
|
+
stderr: Writable;
|
|
72
|
+
}): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Reads the entry recorded for one command at one scope, or `undefined` when there is none to trust.
|
|
75
|
+
*
|
|
76
|
+
* Retention is vouched for separately from the pass that contains it: an excerpt of a shape that this function
|
|
77
|
+
* cannot read is dropped, leaving a pass that skips cleanly and reports its verdict alone.
|
|
78
|
+
*/
|
|
79
|
+
export declare function readCheckCacheEntry(options: {
|
|
80
|
+
anchorDir: string;
|
|
81
|
+
command: string;
|
|
82
|
+
monorepoRoot: string;
|
|
83
|
+
}): Promise<CheckCacheEntry | undefined>;
|
|
84
|
+
/**
|
|
85
|
+
* Reads the whole output that one recorded pass retained, or `undefined` when it retained none.
|
|
86
|
+
*
|
|
87
|
+
* Held to nothing on its own: the entry beside it says which tree the bytes describe, and a caller
|
|
88
|
+
* that has not matched the entry's key is reading a transcript of some other tree.
|
|
89
|
+
*/
|
|
90
|
+
export declare function readTranscript(ref: EntryRef): Promise<string | undefined>;
|
|
91
|
+
/**
|
|
92
|
+
* Makes the transcript beside one entry be exactly what this pass retained, removing what an earlier pass
|
|
93
|
+
* left when this one retained nothing.
|
|
94
|
+
*
|
|
95
|
+
* A composite retains nothing of its own, so without the removal a leaf's transcript would remain beside an
|
|
96
|
+
* entry that never produced it, and `--log` would date another run's bytes by this one's instant.
|
|
97
|
+
*/
|
|
98
|
+
export declare function recordTranscript(ref: EntryRef, transcript: string | undefined): Promise<void>;
|
|
99
|
+
/** Removes every recorded pass for a monorepo, or for a standalone package outside one. */
|
|
100
|
+
export declare function removeCheckCache(scopeDir: string): Promise<void>;
|
|
101
|
+
/** Narrows a recorded replay line, whose three fields a rendered line uses in full. */
|
|
102
|
+
export declare function isReplayLine(value: unknown): value is ReplayLine;
|
|
103
|
+
/** Records a pass, replacing whatever this command last recorded at this scope. */
|
|
104
|
+
export declare function writeCheckCacheEntry(options: {
|
|
105
|
+
anchorDir: string;
|
|
106
|
+
command: string;
|
|
107
|
+
entry: CheckCacheEntry;
|
|
108
|
+
monorepoRoot: string;
|
|
109
|
+
}): Promise<void>;
|