@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.
Files changed (112) hide show
  1. package/.meta/changelog.json +212 -4
  2. package/.readyup/kits/default.js +124 -104
  3. package/.readyup/manifest.json +12 -7
  4. package/CHANGELOG.md +108 -0
  5. package/README.md +41 -47
  6. package/agents/README.md +8 -8
  7. package/agents/guidance/rulebooks/nmr.md +8 -8
  8. package/bin/ensure-prepublish-hooks.js +1 -1
  9. package/bin/nmr-clean.js +1 -1
  10. package/bin/nmr-compile.js +1 -1
  11. package/bin/nmr-fmt.js +1 -1
  12. package/bin/nmr-report-catalog.js +1 -1
  13. package/bin/nmr-report-overrides.js +1 -1
  14. package/bin/nmr-taze.js +1 -1
  15. package/bin/nmr.js +1 -1
  16. package/dist/esm/UserError.d.ts +1 -2
  17. package/dist/esm/check-cache.d.ts +61 -63
  18. package/dist/esm/check-cache.js +24 -24
  19. package/dist/esm/cli-ensure-prepublish-hooks.js +4 -4
  20. package/dist/esm/commands/build-output.d.ts +37 -38
  21. package/dist/esm/commands/build-output.js +9 -9
  22. package/dist/esm/commands/build.d.ts +13 -14
  23. package/dist/esm/commands/build.js +67 -65
  24. package/dist/esm/commands/clean.d.ts +6 -6
  25. package/dist/esm/commands/clean.js +11 -11
  26. package/dist/esm/commands/ensure-prepublish-hooks.d.ts +7 -7
  27. package/dist/esm/commands/ensure-prepublish-hooks.js +41 -41
  28. package/dist/esm/commands/fmt.d.ts +8 -8
  29. package/dist/esm/commands/fmt.js +33 -33
  30. package/dist/esm/commands/report-catalog.d.ts +5 -7
  31. package/dist/esm/commands/report-catalog.js +10 -8
  32. package/dist/esm/commands/report-overrides.d.ts +0 -2
  33. package/dist/esm/commands/report-overrides.js +8 -8
  34. package/dist/esm/commands/taze.d.ts +9 -8
  35. package/dist/esm/config.d.ts +5 -5
  36. package/dist/esm/config.js +27 -27
  37. package/dist/esm/context.d.ts +1 -2
  38. package/dist/esm/context.js +8 -8
  39. package/dist/esm/default-scripts.d.ts +5 -6
  40. package/dist/esm/defineConfig.d.ts +1 -1
  41. package/dist/esm/git-ignored-paths.d.ts +9 -0
  42. package/dist/esm/git-ignored-paths.js +10 -0
  43. package/dist/esm/glyphs.d.ts +3 -3
  44. package/dist/esm/help.d.ts +4 -4
  45. package/dist/esm/help.js +21 -21
  46. package/dist/esm/helpers/clampToBytes.d.ts +4 -5
  47. package/dist/esm/helpers/clampToBytes.js +3 -3
  48. package/dist/esm/helpers/config-path.d.ts +0 -2
  49. package/dist/esm/helpers/createBoundedBuffer.d.ts +3 -3
  50. package/dist/esm/helpers/createBoundedBuffer.js +2 -2
  51. package/dist/esm/helpers/deriveExcerpt.d.ts +4 -5
  52. package/dist/esm/helpers/duration.d.ts +10 -11
  53. package/dist/esm/helpers/duration.js +7 -7
  54. package/dist/esm/helpers/filter-selection.d.ts +19 -19
  55. package/dist/esm/helpers/filter-selection.js +12 -12
  56. package/dist/esm/helpers/findClosestName.d.ts +1 -1
  57. package/dist/esm/helpers/findClosestName.js +15 -15
  58. package/dist/esm/helpers/hook-name.d.ts +2 -5
  59. package/dist/esm/helpers/package-json.d.ts +11 -10
  60. package/dist/esm/helpers/package-json.js +26 -25
  61. package/dist/esm/helpers/readStringValues.d.ts +2 -3
  62. package/dist/esm/helpers/reportClosing.d.ts +8 -9
  63. package/dist/esm/helpers/transcript.d.ts +15 -15
  64. package/dist/esm/helpers/transcript.js +14 -14
  65. package/dist/esm/helpers/type-guards.d.ts +1 -0
  66. package/dist/esm/output-style.d.ts +12 -13
  67. package/dist/esm/prettier.d.ts +2 -2
  68. package/dist/esm/prettier.js +5 -5
  69. package/dist/esm/recording.d.ts +25 -20
  70. package/dist/esm/recording.js +28 -21
  71. package/dist/esm/replay-assembly.d.ts +10 -10
  72. package/dist/esm/report-format.d.ts +15 -20
  73. package/dist/esm/report-format.js +7 -7
  74. package/dist/esm/reportCliFailure.d.ts +2 -3
  75. package/dist/esm/resolve-scripts.d.ts +4 -5
  76. package/dist/esm/resolveBinStyles.d.ts +4 -5
  77. package/dist/esm/resolveBinStyles.js +3 -3
  78. package/dist/esm/resolver.d.ts +16 -20
  79. package/dist/esm/resolver.js +15 -15
  80. package/dist/esm/runCli.d.ts +6 -6
  81. package/dist/esm/runCli.js +261 -195
  82. package/dist/esm/runner.d.ts +20 -20
  83. package/dist/esm/runner.js +18 -18
  84. package/dist/esm/steps.d.ts +51 -38
  85. package/dist/esm/steps.js +32 -27
  86. package/dist/esm/taze.d.ts +1 -1
  87. package/dist/esm/tests.d.ts +23 -18
  88. package/dist/esm/tests.js +21 -9
  89. package/dist/esm/tiers.d.ts +28 -24
  90. package/dist/esm/tiers.js +13 -9
  91. package/dist/esm/types.d.ts +5 -5
  92. package/dist/esm/verbosity.d.ts +23 -21
  93. package/dist/esm/verbosity.js +7 -7
  94. package/dist/esm/verdict.d.ts +39 -32
  95. package/dist/esm/verdict.js +63 -50
  96. package/dist/esm/vitest-source-resolution.d.ts +11 -11
  97. package/dist/esm/vitest-source-resolution.js +49 -47
  98. package/dist/esm/vitest.d.ts +27 -26
  99. package/dist/esm/vitest.js +90 -33
  100. package/dist/esm/workspace.d.ts +6 -22
  101. package/dist/esm/workspace.js +9 -67
  102. package/docs/check-cache.md +57 -57
  103. package/docs/prettier.md +10 -10
  104. package/docs/reporting.md +48 -44
  105. package/docs/scripts.md +29 -29
  106. package/docs/upgrades.md +18 -18
  107. package/docs/utilities.md +13 -13
  108. package/docs/vitest.md +68 -66
  109. package/docs/workspace.md +27 -4
  110. package/package.json +6 -6
  111. package/dist/esm/helpers/workspace-patterns.d.ts +0 -8
  112. package/dist/esm/helpers/workspace-patterns.js +0 -29
@@ -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 to, or a pipe nmr reads. */
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 on. */
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 on. */
11
+ /** The channel on which each of the child's output streams runs. */
12
12
  channels: OutputChannels;
13
- /** When true, suppress output on success and write captured output to stderr on failure. */
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
- retained?: RetainedOutput;
35
+ retainedOutput?: RetainedOutput;
36
36
  }
37
- /** How a command ended, and the exit code that carries that ending to nmr's own caller. */
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` where the stream was handed to the child directly, or where capture stopped early
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 on: the stream's own descriptor when it is a terminal and output is not
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 running an nmr process below this one runs on: nmr's own descriptor whenever it
66
- * has one, so the child writes where nmr writes and no ancestor stands between them relaying bytes. A stream
67
- * carrying no descriptor, which is the one a test injects, falls back to a pipe.
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, carrying the `&&` semantics nmr
72
- * promises: the first non-zero exit ends the sequence and is the code returned.
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 is what lets each step run on the channels its kind calls
75
- * for, and what makes a signal to nmr end the run: the steps after it are never reached, where a shell would
76
- * have gone on running them unsupervised.
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
- * Carries back a bounded copy of what the list's own commands wrote, so 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
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
- * Piped stdout and stderr are ordered by arrival rather than by a shared descriptor, so their interleaving
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>;
@@ -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 resolved = {
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, resolved);
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', useShell: true }, cwd, options);
42
+ return runSpawned({ args: [], file: command, input: 'inherit', shouldUseShell: true }, cwd, options);
43
43
  }
44
- async function runSpawned({ args, file, input, useShell }, cwd, options) {
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: useShell,
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 completion = await awaitCompletion(child, () => {
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
- ...completion,
65
- stdout: stdoutCapture?.toBuffer(),
66
- stderr: stderrCapture?.toBuffer(),
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', useShell: true }, cwd, {
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, useShell: false }, cwd, {
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
- retained: {
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 retained = createBoundedBuffer();
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) => retained.append(chunk));
122
+ source.on('data', (chunk) => retainedCopy.append(chunk));
123
123
  return {
124
- toBuffer: () => (isComplete ? retained.toBuffer() : undefined),
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 completion = describeExit(code, signal);
149
- exitCompletion = completion;
148
+ const exitDescription = describeExit(code, signal);
149
+ exitCompletion = exitDescription;
150
150
  graceTimer = setTimeout(() => {
151
151
  stopReading();
152
- settle(completion);
152
+ settle(exitDescription);
153
153
  }, PIPE_DRAIN_GRACE_MS);
154
154
  });
155
155
  child.on('close', () => {
@@ -1,15 +1,15 @@
1
1
  /**
2
- * One element of a resolved command chain, carrying how it was composed rather than how its text reads.
3
- * A `structural` step is nmr's own composition, held as argv; an `opaque` step is a command nmr does not parse.
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` travels from the composite element that composed the step to the one reader that acts on it,
8
- * the binding of the invocation's trailing arguments. Every stage between the two -- the devBin substitution,
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 where it holds and leaves the rendered chain unchanged.
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, where a `-R` or `-F` sends it. */
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
- /** Whether the command runs against the root registry, where a `-w` anchors it. */
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 under: as its whole value, or alongside other
33
- * steps. `sole` covers an entry carrying trailing arguments, which declare no step of their own.
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 carry nmr's own flags but cannot carry a space-bearing token.
40
- * `-w` is prepended as its own token, so the child selects the root registry on its own.
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 where it holds, so a step that takes the trailing arguments renders and compares
43
- * exactly as it did before any element declared anything.
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, workspaceRoot: boolean, shouldDeclineArguments?: boolean): Step;
50
+ export declare function composeNmrStep(element: string, isWorkspaceRoot: boolean, shouldDeclineArguments?: boolean): Step;
46
51
  /**
47
- * Returns the text of the first opaque step that reaches nmr through a shell, or `undefined` when none does.
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 opens no position, so a command merely naming nmr in an argument is not a crossing.
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: a value-taking flag standing
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 it
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 is one the rendering would quote whole, turning the element into a command nobody wrote.
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 where it stands, and whether it anchors the command at the monorepo root.
68
- * Reports nothing for a step that names no nmr command.
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: an element may lead with
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 under, and whether it declares
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, reaching the
79
- * same entry again without bound, so resolution discards it however it reads. `chained` is what separates an
80
- * entry that thereby loses steps from one that declares nothing to lose.
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 carries the command to other scopes rather than back to this one. `-w` re-enters
83
- * only from the root, a package's entry reaching the root's registry and `package.json` instead of its own.
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: what goes unrecognized re-enters
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: the check-result cache keys on that string, so a change to the rendering
97
- * invalidates every recorded pass by construction rather than by anyone remembering to.
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, workspaceRoot, shouldDeclineArguments = false) {
15
- const flags = workspaceRoot ? ['-w'] : [];
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 reenters = segments.some((segment) => {
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 (!reenters) {
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 current = '';
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
- current += char + (command[index + 1] ?? '');
139
+ currentSegment += char + (command[index + 1] ?? '');
135
140
  index += 2;
136
141
  continue;
137
142
  }
138
- current += char;
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
- current += char + (command[index + 1] ?? '');
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
- current += char;
156
+ currentSegment += char;
152
157
  index += 1;
153
158
  continue;
154
159
  }
155
160
  if (SEGMENT_SEPARATORS.has(char)) {
156
- if (isRedirectionOperator(char, current, command[index + 1])) {
157
- current += char;
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(current);
163
- current = '';
167
+ segments.push(currentSegment);
168
+ currentSegment = '';
164
169
  continue;
165
170
  }
166
- current += char;
171
+ currentSegment += char;
167
172
  index += 1;
168
173
  }
169
- segments.push(current);
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 current = '';
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
- current += char + (segment[index + 1] ?? '');
189
+ currentToken += char + (segment[index + 1] ?? '');
185
190
  index += 2;
186
191
  continue;
187
192
  }
188
- current += char;
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
- current += char + (segment[index + 1] ?? '');
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
- current += char;
206
+ currentToken += char;
202
207
  index += 1;
203
208
  continue;
204
209
  }
205
210
  if (TOKEN_SEPARATORS.has(char)) {
206
- if (current !== '') {
207
- tokens.push(current);
211
+ if (currentToken !== '') {
212
+ tokens.push(currentToken);
208
213
  }
209
- current = '';
214
+ currentToken = '';
210
215
  index += 1;
211
216
  continue;
212
217
  }
213
- current += char;
218
+ currentToken += char;
214
219
  index += 1;
215
220
  }
216
- if (current !== '') {
217
- tokens.push(current);
221
+ if (currentToken !== '') {
222
+ tokens.push(currentToken);
218
223
  }
219
224
  return tokens;
220
225
  }
@@ -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` — the latter restores
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
@@ -1,40 +1,45 @@
1
1
  /**
2
- * Declares a suite asserting that a repo's test files hold to nmr's conventions: every collected file names an
3
- * isolation tier, and every test file sits under a `__tests__` directory.
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
- * Wire it from a one-line test of the repo's own, so the file stays under the repo's `__tests__` and the repo
6
- * scopes what the sweep covers. Both halves are silent without it: `unit` is the residual project, so an untiered
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
- * `exclude` names directory basenames the sweep prunes at any depth, additive to the ones nmr always prunes. Pass
10
- * the same array to `defineVitestConfig`'s `testCollectionExclude`, or the sweep and the collection glob describe
11
- * different trees: a directory pruned here alone still runs the files this suite stopped reporting.
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 is what makes the result trustworthy, and it is the one thing an assertion cannot check: a sweep
18
- * started from the directory Vitest supplies covers one package, finds nothing, and passes.
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({ exclude, rootDir, }?: TestFileConventionsOptions): TestFileConventionsReport;
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. The array a repo
26
- * passes here is the array it passes to `defineVitestConfig`'s `testCollectionExclude`.
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
- exclude?: readonly string[];
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 in. Paths are relative to that root and POSIX-separated. */
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
- misplaced: string[];
42
+ misplacedFiles: string[];
38
43
  rootDir: string;
39
- untiered: string[];
44
+ untieredFiles: string[];
40
45
  }