@williamthorsen/nmr 0.38.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 +212 -4
- package/.readyup/kits/default.js +124 -104
- package/.readyup/manifest.json +12 -7
- package/CHANGELOG.md +108 -0
- package/README.md +41 -47
- 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 +61 -63
- package/dist/esm/check-cache.js +24 -24
- package/dist/esm/cli-ensure-prepublish-hooks.js +4 -4
- package/dist/esm/commands/build-output.d.ts +37 -38
- package/dist/esm/commands/build-output.js +9 -9
- package/dist/esm/commands/build.d.ts +13 -14
- package/dist/esm/commands/build.js +67 -65
- package/dist/esm/commands/clean.d.ts +6 -6
- package/dist/esm/commands/clean.js +11 -11
- package/dist/esm/commands/ensure-prepublish-hooks.d.ts +7 -7
- package/dist/esm/commands/ensure-prepublish-hooks.js +41 -41
- package/dist/esm/commands/fmt.d.ts +8 -8
- package/dist/esm/commands/fmt.js +33 -33
- package/dist/esm/commands/report-catalog.d.ts +5 -7
- package/dist/esm/commands/report-catalog.js +10 -8
- package/dist/esm/commands/report-overrides.d.ts +0 -2
- package/dist/esm/commands/report-overrides.js +8 -8
- package/dist/esm/commands/taze.d.ts +9 -8
- package/dist/esm/config.d.ts +5 -5
- package/dist/esm/config.js +27 -27
- package/dist/esm/context.d.ts +1 -2
- package/dist/esm/context.js +8 -8
- 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 +4 -4
- package/dist/esm/help.js +21 -21
- package/dist/esm/helpers/clampToBytes.d.ts +4 -5
- package/dist/esm/helpers/clampToBytes.js +3 -3
- package/dist/esm/helpers/config-path.d.ts +0 -2
- package/dist/esm/helpers/createBoundedBuffer.d.ts +3 -3
- package/dist/esm/helpers/createBoundedBuffer.js +2 -2
- package/dist/esm/helpers/deriveExcerpt.d.ts +4 -5
- package/dist/esm/helpers/duration.d.ts +10 -11
- package/dist/esm/helpers/duration.js +7 -7
- package/dist/esm/helpers/filter-selection.d.ts +19 -19
- package/dist/esm/helpers/filter-selection.js +12 -12
- package/dist/esm/helpers/findClosestName.d.ts +1 -1
- package/dist/esm/helpers/findClosestName.js +15 -15
- package/dist/esm/helpers/hook-name.d.ts +2 -5
- package/dist/esm/helpers/package-json.d.ts +11 -10
- package/dist/esm/helpers/package-json.js +26 -25
- 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 +15 -15
- package/dist/esm/helpers/transcript.js +14 -14
- 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/prettier.js +5 -5
- package/dist/esm/recording.d.ts +25 -20
- package/dist/esm/recording.js +28 -21
- package/dist/esm/replay-assembly.d.ts +10 -10
- package/dist/esm/report-format.d.ts +15 -20
- package/dist/esm/report-format.js +7 -7
- 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/resolveBinStyles.js +3 -3
- package/dist/esm/resolver.d.ts +16 -20
- package/dist/esm/resolver.js +15 -15
- package/dist/esm/runCli.d.ts +6 -6
- package/dist/esm/runCli.js +261 -195
- package/dist/esm/runner.d.ts +20 -20
- package/dist/esm/runner.js +18 -18
- package/dist/esm/steps.d.ts +51 -38
- package/dist/esm/steps.js +32 -27
- package/dist/esm/taze.d.ts +1 -1
- package/dist/esm/tests.d.ts +23 -18
- package/dist/esm/tests.js +21 -9
- package/dist/esm/tiers.d.ts +28 -24
- package/dist/esm/tiers.js +13 -9
- package/dist/esm/types.d.ts +5 -5
- package/dist/esm/verbosity.d.ts +23 -21
- package/dist/esm/verbosity.js +7 -7
- package/dist/esm/verdict.d.ts +39 -32
- package/dist/esm/verdict.js +63 -50
- package/dist/esm/vitest-source-resolution.d.ts +11 -11
- package/dist/esm/vitest-source-resolution.js +49 -47
- package/dist/esm/vitest.d.ts +27 -26
- package/dist/esm/vitest.js +90 -33
- package/dist/esm/workspace.d.ts +6 -22
- package/dist/esm/workspace.js +9 -67
- package/docs/check-cache.md +57 -57
- package/docs/prettier.md +10 -10
- package/docs/reporting.md +48 -44
- package/docs/scripts.md +29 -29
- package/docs/upgrades.md +18 -18
- package/docs/utilities.md +13 -13
- package/docs/vitest.md +68 -66
- package/docs/workspace.md +27 -4
- package/package.json +6 -6
- package/dist/esm/helpers/workspace-patterns.d.ts +0 -8
- package/dist/esm/helpers/workspace-patterns.js +0 -29
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;
|
|
@@ -32,9 +32,9 @@ export interface RunStepsResult {
|
|
|
32
32
|
* What the list's opaque steps produced, absent unless every one of them was captured whole and there was
|
|
33
33
|
* at least one to capture.
|
|
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>;
|
package/dist/esm/runner.js
CHANGED
|
@@ -14,7 +14,7 @@ export function resolveInheritedChannel(stream) {
|
|
|
14
14
|
return hasDescriptor(stream) ? stream.fd : 'pipe';
|
|
15
15
|
}
|
|
16
16
|
export async function runSteps(steps, cwd, options) {
|
|
17
|
-
const
|
|
17
|
+
const resolvedOptions = {
|
|
18
18
|
...options,
|
|
19
19
|
quiet: options.quiet === true,
|
|
20
20
|
stdout: options.stdout ?? process.stdout,
|
|
@@ -23,7 +23,7 @@ export async function runSteps(steps, cwd, options) {
|
|
|
23
23
|
const parts = [];
|
|
24
24
|
let isCaptureWhole = true;
|
|
25
25
|
for (const step of steps) {
|
|
26
|
-
const result = await runStep(step, cwd,
|
|
26
|
+
const result = await runStep(step, cwd, resolvedOptions);
|
|
27
27
|
if (step.kind === 'opaque') {
|
|
28
28
|
if (result.stderr === undefined || result.stdout === undefined) {
|
|
29
29
|
isCaptureWhole = false;
|
|
@@ -39,31 +39,31 @@ export async function runSteps(steps, cwd, options) {
|
|
|
39
39
|
return { exitCode: 0, ...composeRetainedOutput(parts, isCaptureWhole) };
|
|
40
40
|
}
|
|
41
41
|
export async function runCommand(command, cwd, options) {
|
|
42
|
-
return runSpawned({ args: [], file: command, input: 'inherit',
|
|
42
|
+
return runSpawned({ args: [], file: command, input: 'inherit', shouldUseShell: true }, cwd, options);
|
|
43
43
|
}
|
|
44
|
-
async function runSpawned({ args, file, input,
|
|
44
|
+
async function runSpawned({ args, file, input, shouldUseShell }, cwd, options) {
|
|
45
45
|
const quiet = options.quiet === true;
|
|
46
46
|
const stdout = options.stdout ?? process.stdout;
|
|
47
47
|
const stderr = options.stderr ?? process.stderr;
|
|
48
48
|
const env = options.env ?? process.env;
|
|
49
49
|
const child = spawn(file, args, {
|
|
50
|
-
shell:
|
|
50
|
+
shell: shouldUseShell,
|
|
51
51
|
stdio: [input, options.channels.stdout, options.channels.stderr],
|
|
52
52
|
cwd,
|
|
53
53
|
env,
|
|
54
54
|
});
|
|
55
55
|
const stdoutCapture = child.stdout === null ? undefined : captureStream(child.stdout, quiet ? undefined : stdout);
|
|
56
56
|
const stderrCapture = child.stderr === null ? undefined : captureStream(child.stderr, quiet ? undefined : stderr);
|
|
57
|
-
const
|
|
57
|
+
const commandCompletion = await awaitCompletion(child, () => {
|
|
58
58
|
stdoutCapture?.stopReading();
|
|
59
59
|
stderrCapture?.stopReading();
|
|
60
60
|
});
|
|
61
61
|
stdoutCapture?.detach();
|
|
62
62
|
stderrCapture?.detach();
|
|
63
63
|
const result = {
|
|
64
|
-
...
|
|
65
|
-
stdout: stdoutCapture?.
|
|
66
|
-
stderr: stderrCapture?.
|
|
64
|
+
...commandCompletion,
|
|
65
|
+
stdout: stdoutCapture?.readBuffer(),
|
|
66
|
+
stderr: stderrCapture?.readBuffer(),
|
|
67
67
|
};
|
|
68
68
|
if (result.outcome === 'spawn-failed') {
|
|
69
69
|
reportError(result.error.message, stderr);
|
|
@@ -78,14 +78,14 @@ async function runSpawned({ args, file, input, useShell }, cwd, options) {
|
|
|
78
78
|
function runStep(step, cwd, options) {
|
|
79
79
|
const { quiet, stderr, stdout } = options;
|
|
80
80
|
if (step.kind === 'opaque') {
|
|
81
|
-
return runSpawned({ args: [], file: step.command, input: 'inherit',
|
|
81
|
+
return runSpawned({ args: [], file: step.command, input: 'inherit', shouldUseShell: true }, cwd, {
|
|
82
82
|
...options,
|
|
83
83
|
channels: { stderr: resolveChannel(stderr, quiet), stdout: resolveChannel(stdout, quiet) },
|
|
84
84
|
});
|
|
85
85
|
}
|
|
86
86
|
const [file, ...args] = step.argv;
|
|
87
87
|
const input = step.shouldWithholdInput === true ? 'ignore' : 'inherit';
|
|
88
|
-
return runSpawned({ args, file, input,
|
|
88
|
+
return runSpawned({ args, file, input, shouldUseShell: false }, cwd, {
|
|
89
89
|
...options,
|
|
90
90
|
quiet: false,
|
|
91
91
|
channels: { stderr: resolveInheritedChannel(stderr), stdout: resolveInheritedChannel(stdout) },
|
|
@@ -96,14 +96,14 @@ function composeRetainedOutput(parts, isCaptureWhole) {
|
|
|
96
96
|
return {};
|
|
97
97
|
}
|
|
98
98
|
return {
|
|
99
|
-
|
|
99
|
+
retainedOutput: {
|
|
100
100
|
stderr: Buffer.concat(parts.map((part) => part.stderr)),
|
|
101
101
|
stdout: Buffer.concat(parts.map((part) => part.stdout)),
|
|
102
102
|
},
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
105
|
function captureStream(source, destination) {
|
|
106
|
-
const
|
|
106
|
+
const retainedCopy = createBoundedBuffer();
|
|
107
107
|
let isComplete = true;
|
|
108
108
|
function stopReading() {
|
|
109
109
|
if (destination !== undefined)
|
|
@@ -119,9 +119,9 @@ function captureStream(source, destination) {
|
|
|
119
119
|
destination.on('error', abandon);
|
|
120
120
|
source.pipe(destination, { end: false });
|
|
121
121
|
}
|
|
122
|
-
source.on('data', (chunk) =>
|
|
122
|
+
source.on('data', (chunk) => retainedCopy.append(chunk));
|
|
123
123
|
return {
|
|
124
|
-
|
|
124
|
+
readBuffer: () => (isComplete ? retainedCopy.readBuffer() : undefined),
|
|
125
125
|
detach: () => {
|
|
126
126
|
destination?.off('error', abandon);
|
|
127
127
|
},
|
|
@@ -145,11 +145,11 @@ function awaitCompletion(child, stopReading) {
|
|
|
145
145
|
settle({ outcome: 'spawn-failed', exitCode: 1, error });
|
|
146
146
|
});
|
|
147
147
|
child.on('exit', (code, signal) => {
|
|
148
|
-
const
|
|
149
|
-
exitCompletion =
|
|
148
|
+
const exitDescription = describeExit(code, signal);
|
|
149
|
+
exitCompletion = exitDescription;
|
|
150
150
|
graceTimer = setTimeout(() => {
|
|
151
151
|
stopReading();
|
|
152
|
-
settle(
|
|
152
|
+
settle(exitDescription);
|
|
153
153
|
}, PIPE_DRAIN_GRACE_MS);
|
|
154
154
|
});
|
|
155
155
|
child.on('close', () => {
|
package/dist/esm/steps.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* One element of a resolved command chain,
|
|
3
|
-
* A `structural` step is nmr's own composition,
|
|
2
|
+
* One element of a resolved command chain, recording how it was composed rather than how its text reads.
|
|
3
|
+
* A `structural` step is nmr's own composition, stored as argv; an `opaque` step is a command that nmr does not
|
|
4
|
+
* parse.
|
|
4
5
|
*
|
|
5
6
|
* A structural step's argv leads with the file to spawn, so the runner has one to hand `spawn` without a shell.
|
|
6
7
|
*
|
|
7
|
-
* `shouldDeclineArguments`
|
|
8
|
-
* the binding of the invocation's trailing arguments. Every stage between the two
|
|
9
|
-
* the chain rendering, the replay assembly, the runner -- passes it through and asks nothing of it.
|
|
8
|
+
* `shouldDeclineArguments` is passed from the composite element that composed the step to the one reader that acts
|
|
9
|
+
* on it, the binding of the invocation's trailing arguments. Every stage between the two passes it through unread.
|
|
10
10
|
*
|
|
11
11
|
* `shouldWithholdInput` is read by the runner alone, which gives the step's child the null device as stdin rather
|
|
12
|
-
* than nmr's own. Like `shouldDeclineArguments`, it is set only
|
|
12
|
+
* than nmr's own. Like `shouldDeclineArguments`, it is set only when it holds and leaves the rendered chain unchanged.
|
|
13
13
|
*/
|
|
14
14
|
export type Step = {
|
|
15
15
|
kind: 'opaque';
|
|
@@ -20,69 +20,82 @@ export type Step = {
|
|
|
20
20
|
shouldDeclineArguments?: boolean;
|
|
21
21
|
shouldWithholdInput?: boolean;
|
|
22
22
|
};
|
|
23
|
-
/** What a structural step asks of the nmr process it spawns. */
|
|
23
|
+
/** What a structural step asks of the nmr process that it spawns. */
|
|
24
24
|
export interface NmrStepTarget {
|
|
25
25
|
command: string;
|
|
26
|
-
/** Whether the command is fanned out to other scopes,
|
|
26
|
+
/** Whether the command is fanned out to other scopes, as it is when a `-R` or `-F` sends it. */
|
|
27
27
|
isDelegate: boolean;
|
|
28
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Whether the command is fanned out to every package, as it is when a `-R` sends it. A separate field rather
|
|
30
|
+
* than a narrowing of `isDelegate`, whose reader surveys the scopes that a `-F` may also have reached.
|
|
31
|
+
*/
|
|
32
|
+
isRecursive: boolean;
|
|
33
|
+
/** Whether the command runs against the root registry, as it does when a `-w` anchors it. */
|
|
29
34
|
isWorkspaceRoot: boolean;
|
|
30
35
|
}
|
|
31
36
|
/**
|
|
32
|
-
* How a `package.json` entry names the command it is declared
|
|
33
|
-
* steps. `sole` covers an entry
|
|
37
|
+
* How a `package.json` entry names the command under which it is declared: as its whole value, or alongside
|
|
38
|
+
* other steps. `sole` covers an entry with trailing arguments, which do not declare a step of their own.
|
|
34
39
|
*/
|
|
35
40
|
export type SelfReference = 'chained' | 'sole';
|
|
36
41
|
/**
|
|
37
42
|
* Composes the structural step that re-invokes nmr for one composite element.
|
|
38
43
|
*
|
|
39
|
-
* The element tokenizes on whitespace, so it may
|
|
40
|
-
* `-w` is prepended as its own token
|
|
44
|
+
* The element tokenizes on whitespace, so it may contain nmr's own flags but cannot contain a space-bearing
|
|
45
|
+
* token. `-w` is prepended as its own token: The child selects the root registry on its own.
|
|
41
46
|
*
|
|
42
|
-
* `shouldDeclineArguments` is set only
|
|
43
|
-
*
|
|
47
|
+
* Because `shouldDeclineArguments` is set only when it holds, a step that takes the trailing arguments compares
|
|
48
|
+
* equal to one composed without the option.
|
|
44
49
|
*/
|
|
45
|
-
export declare function composeNmrStep(element: string,
|
|
50
|
+
export declare function composeNmrStep(element: string, isWorkspaceRoot: boolean, shouldDeclineArguments?: boolean): Step;
|
|
46
51
|
/**
|
|
47
|
-
* Returns the
|
|
52
|
+
* Returns the steps that still run in a workspace without any package: every step but the ones that a `-R` fans
|
|
53
|
+
* out.
|
|
54
|
+
*
|
|
55
|
+
* A `-F` step is kept. It names one package, and its absence is a real error whoever composed the step, whereas
|
|
56
|
+
* a `-R` asked for every package and a workspace with none has nothing for it to do.
|
|
57
|
+
*/
|
|
58
|
+
export declare function dropRecursiveSteps(steps: readonly Step[]): Step[];
|
|
59
|
+
/**
|
|
60
|
+
* Returns the text of the first opaque step that runs nmr through a shell, or `undefined` when none does.
|
|
48
61
|
*
|
|
49
62
|
* Recognizes nmr in command position: at the start of the step, after `&&`, `||`, `;`, `|`, or a newline, past
|
|
50
63
|
* any leading environment assignments, and behind a launcher such as `npx` or `pnpm exec`. A separator inside
|
|
51
|
-
* quotes
|
|
64
|
+
* quotes does not open a command position, so a command merely naming nmr in an argument is not a crossing.
|
|
52
65
|
*
|
|
53
|
-
* Partial by construction, and partial in stated ways rather than arbitrary ones:
|
|
66
|
+
* Partial by construction, and partial in stated ways rather than arbitrary ones: A value-taking flag standing
|
|
54
67
|
* immediately before the program name hides it (`npx -p foo nmr`), and a launcher outside `LAUNCHERS` goes
|
|
55
|
-
* unreported. What it finds is the boundary below which nmr cannot tell its own processes from the tools
|
|
56
|
-
* runs.
|
|
68
|
+
* unreported. What it finds is the boundary below which nmr cannot tell its own processes from the tools that
|
|
69
|
+
* it runs.
|
|
57
70
|
*/
|
|
58
71
|
export declare function findNmrCrossing(steps: readonly Step[]): string | undefined;
|
|
59
72
|
/**
|
|
60
73
|
* Returns the first token of a composite element that falls outside the grammar, or `undefined` when the
|
|
61
|
-
* element is a command name optionally preceded by nmr's own flags. A token the shell would not read
|
|
62
|
-
* literally
|
|
74
|
+
* element is a command name optionally preceded by nmr's own flags. A token that the shell would not read
|
|
75
|
+
* literally would be quoted whole by the rendering, turning the element into a command that nobody wrote.
|
|
63
76
|
*/
|
|
64
77
|
export declare function findUnexpressibleToken(element: string): string | undefined;
|
|
65
78
|
/**
|
|
66
|
-
* Returns the command a structural step's nmr process runs, whether the step hands that command to other
|
|
67
|
-
* scopes rather than running it
|
|
68
|
-
* Reports nothing for a step that
|
|
79
|
+
* Returns the command that a structural step's nmr process runs, whether the step hands that command to other
|
|
80
|
+
* scopes rather than running it in its own scope, and whether it anchors the command at the monorepo root.
|
|
81
|
+
* Reports nothing for a step that does not name an nmr command.
|
|
69
82
|
*
|
|
70
|
-
* The inverse of `composeNmrStep`, and beside it because the two share one grammar:
|
|
83
|
+
* The inverse of `composeNmrStep`, and beside it because the two share one grammar: An element may lead with
|
|
71
84
|
* nmr's own flags, and the command is the first token that is not one.
|
|
72
85
|
*/
|
|
73
86
|
export declare function readNmrStep(step: Step): NmrStepTarget | undefined;
|
|
74
87
|
/**
|
|
75
|
-
* Reports whether a `package.json` entry re-invokes the command it is declared
|
|
76
|
-
* anything besides. Reports nothing for an entry that names another command, or none.
|
|
88
|
+
* Reports whether a `package.json` entry re-invokes the command under which it is declared, and whether it
|
|
89
|
+
* declares anything besides. Reports nothing for an entry that names another command, or none.
|
|
77
90
|
*
|
|
78
|
-
* Honouring such an entry would spawn a shell running the same command in the same directory,
|
|
79
|
-
* same entry again without bound, so resolution discards it however it reads. `chained`
|
|
80
|
-
*
|
|
91
|
+
* Honouring such an entry would spawn a shell running the same command in the same directory, which reads the
|
|
92
|
+
* same entry again without bound, so resolution discards it however it reads. `chained` separates an entry that
|
|
93
|
+
* thereby loses steps from one that declares nothing to lose.
|
|
81
94
|
*
|
|
82
|
-
* A segment that delegates
|
|
83
|
-
* only from the root, a package's entry
|
|
95
|
+
* A segment that delegates sends the command to other scopes rather than back to this one. `-w` re-enters
|
|
96
|
+
* only from the root, a package's `-w` entry resolving against the root's registry and `package.json` instead of its own.
|
|
84
97
|
*
|
|
85
|
-
* Partial in the same ways `findNmrCrossing` is, and for the same reason:
|
|
98
|
+
* Partial in the same ways `findNmrCrossing` is, and for the same reason: What goes unrecognized re-enters
|
|
86
99
|
* without bound, which hangs rather than passing quietly.
|
|
87
100
|
*/
|
|
88
101
|
export declare function readSelfReference(options: {
|
|
@@ -91,9 +104,9 @@ export declare function readSelfReference(options: {
|
|
|
91
104
|
script: string;
|
|
92
105
|
}): SelfReference | undefined;
|
|
93
106
|
/**
|
|
94
|
-
* Renders a step list as the `&&` chain a shell runs.
|
|
107
|
+
* Renders a step list as the `&&` chain that a shell runs.
|
|
95
108
|
*
|
|
96
|
-
* The sole producer of a chain string:
|
|
97
|
-
* invalidates every recorded pass
|
|
109
|
+
* The sole producer of a chain string: The check-result cache keys on that string, so a change to the rendering
|
|
110
|
+
* invalidates every recorded pass.
|
|
98
111
|
*/
|
|
99
112
|
export declare function renderChain(steps: readonly Step[]): string;
|
package/dist/esm/steps.js
CHANGED
|
@@ -11,14 +11,17 @@ const QUOTES = new Set(['"', "'"]);
|
|
|
11
11
|
const SEGMENT_SEPARATORS = new Set([';', '|', '&', '\n', '\r']);
|
|
12
12
|
const SHELL_SAFE_TOKEN = /^[\w@%+=:,./-]+$/;
|
|
13
13
|
const TOKEN_SEPARATORS = new Set([' ', '\t', '\n', '\r']);
|
|
14
|
-
export function composeNmrStep(element,
|
|
15
|
-
const flags =
|
|
14
|
+
export function composeNmrStep(element, isWorkspaceRoot, shouldDeclineArguments = false) {
|
|
15
|
+
const flags = isWorkspaceRoot ? ['-w'] : [];
|
|
16
16
|
return {
|
|
17
17
|
kind: 'structural',
|
|
18
18
|
argv: ['nmr', ...flags, ...tokenize(element)],
|
|
19
19
|
...(shouldDeclineArguments && { shouldDeclineArguments }),
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
|
+
export function dropRecursiveSteps(steps) {
|
|
23
|
+
return steps.filter((step) => readNmrStep(step)?.isRecursive !== true);
|
|
24
|
+
}
|
|
22
25
|
export function findNmrCrossing(steps) {
|
|
23
26
|
for (const step of steps) {
|
|
24
27
|
if (step.kind === 'opaque' && splitSegments(step.command).some((segment) => readNmrTail(segment) !== undefined)) {
|
|
@@ -40,12 +43,12 @@ export function readNmrStep(step) {
|
|
|
40
43
|
export function readSelfReference(options) {
|
|
41
44
|
const { anchoredAtRoot, commandName, script } = options;
|
|
42
45
|
const segments = splitSegments(script).filter((segment) => segment.trim() !== '');
|
|
43
|
-
const
|
|
46
|
+
const doesReenter = segments.some((segment) => {
|
|
44
47
|
const tail = readNmrTail(segment);
|
|
45
48
|
const target = tail === undefined ? undefined : readNmrTarget(tail);
|
|
46
49
|
return target?.command === commandName && !target.isDelegate && (anchoredAtRoot || !target.isWorkspaceRoot);
|
|
47
50
|
});
|
|
48
|
-
if (!
|
|
51
|
+
if (!doesReenter) {
|
|
49
52
|
return undefined;
|
|
50
53
|
}
|
|
51
54
|
return segments.length === 1 ? 'sole' : 'chained';
|
|
@@ -90,6 +93,7 @@ function readNmrTail(segment) {
|
|
|
90
93
|
}
|
|
91
94
|
function readNmrTarget(tokens) {
|
|
92
95
|
let isDelegate = false;
|
|
96
|
+
let isRecursive = false;
|
|
93
97
|
let isWorkspaceRoot = false;
|
|
94
98
|
let index = 0;
|
|
95
99
|
while (index < tokens.length) {
|
|
@@ -103,6 +107,7 @@ function readNmrTarget(tokens) {
|
|
|
103
107
|
case '-R':
|
|
104
108
|
case '--recursive':
|
|
105
109
|
isDelegate = true;
|
|
110
|
+
isRecursive = true;
|
|
106
111
|
index += 1;
|
|
107
112
|
break;
|
|
108
113
|
case '-w':
|
|
@@ -112,7 +117,7 @@ function readNmrTarget(tokens) {
|
|
|
112
117
|
break;
|
|
113
118
|
default:
|
|
114
119
|
if (!token.startsWith('-')) {
|
|
115
|
-
return { command: token, isDelegate, isWorkspaceRoot };
|
|
120
|
+
return { command: token, isDelegate, isRecursive, isWorkspaceRoot };
|
|
116
121
|
}
|
|
117
122
|
index += 1;
|
|
118
123
|
}
|
|
@@ -124,49 +129,49 @@ function renderStep(step) {
|
|
|
124
129
|
}
|
|
125
130
|
function splitSegments(command) {
|
|
126
131
|
const segments = [];
|
|
127
|
-
let
|
|
132
|
+
let currentSegment = '';
|
|
128
133
|
let quote;
|
|
129
134
|
let index = 0;
|
|
130
135
|
while (index < command.length) {
|
|
131
136
|
const char = command[index] ?? '';
|
|
132
137
|
if (quote !== undefined) {
|
|
133
138
|
if (char === '\\' && quote === '"') {
|
|
134
|
-
|
|
139
|
+
currentSegment += char + (command[index + 1] ?? '');
|
|
135
140
|
index += 2;
|
|
136
141
|
continue;
|
|
137
142
|
}
|
|
138
|
-
|
|
143
|
+
currentSegment += char;
|
|
139
144
|
if (char === quote)
|
|
140
145
|
quote = undefined;
|
|
141
146
|
index += 1;
|
|
142
147
|
continue;
|
|
143
148
|
}
|
|
144
149
|
if (char === '\\') {
|
|
145
|
-
|
|
150
|
+
currentSegment += char + (command[index + 1] ?? '');
|
|
146
151
|
index += 2;
|
|
147
152
|
continue;
|
|
148
153
|
}
|
|
149
154
|
if (QUOTES.has(char)) {
|
|
150
155
|
quote = char;
|
|
151
|
-
|
|
156
|
+
currentSegment += char;
|
|
152
157
|
index += 1;
|
|
153
158
|
continue;
|
|
154
159
|
}
|
|
155
160
|
if (SEGMENT_SEPARATORS.has(char)) {
|
|
156
|
-
if (isRedirectionOperator(char,
|
|
157
|
-
|
|
161
|
+
if (isRedirectionOperator(char, currentSegment, command[index + 1])) {
|
|
162
|
+
currentSegment += char;
|
|
158
163
|
index += 1;
|
|
159
164
|
continue;
|
|
160
165
|
}
|
|
161
166
|
index += command[index + 1] === char ? 2 : 1;
|
|
162
|
-
segments.push(
|
|
163
|
-
|
|
167
|
+
segments.push(currentSegment);
|
|
168
|
+
currentSegment = '';
|
|
164
169
|
continue;
|
|
165
170
|
}
|
|
166
|
-
|
|
171
|
+
currentSegment += char;
|
|
167
172
|
index += 1;
|
|
168
173
|
}
|
|
169
|
-
segments.push(
|
|
174
|
+
segments.push(currentSegment);
|
|
170
175
|
return segments;
|
|
171
176
|
}
|
|
172
177
|
function tokenize(element) {
|
|
@@ -174,47 +179,47 @@ function tokenize(element) {
|
|
|
174
179
|
}
|
|
175
180
|
function tokenizeSegment(segment) {
|
|
176
181
|
const tokens = [];
|
|
177
|
-
let
|
|
182
|
+
let currentToken = '';
|
|
178
183
|
let quote;
|
|
179
184
|
let index = 0;
|
|
180
185
|
while (index < segment.length) {
|
|
181
186
|
const char = segment[index] ?? '';
|
|
182
187
|
if (quote !== undefined) {
|
|
183
188
|
if (char === '\\' && quote === '"') {
|
|
184
|
-
|
|
189
|
+
currentToken += char + (segment[index + 1] ?? '');
|
|
185
190
|
index += 2;
|
|
186
191
|
continue;
|
|
187
192
|
}
|
|
188
|
-
|
|
193
|
+
currentToken += char;
|
|
189
194
|
if (char === quote)
|
|
190
195
|
quote = undefined;
|
|
191
196
|
index += 1;
|
|
192
197
|
continue;
|
|
193
198
|
}
|
|
194
199
|
if (char === '\\') {
|
|
195
|
-
|
|
200
|
+
currentToken += char + (segment[index + 1] ?? '');
|
|
196
201
|
index += 2;
|
|
197
202
|
continue;
|
|
198
203
|
}
|
|
199
204
|
if (QUOTES.has(char)) {
|
|
200
205
|
quote = char;
|
|
201
|
-
|
|
206
|
+
currentToken += char;
|
|
202
207
|
index += 1;
|
|
203
208
|
continue;
|
|
204
209
|
}
|
|
205
210
|
if (TOKEN_SEPARATORS.has(char)) {
|
|
206
|
-
if (
|
|
207
|
-
tokens.push(
|
|
211
|
+
if (currentToken !== '') {
|
|
212
|
+
tokens.push(currentToken);
|
|
208
213
|
}
|
|
209
|
-
|
|
214
|
+
currentToken = '';
|
|
210
215
|
index += 1;
|
|
211
216
|
continue;
|
|
212
217
|
}
|
|
213
|
-
|
|
218
|
+
currentToken += char;
|
|
214
219
|
index += 1;
|
|
215
220
|
}
|
|
216
|
-
if (
|
|
217
|
-
tokens.push(
|
|
221
|
+
if (currentToken !== '') {
|
|
222
|
+
tokens.push(currentToken);
|
|
218
223
|
}
|
|
219
224
|
return tokens;
|
|
220
225
|
}
|
package/dist/esm/taze.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export type TazeConfig = {
|
|
|
12
12
|
/**
|
|
13
13
|
* Builds a taze configuration from nmr's shared upgrade policy and a repo's own settings.
|
|
14
14
|
*
|
|
15
|
-
* Any property the caller supplies wins, including `0` and an explicit `undefined
|
|
15
|
+
* Any property that the caller supplies wins, including `0` and an explicit `undefined`. The latter restores
|
|
16
16
|
* taze's inheritance of pnpm's `minimumReleaseAge`, which only applies while `maturityPeriod` is unset.
|
|
17
17
|
*
|
|
18
18
|
* The merge is shallow, which suits a policy of scalar defaults. A nested default would clobber rather
|
package/dist/esm/tests.d.ts
CHANGED
|
@@ -1,40 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Declares a suite asserting that a repo's test files
|
|
3
|
-
* isolation tier, and every test file
|
|
2
|
+
* Declares a suite asserting that a repo's test files follow nmr's conventions: Every collected file names an
|
|
3
|
+
* isolation tier, and every test file is under a `__tests__` directory.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* scopes what the sweep covers.
|
|
7
|
-
* file runs and passes, and a file outside `__tests__` is collected by nothing at all.
|
|
5
|
+
* Call it from a one-line test of the repo's own, so that the file stays under the repo's `__tests__` and the repo
|
|
6
|
+
* scopes what the sweep covers. Without it, a violation of either half goes unreported: `unit` is the residual
|
|
7
|
+
* project, so an untiered file runs and passes, and a file outside `__tests__` is collected by nothing at all.
|
|
8
8
|
*
|
|
9
|
-
* `
|
|
10
|
-
* the same array to `defineVitestConfig`'s `testCollectionExclude`, or the sweep and the collection
|
|
11
|
-
* different trees: a directory pruned here alone
|
|
9
|
+
* `excludedBasenames` names directory basenames that the sweep prunes at any depth, additive to the ones nmr always
|
|
10
|
+
* prunes. Pass the same array to `defineVitestConfig`'s `testCollectionExclude`, or the sweep and the collection
|
|
11
|
+
* glob describe different trees: Vitest still runs the files in a directory pruned here alone, and this suite no
|
|
12
|
+
* longer reports them.
|
|
13
|
+
*
|
|
14
|
+
* Because untracked paths that git ignores are out of scope for the sweep and the shared configs' collection alike,
|
|
15
|
+
* a directory that `.gitignore` covers does not need an entry in `excludedBasenames`. Outside a git repository, only
|
|
16
|
+
* the named directories are pruned.
|
|
12
17
|
*/
|
|
13
18
|
export declare function checkTestFileConventions(options?: TestFileConventionsOptions): void;
|
|
14
19
|
/**
|
|
15
|
-
* Sweeps a repo for both halves of the convention, reporting the root it swept alongside what it found.
|
|
20
|
+
* Sweeps a repo for both halves of the convention, reporting the root that it swept alongside what it found.
|
|
16
21
|
*
|
|
17
|
-
* The root
|
|
18
|
-
*
|
|
22
|
+
* The root makes the result trustworthy, and it is the one thing that an assertion cannot check: A sweep started
|
|
23
|
+
* from the directory that Vitest supplies covers one package, finds nothing, and passes.
|
|
19
24
|
*
|
|
20
25
|
* @internal - Exported only to enable testing
|
|
21
26
|
*/
|
|
22
|
-
export declare function reportTestFileConventions(
|
|
27
|
+
export declare function reportTestFileConventions(options?: TestFileConventionsOptions): TestFileConventionsReport;
|
|
23
28
|
export interface TestFileConventionsOptions {
|
|
24
29
|
/**
|
|
25
|
-
* Directory basenames the sweep prunes at any depth, additive to the ones nmr always prunes
|
|
26
|
-
*
|
|
30
|
+
* Directory basenames that the sweep prunes at any depth, additive to the ones nmr always prunes and to the paths
|
|
31
|
+
* that git ignores. A repo passes the same array here and to `defineVitestConfig`'s `testCollectionExclude`.
|
|
27
32
|
*/
|
|
28
|
-
|
|
33
|
+
excludedBasenames?: readonly string[];
|
|
29
34
|
/**
|
|
30
35
|
* The directory to sweep. Defaults to the monorepo root found from the working directory, so one call covers the
|
|
31
36
|
* whole repo from whichever package Vitest started in.
|
|
32
37
|
*/
|
|
33
38
|
rootDir?: string;
|
|
34
39
|
}
|
|
35
|
-
/** What one sweep found, and the root it found it
|
|
40
|
+
/** What one sweep found, and the root in which it found it. Paths are relative to that root and POSIX-separated. */
|
|
36
41
|
export interface TestFileConventionsReport {
|
|
37
|
-
|
|
42
|
+
misplacedFiles: string[];
|
|
38
43
|
rootDir: string;
|
|
39
|
-
|
|
44
|
+
untieredFiles: string[];
|
|
40
45
|
}
|