@williamthorsen/nmr 0.39.0 → 0.40.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 +128 -4
- package/.readyup/kits/default.js +47 -32
- package/.readyup/manifest.json +12 -7
- package/CHANGELOG.md +38 -0
- package/README.md +41 -73
- 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 +21 -21
- package/dist/esm/runner.d.ts +19 -19
- package/dist/esm/steps.d.ts +43 -42
- 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 +5 -4
package/dist/esm/recording.d.ts
CHANGED
|
@@ -2,16 +2,22 @@ import { type OutputStyle } from '@williamthorsen/nmr-core';
|
|
|
2
2
|
import { type CheckCacheEntry } from './check-cache.js';
|
|
3
3
|
/**
|
|
4
4
|
* What this invocation would be recorded under, insofar as an entry records the same facts. A mismatch is read
|
|
5
|
-
* against these
|
|
5
|
+
* against these so that a refusal names the ingredient that moved rather than blaming the tree for all of them.
|
|
6
6
|
*/
|
|
7
7
|
export interface RunIdentity {
|
|
8
8
|
commandString: string;
|
|
9
9
|
nmrVersion: string;
|
|
10
10
|
nodeVersion: string;
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Absent when this invocation did not take a snapshot, which leaves the tree unattributable rather than assumed
|
|
13
|
+
* equal.
|
|
14
|
+
*/
|
|
12
15
|
treeHash: string | undefined;
|
|
13
16
|
}
|
|
14
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* A recorded pass and what it left a reader: its command's own transcript, or the assembly that a composite
|
|
19
|
+
* stores.
|
|
20
|
+
*/
|
|
15
21
|
export interface Recording {
|
|
16
22
|
entry: CheckCacheEntry;
|
|
17
23
|
/** What the command wrote, absent on a composite, which retains nothing of its own. */
|
|
@@ -19,8 +25,8 @@ export interface Recording {
|
|
|
19
25
|
}
|
|
20
26
|
/**
|
|
21
27
|
* Which of the pass key's ingredients a recorded pass and this invocation disagree on, insofar as an entry
|
|
22
|
-
* records enough to tell:
|
|
23
|
-
* environment variables the key folds in.
|
|
28
|
+
* records enough to tell: The residual covers what it does not, which is the install, the platform, and the
|
|
29
|
+
* environment variables that the key folds in.
|
|
24
30
|
*/
|
|
25
31
|
export type KeyDifference = {
|
|
26
32
|
ingredient: 'tree';
|
|
@@ -48,12 +54,12 @@ export type RecordingLookup = {
|
|
|
48
54
|
/**
|
|
49
55
|
* Why there is nothing to print. Each is a separate answer to a reader asking for the last run's output, and
|
|
50
56
|
* each names a different next move: run the command, run it on this tree, run it through a pipe, or stop
|
|
51
|
-
* asking for a command
|
|
57
|
+
* asking for a command that the check-result cache does not record.
|
|
52
58
|
*/
|
|
53
59
|
export type RecordingRefusal = {
|
|
54
60
|
kind: 'uncacheable';
|
|
55
61
|
} | {
|
|
56
|
-
kind: '
|
|
62
|
+
kind: 'cache-inapplicable';
|
|
57
63
|
} | {
|
|
58
64
|
kind: 'unrecorded';
|
|
59
65
|
} | {
|
|
@@ -68,9 +74,9 @@ export type RecordingRefusal = {
|
|
|
68
74
|
* Renders a recording as the reader sees it: a header dating what follows, the command string that produced
|
|
69
75
|
* it, and then the run's own bytes.
|
|
70
76
|
*
|
|
71
|
-
* The header
|
|
72
|
-
*
|
|
73
|
-
*
|
|
77
|
+
* The header presents the body as a recording rather than as this invocation's output, which lets nmr show a
|
|
78
|
+
* reader at a terminal what a piped run wrote. The command string is the whole chain, hooks included, so the
|
|
79
|
+
* header names what earned the pass and not merely what was typed.
|
|
74
80
|
*/
|
|
75
81
|
export declare function renderRecording(options: {
|
|
76
82
|
command: string;
|
|
@@ -79,8 +85,8 @@ export declare function renderRecording(options: {
|
|
|
79
85
|
style: OutputStyle;
|
|
80
86
|
}): string;
|
|
81
87
|
/**
|
|
82
|
-
* Renders a refusal on the one line a fan-out can attribute, in the grammar a verdict
|
|
83
|
-
* command, and what is missing.
|
|
88
|
+
* Renders a refusal on the one line that a fan-out can attribute, in the grammar used by a verdict: the scope,
|
|
89
|
+
* the command, and what is missing.
|
|
84
90
|
*/
|
|
85
91
|
export declare function renderRefusal(options: {
|
|
86
92
|
command: string;
|
|
@@ -92,9 +98,8 @@ export declare function renderRefusal(options: {
|
|
|
92
98
|
* Resolves what one scope has to show for one command.
|
|
93
99
|
*
|
|
94
100
|
* Admitted on the pass key alone, so `--log` prints exactly what a skip would have recalled and never a
|
|
95
|
-
* recording of some other tree. The retention key is
|
|
96
|
-
*
|
|
97
|
-
* does not.
|
|
101
|
+
* recording of some other tree. The retention key is not consulted: It certifies that a recording describes
|
|
102
|
+
* this presentation environment, which a replayed excerpt needs and a dated recording does not.
|
|
98
103
|
*/
|
|
99
104
|
export declare function resolveRecording(options: {
|
|
100
105
|
anchorDir: string;
|
package/dist/esm/recording.js
CHANGED
|
@@ -19,7 +19,7 @@ export async function resolveRecording(options) {
|
|
|
19
19
|
return { ok: false, refusal: { kind: 'uncacheable' } };
|
|
20
20
|
}
|
|
21
21
|
if (options.key === undefined) {
|
|
22
|
-
return { ok: false, refusal: { kind: '
|
|
22
|
+
return { ok: false, refusal: { kind: 'cache-inapplicable' } };
|
|
23
23
|
}
|
|
24
24
|
const entry = await readCheckCacheEntry({ anchorDir, command, monorepoRoot });
|
|
25
25
|
if (entry === undefined) {
|
|
@@ -44,15 +44,15 @@ function appendNewline(body) {
|
|
|
44
44
|
function describeDifference(difference) {
|
|
45
45
|
switch (difference.ingredient) {
|
|
46
46
|
case 'tree':
|
|
47
|
-
return 'on a tree this is not';
|
|
47
|
+
return 'on a tree that this is not';
|
|
48
48
|
case 'command-string':
|
|
49
|
-
return 'over a command chain this is not';
|
|
49
|
+
return 'over a command chain that this is not';
|
|
50
50
|
case 'nmr-version':
|
|
51
51
|
return `under nmr ${difference.recordedVersion}, not ${difference.currentVersion}`;
|
|
52
52
|
case 'node-version':
|
|
53
53
|
return `under Node ${difference.recordedVersion}, not ${difference.currentVersion}`;
|
|
54
54
|
case 'other':
|
|
55
|
-
return 'under an install or environment this run does not share';
|
|
55
|
+
return 'under an install or environment that this run does not share';
|
|
56
56
|
default: {
|
|
57
57
|
const unhandledDifference = difference;
|
|
58
58
|
throw new Error(`Unhandled key difference: ${JSON.stringify(unhandledDifference)}`);
|
|
@@ -62,9 +62,9 @@ function describeDifference(difference) {
|
|
|
62
62
|
function describeRefusal(command, refusal) {
|
|
63
63
|
switch (refusal.kind) {
|
|
64
64
|
case 'uncacheable':
|
|
65
|
-
return `\`${command}\` is outside the check-result cache, so
|
|
66
|
-
case '
|
|
67
|
-
return 'the check-result cache
|
|
65
|
+
return `\`${command}\` is outside the check-result cache, so the cache does not record any run of it`;
|
|
66
|
+
case 'cache-inapplicable':
|
|
67
|
+
return 'the check-result cache does not apply here (NMR_DEBUG=1 reports why)';
|
|
68
68
|
case 'unrecorded':
|
|
69
69
|
return 'nothing has recorded a pass for this scope';
|
|
70
70
|
case 'mismatched':
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { type ReplayLine } from './check-cache.js';
|
|
2
2
|
import { type Step } from './steps.js';
|
|
3
3
|
/**
|
|
4
|
-
* Assembles what a composite's skip replays: the excerpts its constituents
|
|
5
|
-
* steps name them.
|
|
4
|
+
* Assembles what a composite's skip replays: the excerpts recorded by its constituents, in the order in which
|
|
5
|
+
* its own steps name them.
|
|
6
6
|
*
|
|
7
|
-
* A constituent that is itself a composite
|
|
7
|
+
* A constituent that is itself a composite has a flat, attributed list of its own, so splicing one in keeps
|
|
8
8
|
* the assembly flat and a skipped `ci` replays a package's `test` rather than one opaque `check:strict`
|
|
9
|
-
* line. Nothing is computed and nothing is inferred:
|
|
10
|
-
* the assembly.
|
|
9
|
+
* line. Nothing is computed and nothing is inferred: A constituent without an admissible entry is absent
|
|
10
|
+
* from the assembly.
|
|
11
11
|
*
|
|
12
|
-
* A constituent is looked up at the scope its own process anchors
|
|
13
|
-
* root for an element
|
|
12
|
+
* A constituent is looked up at the scope at which its own process anchors: the composite's anchor, or the
|
|
13
|
+
* monorepo root for an element that passes `-w`, which is how a package-scoped composite finds a root command's entry.
|
|
14
14
|
*
|
|
15
|
-
* An entry is admissible
|
|
16
|
-
* The witness is what a run stamps on an excerpt it records and on one it recalls and replays; the
|
|
17
|
-
*
|
|
15
|
+
* An entry is admissible when the run that certified it is this one and the tree that it describes is this one.
|
|
16
|
+
* The witness is what a run stamps on an excerpt that it records and on one that it recalls and replays; the
|
|
17
|
+
* tree hash bounds an identity carried by a process out of the run that issued it.
|
|
18
18
|
*/
|
|
19
19
|
export declare function assembleReplay(options: {
|
|
20
20
|
anchorDir: string;
|
|
@@ -1,32 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Renders the rejection of a value off the format ladder, naming where it was written. Every source that
|
|
3
|
-
* accepts a format renders through this, so
|
|
3
|
+
* accepts a format renders through this, so two of them can never come to name different ladders.
|
|
4
4
|
*/
|
|
5
5
|
export declare function formatReportFormatRejection(source: string, value: string): string;
|
|
6
6
|
/** Narrows a raw value to a point on the format ladder. */
|
|
7
7
|
export declare function isReportFormat(value: string): value is ReportFormat;
|
|
8
8
|
/**
|
|
9
|
-
* Reads the format the environment names. An unrecognized value
|
|
10
|
-
*
|
|
9
|
+
* Reads the format that the environment names. An unrecognized value is returned as an error, which keeps a
|
|
10
|
+
* misspelling from going unnoticed for the life of the shell.
|
|
11
11
|
*
|
|
12
|
-
* An unset or empty variable
|
|
13
|
-
* with the one the loudness ladder performs and leaves room for a level below the environment.
|
|
12
|
+
* An unset or empty variable does not name a format, and `resolveReportFormat` supplies the default.
|
|
14
13
|
*/
|
|
15
14
|
export declare function readReportFormatEnv(env: NodeJS.ProcessEnv): ReportFormatRead;
|
|
16
15
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* Passes the resolved format down the spawned chain, so that `--json` takes effect in every process and not only
|
|
17
|
+
* the first. It is not a keyed variable: It changes how a run reports and never what a command concludes, so a
|
|
18
|
+
* machine-readable run recalls a pass that a prose run recorded.
|
|
20
19
|
*/
|
|
21
20
|
export declare const REPORT_FORMAT_ENV_VAR = "NMR_REPORT_FORMAT";
|
|
22
21
|
/**
|
|
23
|
-
* The points on the format ladder.
|
|
24
|
-
*
|
|
22
|
+
* The points on the format ladder. `text` is a named point, so that one invocation can override a format
|
|
23
|
+
* exported for the shell in either direction.
|
|
25
24
|
*/
|
|
26
25
|
export declare const REPORT_FORMATS: readonly ["text", "json"];
|
|
27
|
-
/** How nmr renders its own verdicts, which is never the output of the commands it runs. */
|
|
26
|
+
/** How nmr renders its own verdicts, which is never the output of the commands that it runs. */
|
|
28
27
|
export type ReportFormat = (typeof REPORT_FORMATS)[number];
|
|
29
|
-
/** The format the environment names, if any, or the message naming why its value could not be read. */
|
|
28
|
+
/** The format that the environment names, if any, or the message naming why its value could not be read. */
|
|
30
29
|
export type ReportFormatRead = {
|
|
31
30
|
ok: true;
|
|
32
31
|
format?: ReportFormat;
|
|
@@ -35,14 +34,10 @@ export type ReportFormatRead = {
|
|
|
35
34
|
error: string;
|
|
36
35
|
};
|
|
37
36
|
/**
|
|
38
|
-
* Resolves the format this process reports
|
|
37
|
+
* Resolves the format in which this process reports: the flag, then the environment, then `text`.
|
|
39
38
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* teaches; either is additive later.
|
|
43
|
-
*
|
|
44
|
-
* Total by construction. The environment was validated as it was read, so no rejection is left for this to
|
|
45
|
-
* report and the whole ladder reads in one place.
|
|
39
|
+
* Total by construction. The environment was validated as it was read, so this does not have any rejection left
|
|
40
|
+
* to report and the whole ladder reads in one place.
|
|
46
41
|
*/
|
|
47
42
|
export declare function resolveReportFormat(options: ResolveReportFormatOptions): ReportFormat;
|
|
48
43
|
export interface ResolveReportFormatOptions {
|
|
@@ -3,8 +3,7 @@ import type { Writable } from 'node:stream';
|
|
|
3
3
|
* Writes a failure to the CLI's error stream: as its message alone when the user's own declaration caused it,
|
|
4
4
|
* and with its stack otherwise.
|
|
5
5
|
*
|
|
6
|
-
* A stack records where nmr noticed a problem, which says nothing about the file a user has to edit; for
|
|
7
|
-
* own fault it is where a report has to start.
|
|
8
|
-
* non-`Error` alike.
|
|
6
|
+
* A stack records where nmr noticed a problem, which says nothing about the file that a user has to edit; for
|
|
7
|
+
* nmr's own fault it is where a report has to start.
|
|
9
8
|
*/
|
|
10
9
|
export declare function reportCliFailure(error: unknown, stderr: Writable): void;
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { type ScriptRegistry } from './default-scripts.js';
|
|
2
2
|
export type { ScriptRegistry, ScriptValue, StepSpec } from './default-scripts.js';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* A function rather than the exported table itself: callers merge overrides into the result, and `generateHelp`
|
|
7
|
-
* rewrites entries in place, so each caller needs a copy the module does not share.
|
|
4
|
+
* Returns a shallow copy of the default workspace scripts, whose entries a caller may replace without changing the
|
|
5
|
+
* defaults.
|
|
8
6
|
*/
|
|
9
7
|
export declare function getDefaultWorkspaceScripts(): ScriptRegistry;
|
|
10
8
|
/**
|
|
11
|
-
*
|
|
9
|
+
* Returns a shallow copy of the default root scripts, whose entries a caller may replace without changing the
|
|
10
|
+
* defaults.
|
|
12
11
|
*/
|
|
13
12
|
export declare function getDefaultRootScripts(): ScriptRegistry;
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { type StreamStyles } from '@williamthorsen/nmr-core';
|
|
2
2
|
/**
|
|
3
|
-
* Resolves the style of the running process's own output streams, and rejects a variable
|
|
3
|
+
* Resolves the style of the running process's own output streams, and rejects a variable that does not name any
|
|
4
|
+
* style.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
* reaches a bin is the variable that `nmr <command>` exports to it, so a bin run through nmr renders as nmr
|
|
7
|
-
* does and one run on its own detects its streams.
|
|
6
|
+
* Each standalone bin calls this and does not pass a flag value, because only `nmr` accepts `OUTPUT_STYLE_FLAG`.
|
|
8
7
|
*
|
|
9
|
-
* Throws rather than exiting, so the bin's own boundary reports the message and sets the exit code, as it does
|
|
8
|
+
* Throws rather than exiting, so that the bin's own boundary reports the message and sets the exit code, as it does
|
|
10
9
|
* for every other failure in what the caller declared.
|
|
11
10
|
*/
|
|
12
11
|
export declare function resolveBinStyles(): StreamStyles;
|
package/dist/esm/resolver.d.ts
CHANGED
|
@@ -2,22 +2,22 @@ import { type ScriptRegistry, type ScriptValue } from './resolve-scripts.js';
|
|
|
2
2
|
import { type Step } from './steps.js';
|
|
3
3
|
import type { NmrConfig } from './types.js';
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Replaces the first token of a command with a `devBin` substitute.
|
|
6
6
|
* Relative paths in the replacement are resolved from `monorepoRoot`.
|
|
7
7
|
*/
|
|
8
8
|
export declare function applyDevBin(command: string, devBin: Record<string, string> | undefined, monorepoRoot: string): string;
|
|
9
9
|
/**
|
|
10
10
|
* Applies a `devBin` substitution to each opaque step, leaving nmr's own compositions alone.
|
|
11
11
|
*
|
|
12
|
-
* A structural step names a command nmr resolves for itself, so substituting its first token would replace
|
|
13
|
-
* `nmr` that
|
|
12
|
+
* A structural step names a command that nmr resolves for itself, so substituting its first token would replace
|
|
13
|
+
* the `nmr` that runs the composite rather than the leaf tool that `devBin` documents replacing.
|
|
14
14
|
*/
|
|
15
15
|
export declare function applyDevBinToSteps(steps: readonly Step[], devBin: Record<string, string> | undefined, monorepoRoot: string): readonly Step[];
|
|
16
16
|
/**
|
|
17
17
|
* Where a resolved script was read from.
|
|
18
18
|
*
|
|
19
19
|
* `registry` covers the built-in defaults and the repo-wide config together, which resolution cannot tell
|
|
20
|
-
* apart:
|
|
20
|
+
* apart: It receives the two already merged. A caller that has the config refines the two.
|
|
21
21
|
*/
|
|
22
22
|
export type ScriptOrigin = {
|
|
23
23
|
tier: 'registry';
|
|
@@ -32,11 +32,11 @@ export interface ResolvedScript {
|
|
|
32
32
|
steps: readonly Step[];
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
|
-
* Expands a script value into the ordered steps it runs as: a string is one opaque step, and an array is one
|
|
35
|
+
* Expands a script value into the ordered steps that it runs as: a string is one opaque step, and an array is one
|
|
36
36
|
* structural step per element.
|
|
37
37
|
*
|
|
38
38
|
* A bare string element and the `{ run }` spec compose the same step. The spec's only addition is the
|
|
39
|
-
* declaration of what the step does with the invocation's trailing arguments, which position cannot
|
|
39
|
+
* declaration of what the step does with the invocation's trailing arguments, which position cannot express.
|
|
40
40
|
*/
|
|
41
41
|
export declare function expandScript(script: ScriptValue, isWorkspaceRoot: boolean): readonly Step[];
|
|
42
42
|
/**
|
|
@@ -47,8 +47,7 @@ export declare function describeScript(script: ScriptValue): string;
|
|
|
47
47
|
* Reads a package.json's `scripts`, rejecting any value that is not a string.
|
|
48
48
|
*
|
|
49
49
|
* npm and pnpm read a script as a string too, so a value of any other type is malformed however it got there.
|
|
50
|
-
* Dropping one silently would run the registry's entry in its place
|
|
51
|
-
* being told a step list resolves a shelled-nmr crossing would otherwise do.
|
|
50
|
+
* Dropping one silently would run the registry's entry in its place.
|
|
52
51
|
*/
|
|
53
52
|
export declare function readPackageJsonScripts(packageDir: string): Record<string, string> | undefined;
|
|
54
53
|
/**
|
|
@@ -62,20 +61,17 @@ export declare function buildWorkspaceRegistry(config: NmrConfig): ScriptRegistr
|
|
|
62
61
|
*/
|
|
63
62
|
export declare function buildRootRegistry(config: NmrConfig): ScriptRegistry;
|
|
64
63
|
/**
|
|
65
|
-
* Returns a `package.json` entry that re-invokes the command it is declared
|
|
66
|
-
* `undefined`
|
|
64
|
+
* Returns a `package.json` entry that re-invokes the command under which it is declared alongside other steps,
|
|
65
|
+
* or `undefined` when the entry does not.
|
|
67
66
|
*
|
|
68
|
-
* Resolution discards a self-referential entry however it reads, so an entry that chains loses the steps it
|
|
67
|
+
* Resolution discards a self-referential entry however it reads, so an entry that chains loses the steps that it
|
|
69
68
|
* chained. This is what an invocation rejects on, read where the command runs rather than raised from
|
|
70
|
-
* resolution:
|
|
69
|
+
* resolution: The same scripts are resolved speculatively, for packages that nobody named.
|
|
71
70
|
*/
|
|
72
71
|
export declare function findChainedSelfReference(packageDir: string | undefined, commandName: string): string | undefined;
|
|
73
72
|
/**
|
|
74
|
-
* Reports whether a `package.json` entry re-invokes the command it is declared
|
|
75
|
-
* re-invocation
|
|
76
|
-
*
|
|
77
|
-
* Honouring one spawns a shell that runs the same command in the same directory, reaching the same entry
|
|
78
|
-
* again without bound, so resolution discards it.
|
|
73
|
+
* Reports whether a `package.json` entry re-invokes the command under which it is declared, wherever the
|
|
74
|
+
* re-invocation appears in it, e.g. `"build": "nmr build"` or `"build": "rdy compile && nmr build"`.
|
|
79
75
|
*/
|
|
80
76
|
export declare function isSelfReferential(script: string, commandName: string, packageDir: string): boolean;
|
|
81
77
|
/**
|
|
@@ -85,6 +81,6 @@ export declare function isSelfReferential(script: string, commandName: string, p
|
|
|
85
81
|
* 3. Per-package overrides (package.json scripts)
|
|
86
82
|
*
|
|
87
83
|
* Returns undefined if the command is not found in the registry. A package.json override of `""` (indicating
|
|
88
|
-
* skip) resolves to a single opaque step
|
|
84
|
+
* skip) resolves to a single opaque step containing it, which renders back to the empty string.
|
|
89
85
|
*/
|
|
90
86
|
export declare function resolveScript(commandName: string, registry: ScriptRegistry, packageDir: string | undefined, isWorkspaceRoot: boolean): ResolvedScript | undefined;
|
package/dist/esm/runCli.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Writable } from 'node:stream';
|
|
2
2
|
/**
|
|
3
|
-
* Marks a run made on a delegating caller's behalf,
|
|
4
|
-
* rather than failing.
|
|
3
|
+
* Marks a run made on a delegating caller's behalf, in which a command that the registry does not define, and a
|
|
4
|
+
* `--log` with nothing to show, exit 0 rather than failing.
|
|
5
5
|
*/
|
|
6
6
|
export declare const RUN_IF_PRESENT_ENV_VAR = "NMR_RUN_IF_PRESENT";
|
|
7
7
|
/** @internal */
|
|
@@ -10,11 +10,11 @@ export interface RunCliOptions {
|
|
|
10
10
|
args: string[];
|
|
11
11
|
/** Working directory used to resolve the nmr execution context. */
|
|
12
12
|
cwd: string;
|
|
13
|
-
/** Environment
|
|
13
|
+
/** Environment from which `runCli` reads its settings, and the base of the one that its children inherit. */
|
|
14
14
|
env: NodeJS.ProcessEnv;
|
|
15
|
-
/** Stream for normal output
|
|
15
|
+
/** Stream for normal output: help, version, verdicts, recordings, and override notices. */
|
|
16
16
|
stdout: Writable;
|
|
17
|
-
/** Stream for
|
|
17
|
+
/** Stream for errors, warnings, and debug notes. */
|
|
18
18
|
stderr: Writable;
|
|
19
19
|
}
|
|
20
20
|
/** @internal */
|
|
@@ -23,7 +23,7 @@ export interface RunCliResult {
|
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
25
|
* Executes the nmr CLI flow in-process and returns the resulting exit code.
|
|
26
|
-
*
|
|
26
|
+
* Does not hold global state, does not read `process.*` globals, and never calls `process.exit`.
|
|
27
27
|
*
|
|
28
28
|
* @internal
|
|
29
29
|
*/
|
package/dist/esm/runCli.js
CHANGED
|
@@ -25,7 +25,7 @@ import { writeVerdict } from "./verdict.js";
|
|
|
25
25
|
import { readWorkspacePackageNames, resolveWorkspace } from "./workspace.js";
|
|
26
26
|
const VERSION = readPackageVersion(import.meta.url);
|
|
27
27
|
const CROSSING_CONSEQUENCE = "so nmr handles the nested run's output as a tool's.";
|
|
28
|
-
const RECURSIVE_REJECTION = '-R/--recursive
|
|
28
|
+
const RECURSIVE_REJECTION = '-R/--recursive did not match any workspace:';
|
|
29
29
|
const LIST_CEILING = 10;
|
|
30
30
|
const NAMED_ESCAPES = new Map([
|
|
31
31
|
['\n', String.raw `\n`],
|
|
@@ -249,7 +249,7 @@ function bindPassthrough(steps, passthrough, command) {
|
|
|
249
249
|
return { ok: true, steps: boundSteps };
|
|
250
250
|
}
|
|
251
251
|
function formatUnroutableArgumentsError(command) {
|
|
252
|
-
return (`\`${command}\`
|
|
252
|
+
return (`\`${command}\` does not take trailing arguments: every step of its chain declines them. ` +
|
|
253
253
|
'Running it unnarrowed is not what the arguments asked for, so nothing ran.');
|
|
254
254
|
}
|
|
255
255
|
function composeDelegation(options) {
|
|
@@ -320,7 +320,7 @@ function describeCrossingRemedy(options) {
|
|
|
320
320
|
}
|
|
321
321
|
function describeStepDestination(key) {
|
|
322
322
|
if (isHookName(key)) {
|
|
323
|
-
return 'a script of their own that the step list names, because a hook
|
|
323
|
+
return 'a script of their own that the step list names, because a hook does not have a `:pre` or `:post` of its own';
|
|
324
324
|
}
|
|
325
325
|
return `a \`${key}:pre\` or \`${key}:post\` script`;
|
|
326
326
|
}
|
|
@@ -357,7 +357,7 @@ function formatOverrideNotice(resolvedScript, registry, command, anchorDir, quie
|
|
|
357
357
|
function formatSelfReferenceRemedy(options) {
|
|
358
358
|
const { command, isWorkspaceRoot, monorepoRoot, registry, script } = options;
|
|
359
359
|
if (isHookName(command)) {
|
|
360
|
-
return `Delete the re-invocation: \`${command}\` runs the steps
|
|
360
|
+
return `Delete the re-invocation: \`${command}\` runs the steps beside it.`;
|
|
361
361
|
}
|
|
362
362
|
const configSite = path.relative(monorepoRoot, resolveConfigPath(monorepoRoot));
|
|
363
363
|
return formatPackageRemedy({ configSite, crossingStep: script, isWorkspaceRoot, key: command, registry });
|
|
@@ -366,14 +366,14 @@ function formatPackageRemedy(options) {
|
|
|
366
366
|
const { configSite, crossingStep, isWorkspaceRoot, key, registry } = options;
|
|
367
367
|
const registryEntry = Object.hasOwn(registry, key) ? registry[key] : undefined;
|
|
368
368
|
if (registryEntry === undefined) {
|
|
369
|
-
return (`A \`package.json\` script
|
|
369
|
+
return (`A \`package.json\` script cannot contain a step list: define \`${key}\` in \`${configSite}\` and move the ` +
|
|
370
370
|
`package-specific steps to ${describeStepDestination(key)}.`);
|
|
371
371
|
}
|
|
372
372
|
const registryChain = renderChain(expandScript(registryEntry, isWorkspaceRoot));
|
|
373
373
|
if (registryChain === crossingStep) {
|
|
374
374
|
return `Delete the entry: nmr's own \`${key}\` already runs \`${escapeControlCharacters(registryChain)}\`.`;
|
|
375
375
|
}
|
|
376
|
-
return `Delete the entry and move the steps it adds to ${describeStepDestination(key)}.`;
|
|
376
|
+
return `Delete the entry and move the steps that it adds to ${describeStepDestination(key)}.`;
|
|
377
377
|
}
|
|
378
378
|
function findArgError(parsedArgs) {
|
|
379
379
|
if (parsedArgs.shouldPrintLog &&
|
|
@@ -401,15 +401,15 @@ function findEmptySelectionRefusal(options) {
|
|
|
401
401
|
function formatEmptyFilterError(pattern, names) {
|
|
402
402
|
const rejection = formatFilterRejection(pattern);
|
|
403
403
|
if (pattern.startsWith('!')) {
|
|
404
|
-
return `${rejection} A pattern beginning with \`!\` excludes what it matches, and this one
|
|
404
|
+
return `${rejection} A pattern beginning with \`!\` excludes what it matches, and this one excludes every package.`;
|
|
405
405
|
}
|
|
406
406
|
if (pattern.includes('[')) {
|
|
407
|
-
return `${rejection} A pattern
|
|
407
|
+
return `${rejection} A pattern containing \`[<ref>]\` selects the packages changed since a git ref, and none has changed.`;
|
|
408
408
|
}
|
|
409
409
|
if (pattern.startsWith('.') || pattern.startsWith('/') || pattern.startsWith('{')) {
|
|
410
410
|
return (`${rejection} ` +
|
|
411
411
|
'A pattern written `./dir`, `/dir`, or `{dir}` selects the packages under a directory, ' +
|
|
412
|
-
'and
|
|
412
|
+
'and this directory does not contain any package.');
|
|
413
413
|
}
|
|
414
414
|
return (`${rejection} A pattern matches a package's manifest \`name\`, ` +
|
|
415
415
|
`not its directory name.${suggestWorkspaceNames(pattern, names)}`);
|
|
@@ -426,23 +426,23 @@ function describeEmptyWorkspace(monorepoRoot) {
|
|
|
426
426
|
return (`pnpm-workspace.yaml ${declaredClause}, whose \`!\` entries exclude every directory matched by the positive ` +
|
|
427
427
|
'patterns. Drop or narrow the exclusion.');
|
|
428
428
|
case 'no-package':
|
|
429
|
-
return (`pnpm-workspace.yaml ${declaredClause}, and the matcher
|
|
430
|
-
'nmr counts a directory as a package only
|
|
429
|
+
return (`pnpm-workspace.yaml ${declaredClause}, and the matcher did not find any directory containing a \`package.json\`. ` +
|
|
430
|
+
'nmr counts a directory as a package only when it contains `package.json`; unlike pnpm, it recognizes ' +
|
|
431
431
|
'neither `package.yaml` nor `package.json5`. Add a `package.json` to the directory that should be a ' +
|
|
432
|
-
'package, or declare a pattern reaching a directory that
|
|
432
|
+
'package, or declare a pattern reaching a directory that contains one.');
|
|
433
433
|
case 'no-packages-list':
|
|
434
|
-
return (`pnpm-workspace.yaml at ${monorepoRoot}
|
|
434
|
+
return (`pnpm-workspace.yaml at ${monorepoRoot} does not declare a \`packages\` list, so the workspace contains the root ` +
|
|
435
435
|
'package alone. Declare a positive pattern such as `packages/*` to reach the packages beneath it.');
|
|
436
436
|
case 'no-pattern':
|
|
437
|
-
return (`pnpm-workspace.yaml ${declaredClause}, so
|
|
438
|
-
'as `packages/*`, and quote any `!` entry, which YAML reads as a tag rather than a string
|
|
439
|
-
'
|
|
437
|
+
return (`pnpm-workspace.yaml ${declaredClause}, so the matcher does not receive any pattern. Declare a positive ` +
|
|
438
|
+
'pattern such as `packages/*`, and quote any `!` entry, which YAML reads as a tag rather than a string ' +
|
|
439
|
+
'when it is unquoted.');
|
|
440
440
|
case 'unreadable-packages':
|
|
441
441
|
return (`pnpm-workspace.yaml at ${monorepoRoot} declares a \`packages\` value that is not a list of strings, so ` +
|
|
442
442
|
'nothing it declares reaches the matcher. Make it a list whose every entry is a quoted pattern; a bare ' +
|
|
443
443
|
'value and an entry YAML read as a number or a map are the usual ones.');
|
|
444
444
|
case 'unreadable-manifest':
|
|
445
|
-
return (`pnpm-workspace.yaml at ${monorepoRoot}
|
|
445
|
+
return (`pnpm-workspace.yaml at ${monorepoRoot} is not valid YAML, so the matcher does not receive anything that it declares. ` +
|
|
446
446
|
'Repair the syntax error and run the command again. An unterminated quoted string and a mis-indented ' +
|
|
447
447
|
'entry are the usual ones.');
|
|
448
448
|
default: {
|
|
@@ -463,7 +463,7 @@ function describeDeclaredPatterns(patterns) {
|
|
|
463
463
|
: `declares ${renderQuotedList(quotablePatterns)}, beside ${emptiedClause}`;
|
|
464
464
|
}
|
|
465
465
|
function formatFilterRejection(pattern) {
|
|
466
|
-
return `-F/--filter
|
|
466
|
+
return `-F/--filter did not match any workspace: \`${pattern}\`.`;
|
|
467
467
|
}
|
|
468
468
|
function suggestWorkspaceNames(pattern, names) {
|
|
469
469
|
if (names.length === 0) {
|
|
@@ -511,7 +511,7 @@ function assertNoSelfReference(options) {
|
|
|
511
511
|
const remedy = formatSelfReferenceRemedy({ command, isWorkspaceRoot, monorepoRoot, registry, script });
|
|
512
512
|
const site = path.relative(monorepoRoot, resolvePackageJsonPath(anchorDir));
|
|
513
513
|
throw new UserError(`${site}: \`scripts.${command}\` re-invokes \`nmr ${command}\` (\`${escapeControlCharacters(script)}\`), ` +
|
|
514
|
-
`so nmr cannot run the steps it chains. ${remedy}`);
|
|
514
|
+
`so nmr cannot run the steps that it chains. ${remedy}`);
|
|
515
515
|
}
|
|
516
516
|
function hasRunnableHook(hookName, registry, anchorDir, isWorkspaceRoot) {
|
|
517
517
|
if (findChainedSelfReference(anchorDir, hookName) !== undefined) {
|
|
@@ -536,7 +536,7 @@ async function lookUpRecordedPass(options) {
|
|
|
536
536
|
}
|
|
537
537
|
const [missingPackage] = buildOutput.missingPackages;
|
|
538
538
|
if (missingPackage !== undefined) {
|
|
539
|
-
writeDebugNote(`running ${command}: ${missingPackage}
|
|
539
|
+
writeDebugNote(`running ${command}: ${missingPackage} does not have build output`, env, stderr);
|
|
540
540
|
return undefined;
|
|
541
541
|
}
|
|
542
542
|
const stalePackage = findStaleBuildOutput(entry.buildDigests, buildOutput.digests);
|
|
@@ -715,7 +715,7 @@ async function recordPass(options) {
|
|
|
715
715
|
const output = await readBuildOutputState(monorepoRoot, options.config);
|
|
716
716
|
const [missingPackage] = output.missingPackages;
|
|
717
717
|
if (missingPackage !== undefined) {
|
|
718
|
-
writeDebugNote(`not recording ${command}: ${missingPackage}
|
|
718
|
+
writeDebugNote(`not recording ${command}: ${missingPackage} does not have build output`, env, stderr);
|
|
719
719
|
return;
|
|
720
720
|
}
|
|
721
721
|
const changedPackage = findStaleBuildOutput(options.buildOutputBefore.digests, output.digests);
|
package/dist/esm/runner.d.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import type { Writable } from 'node:stream';
|
|
2
2
|
import type { Step } from './steps.js';
|
|
3
|
-
/** Where one of a child's output streams goes: a descriptor the child writes
|
|
3
|
+
/** Where one of a child's output streams goes: a descriptor to which the child writes, or a pipe read by nmr. */
|
|
4
4
|
export type OutputChannel = number | 'pipe';
|
|
5
|
-
/** The channel each of a child's output streams runs
|
|
5
|
+
/** The channel on which each of a child's output streams runs. */
|
|
6
6
|
export interface OutputChannels {
|
|
7
7
|
stderr: OutputChannel;
|
|
8
8
|
stdout: OutputChannel;
|
|
9
9
|
}
|
|
10
10
|
export interface RunCommandOptions {
|
|
11
|
-
/** The channel each of the child's output streams runs
|
|
11
|
+
/** The channel on which each of the child's output streams runs. */
|
|
12
12
|
channels: OutputChannels;
|
|
13
|
-
/** When true,
|
|
13
|
+
/** When true, suppresses output on success and writes captured output to stderr on failure. */
|
|
14
14
|
quiet?: boolean;
|
|
15
15
|
/** Stream that subprocess stdout flows to in non-quiet mode. Defaults to `process.stdout`. */
|
|
16
16
|
stdout?: Writable;
|
|
@@ -34,7 +34,7 @@ export interface RunStepsResult {
|
|
|
34
34
|
*/
|
|
35
35
|
retainedOutput?: RetainedOutput;
|
|
36
36
|
}
|
|
37
|
-
/** How a command ended, and the exit code that
|
|
37
|
+
/** How a command ended, and the exit code that reports that ending to nmr's own caller. */
|
|
38
38
|
type CommandCompletion = {
|
|
39
39
|
outcome: 'exited';
|
|
40
40
|
exitCode: number;
|
|
@@ -50,33 +50,33 @@ type CommandCompletion = {
|
|
|
50
50
|
export type RunCommandResult = CommandCompletion & {
|
|
51
51
|
/**
|
|
52
52
|
* Output retained while the command ran, elided in the middle when it overran the bound.
|
|
53
|
-
* `undefined`
|
|
53
|
+
* `undefined` when the stream was handed to the child directly, or when capture stopped early
|
|
54
54
|
* and the retained copy would understate what the command produced.
|
|
55
55
|
*/
|
|
56
56
|
stdout: Buffer | undefined;
|
|
57
57
|
stderr: Buffer | undefined;
|
|
58
58
|
};
|
|
59
59
|
/**
|
|
60
|
-
* Returns the channel a stream runs
|
|
61
|
-
* being withheld, so the child's terminal detection matches nmr's, and a pipe everywhere else.
|
|
60
|
+
* Returns the channel on which a stream runs: the stream's own descriptor when it is a terminal and output is
|
|
61
|
+
* not being withheld, so that the child's terminal detection matches nmr's, and a pipe everywhere else.
|
|
62
62
|
*/
|
|
63
63
|
export declare function resolveChannel(stream: Writable, quiet: boolean): OutputChannel;
|
|
64
64
|
/**
|
|
65
|
-
* Returns the channel a step
|
|
66
|
-
*
|
|
67
|
-
*
|
|
65
|
+
* Returns the channel for a step that runs an nmr process below this one: nmr's own descriptor whenever it has
|
|
66
|
+
* one, so that the child writes where nmr writes, without an ancestor between them relaying bytes. A stream
|
|
67
|
+
* without a descriptor falls back to a pipe.
|
|
68
68
|
*/
|
|
69
69
|
export declare function resolveInheritedChannel(stream: Writable): OutputChannel;
|
|
70
70
|
/**
|
|
71
|
-
* Runs a step list in order and resolves once one fails or all have run,
|
|
72
|
-
* promises:
|
|
71
|
+
* Runs a step list in order and resolves once one fails or all have run, implementing the `&&` semantics that
|
|
72
|
+
* nmr promises: The first non-zero exit ends the sequence and is the code returned.
|
|
73
73
|
*
|
|
74
|
-
* Sequencing here rather than in a spawned shell
|
|
75
|
-
*
|
|
76
|
-
*
|
|
74
|
+
* Sequencing here rather than in a spawned shell lets each step run on the channels that its kind calls for, and
|
|
75
|
+
* makes a signal to nmr end the run: The steps after it are never reached, whereas a shell would have gone on
|
|
76
|
+
* running them unsupervised.
|
|
77
77
|
*
|
|
78
|
-
*
|
|
79
|
-
* steps contribute:
|
|
78
|
+
* Returns a bounded copy of what the list's own commands wrote, so that a caller can retain it. Only opaque
|
|
79
|
+
* steps contribute: A structural step is another nmr process reporting for the subtree beneath it, and its
|
|
80
80
|
* output is that subtree's to account for.
|
|
81
81
|
*/
|
|
82
82
|
export declare function runSteps(steps: readonly Step[], cwd: string | undefined, options: RunStepsOptions): Promise<RunStepsResult>;
|
|
@@ -89,7 +89,7 @@ export declare function runSteps(steps: readonly Step[], cwd: string | undefined
|
|
|
89
89
|
* discarding the retained copy on success and writing it to `options.stderr` on failure, so it has nothing to
|
|
90
90
|
* report unless the caller chose a pipe on both streams.
|
|
91
91
|
*
|
|
92
|
-
*
|
|
92
|
+
* Because piped stdout and stderr are ordered by arrival rather than by a shared descriptor, their interleaving
|
|
93
93
|
* is approximate.
|
|
94
94
|
*/
|
|
95
95
|
export declare function runCommand(command: string, cwd: string | undefined, options: RunCommandOptions): Promise<RunCommandResult>;
|