@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/runCli.js
CHANGED
|
@@ -18,14 +18,15 @@ import { assembleReplay } from "./replay-assembly.js";
|
|
|
18
18
|
import { readReportFormatEnv, REPORT_FORMAT_ENV_VAR, resolveReportFormat } from "./report-format.js";
|
|
19
19
|
import { applyDevBinToSteps, buildRootRegistry, buildWorkspaceRegistry, expandScript, findChainedSelfReference, resolveScript, } from "./resolver.js";
|
|
20
20
|
import { resolveChannel, runSteps } from "./runner.js";
|
|
21
|
-
import { composeNmrStep, findNmrCrossing, renderChain } from "./steps.js";
|
|
21
|
+
import { composeNmrStep, dropRecursiveSteps, findNmrCrossing, renderChain } from "./steps.js";
|
|
22
22
|
import { UserError } from "./UserError.js";
|
|
23
23
|
import { COMMAND_VERBOSITY_ENV_VAR, readVerbosityEnv, resolveVerbosity, } from "./verbosity.js";
|
|
24
24
|
import { writeVerdict } from "./verdict.js";
|
|
25
|
-
import { readWorkspacePackageNames } from "./workspace.js";
|
|
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
|
|
28
|
+
const RECURSIVE_REJECTION = '-R/--recursive did not match any workspace:';
|
|
29
|
+
const LIST_CEILING = 10;
|
|
29
30
|
const NAMED_ESCAPES = new Map([
|
|
30
31
|
['\n', String.raw `\n`],
|
|
31
32
|
['\r', String.raw `\r`],
|
|
@@ -39,56 +40,56 @@ export async function runCli(options) {
|
|
|
39
40
|
reportError(parseResult.error, stderr);
|
|
40
41
|
return { exitCode: 1 };
|
|
41
42
|
}
|
|
42
|
-
const {
|
|
43
|
-
const
|
|
43
|
+
const { parsedArgs } = parseResult;
|
|
44
|
+
const presentationRead = readPresentation({
|
|
44
45
|
env,
|
|
45
|
-
flagValue:
|
|
46
|
+
flagValue: parsedArgs.outputStyle,
|
|
46
47
|
stderrIsTty: isTerminalStream(stderr),
|
|
47
48
|
stdoutIsTty: isTerminalStream(stdout),
|
|
48
49
|
});
|
|
49
|
-
if (!
|
|
50
|
-
reportError(
|
|
50
|
+
if (!presentationRead.ok) {
|
|
51
|
+
reportError(presentationRead.error, stderr);
|
|
51
52
|
return { exitCode: 1 };
|
|
52
53
|
}
|
|
53
|
-
const { styles } =
|
|
54
|
-
if (
|
|
54
|
+
const { styles } = presentationRead;
|
|
55
|
+
if (parsedArgs.shouldShowVersion) {
|
|
55
56
|
stdout.write(`${VERSION}\n`);
|
|
56
57
|
return { exitCode: 0 };
|
|
57
58
|
}
|
|
58
59
|
const context = await resolveContext(cwd);
|
|
59
|
-
const format = resolveReportFormat({ envFormat:
|
|
60
|
+
const format = resolveReportFormat({ envFormat: presentationRead.format, hasJsonFlag: parsedArgs.shouldEmitJson });
|
|
60
61
|
const verbosity = resolveReportingVerbosity({
|
|
61
62
|
env,
|
|
62
|
-
envVerbosity:
|
|
63
|
+
envVerbosity: presentationRead.verbosity,
|
|
63
64
|
format,
|
|
64
65
|
output: context.config.output,
|
|
65
|
-
|
|
66
|
+
hasQuietFlag: parsedArgs.quiet,
|
|
66
67
|
});
|
|
67
68
|
const quiet = verbosity === 'quiet';
|
|
68
|
-
const
|
|
69
|
-
const anchorDir =
|
|
70
|
-
if (
|
|
71
|
-
stdout.write(`${generateHelp(context.config, anchorDir,
|
|
69
|
+
const shouldUseRoot = parsedArgs.isWorkspaceRoot || context.isRoot;
|
|
70
|
+
const anchorDir = shouldUseRoot ? context.monorepoRoot : (context.packageDir ?? context.monorepoRoot);
|
|
71
|
+
if (parsedArgs.shouldShowHelp || !parsedArgs.command) {
|
|
72
|
+
stdout.write(`${generateHelp(context.config, anchorDir, shouldUseRoot)}\n`);
|
|
72
73
|
return { exitCode: 0 };
|
|
73
74
|
}
|
|
74
|
-
const { command } =
|
|
75
|
+
const { command } = parsedArgs;
|
|
75
76
|
const scope = path.basename(anchorDir);
|
|
76
77
|
const snapshot = openGate({
|
|
77
78
|
command,
|
|
78
79
|
config: context.config,
|
|
79
80
|
env,
|
|
80
81
|
monorepoRoot: context.monorepoRoot,
|
|
81
|
-
passthrough:
|
|
82
|
+
passthrough: parsedArgs.passthrough,
|
|
82
83
|
stderr,
|
|
83
84
|
style: styles.stderr,
|
|
84
85
|
});
|
|
85
|
-
const
|
|
86
|
+
const shouldBypassCache = parsedArgs.shouldBypassCache || env[NO_CACHE_ENV_VAR] === '1';
|
|
86
87
|
const runId = resolveRunId(env);
|
|
87
88
|
const childEnv = buildChildEnv({
|
|
88
89
|
env,
|
|
89
90
|
format,
|
|
90
|
-
|
|
91
|
-
passthrough:
|
|
91
|
+
shouldBypassCache,
|
|
92
|
+
passthrough: parsedArgs.passthrough,
|
|
92
93
|
runId,
|
|
93
94
|
snapshot,
|
|
94
95
|
styles,
|
|
@@ -100,56 +101,57 @@ export async function runCli(options) {
|
|
|
100
101
|
stderr,
|
|
101
102
|
env: childEnv,
|
|
102
103
|
};
|
|
103
|
-
const delegation = composeDelegation({ childEnv, command,
|
|
104
|
+
const delegation = composeDelegation({ childEnv, command, parsedArgs });
|
|
104
105
|
if (delegation !== undefined) {
|
|
105
|
-
return runDelegation({ context, delegation,
|
|
106
|
+
return runDelegation({ context, delegation, parsedArgs, runOptions, stderr });
|
|
106
107
|
}
|
|
107
|
-
const registry =
|
|
108
|
+
const registry = shouldUseRoot ? buildRootRegistry(context.config) : buildWorkspaceRegistry(context.config);
|
|
108
109
|
assertNoSelfReference({
|
|
109
110
|
anchorDir,
|
|
110
111
|
command,
|
|
111
|
-
isReading:
|
|
112
|
+
isReading: parsedArgs.shouldPrintLog,
|
|
113
|
+
isWorkspaceRoot: parsedArgs.isWorkspaceRoot,
|
|
112
114
|
monorepoRoot: context.monorepoRoot,
|
|
113
115
|
registry,
|
|
114
|
-
workspaceRoot: parsed.workspaceRoot,
|
|
115
116
|
});
|
|
116
|
-
const
|
|
117
|
-
if (!
|
|
117
|
+
const resolvedScript = resolveScript(command, registry, anchorDir, parsedArgs.isWorkspaceRoot);
|
|
118
|
+
if (!resolvedScript) {
|
|
118
119
|
if (env[RUN_IF_PRESENT_ENV_VAR] === '1') {
|
|
119
120
|
return { exitCode: 0 };
|
|
120
121
|
}
|
|
121
122
|
reportError(`Unknown command: ${command}`, stderr);
|
|
122
123
|
return { exitCode: 1 };
|
|
123
124
|
}
|
|
124
|
-
const
|
|
125
|
-
const
|
|
126
|
-
|
|
125
|
+
const { isEmptiedByWorkspace, steps: runnableSteps } = readRunnableSteps(resolvedScript, context.workspacePackageDirs);
|
|
126
|
+
const resolvedCommand = renderChain(runnableSteps);
|
|
127
|
+
const noOpReason = findNoOpReason(resolvedCommand, isEmptiedByWorkspace);
|
|
128
|
+
if (noOpReason !== undefined && !parsedArgs.shouldPrintLog) {
|
|
127
129
|
reportVerdict({ command, scope, outcome: 'no-op', reason: noOpReason }, stdout, format, styles.stdout);
|
|
128
130
|
return { exitCode: 0 };
|
|
129
131
|
}
|
|
130
|
-
const substitutedSteps = applyDevBinToSteps(
|
|
132
|
+
const substitutedSteps = applyDevBinToSteps(runnableSteps, context.config.devBin, context.monorepoRoot);
|
|
131
133
|
const substitutedCommand = renderChain(substitutedSteps);
|
|
132
|
-
const
|
|
133
|
-
if (!
|
|
134
|
-
reportError(
|
|
134
|
+
const bindResult = bindPassthrough(substitutedSteps, parsedArgs.passthrough, command);
|
|
135
|
+
if (!bindResult.ok) {
|
|
136
|
+
reportError(bindResult.error, stderr);
|
|
135
137
|
return { exitCode: 1 };
|
|
136
138
|
}
|
|
137
|
-
const mainSteps =
|
|
139
|
+
const mainSteps = bindResult.steps;
|
|
138
140
|
const isHookInvocation = isHookName(command);
|
|
139
141
|
const fullSteps = isHookInvocation
|
|
140
142
|
? mainSteps
|
|
141
|
-
: wrapWithHooks(command, mainSteps, registry, anchorDir,
|
|
143
|
+
: wrapWithHooks(command, mainSteps, registry, anchorDir, parsedArgs.isWorkspaceRoot);
|
|
142
144
|
const fullCommand = renderChain(fullSteps);
|
|
143
145
|
reportNmrCrossing({
|
|
144
146
|
config: context.config,
|
|
145
|
-
isReading:
|
|
147
|
+
isReading: parsedArgs.shouldPrintLog,
|
|
148
|
+
isWorkspaceRoot: parsedArgs.isWorkspaceRoot,
|
|
146
149
|
monorepoRoot: context.monorepoRoot,
|
|
147
150
|
registry,
|
|
148
|
-
|
|
151
|
+
resolvedScript,
|
|
152
|
+
shouldUseRoot,
|
|
149
153
|
stderr,
|
|
150
154
|
style: styles.stderr,
|
|
151
|
-
useRoot,
|
|
152
|
-
workspaceRoot: parsed.workspaceRoot,
|
|
153
155
|
});
|
|
154
156
|
const key = resolveCacheKey({
|
|
155
157
|
anchorDir,
|
|
@@ -161,7 +163,7 @@ export async function runCli(options) {
|
|
|
161
163
|
stderr,
|
|
162
164
|
substitution: substitutedCommand === resolvedCommand ? undefined : substitutedCommand,
|
|
163
165
|
});
|
|
164
|
-
if (
|
|
166
|
+
if (parsedArgs.shouldPrintLog) {
|
|
165
167
|
return reportRecording({
|
|
166
168
|
anchorDir,
|
|
167
169
|
command,
|
|
@@ -186,11 +188,11 @@ export async function runCli(options) {
|
|
|
186
188
|
env,
|
|
187
189
|
key,
|
|
188
190
|
monorepoRoot: context.monorepoRoot,
|
|
189
|
-
|
|
190
|
-
overrideNotice: formatOverrideNotice(resolved, registry, command, anchorDir, quiet, styles.stdout),
|
|
191
|
+
overrideNotice: formatOverrideNotice(resolvedScript, registry, command, anchorDir, quiet, styles.stdout),
|
|
191
192
|
ownSteps: mainSteps,
|
|
192
193
|
runId,
|
|
193
194
|
runOptions,
|
|
195
|
+
shouldBypassCache,
|
|
194
196
|
snapshot,
|
|
195
197
|
stderr,
|
|
196
198
|
stdout,
|
|
@@ -199,23 +201,23 @@ export async function runCli(options) {
|
|
|
199
201
|
return { exitCode };
|
|
200
202
|
}
|
|
201
203
|
const BOOLEAN_FLAGS = new Map([
|
|
202
|
-
['-?', '
|
|
203
|
-
['--help', '
|
|
204
|
-
['--json', '
|
|
205
|
-
['--log', '
|
|
206
|
-
['--no-cache', '
|
|
204
|
+
['-?', 'shouldShowHelp'],
|
|
205
|
+
['--help', 'shouldShowHelp'],
|
|
206
|
+
['--json', 'shouldEmitJson'],
|
|
207
|
+
['--log', 'shouldPrintLog'],
|
|
208
|
+
['--no-cache', 'shouldBypassCache'],
|
|
207
209
|
['-q', 'quiet'],
|
|
208
210
|
['--quiet', 'quiet'],
|
|
209
211
|
['-R', 'recursive'],
|
|
210
212
|
['--recursive', 'recursive'],
|
|
211
|
-
['-V', '
|
|
212
|
-
['--version', '
|
|
213
|
-
['-w', '
|
|
214
|
-
['--workspace-root', '
|
|
213
|
+
['-V', 'shouldShowVersion'],
|
|
214
|
+
['--version', 'shouldShowVersion'],
|
|
215
|
+
['-w', 'isWorkspaceRoot'],
|
|
216
|
+
['--workspace-root', 'isWorkspaceRoot'],
|
|
215
217
|
]);
|
|
216
218
|
function buildChildEnv(options) {
|
|
217
|
-
const { env, format,
|
|
218
|
-
const bypassesCache =
|
|
219
|
+
const { env, format, passthrough, runId, shouldBypassCache, snapshot, styles, verbosity } = options;
|
|
220
|
+
const bypassesCache = shouldBypassCache || passthrough.length > 0;
|
|
219
221
|
return {
|
|
220
222
|
...env,
|
|
221
223
|
...(snapshot !== undefined && { [TREE_SNAPSHOT_ENV_VAR]: encodeTreeSnapshot(snapshot) }),
|
|
@@ -242,41 +244,41 @@ function bindPassthrough(steps, passthrough, command) {
|
|
|
242
244
|
}
|
|
243
245
|
return step.kind === 'structural'
|
|
244
246
|
? { ...step, argv: [...step.argv, ...passthrough] }
|
|
245
|
-
: { kind: 'opaque', command: `${step.command} ${passthrough.map(
|
|
247
|
+
: { kind: 'opaque', command: `${step.command} ${passthrough.map(quoteForShell).join(' ')}` };
|
|
246
248
|
});
|
|
247
249
|
return { ok: true, steps: boundSteps };
|
|
248
250
|
}
|
|
249
251
|
function formatUnroutableArgumentsError(command) {
|
|
250
|
-
return (`\`${command}\`
|
|
252
|
+
return (`\`${command}\` does not take trailing arguments: every step of its chain declines them. ` +
|
|
251
253
|
'Running it unnarrowed is not what the arguments asked for, so nothing ran.');
|
|
252
254
|
}
|
|
253
255
|
function composeDelegation(options) {
|
|
254
|
-
const { childEnv, command,
|
|
256
|
+
const { childEnv, command, parsedArgs } = options;
|
|
255
257
|
let scope;
|
|
256
|
-
let
|
|
257
|
-
if (
|
|
258
|
-
scope = ['--filter',
|
|
258
|
+
let shouldRunIfPresent = parsedArgs.shouldPrintLog;
|
|
259
|
+
if (parsedArgs.filter) {
|
|
260
|
+
scope = ['--filter', parsedArgs.filter];
|
|
259
261
|
}
|
|
260
|
-
else if (
|
|
262
|
+
else if (parsedArgs.recursive) {
|
|
261
263
|
scope = ['--recursive'];
|
|
262
|
-
|
|
264
|
+
shouldRunIfPresent = true;
|
|
263
265
|
}
|
|
264
266
|
if (scope === undefined) {
|
|
265
267
|
return undefined;
|
|
266
268
|
}
|
|
267
|
-
const flags =
|
|
269
|
+
const flags = parsedArgs.shouldPrintLog ? ['--log'] : [];
|
|
268
270
|
return {
|
|
269
|
-
env:
|
|
271
|
+
env: shouldRunIfPresent ? { ...childEnv, [RUN_IF_PRESENT_ENV_VAR]: '1' } : childEnv,
|
|
270
272
|
step: {
|
|
271
273
|
kind: 'structural',
|
|
272
|
-
argv: ['pnpm', ...scope, 'exec', 'nmr', ...flags, command, ...
|
|
274
|
+
argv: ['pnpm', ...scope, 'exec', 'nmr', ...flags, command, ...parsedArgs.passthrough],
|
|
273
275
|
},
|
|
274
276
|
};
|
|
275
277
|
}
|
|
276
278
|
async function composeRetention(options) {
|
|
277
|
-
const { anchorDir, command, key,
|
|
278
|
-
if (
|
|
279
|
-
const excerpt = deriveExcerpt(
|
|
279
|
+
const { anchorDir, command, key, retainedOutput, runId } = options;
|
|
280
|
+
if (retainedOutput !== undefined) {
|
|
281
|
+
const excerpt = deriveExcerpt(retainedOutput.stdout.toString('utf8')) ?? deriveExcerpt(retainedOutput.stderr.toString('utf8'));
|
|
280
282
|
if (excerpt === undefined) {
|
|
281
283
|
return undefined;
|
|
282
284
|
}
|
|
@@ -292,7 +294,7 @@ async function composeRetention(options) {
|
|
|
292
294
|
return replay.length === 0 ? undefined : { key, replay, runId };
|
|
293
295
|
}
|
|
294
296
|
function describeCrossingRemedy(options) {
|
|
295
|
-
const {
|
|
297
|
+
const { crossingStep, isWorkspaceRoot, monorepoRoot, origin, registry } = options;
|
|
296
298
|
const configSite = path.relative(monorepoRoot, resolveConfigPath(monorepoRoot));
|
|
297
299
|
switch (origin.tier) {
|
|
298
300
|
case 'default':
|
|
@@ -307,131 +309,188 @@ function describeCrossingRemedy(options) {
|
|
|
307
309
|
};
|
|
308
310
|
case 'package':
|
|
309
311
|
return {
|
|
310
|
-
remedy: formatPackageRemedy({ configSite,
|
|
312
|
+
remedy: formatPackageRemedy({ configSite, crossingStep, isWorkspaceRoot, key: origin.key, registry }),
|
|
311
313
|
subject: `${path.relative(monorepoRoot, origin.file)}: \`scripts.${origin.key}\``,
|
|
312
314
|
};
|
|
313
315
|
default: {
|
|
314
|
-
const
|
|
315
|
-
throw new Error(`Unhandled script origin: ${JSON.stringify(
|
|
316
|
+
const unhandledOrigin = origin;
|
|
317
|
+
throw new Error(`Unhandled script origin: ${JSON.stringify(unhandledOrigin)}`);
|
|
316
318
|
}
|
|
317
319
|
}
|
|
318
320
|
}
|
|
319
321
|
function describeStepDestination(key) {
|
|
320
322
|
if (isHookName(key)) {
|
|
321
|
-
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';
|
|
322
324
|
}
|
|
323
325
|
return `a \`${key}:pre\` or \`${key}:post\` script`;
|
|
324
326
|
}
|
|
325
|
-
function describeOrigin(origin, config,
|
|
327
|
+
function describeOrigin(origin, config, shouldUseRoot) {
|
|
326
328
|
if (origin.tier === 'package') {
|
|
327
329
|
return origin;
|
|
328
330
|
}
|
|
329
|
-
const field =
|
|
331
|
+
const field = shouldUseRoot ? 'rootScripts' : 'workspaceScripts';
|
|
330
332
|
const configScripts = config[field];
|
|
331
333
|
return configScripts !== undefined && Object.hasOwn(configScripts, origin.key)
|
|
332
334
|
? { tier: 'config', field, key: origin.key }
|
|
333
335
|
: { tier: 'default', key: origin.key };
|
|
334
336
|
}
|
|
335
337
|
function escapeControlCharacters(text) {
|
|
336
|
-
let
|
|
338
|
+
let escapedText = text;
|
|
337
339
|
for (const [char, escape] of NAMED_ESCAPES) {
|
|
338
|
-
|
|
340
|
+
escapedText = escapedText.split(char).join(escape);
|
|
339
341
|
}
|
|
340
|
-
return
|
|
342
|
+
return escapedText;
|
|
341
343
|
}
|
|
342
344
|
function formatNmrCrossingWarning(options) {
|
|
343
345
|
const { remedy, subject } = describeCrossingRemedy(options);
|
|
344
346
|
return (`${STATUS_GLYPHS[options.style].warning.text} ${subject} reaches nmr through a shell ` +
|
|
345
|
-
`(\`${escapeControlCharacters(options.
|
|
347
|
+
`(\`${escapeControlCharacters(options.crossingStep)}\`), ${CROSSING_CONSEQUENCE} ${remedy}`);
|
|
346
348
|
}
|
|
347
|
-
function formatOverrideNotice(
|
|
349
|
+
function formatOverrideNotice(resolvedScript, registry, command, anchorDir, quiet, style) {
|
|
348
350
|
const registryEntry = Object.hasOwn(registry, command) ? registry[command] : undefined;
|
|
349
|
-
if (quiet ||
|
|
351
|
+
if (quiet || resolvedScript.origin.tier !== 'package' || registryEntry === undefined) {
|
|
350
352
|
return undefined;
|
|
351
353
|
}
|
|
352
|
-
const notice = `${path.basename(anchorDir)}: Using override script: ${renderChain(
|
|
354
|
+
const notice = `${path.basename(anchorDir)}: Using override script: ${renderChain(resolvedScript.steps)}`;
|
|
353
355
|
return `${formatGlyphLine(NMR_GLYPHS, style, 'package', notice)}\n`;
|
|
354
356
|
}
|
|
355
357
|
function formatSelfReferenceRemedy(options) {
|
|
356
|
-
const { command, monorepoRoot, registry, script
|
|
358
|
+
const { command, isWorkspaceRoot, monorepoRoot, registry, script } = options;
|
|
357
359
|
if (isHookName(command)) {
|
|
358
|
-
return `Delete the re-invocation: \`${command}\` runs the steps
|
|
360
|
+
return `Delete the re-invocation: \`${command}\` runs the steps beside it.`;
|
|
359
361
|
}
|
|
360
362
|
const configSite = path.relative(monorepoRoot, resolveConfigPath(monorepoRoot));
|
|
361
|
-
return formatPackageRemedy({ configSite,
|
|
363
|
+
return formatPackageRemedy({ configSite, crossingStep: script, isWorkspaceRoot, key: command, registry });
|
|
362
364
|
}
|
|
363
365
|
function formatPackageRemedy(options) {
|
|
364
|
-
const { configSite,
|
|
366
|
+
const { configSite, crossingStep, isWorkspaceRoot, key, registry } = options;
|
|
365
367
|
const registryEntry = Object.hasOwn(registry, key) ? registry[key] : undefined;
|
|
366
368
|
if (registryEntry === undefined) {
|
|
367
|
-
return (`A \`package.json\` script
|
|
369
|
+
return (`A \`package.json\` script cannot contain a step list: define \`${key}\` in \`${configSite}\` and move the ` +
|
|
368
370
|
`package-specific steps to ${describeStepDestination(key)}.`);
|
|
369
371
|
}
|
|
370
|
-
const registryChain = renderChain(expandScript(registryEntry,
|
|
371
|
-
if (registryChain ===
|
|
372
|
+
const registryChain = renderChain(expandScript(registryEntry, isWorkspaceRoot));
|
|
373
|
+
if (registryChain === crossingStep) {
|
|
372
374
|
return `Delete the entry: nmr's own \`${key}\` already runs \`${escapeControlCharacters(registryChain)}\`.`;
|
|
373
375
|
}
|
|
374
|
-
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)}.`;
|
|
375
377
|
}
|
|
376
|
-
function findArgError(
|
|
377
|
-
if (
|
|
378
|
+
function findArgError(parsedArgs) {
|
|
379
|
+
if (parsedArgs.shouldPrintLog &&
|
|
380
|
+
parsedArgs.command === undefined &&
|
|
381
|
+
!parsedArgs.shouldShowHelp &&
|
|
382
|
+
!parsedArgs.shouldShowVersion) {
|
|
378
383
|
return '--log requires a command name: `nmr --log <command>`';
|
|
379
384
|
}
|
|
380
385
|
return undefined;
|
|
381
386
|
}
|
|
382
387
|
function findEmptySelectionRefusal(options) {
|
|
383
|
-
const { context,
|
|
384
|
-
|
|
388
|
+
const { context, parsedArgs, selection } = options;
|
|
389
|
+
const isPackageless = context.workspacePackageDirs.length === 0;
|
|
390
|
+
if (parsedArgs.filter !== undefined) {
|
|
385
391
|
if (selection !== 'empty') {
|
|
386
392
|
return undefined;
|
|
387
393
|
}
|
|
388
|
-
|
|
394
|
+
if (isPackageless) {
|
|
395
|
+
return `${formatFilterRejection(parsedArgs.filter)} ${describeEmptyWorkspace(context.monorepoRoot)}`;
|
|
396
|
+
}
|
|
397
|
+
return formatEmptyFilterError(parsedArgs.filter, readWorkspacePackageNames(context.workspacePackageDirs));
|
|
389
398
|
}
|
|
390
|
-
return
|
|
399
|
+
return isPackageless ? `${RECURSIVE_REJECTION} ${describeEmptyWorkspace(context.monorepoRoot)}` : undefined;
|
|
391
400
|
}
|
|
392
401
|
function formatEmptyFilterError(pattern, names) {
|
|
393
|
-
const rejection =
|
|
402
|
+
const rejection = formatFilterRejection(pattern);
|
|
394
403
|
if (pattern.startsWith('!')) {
|
|
395
|
-
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.`;
|
|
396
405
|
}
|
|
397
406
|
if (pattern.includes('[')) {
|
|
398
|
-
return `${rejection} A pattern
|
|
407
|
+
return `${rejection} A pattern containing \`[<ref>]\` selects the packages changed since a git ref, and none has changed.`;
|
|
399
408
|
}
|
|
400
409
|
if (pattern.startsWith('.') || pattern.startsWith('/') || pattern.startsWith('{')) {
|
|
401
410
|
return (`${rejection} ` +
|
|
402
411
|
'A pattern written `./dir`, `/dir`, or `{dir}` selects the packages under a directory, ' +
|
|
403
|
-
'and
|
|
412
|
+
'and this directory does not contain any package.');
|
|
404
413
|
}
|
|
405
414
|
return (`${rejection} A pattern matches a package's manifest \`name\`, ` +
|
|
406
415
|
`not its directory name.${suggestWorkspaceNames(pattern, names)}`);
|
|
407
416
|
}
|
|
408
|
-
function
|
|
409
|
-
|
|
410
|
-
|
|
417
|
+
function describeEmptyWorkspace(monorepoRoot) {
|
|
418
|
+
const resolution = resolveWorkspace(monorepoRoot);
|
|
419
|
+
if (resolution.kind !== 'empty') {
|
|
420
|
+
return `pnpm-workspace.yaml at ${monorepoRoot} resolves differently now than it did when the run started.`;
|
|
421
|
+
}
|
|
422
|
+
const { cause, patterns } = resolution;
|
|
423
|
+
const declaredClause = describeDeclaredPatterns(patterns);
|
|
424
|
+
switch (cause) {
|
|
425
|
+
case 'all-excluded':
|
|
426
|
+
return (`pnpm-workspace.yaml ${declaredClause}, whose \`!\` entries exclude every directory matched by the positive ` +
|
|
427
|
+
'patterns. Drop or narrow the exclusion.');
|
|
428
|
+
case 'no-package':
|
|
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
|
+
'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 contains one.');
|
|
433
|
+
case 'no-packages-list':
|
|
434
|
+
return (`pnpm-workspace.yaml at ${monorepoRoot} does not declare a \`packages\` list, so the workspace contains the root ` +
|
|
435
|
+
'package alone. Declare a positive pattern such as `packages/*` to reach the packages beneath it.');
|
|
436
|
+
case 'no-pattern':
|
|
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
|
+
case 'unreadable-packages':
|
|
441
|
+
return (`pnpm-workspace.yaml at ${monorepoRoot} declares a \`packages\` value that is not a list of strings, so ` +
|
|
442
|
+
'nothing it declares reaches the matcher. Make it a list whose every entry is a quoted pattern; a bare ' +
|
|
443
|
+
'value and an entry YAML read as a number or a map are the usual ones.');
|
|
444
|
+
case 'unreadable-manifest':
|
|
445
|
+
return (`pnpm-workspace.yaml at ${monorepoRoot} is not valid YAML, so the matcher does not receive anything that it declares. ` +
|
|
446
|
+
'Repair the syntax error and run the command again. An unterminated quoted string and a mis-indented ' +
|
|
447
|
+
'entry are the usual ones.');
|
|
448
|
+
default: {
|
|
449
|
+
const unhandledCause = cause;
|
|
450
|
+
throw new Error(`Unhandled empty-workspace cause: ${String(unhandledCause)}`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
function describeDeclaredPatterns(patterns) {
|
|
455
|
+
const quotablePatterns = patterns.filter((pattern) => pattern.trim() !== '');
|
|
456
|
+
const emptiedCount = patterns.length - quotablePatterns.length;
|
|
457
|
+
if (emptiedCount === 0) {
|
|
458
|
+
return `declares ${renderQuotedList(patterns)}`;
|
|
459
|
+
}
|
|
460
|
+
const emptiedClause = `${emptiedCount} ${emptiedCount === 1 ? 'entry' : 'entries'} that YAML left empty`;
|
|
461
|
+
return quotablePatterns.length === 0
|
|
462
|
+
? `declares ${emptiedClause}`
|
|
463
|
+
: `declares ${renderQuotedList(quotablePatterns)}, beside ${emptiedClause}`;
|
|
464
|
+
}
|
|
465
|
+
function formatFilterRejection(pattern) {
|
|
466
|
+
return `-F/--filter did not match any workspace: \`${pattern}\`.`;
|
|
411
467
|
}
|
|
412
468
|
function suggestWorkspaceNames(pattern, names) {
|
|
413
469
|
if (names.length === 0) {
|
|
414
470
|
return '';
|
|
415
471
|
}
|
|
416
|
-
const
|
|
417
|
-
if (
|
|
418
|
-
return ` Did you mean ${
|
|
472
|
+
const containingNames = names.filter((name) => name.toLowerCase().includes(pattern.toLowerCase()));
|
|
473
|
+
if (containingNames.length > 0) {
|
|
474
|
+
return ` Did you mean ${renderQuotedList(containingNames)}?`;
|
|
419
475
|
}
|
|
420
|
-
const
|
|
421
|
-
if (
|
|
422
|
-
return ` Did you mean \`${
|
|
476
|
+
const closestName = findClosestName(pattern, names);
|
|
477
|
+
if (closestName !== undefined) {
|
|
478
|
+
return ` Did you mean \`${closestName}\`?`;
|
|
423
479
|
}
|
|
424
|
-
return ` The workspace declares ${
|
|
480
|
+
return ` The workspace declares ${renderQuotedList(names)}.`;
|
|
425
481
|
}
|
|
426
|
-
function
|
|
427
|
-
const
|
|
428
|
-
.slice(0,
|
|
429
|
-
.map((
|
|
482
|
+
function renderQuotedList(items) {
|
|
483
|
+
const shownItems = items
|
|
484
|
+
.slice(0, LIST_CEILING)
|
|
485
|
+
.map((item) => `\`${item}\``)
|
|
430
486
|
.join(', ');
|
|
431
|
-
const remainder =
|
|
432
|
-
return remainder > 0 ? `${
|
|
487
|
+
const remainder = items.length - LIST_CEILING;
|
|
488
|
+
return remainder > 0 ? `${shownItems}, and ${remainder} more` : shownItems;
|
|
433
489
|
}
|
|
434
|
-
function findNoOpReason(resolvedCommand) {
|
|
490
|
+
function findNoOpReason(resolvedCommand, isEmptiedByWorkspace) {
|
|
491
|
+
if (isEmptiedByWorkspace) {
|
|
492
|
+
return 'empty-workspace';
|
|
493
|
+
}
|
|
435
494
|
if (resolvedCommand === '') {
|
|
436
495
|
return 'empty-override';
|
|
437
496
|
}
|
|
@@ -441,7 +500,7 @@ function findNoOpReason(resolvedCommand) {
|
|
|
441
500
|
return undefined;
|
|
442
501
|
}
|
|
443
502
|
function assertNoSelfReference(options) {
|
|
444
|
-
const { anchorDir, command, isReading, monorepoRoot, registry
|
|
503
|
+
const { anchorDir, command, isReading, isWorkspaceRoot, monorepoRoot, registry } = options;
|
|
445
504
|
if (isReading) {
|
|
446
505
|
return;
|
|
447
506
|
}
|
|
@@ -449,19 +508,19 @@ function assertNoSelfReference(options) {
|
|
|
449
508
|
if (script === undefined) {
|
|
450
509
|
return;
|
|
451
510
|
}
|
|
452
|
-
const remedy = formatSelfReferenceRemedy({ command, monorepoRoot, registry, script
|
|
511
|
+
const remedy = formatSelfReferenceRemedy({ command, isWorkspaceRoot, monorepoRoot, registry, script });
|
|
453
512
|
const site = path.relative(monorepoRoot, resolvePackageJsonPath(anchorDir));
|
|
454
513
|
throw new UserError(`${site}: \`scripts.${command}\` re-invokes \`nmr ${command}\` (\`${escapeControlCharacters(script)}\`), ` +
|
|
455
|
-
`so nmr cannot run the steps it chains. ${remedy}`);
|
|
514
|
+
`so nmr cannot run the steps that it chains. ${remedy}`);
|
|
456
515
|
}
|
|
457
|
-
function hasRunnableHook(hookName, registry, anchorDir,
|
|
516
|
+
function hasRunnableHook(hookName, registry, anchorDir, isWorkspaceRoot) {
|
|
458
517
|
if (findChainedSelfReference(anchorDir, hookName) !== undefined) {
|
|
459
518
|
return true;
|
|
460
519
|
}
|
|
461
|
-
const
|
|
462
|
-
if (!
|
|
520
|
+
const resolvedScript = resolveScript(hookName, registry, anchorDir, isWorkspaceRoot);
|
|
521
|
+
if (!resolvedScript)
|
|
463
522
|
return false;
|
|
464
|
-
const chain = renderChain(
|
|
523
|
+
const chain = renderChain(resolvedScript.steps);
|
|
465
524
|
return chain !== '' && chain !== ':';
|
|
466
525
|
}
|
|
467
526
|
async function lookUpRecordedPass(options) {
|
|
@@ -475,14 +534,14 @@ async function lookUpRecordedPass(options) {
|
|
|
475
534
|
writeDebugNote(`running ${command}: the tree or its inputs changed since the last pass`, env, stderr);
|
|
476
535
|
return undefined;
|
|
477
536
|
}
|
|
478
|
-
const [
|
|
479
|
-
if (
|
|
480
|
-
writeDebugNote(`running ${command}: ${
|
|
537
|
+
const [missingPackage] = buildOutput.missingPackages;
|
|
538
|
+
if (missingPackage !== undefined) {
|
|
539
|
+
writeDebugNote(`running ${command}: ${missingPackage} does not have build output`, env, stderr);
|
|
481
540
|
return undefined;
|
|
482
541
|
}
|
|
483
|
-
const
|
|
484
|
-
if (
|
|
485
|
-
writeDebugNote(`running ${command}: ${
|
|
542
|
+
const stalePackage = findStaleBuildOutput(entry.buildDigests, buildOutput.digests);
|
|
543
|
+
if (stalePackage !== undefined) {
|
|
544
|
+
writeDebugNote(`running ${command}: ${stalePackage}'s build output came from a different tree`, env, stderr);
|
|
486
545
|
return undefined;
|
|
487
546
|
}
|
|
488
547
|
return {
|
|
@@ -512,60 +571,60 @@ function openGate(options) {
|
|
|
512
571
|
return snapshot.snapshot;
|
|
513
572
|
}
|
|
514
573
|
function parseArgs(args) {
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
log: false,
|
|
518
|
-
noCache: false,
|
|
574
|
+
const parsedArgs = {
|
|
575
|
+
isWorkspaceRoot: false,
|
|
519
576
|
quiet: false,
|
|
520
577
|
recursive: false,
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
578
|
+
shouldBypassCache: false,
|
|
579
|
+
shouldEmitJson: false,
|
|
580
|
+
shouldPrintLog: false,
|
|
581
|
+
shouldShowHelp: false,
|
|
582
|
+
shouldShowVersion: false,
|
|
524
583
|
passthrough: [],
|
|
525
584
|
};
|
|
526
|
-
let
|
|
527
|
-
while (
|
|
528
|
-
const arg = args[
|
|
585
|
+
let index = 0;
|
|
586
|
+
while (index < args.length) {
|
|
587
|
+
const arg = args[index];
|
|
529
588
|
if (arg === undefined)
|
|
530
589
|
break;
|
|
531
590
|
if (arg === '-F' || arg === '--filter') {
|
|
532
|
-
|
|
533
|
-
const filterValue = args[
|
|
591
|
+
index++;
|
|
592
|
+
const filterValue = args[index];
|
|
534
593
|
if (!filterValue) {
|
|
535
594
|
return { ok: false, error: '-F/--filter requires a pattern argument' };
|
|
536
595
|
}
|
|
537
|
-
|
|
538
|
-
|
|
596
|
+
parsedArgs.filter = filterValue;
|
|
597
|
+
index++;
|
|
539
598
|
continue;
|
|
540
599
|
}
|
|
541
600
|
const booleanFlag = BOOLEAN_FLAGS.get(arg);
|
|
542
601
|
if (booleanFlag !== undefined) {
|
|
543
|
-
|
|
544
|
-
|
|
602
|
+
parsedArgs[booleanFlag] = true;
|
|
603
|
+
index++;
|
|
545
604
|
continue;
|
|
546
605
|
}
|
|
547
|
-
const styleArgument = readOutputStyleArgument(args,
|
|
606
|
+
const styleArgument = readOutputStyleArgument(args, index);
|
|
548
607
|
if (styleArgument !== undefined) {
|
|
549
608
|
if (!styleArgument.ok) {
|
|
550
609
|
return styleArgument;
|
|
551
610
|
}
|
|
552
|
-
|
|
553
|
-
|
|
611
|
+
parsedArgs.outputStyle = styleArgument.value;
|
|
612
|
+
index += styleArgument.consumedCount;
|
|
554
613
|
continue;
|
|
555
614
|
}
|
|
556
|
-
|
|
557
|
-
|
|
615
|
+
parsedArgs.command = arg;
|
|
616
|
+
parsedArgs.passthrough = args.slice(index + 1);
|
|
558
617
|
break;
|
|
559
618
|
}
|
|
560
|
-
const error = findArgError(
|
|
561
|
-
return error === undefined ? { ok: true,
|
|
619
|
+
const error = findArgError(parsedArgs);
|
|
620
|
+
return error === undefined ? { ok: true, parsedArgs } : { ok: false, error };
|
|
562
621
|
}
|
|
563
622
|
async function reportRecording(options) {
|
|
564
623
|
const { command, config, scope, styles } = options;
|
|
565
624
|
const lookup = await resolveRecording({
|
|
566
625
|
anchorDir: options.anchorDir,
|
|
567
626
|
command,
|
|
568
|
-
|
|
627
|
+
currentIdentity: {
|
|
569
628
|
commandString: options.commandString,
|
|
570
629
|
nmrVersion: VERSION,
|
|
571
630
|
nodeVersion: CURRENT_RUNTIME.nodeVersion,
|
|
@@ -592,9 +651,9 @@ function readPresentation(options) {
|
|
|
592
651
|
if (!format.ok) {
|
|
593
652
|
return format;
|
|
594
653
|
}
|
|
595
|
-
const { invalid, styles } = resolveOutputStyles(options);
|
|
596
|
-
if (
|
|
597
|
-
return { ok: false, error: describeInvalidOutputStyle(
|
|
654
|
+
const { invalid: invalidStyle, styles } = resolveOutputStyles(options);
|
|
655
|
+
if (invalidStyle !== undefined) {
|
|
656
|
+
return { ok: false, error: describeInvalidOutputStyle(invalidStyle) };
|
|
598
657
|
}
|
|
599
658
|
return { ok: true, format: format.format, styles, verbosity: verbosity.verbosity };
|
|
600
659
|
}
|
|
@@ -608,20 +667,27 @@ function readOutputStyleArgument(args, index) {
|
|
|
608
667
|
if (!value) {
|
|
609
668
|
return { ok: false, error: `${OUTPUT_STYLE_FLAG} requires a value argument: auto, plain, or rich` };
|
|
610
669
|
}
|
|
611
|
-
return { ok: true, value,
|
|
670
|
+
return { ok: true, value, consumedCount: assignment === undefined ? 2 : 1 };
|
|
671
|
+
}
|
|
672
|
+
function readRunnableSteps(resolvedScript, workspacePackageDirs) {
|
|
673
|
+
if (workspacePackageDirs.length > 0) {
|
|
674
|
+
return { isEmptiedByWorkspace: false, steps: resolvedScript.steps };
|
|
675
|
+
}
|
|
676
|
+
const steps = dropRecursiveSteps(resolvedScript.steps);
|
|
677
|
+
return { isEmptiedByWorkspace: steps.length === 0 && resolvedScript.steps.length > 0, steps };
|
|
612
678
|
}
|
|
613
679
|
function reportNmrCrossing(options) {
|
|
614
|
-
const
|
|
615
|
-
if (options.isReading ||
|
|
680
|
+
const crossingStep = findNmrCrossing(options.resolvedScript.steps);
|
|
681
|
+
if (options.isReading || crossingStep === undefined) {
|
|
616
682
|
return;
|
|
617
683
|
}
|
|
618
684
|
const warning = formatNmrCrossingWarning({
|
|
619
|
-
|
|
685
|
+
crossingStep,
|
|
686
|
+
isWorkspaceRoot: options.isWorkspaceRoot,
|
|
620
687
|
monorepoRoot: options.monorepoRoot,
|
|
621
|
-
origin: describeOrigin(options.
|
|
688
|
+
origin: describeOrigin(options.resolvedScript.origin, options.config, options.shouldUseRoot),
|
|
622
689
|
registry: options.registry,
|
|
623
690
|
style: options.style,
|
|
624
|
-
workspaceRoot: options.workspaceRoot,
|
|
625
691
|
});
|
|
626
692
|
options.stderr.write(`${warning}\n`);
|
|
627
693
|
}
|
|
@@ -637,24 +703,24 @@ function resolveReportingVerbosity(options) {
|
|
|
637
703
|
}
|
|
638
704
|
async function recordPass(options) {
|
|
639
705
|
const { anchorDir, command, env, monorepoRoot, snapshot, stderr } = options;
|
|
640
|
-
const
|
|
641
|
-
if (!
|
|
642
|
-
writeDebugNote(`not recording ${command}: ${
|
|
706
|
+
const currentRead = resolveTreeSnapshot({ monorepoRoot, env: {} });
|
|
707
|
+
if (!currentRead.ok) {
|
|
708
|
+
writeDebugNote(`not recording ${command}: ${currentRead.reason}`, env, stderr);
|
|
643
709
|
return;
|
|
644
710
|
}
|
|
645
|
-
if (
|
|
711
|
+
if (currentRead.snapshot.hash !== snapshot.hash) {
|
|
646
712
|
writeDebugNote(`not recording ${command}: the tree changed while it ran`, env, stderr);
|
|
647
713
|
return;
|
|
648
714
|
}
|
|
649
715
|
const output = await readBuildOutputState(monorepoRoot, options.config);
|
|
650
|
-
const [
|
|
651
|
-
if (
|
|
652
|
-
writeDebugNote(`not recording ${command}: ${
|
|
716
|
+
const [missingPackage] = output.missingPackages;
|
|
717
|
+
if (missingPackage !== undefined) {
|
|
718
|
+
writeDebugNote(`not recording ${command}: ${missingPackage} does not have build output`, env, stderr);
|
|
653
719
|
return;
|
|
654
720
|
}
|
|
655
|
-
const
|
|
656
|
-
if (
|
|
657
|
-
writeDebugNote(`not recording ${command}: ${
|
|
721
|
+
const changedPackage = findStaleBuildOutput(options.buildOutputBefore.digests, output.digests);
|
|
722
|
+
if (changedPackage !== undefined) {
|
|
723
|
+
writeDebugNote(`not recording ${command}: ${changedPackage}'s build output changed while it ran`, env, stderr);
|
|
658
724
|
return;
|
|
659
725
|
}
|
|
660
726
|
const retention = await composeRetention({
|
|
@@ -662,13 +728,13 @@ async function recordPass(options) {
|
|
|
662
728
|
command,
|
|
663
729
|
key: options.retentionKey,
|
|
664
730
|
monorepoRoot,
|
|
665
|
-
|
|
731
|
+
retainedOutput: options.retainedOutput,
|
|
666
732
|
runId: options.runId,
|
|
667
733
|
steps: options.ownSteps,
|
|
668
734
|
treeHash: snapshot.hash,
|
|
669
735
|
});
|
|
670
736
|
const ref = { anchorDir, command, monorepoRoot };
|
|
671
|
-
const transcript = options.
|
|
737
|
+
const transcript = options.retainedOutput === undefined ? undefined : composeTranscript(options.retainedOutput);
|
|
672
738
|
try {
|
|
673
739
|
await recordTranscript(ref, transcript);
|
|
674
740
|
await writeCheckCacheEntry({
|
|
@@ -721,14 +787,14 @@ function resolveCacheKey(options) {
|
|
|
721
787
|
return result.key;
|
|
722
788
|
}
|
|
723
789
|
async function runDelegation(options) {
|
|
724
|
-
const { context, delegation,
|
|
725
|
-
const selection =
|
|
726
|
-
const refusal = findEmptySelectionRefusal({ context,
|
|
790
|
+
const { context, delegation, parsedArgs, runOptions, stderr } = options;
|
|
791
|
+
const selection = parsedArgs.filter === undefined ? undefined : readFilterSelection(parsedArgs.filter, context.monorepoRoot);
|
|
792
|
+
const refusal = findEmptySelectionRefusal({ context, parsedArgs, selection });
|
|
727
793
|
if (refusal !== undefined) {
|
|
728
794
|
reportError(refusal, stderr);
|
|
729
795
|
return { exitCode: 1 };
|
|
730
796
|
}
|
|
731
|
-
const shouldWithholdInput =
|
|
797
|
+
const shouldWithholdInput = parsedArgs.filter === undefined || selection === 'multiple';
|
|
732
798
|
const step = shouldWithholdInput ? { ...delegation.step, shouldWithholdInput } : delegation.step;
|
|
733
799
|
return runSteps([step], context.monorepoRoot, { ...runOptions, env: delegation.env });
|
|
734
800
|
}
|
|
@@ -747,8 +813,8 @@ async function runGated(options) {
|
|
|
747
813
|
snapshot,
|
|
748
814
|
}
|
|
749
815
|
: undefined;
|
|
750
|
-
if (gate !== undefined && !options.
|
|
751
|
-
const
|
|
816
|
+
if (gate !== undefined && !options.shouldBypassCache) {
|
|
817
|
+
const recalledPass = await lookUpRecordedPass({
|
|
752
818
|
anchorDir,
|
|
753
819
|
buildOutput: gate.buildOutputBefore,
|
|
754
820
|
command,
|
|
@@ -758,8 +824,8 @@ async function runGated(options) {
|
|
|
758
824
|
retentionKey: gate.retentionKey,
|
|
759
825
|
stderr,
|
|
760
826
|
});
|
|
761
|
-
if (
|
|
762
|
-
const { entry, ...recall } =
|
|
827
|
+
if (recalledPass !== undefined) {
|
|
828
|
+
const { entry, ...recall } = recalledPass;
|
|
763
829
|
if (recall.replay !== undefined) {
|
|
764
830
|
await certifyRetention({ anchorDir, command, entry, env, monorepoRoot, runId: options.runId, stderr });
|
|
765
831
|
}
|
|
@@ -769,9 +835,9 @@ async function runGated(options) {
|
|
|
769
835
|
if (options.overrideNotice !== undefined) {
|
|
770
836
|
stdout.write(options.overrideNotice);
|
|
771
837
|
}
|
|
772
|
-
const
|
|
773
|
-
const { exitCode,
|
|
774
|
-
const durationMs = Date.now() -
|
|
838
|
+
const startedAtMs = Date.now();
|
|
839
|
+
const { exitCode, retainedOutput } = await runSteps(options.steps, anchorDir, options.runOptions);
|
|
840
|
+
const durationMs = Date.now() - startedAtMs;
|
|
775
841
|
if (exitCode === 0 && gate !== undefined) {
|
|
776
842
|
await recordPass({
|
|
777
843
|
anchorDir,
|
|
@@ -784,7 +850,7 @@ async function runGated(options) {
|
|
|
784
850
|
key: gate.key,
|
|
785
851
|
monorepoRoot,
|
|
786
852
|
ownSteps: options.ownSteps,
|
|
787
|
-
|
|
853
|
+
retainedOutput,
|
|
788
854
|
retentionKey: gate.retentionKey,
|
|
789
855
|
runId: options.runId,
|
|
790
856
|
snapshot: gate.snapshot,
|
|
@@ -796,17 +862,17 @@ async function runGated(options) {
|
|
|
796
862
|
outcome: exitCode === 0 ? { outcome: 'passed', durationMs } : { outcome: 'failed', durationMs, exitCode },
|
|
797
863
|
};
|
|
798
864
|
}
|
|
799
|
-
function
|
|
865
|
+
function quoteForShell(arg) {
|
|
800
866
|
return "'" + arg.replace(/'/g, String.raw `'\''`) + "'";
|
|
801
867
|
}
|
|
802
|
-
function wrapWithHooks(command, mainSteps, registry, anchorDir,
|
|
868
|
+
function wrapWithHooks(command, mainSteps, registry, anchorDir, isWorkspaceRoot) {
|
|
803
869
|
const steps = [];
|
|
804
|
-
if (hasRunnableHook(`${command}:pre`, registry, anchorDir,
|
|
805
|
-
steps.push(composeNmrStep(`${command}:pre`,
|
|
870
|
+
if (hasRunnableHook(`${command}:pre`, registry, anchorDir, isWorkspaceRoot)) {
|
|
871
|
+
steps.push(composeNmrStep(`${command}:pre`, isWorkspaceRoot));
|
|
806
872
|
}
|
|
807
873
|
steps.push(...mainSteps);
|
|
808
|
-
if (hasRunnableHook(`${command}:post`, registry, anchorDir,
|
|
809
|
-
steps.push(composeNmrStep(`${command}:post`,
|
|
874
|
+
if (hasRunnableHook(`${command}:post`, registry, anchorDir, isWorkspaceRoot)) {
|
|
875
|
+
steps.push(composeNmrStep(`${command}:post`, isWorkspaceRoot));
|
|
810
876
|
}
|
|
811
877
|
return steps;
|
|
812
878
|
}
|