@williamthorsen/nmr 0.48.0 → 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.meta/changelog.json +158 -0
- package/.readyup/kits/default.js +1527 -1460
- package/.readyup/manifest.json +96 -11
- package/CHANGELOG.md +41 -0
- package/README.md +36 -8
- package/agents/guidance/rulebooks/nmr.md +2 -2
- package/dist/esm/check-cache-build-output.d.ts +24 -0
- package/dist/esm/check-cache-build-output.js +49 -0
- package/dist/esm/check-cache-delegation.d.ts +27 -0
- package/dist/esm/check-cache-delegation.js +92 -0
- package/dist/esm/check-cache-entries.d.ts +109 -0
- package/dist/esm/check-cache-entries.js +87 -0
- package/dist/esm/check-cache-gate.d.ts +64 -0
- package/dist/esm/check-cache-gate.js +226 -0
- package/dist/esm/check-cache-key.d.ts +47 -0
- package/dist/esm/check-cache-key.js +77 -0
- package/dist/esm/check-cache-record.d.ts +69 -0
- package/dist/esm/check-cache-record.js +167 -0
- package/dist/esm/check-cache.d.ts +8 -176
- package/dist/esm/check-cache.js +9 -214
- package/dist/esm/child-env.d.ts +57 -0
- package/dist/esm/child-env.js +59 -0
- package/dist/esm/child-process.d.ts +55 -0
- package/dist/esm/child-process.js +112 -0
- package/dist/esm/commands/build.d.ts +0 -16
- package/dist/esm/commands/build.js +9 -214
- package/dist/esm/commands/clean.js +3 -2
- package/dist/esm/commands/compiler.d.ts +18 -0
- package/dist/esm/commands/compiler.js +49 -0
- package/dist/esm/commands/fmt.d.ts +0 -39
- package/dist/esm/commands/fmt.js +4 -81
- package/dist/esm/commands/format-targets.d.ts +39 -0
- package/dist/esm/commands/format-targets.js +83 -0
- package/dist/esm/commands/rewrite-specifiers.d.ts +17 -0
- package/dist/esm/commands/rewrite-specifiers.js +122 -0
- package/dist/esm/commands/tsconfig-chain.d.ts +8 -0
- package/dist/esm/commands/tsconfig-chain.js +63 -0
- package/dist/esm/config-check-cache.d.ts +17 -0
- package/dist/esm/config-check-cache.js +63 -0
- package/dist/esm/config-validation.d.ts +20 -0
- package/dist/esm/config-validation.js +236 -0
- package/dist/esm/config.js +4 -286
- package/dist/esm/crossing-diagnostics.d.ts +39 -0
- package/dist/esm/crossing-diagnostics.js +132 -0
- package/dist/esm/default-scripts.js +1 -1
- package/dist/esm/delegate-host.d.ts +43 -0
- package/dist/esm/delegate-host.js +178 -0
- package/dist/esm/delegation-target.d.ts +52 -0
- package/dist/esm/delegation-target.js +71 -0
- package/dist/esm/fan-out.d.ts +57 -0
- package/dist/esm/fan-out.js +146 -0
- package/dist/esm/glyphs.d.ts +1 -1
- package/dist/esm/help.js +9 -3
- package/dist/esm/helpers/deriveExcerpt.d.ts +9 -0
- package/dist/esm/helpers/deriveExcerpt.js +1 -1
- package/dist/esm/helpers/renderQuotedList.d.ts +2 -0
- package/dist/esm/helpers/renderQuotedList.js +9 -0
- package/dist/esm/hooks.d.ts +17 -0
- package/dist/esm/hooks.js +23 -0
- package/dist/esm/invocation-args.d.ts +40 -0
- package/dist/esm/invocation-args.js +178 -0
- package/dist/esm/invocation.d.ts +67 -0
- package/dist/esm/invocation.js +3 -0
- package/dist/esm/machine-lock.d.ts +2 -0
- package/dist/esm/machine-lock.js +1 -1
- package/dist/esm/output-channel.d.ts +23 -0
- package/dist/esm/output-channel.js +14 -0
- package/dist/esm/pass-manifest.d.ts +53 -0
- package/dist/esm/pass-manifest.js +89 -0
- package/dist/esm/presentation.d.ts +47 -0
- package/dist/esm/presentation.js +32 -0
- package/dist/esm/recording.d.ts +1 -1
- package/dist/esm/recording.js +2 -2
- package/dist/esm/remote-host-session.d.ts +62 -0
- package/dist/esm/remote-host-session.js +277 -0
- package/dist/esm/remote-probe-host.d.ts +22 -0
- package/dist/esm/remote-probe-host.js +50 -0
- package/dist/esm/remote-probe.d.ts +31 -0
- package/dist/esm/remote-probe.js +78 -0
- package/dist/esm/remote-run.d.ts +28 -73
- package/dist/esm/remote-run.js +103 -165
- package/dist/esm/remote-session.d.ts +40 -0
- package/dist/esm/remote-session.js +151 -0
- package/dist/esm/remote-snapshot.d.ts +21 -0
- package/dist/esm/remote-snapshot.js +80 -0
- package/dist/esm/replay-assembly.d.ts +2 -2
- package/dist/esm/replay-assembly.js +1 -1
- package/dist/esm/run-chain.d.ts +21 -0
- package/dist/esm/run-chain.js +108 -0
- package/dist/esm/runCli.d.ts +1 -9
- package/dist/esm/runCli.js +39 -1002
- package/dist/esm/runner.d.ts +1 -54
- package/dist/esm/runner.js +2 -125
- package/dist/esm/shell-syntax.d.ts +30 -0
- package/dist/esm/shell-syntax.js +120 -0
- package/dist/esm/steps.d.ts +0 -2
- package/dist/esm/steps.js +3 -119
- package/dist/esm/types.d.ts +5 -0
- package/dist/esm/user-config.d.ts +34 -0
- package/dist/esm/user-config.js +93 -0
- package/dist/esm/verbosity.d.ts +6 -0
- package/dist/esm/verbosity.js +7 -7
- package/dist/esm/verdict.d.ts +1 -1
- package/dist/esm/verdict.js +1 -1
- package/docs/check-cache.md +28 -12
- package/docs/delegate.md +96 -16
- package/docs/reporting.md +6 -1
- package/docs/scripts.md +2 -1
- package/package.json +3 -3
- /package/dist/esm/{prettier-default-config.d.ts → prettier-default.config.d.ts} +0 -0
- /package/dist/esm/{prettier-default-config.js → prettier-default.config.js} +0 -0
package/dist/esm/runCli.js
CHANGED
|
@@ -1,50 +1,44 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import {
|
|
3
|
-
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
2
|
+
import { reportError } from '@williamthorsen/nmr-core';
|
|
4
3
|
import { prependBinDirs } from "./bin-path.js";
|
|
5
|
-
import {
|
|
4
|
+
import { NO_CACHE_ENV_VAR, resolveRunId, RUN_ID_ENV_VAR } from "./check-cache.js";
|
|
5
|
+
import { openGate, runGated } from "./check-cache-gate.js";
|
|
6
|
+
import { reportRecording } from "./check-cache-record.js";
|
|
7
|
+
import { addLockEnv, buildChildEnv, takeMachineLock } from "./child-env.js";
|
|
6
8
|
import { resolveContext } from "./context.js";
|
|
9
|
+
import { assertNoSelfReference } from "./crossing-diagnostics.js";
|
|
10
|
+
import { resolveExecutionTarget, runInvocationOnHost } from "./delegate-host.js";
|
|
7
11
|
import { enterChain } from "./entry-chain.js";
|
|
8
|
-
import {
|
|
12
|
+
import { composeDelegation, isDefinedInAnyScope, runDelegation } from "./fan-out.js";
|
|
9
13
|
import { generateHelp } from "./help.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import { composeTranscript } from "./helpers/transcript.js";
|
|
19
|
-
import { acquireMachineLock, formatMisplacedNoLockWarning, HEAVY_COMMANDS, LOCK_TOKEN_ENV_VAR, NO_LOCK_ENV_VAR, } from "./machine-lock.js";
|
|
20
|
-
import { isTerminalStream, OUTPUT_STYLE_ENV_VAR, OUTPUT_STYLE_FLAG, resolveOutputStyles } from "./output-style.js";
|
|
21
|
-
import { renderRecording, renderRefusal, resolveRecording } from "./recording.js";
|
|
22
|
-
import { createSnapshot, DELEGATE_ENV_VAR, DELEGATE_FLAG, DELEGATION_FAILURE_EXIT_CODE, formatMisplacedDelegationWarning, NO_DELEGATE_FLAG, resolveDelegationTarget, runRemotely, } from "./remote-run.js";
|
|
23
|
-
import { assembleReplay } from "./replay-assembly.js";
|
|
24
|
-
import { readReportFormatEnv, REPORT_FORMAT_ENV_VAR, resolveReportFormat } from "./report-format.js";
|
|
25
|
-
import { applyDevBinToSteps, buildRootRegistry, buildWorkspaceRegistry, expandScript, findChainedSelfReference, readPackageJsonScripts, resolveScript, } from "./resolver.js";
|
|
26
|
-
import { resolveChannel, runSteps } from "./runner.js";
|
|
27
|
-
import { composeNmrStep, dropRecursiveSteps, findNmrCrossing, quoteToken, readSoleNmrCommand, renderChain, } from "./steps.js";
|
|
28
|
-
import { UserError } from "./UserError.js";
|
|
29
|
-
import { COMMAND_VERBOSITY_ENV_VAR, readVerbosityEnv, resolveVerbosity, } from "./verbosity.js";
|
|
30
|
-
import { writeVerdict } from "./verdict.js";
|
|
31
|
-
import { readWorkspacePackageNames, resolveWorkspace } from "./workspace.js";
|
|
32
|
-
const VERSION = resolveSelfVersion(import.meta.url);
|
|
33
|
-
const CROSSING_CONSEQUENCE = "so nmr handles the nested run's output as a tool's.";
|
|
34
|
-
const RECURSIVE_REJECTION = '-R/--recursive did not match any workspace:';
|
|
35
|
-
const LIST_CEILING = 10;
|
|
36
|
-
export const RUN_IF_PRESENT_ENV_VAR = 'NMR_RUN_IF_PRESENT';
|
|
14
|
+
import { RUN_IF_PRESENT_ENV_VAR, VERSION, } from "./invocation.js";
|
|
15
|
+
import { formatMisplacedFlagHint, parseArgs } from "./invocation-args.js";
|
|
16
|
+
import { isTerminalStream } from "./output-style.js";
|
|
17
|
+
import { readPresentation, reportVerdict, resolveReportingVerbosity } from "./presentation.js";
|
|
18
|
+
import { DELEGATE_TO_FLAG } from "./remote-run.js";
|
|
19
|
+
import { resolveReportFormat } from "./report-format.js";
|
|
20
|
+
import { buildRootRegistry, buildWorkspaceRegistry, resolveScript } from "./resolver.js";
|
|
21
|
+
import { assembleChain, formatOverrideNotice } from "./run-chain.js";
|
|
37
22
|
export async function runCli(options) {
|
|
38
23
|
const established = await establishInvocation(options);
|
|
39
24
|
if (established.settled) {
|
|
40
25
|
return { exitCode: established.result.exitCode };
|
|
41
26
|
}
|
|
42
27
|
const invocation = established.value;
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
45
|
-
return
|
|
28
|
+
const resolvedTarget = await resolveExecutionTarget(invocation);
|
|
29
|
+
if (resolvedTarget.settled) {
|
|
30
|
+
return { exitCode: resolvedTarget.result.exitCode };
|
|
46
31
|
}
|
|
47
|
-
|
|
32
|
+
let target = resolvedTarget.value;
|
|
33
|
+
const { filter, recursive } = invocation.parsedArgs;
|
|
34
|
+
if (target.kind === 'remote' && (filter !== undefined || recursive)) {
|
|
35
|
+
const delegated = await runInvocationOnHost(invocation, target);
|
|
36
|
+
if (delegated !== undefined) {
|
|
37
|
+
return delegated;
|
|
38
|
+
}
|
|
39
|
+
target = { kind: 'local' };
|
|
40
|
+
}
|
|
41
|
+
const planned = await planRun(invocation, target);
|
|
48
42
|
const { exitCode, isUnknownCommand = false } = planned.settled ? planned.result : await executePlan(planned.value);
|
|
49
43
|
if (exitCode !== 0 && !isUnknownCommand && isTopLevelInvocation(options.env)) {
|
|
50
44
|
const hint = formatMisplacedFlagHint(options.args, invocation.parsedArgs.passthrough, invocation.styles.stderr);
|
|
@@ -111,7 +105,7 @@ async function establishInvocation(options) {
|
|
|
111
105
|
},
|
|
112
106
|
};
|
|
113
107
|
}
|
|
114
|
-
async function planRun(invocation) {
|
|
108
|
+
async function planRun(invocation, target) {
|
|
115
109
|
const { anchorDir, command, context, env, parsedArgs, stderr, stdout, styles } = invocation;
|
|
116
110
|
const quiet = invocation.verbosity === 'quiet';
|
|
117
111
|
const snapshot = openGate({
|
|
@@ -174,7 +168,10 @@ async function planRun(invocation) {
|
|
|
174
168
|
if (env[RUN_IF_PRESENT_ENV_VAR] === '1') {
|
|
175
169
|
return { settled: true, result: { exitCode: 0 } };
|
|
176
170
|
}
|
|
177
|
-
|
|
171
|
+
const delegateToHint = parsedArgs.shouldDelegate && parsedArgs.delegateHosts === undefined
|
|
172
|
+
? `. \`${command}\` is not a command; name a host with ${DELEGATE_TO_FLAG} ${command}`
|
|
173
|
+
: '';
|
|
174
|
+
reportError(`Unknown command: ${command}${delegateToHint}`, stderr);
|
|
178
175
|
return { settled: true, result: { exitCode: 1, isUnknownCommand: true } };
|
|
179
176
|
}
|
|
180
177
|
const entryRunOptions = parsedArgs.shouldPrintLog
|
|
@@ -202,982 +199,22 @@ async function planRun(invocation) {
|
|
|
202
199
|
runOptions: entryRunOptions,
|
|
203
200
|
shouldBypassCache,
|
|
204
201
|
snapshot,
|
|
202
|
+
target,
|
|
205
203
|
},
|
|
206
204
|
};
|
|
207
205
|
}
|
|
208
|
-
function assembleChain(options) {
|
|
209
|
-
const { invocation, registry, resolvedScript, snapshot } = options;
|
|
210
|
-
const { anchorDir, command, context, env, parsedArgs, stderr, stdout, styles } = invocation;
|
|
211
|
-
const { isEmptiedByWorkspace, steps: runnableSteps } = readRunnableSteps(resolvedScript, context.workspacePackageDirs);
|
|
212
|
-
const resolvedCommand = renderChain(runnableSteps);
|
|
213
|
-
const noOpReason = findNoOpReason(resolvedCommand, isEmptiedByWorkspace);
|
|
214
|
-
if (noOpReason !== undefined && !parsedArgs.shouldPrintLog) {
|
|
215
|
-
reportVerdict({ command, scope: invocation.scope, outcome: 'no-op', reason: noOpReason }, stdout, invocation.format, styles.stdout);
|
|
216
|
-
return { settled: true, result: { exitCode: 0 } };
|
|
217
|
-
}
|
|
218
|
-
const substitutedSteps = applyDevBinToSteps(runnableSteps, context.config.devBin, context.monorepoRoot);
|
|
219
|
-
const substitutedCommand = renderChain(substitutedSteps);
|
|
220
|
-
const bindResult = bindPassthrough(substitutedSteps, parsedArgs.passthrough, command);
|
|
221
|
-
if (!bindResult.ok) {
|
|
222
|
-
reportError(bindResult.error, stderr);
|
|
223
|
-
return { settled: true, result: { exitCode: 1 } };
|
|
224
|
-
}
|
|
225
|
-
const mainSteps = bindResult.steps;
|
|
226
|
-
const isHookInvocation = isHookName(command);
|
|
227
|
-
const fullSteps = isHookInvocation
|
|
228
|
-
? mainSteps
|
|
229
|
-
: wrapWithHooks(command, mainSteps, registry, anchorDir, parsedArgs.isWorkspaceRoot);
|
|
230
|
-
const fullCommand = renderChain(fullSteps);
|
|
231
|
-
reportNmrCrossing({
|
|
232
|
-
config: context.config,
|
|
233
|
-
isReading: parsedArgs.shouldPrintLog,
|
|
234
|
-
isWorkspaceRoot: parsedArgs.isWorkspaceRoot,
|
|
235
|
-
monorepoRoot: context.monorepoRoot,
|
|
236
|
-
registry,
|
|
237
|
-
resolvedScript,
|
|
238
|
-
shouldUseRoot: invocation.shouldUseRoot,
|
|
239
|
-
stderr,
|
|
240
|
-
style: styles.stderr,
|
|
241
|
-
});
|
|
242
|
-
const key = resolveCacheKey({
|
|
243
|
-
anchorDir,
|
|
244
|
-
command,
|
|
245
|
-
commandString: fullCommand,
|
|
246
|
-
env,
|
|
247
|
-
monorepoRoot: context.monorepoRoot,
|
|
248
|
-
snapshot,
|
|
249
|
-
stderr,
|
|
250
|
-
substitution: substitutedCommand === resolvedCommand ? undefined : substitutedCommand,
|
|
251
|
-
});
|
|
252
|
-
return { settled: false, value: { commandString: fullCommand, key, ownSteps: mainSteps, steps: fullSteps } };
|
|
253
|
-
}
|
|
254
206
|
async function executePlan(plan) {
|
|
255
207
|
const { command, format, parsedArgs, scope, stdout, styles } = plan.invocation;
|
|
256
208
|
if (parsedArgs.shouldPrintLog) {
|
|
257
209
|
return reportRecording(plan);
|
|
258
210
|
}
|
|
259
|
-
const { exitCode, outcome } = await runGated(plan);
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
263
|
-
async function runOnDelegateHost(invocation) {
|
|
264
|
-
const { command, context, cwd, env, format, parsedArgs, scope, stderr, stdout, styles } = invocation;
|
|
265
|
-
if (parsedArgs.passthrough.some(isDelegationFlag)) {
|
|
266
|
-
stderr.write(`${formatMisplacedDelegationWarning(command, styles.stderr)}
|
|
267
|
-
`);
|
|
268
|
-
}
|
|
269
|
-
const target = resolveDelegationTarget({
|
|
270
|
-
command,
|
|
271
|
-
env,
|
|
272
|
-
flagHost: parsedArgs.delegateHost,
|
|
273
|
-
isOptedOut: parsedArgs.shouldRunLocally,
|
|
274
|
-
isReading: parsedArgs.shouldPrintLog,
|
|
275
|
-
});
|
|
276
|
-
if (target.kind === 'local') {
|
|
277
|
-
return undefined;
|
|
278
|
-
}
|
|
279
|
-
if (target.kind === 'refused') {
|
|
280
|
-
reportError(target.error, stderr);
|
|
281
|
-
return { exitCode: 1 };
|
|
282
|
-
}
|
|
283
|
-
const { host } = target;
|
|
284
|
-
const startedAtMs = Date.now();
|
|
285
|
-
const snapshot = createSnapshot(cwd, env);
|
|
286
|
-
if (!snapshot.ok && snapshot.isUnsupported) {
|
|
287
|
-
if (target.source === 'flag') {
|
|
288
|
-
reportError(`Cannot delegate to ${host}: ${snapshot.reason}`, stderr);
|
|
289
|
-
return { exitCode: 1 };
|
|
290
|
-
}
|
|
291
|
-
stderr.write(`nmr: running ${command} here rather than on ${host}: ${snapshot.reason}\n`);
|
|
292
|
-
return undefined;
|
|
293
|
-
}
|
|
294
|
-
const result = snapshot.ok
|
|
295
|
-
? await runRemotely({
|
|
296
|
-
cwd,
|
|
297
|
-
env,
|
|
298
|
-
host,
|
|
299
|
-
monorepoRoot: context.monorepoRoot,
|
|
300
|
-
nmrArgs: composeForwardedArgs(command, parsedArgs),
|
|
301
|
-
remoteEnv: {
|
|
302
|
-
[COMMAND_VERBOSITY_ENV_VAR]: invocation.verbosity,
|
|
303
|
-
[OUTPUT_STYLE_ENV_VAR]: styles.stdout,
|
|
304
|
-
[REPORT_FORMAT_ENV_VAR]: format,
|
|
305
|
-
},
|
|
306
|
-
snapshot,
|
|
307
|
-
stderr,
|
|
308
|
-
stdout,
|
|
309
|
-
})
|
|
310
|
-
: { ok: false, message: 'could not snapshot the working tree', output: snapshot.reason };
|
|
311
|
-
const durationMs = Date.now() - startedAtMs;
|
|
312
|
-
if (!result.ok) {
|
|
313
|
-
if (result.output.trim() !== '') {
|
|
314
|
-
stderr.write(result.output.endsWith('\n') ? result.output : `${result.output}\n`);
|
|
315
|
-
}
|
|
316
|
-
const verdict = {
|
|
317
|
-
command,
|
|
318
|
-
detail: result.message,
|
|
319
|
-
durationMs,
|
|
320
|
-
exitCode: DELEGATION_FAILURE_EXIT_CODE,
|
|
321
|
-
host,
|
|
322
|
-
outcome: 'failed',
|
|
323
|
-
scope,
|
|
324
|
-
};
|
|
325
|
-
reportVerdict(verdict, stdout, format, styles.stdout);
|
|
326
|
-
return { exitCode: DELEGATION_FAILURE_EXIT_CODE };
|
|
327
|
-
}
|
|
328
|
-
const outcome = result.exitCode === 0
|
|
329
|
-
? { outcome: 'passed', durationMs }
|
|
330
|
-
: { outcome: 'failed', durationMs, exitCode: result.exitCode };
|
|
331
|
-
reportVerdict({ command, host, scope, ...outcome }, stdout, format, styles.stdout);
|
|
332
|
-
return { exitCode: result.exitCode };
|
|
333
|
-
}
|
|
334
|
-
const FILTER_FLAGS = new Set(['-F', '--filter']);
|
|
335
|
-
const BOOLEAN_FLAGS = new Map([
|
|
336
|
-
['-?', 'shouldShowHelp'],
|
|
337
|
-
['--help', 'shouldShowHelp'],
|
|
338
|
-
['--json', 'shouldEmitJson'],
|
|
339
|
-
['--log', 'shouldPrintLog'],
|
|
340
|
-
['--no-cache', 'shouldBypassCache'],
|
|
341
|
-
[NO_DELEGATE_FLAG, 'shouldRunLocally'],
|
|
342
|
-
['--no-lock', 'shouldBypassLock'],
|
|
343
|
-
['-q', 'quiet'],
|
|
344
|
-
['--quiet', 'quiet'],
|
|
345
|
-
['-R', 'recursive'],
|
|
346
|
-
['--recursive', 'recursive'],
|
|
347
|
-
['-V', 'shouldShowVersion'],
|
|
348
|
-
['--version', 'shouldShowVersion'],
|
|
349
|
-
['-w', 'isWorkspaceRoot'],
|
|
350
|
-
['--workspace-root', 'isWorkspaceRoot'],
|
|
351
|
-
]);
|
|
352
|
-
function buildChildEnv(options) {
|
|
353
|
-
const { env, format, passthrough, runId, shouldBypassCache, shouldBypassLock, snapshot, styles, verbosity } = options;
|
|
354
|
-
const bypassesCache = shouldBypassCache || passthrough.length > 0;
|
|
355
|
-
const { [DELEGATE_ENV_VAR]: _delegateHost, ...inheritedEnv } = env;
|
|
356
|
-
return {
|
|
357
|
-
...inheritedEnv,
|
|
358
|
-
...(snapshot !== undefined && { [TREE_SNAPSHOT_ENV_VAR]: encodeTreeSnapshot(snapshot) }),
|
|
359
|
-
...(bypassesCache && { [NO_CACHE_ENV_VAR]: '1' }),
|
|
360
|
-
...(shouldBypassLock && { [NO_LOCK_ENV_VAR]: '1' }),
|
|
361
|
-
[COMMAND_VERBOSITY_ENV_VAR]: verbosity,
|
|
362
|
-
[OUTPUT_STYLE_ENV_VAR]: styles.stdout,
|
|
363
|
-
[REPORT_FORMAT_ENV_VAR]: format,
|
|
364
|
-
[RUN_ID_ENV_VAR]: runId,
|
|
365
|
-
};
|
|
366
|
-
}
|
|
367
|
-
function addLockEnv(env, lock) {
|
|
368
|
-
if (lock?.token !== undefined) {
|
|
369
|
-
return { ...env, [LOCK_TOKEN_ENV_VAR]: lock.token };
|
|
370
|
-
}
|
|
371
|
-
if (lock?.isChainUnlocked === true) {
|
|
372
|
-
return { ...env, [NO_LOCK_ENV_VAR]: '1' };
|
|
373
|
-
}
|
|
374
|
-
return env;
|
|
375
|
-
}
|
|
376
|
-
function acceptsArguments(step) {
|
|
377
|
-
return step.kind === 'opaque' || step.shouldDeclineArguments !== true;
|
|
378
|
-
}
|
|
379
|
-
function bindPassthrough(steps, passthrough, command) {
|
|
380
|
-
if (passthrough.length === 0) {
|
|
381
|
-
return { ok: true, steps };
|
|
382
|
-
}
|
|
383
|
-
if (!steps.some(acceptsArguments)) {
|
|
384
|
-
return { ok: false, error: formatUnroutableArgumentsError(command) };
|
|
385
|
-
}
|
|
386
|
-
const boundSteps = steps.map((step) => {
|
|
387
|
-
if (!acceptsArguments(step)) {
|
|
388
|
-
return step;
|
|
389
|
-
}
|
|
390
|
-
return step.kind === 'structural'
|
|
391
|
-
? { ...step, argv: [...step.argv, ...passthrough] }
|
|
392
|
-
: { kind: 'opaque', command: `${step.command} ${passthrough.map(quoteForShell).join(' ')}` };
|
|
393
|
-
});
|
|
394
|
-
return { ok: true, steps: boundSteps };
|
|
395
|
-
}
|
|
396
|
-
function formatUnroutableArgumentsError(command) {
|
|
397
|
-
return (`\`${command}\` does not take trailing arguments: every step of its chain declines them. ` +
|
|
398
|
-
'Running it unnarrowed is not what the arguments asked for, so nothing ran.');
|
|
399
|
-
}
|
|
400
|
-
function composeDelegation(options) {
|
|
401
|
-
const { childEnv, command, parsedArgs } = options;
|
|
402
|
-
let scope;
|
|
403
|
-
let shouldRunIfPresent = parsedArgs.shouldPrintLog;
|
|
404
|
-
if (parsedArgs.filter) {
|
|
405
|
-
scope = ['--filter', parsedArgs.filter];
|
|
406
|
-
}
|
|
407
|
-
else if (parsedArgs.recursive) {
|
|
408
|
-
scope = ['--recursive'];
|
|
409
|
-
shouldRunIfPresent = true;
|
|
410
|
-
}
|
|
411
|
-
if (scope === undefined) {
|
|
412
|
-
return undefined;
|
|
413
|
-
}
|
|
414
|
-
const flags = parsedArgs.shouldPrintLog ? ['--log'] : [];
|
|
415
|
-
return {
|
|
416
|
-
env: shouldRunIfPresent ? { ...childEnv, [RUN_IF_PRESENT_ENV_VAR]: '1' } : childEnv,
|
|
417
|
-
step: {
|
|
418
|
-
kind: 'structural',
|
|
419
|
-
argv: ['pnpm', ...scope, 'exec', 'nmr', ...flags, command, ...parsedArgs.passthrough],
|
|
420
|
-
},
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
|
-
function composeForwardedArgs(command, parsedArgs) {
|
|
424
|
-
return [
|
|
425
|
-
...(parsedArgs.filter === undefined ? [] : ['--filter', parsedArgs.filter]),
|
|
426
|
-
...(parsedArgs.recursive ? ['--recursive'] : []),
|
|
427
|
-
...(parsedArgs.isWorkspaceRoot ? ['--workspace-root'] : []),
|
|
428
|
-
...(parsedArgs.quiet ? ['--quiet'] : []),
|
|
429
|
-
...(parsedArgs.shouldEmitJson ? ['--json'] : []),
|
|
430
|
-
...(parsedArgs.shouldBypassCache ? ['--no-cache'] : []),
|
|
431
|
-
...(parsedArgs.shouldBypassLock ? ['--no-lock'] : []),
|
|
432
|
-
command,
|
|
433
|
-
...parsedArgs.passthrough,
|
|
434
|
-
];
|
|
435
|
-
}
|
|
436
|
-
async function composeRetention(options) {
|
|
437
|
-
const { anchorDir, command, key, retainedOutput, runId } = options;
|
|
438
|
-
if (retainedOutput !== undefined) {
|
|
439
|
-
const excerpt = deriveExcerpt(retainedOutput.stdout.toString('utf8')) ?? deriveExcerpt(retainedOutput.stderr.toString('utf8'));
|
|
440
|
-
if (excerpt === undefined) {
|
|
441
|
-
return undefined;
|
|
442
|
-
}
|
|
443
|
-
return { key, replay: [{ command, excerpt, scope: path.basename(anchorDir) }], runId };
|
|
444
|
-
}
|
|
445
|
-
const replay = await assembleReplay({
|
|
446
|
-
anchorDir,
|
|
447
|
-
monorepoRoot: options.monorepoRoot,
|
|
448
|
-
runId,
|
|
449
|
-
steps: options.steps,
|
|
450
|
-
treeHash: options.treeHash,
|
|
451
|
-
});
|
|
452
|
-
return replay.length === 0 ? undefined : { key, replay, runId };
|
|
453
|
-
}
|
|
454
|
-
function describeCrossingRemedy(options) {
|
|
455
|
-
const { crossingStep, isWorkspaceRoot, monorepoRoot, origin, registry } = options;
|
|
456
|
-
const configSite = path.relative(monorepoRoot, resolveConfigPath(monorepoRoot));
|
|
457
|
-
switch (origin.tier) {
|
|
458
|
-
case 'default':
|
|
459
|
-
return {
|
|
460
|
-
remedy: 'A built-in default reaching nmr through a shell is an nmr defect: please report it.',
|
|
461
|
-
subject: `nmr built-in \`${origin.key}\``,
|
|
462
|
-
};
|
|
463
|
-
case 'config':
|
|
464
|
-
return {
|
|
465
|
-
remedy: `Write the nmr steps as a step list, and move any others to ${describeStepDestination(origin.key)}.`,
|
|
466
|
-
subject: `${configSite}: \`${origin.field}.${origin.key}\``,
|
|
467
|
-
};
|
|
468
|
-
case 'package':
|
|
469
|
-
return {
|
|
470
|
-
remedy: formatInlineRemedy({ crossingStep, file: origin.file, isWorkspaceRoot, key: origin.key, registry }) ??
|
|
471
|
-
formatPackageRemedy({ configSite, crossingStep, isWorkspaceRoot, key: origin.key, registry }),
|
|
472
|
-
subject: `${path.relative(monorepoRoot, origin.file)}: \`scripts.${origin.key}\``,
|
|
473
|
-
};
|
|
474
|
-
default: {
|
|
475
|
-
const unhandledOrigin = origin;
|
|
476
|
-
throw new Error(`Unhandled script origin: ${JSON.stringify(unhandledOrigin)}`);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
function describeStepDestination(key) {
|
|
481
|
-
if (isHookName(key)) {
|
|
482
|
-
return 'a script of their own that the step list names, because a hook does not have a `:pre` or `:post` of its own';
|
|
483
|
-
}
|
|
484
|
-
return `a \`${key}:pre\` or \`${key}:post\` script`;
|
|
485
|
-
}
|
|
486
|
-
function describeOrigin(origin, config, shouldUseRoot) {
|
|
487
|
-
if (origin.tier === 'package') {
|
|
488
|
-
return origin;
|
|
489
|
-
}
|
|
490
|
-
const field = shouldUseRoot ? 'rootScripts' : 'workspaceScripts';
|
|
491
|
-
const configScripts = config[field];
|
|
492
|
-
return configScripts !== undefined && Object.hasOwn(configScripts, origin.key)
|
|
493
|
-
? { tier: 'config', field, key: origin.key }
|
|
494
|
-
: { tier: 'default', key: origin.key };
|
|
495
|
-
}
|
|
496
|
-
function formatInlineRemedy(options) {
|
|
497
|
-
const { crossingStep, file, isWorkspaceRoot, key, registry } = options;
|
|
498
|
-
const name = readSoleNmrCommand(crossingStep);
|
|
499
|
-
if (name === undefined || name === key) {
|
|
500
|
-
return undefined;
|
|
501
|
-
}
|
|
502
|
-
const scripts = readPackageJsonScripts(path.dirname(file));
|
|
503
|
-
const value = scripts !== undefined && Object.hasOwn(scripts, name) ? scripts[name]?.trim() : undefined;
|
|
504
|
-
if (value === undefined || ['', ':'].includes(value)) {
|
|
505
|
-
return undefined;
|
|
506
|
-
}
|
|
507
|
-
if (findNmrCrossing([{ kind: 'opaque', command: value }]) !== undefined) {
|
|
508
|
-
return undefined;
|
|
509
|
-
}
|
|
510
|
-
const anchorDir = path.dirname(file);
|
|
511
|
-
if (!isHookName(name) &&
|
|
512
|
-
[`${name}:pre`, `${name}:post`].some((hook) => hasRunnableHook(hook, registry, anchorDir, isWorkspaceRoot))) {
|
|
513
|
-
return undefined;
|
|
514
|
-
}
|
|
515
|
-
const inlinedValue = escapeControlCharacters(value);
|
|
516
|
-
return Object.hasOwn(registry, name)
|
|
517
|
-
? `Replace the value with \`${inlinedValue}\`, and keep \`${name}\`, which overrides nmr's own.`
|
|
518
|
-
: `Replace the value with \`${inlinedValue}\` and delete \`${name}\`, the script that it names.`;
|
|
519
|
-
}
|
|
520
|
-
function formatNmrCrossingWarning(options) {
|
|
521
|
-
const { remedy, subject } = describeCrossingRemedy(options);
|
|
522
|
-
return (`${STATUS_GLYPHS[options.style].warning.text} ${subject} reaches nmr through a shell ` +
|
|
523
|
-
`(\`${escapeControlCharacters(options.crossingStep)}\`), ${CROSSING_CONSEQUENCE} ${remedy}`);
|
|
524
|
-
}
|
|
525
|
-
function formatOverrideNotice(resolvedScript, registry, command, anchorDir, quiet, style) {
|
|
526
|
-
const registryEntry = Object.hasOwn(registry, command) ? registry[command] : undefined;
|
|
527
|
-
if (quiet || resolvedScript.origin.tier !== 'package' || registryEntry === undefined) {
|
|
528
|
-
return undefined;
|
|
529
|
-
}
|
|
530
|
-
const notice = `${path.basename(anchorDir)}: Using override script: ${renderChain(resolvedScript.steps)}`;
|
|
531
|
-
return `${formatGlyphLine(NMR_GLYPHS, style, 'package', notice)}\n`;
|
|
532
|
-
}
|
|
533
|
-
function formatSelfReferenceRemedy(options) {
|
|
534
|
-
const { command, isWorkspaceRoot, monorepoRoot, registry, script } = options;
|
|
535
|
-
if (isHookName(command)) {
|
|
536
|
-
return `Delete the re-invocation: \`${command}\` runs the steps beside it.`;
|
|
537
|
-
}
|
|
538
|
-
const configSite = path.relative(monorepoRoot, resolveConfigPath(monorepoRoot));
|
|
539
|
-
return formatPackageRemedy({ configSite, crossingStep: script, isWorkspaceRoot, key: command, registry });
|
|
540
|
-
}
|
|
541
|
-
function formatPackageRemedy(options) {
|
|
542
|
-
const { configSite, crossingStep, isWorkspaceRoot, key, registry } = options;
|
|
543
|
-
const registryEntry = Object.hasOwn(registry, key) ? registry[key] : undefined;
|
|
544
|
-
if (registryEntry === undefined) {
|
|
545
|
-
return (`A \`package.json\` script cannot contain a step list: define \`${key}\` in \`${configSite}\` and move the ` +
|
|
546
|
-
`package-specific steps to ${describeStepDestination(key)}.`);
|
|
547
|
-
}
|
|
548
|
-
const registryChain = renderChain(expandScript(registryEntry, isWorkspaceRoot));
|
|
549
|
-
if (registryChain === crossingStep) {
|
|
550
|
-
return `Delete the entry: nmr's own \`${key}\` already runs \`${escapeControlCharacters(registryChain)}\`.`;
|
|
551
|
-
}
|
|
552
|
-
return `Delete the entry and move the steps that it adds to ${describeStepDestination(key)}.`;
|
|
553
|
-
}
|
|
554
|
-
function findArgError(parsedArgs) {
|
|
555
|
-
if (parsedArgs.shouldPrintLog &&
|
|
556
|
-
parsedArgs.command === undefined &&
|
|
557
|
-
!parsedArgs.shouldShowHelp &&
|
|
558
|
-
!parsedArgs.shouldShowVersion) {
|
|
559
|
-
return '--log requires a command name: `nmr --log <command>`';
|
|
560
|
-
}
|
|
561
|
-
return undefined;
|
|
562
|
-
}
|
|
563
|
-
function findEmptySelectionRefusal(options) {
|
|
564
|
-
const { context, parsedArgs, selection } = options;
|
|
565
|
-
const isPackageless = context.workspacePackageDirs.length === 0;
|
|
566
|
-
if (parsedArgs.filter !== undefined) {
|
|
567
|
-
if (selection !== 'empty') {
|
|
568
|
-
return undefined;
|
|
569
|
-
}
|
|
570
|
-
if (isPackageless) {
|
|
571
|
-
return `${formatFilterRejection(parsedArgs.filter)} ${describeEmptyWorkspace(context.monorepoRoot)}`;
|
|
572
|
-
}
|
|
573
|
-
return formatEmptyFilterError(parsedArgs.filter, readWorkspacePackageNames(context.workspacePackageDirs));
|
|
574
|
-
}
|
|
575
|
-
return isPackageless ? `${RECURSIVE_REJECTION} ${describeEmptyWorkspace(context.monorepoRoot)}` : undefined;
|
|
576
|
-
}
|
|
577
|
-
function formatEmptyFilterError(pattern, names) {
|
|
578
|
-
const rejection = formatFilterRejection(pattern);
|
|
579
|
-
if (pattern.startsWith('!')) {
|
|
580
|
-
return `${rejection} A pattern beginning with \`!\` excludes what it matches, and this one excludes every package.`;
|
|
581
|
-
}
|
|
582
|
-
if (pattern.includes('[')) {
|
|
583
|
-
return `${rejection} A pattern containing \`[<ref>]\` selects the packages changed since a git ref, and none has changed.`;
|
|
584
|
-
}
|
|
585
|
-
if (pattern.startsWith('.') || pattern.startsWith('/') || pattern.startsWith('{')) {
|
|
586
|
-
return (`${rejection} ` +
|
|
587
|
-
'A pattern written `./dir`, `/dir`, or `{dir}` selects the packages under a directory, ' +
|
|
588
|
-
'and this directory does not contain any package.');
|
|
589
|
-
}
|
|
590
|
-
return (`${rejection} A pattern matches a package's manifest \`name\`, ` +
|
|
591
|
-
`not its directory name.${suggestWorkspaceNames(pattern, names)}`);
|
|
592
|
-
}
|
|
593
|
-
function describeEmptyWorkspace(monorepoRoot) {
|
|
594
|
-
const resolution = resolveWorkspace(monorepoRoot);
|
|
595
|
-
if (resolution.kind !== 'empty') {
|
|
596
|
-
return `pnpm-workspace.yaml at ${monorepoRoot} resolves differently now than it did when the run started.`;
|
|
597
|
-
}
|
|
598
|
-
const { cause, patterns } = resolution;
|
|
599
|
-
const declaredClause = describeDeclaredPatterns(patterns);
|
|
600
|
-
switch (cause) {
|
|
601
|
-
case 'all-excluded':
|
|
602
|
-
return (`pnpm-workspace.yaml ${declaredClause}, whose \`!\` entries exclude every directory matched by the positive ` +
|
|
603
|
-
'patterns. Drop or narrow the exclusion.');
|
|
604
|
-
case 'no-package':
|
|
605
|
-
return (`pnpm-workspace.yaml ${declaredClause}, and the matcher did not find any directory containing a \`package.json\`. ` +
|
|
606
|
-
'nmr counts a directory as a package only when it contains `package.json`; unlike pnpm, it recognizes ' +
|
|
607
|
-
'neither `package.yaml` nor `package.json5`. Add a `package.json` to the directory that should be a ' +
|
|
608
|
-
'package, or declare a pattern reaching a directory that contains one.');
|
|
609
|
-
case 'no-packages-list':
|
|
610
|
-
return (`pnpm-workspace.yaml at ${monorepoRoot} does not declare a \`packages\` list, so the workspace contains the root ` +
|
|
611
|
-
'package alone. Declare a positive pattern such as `packages/*` to reach the packages beneath it.');
|
|
612
|
-
case 'no-pattern':
|
|
613
|
-
return (`pnpm-workspace.yaml ${declaredClause}, so the matcher does not receive any pattern. Declare a positive ` +
|
|
614
|
-
'pattern such as `packages/*`, and quote any `!` entry, which YAML reads as a tag rather than a string ' +
|
|
615
|
-
'when it is unquoted.');
|
|
616
|
-
case 'unreadable-packages':
|
|
617
|
-
return (`pnpm-workspace.yaml at ${monorepoRoot} declares a \`packages\` value that is not a list of strings, so ` +
|
|
618
|
-
'nothing it declares reaches the matcher. Make it a list whose every entry is a quoted pattern; a bare ' +
|
|
619
|
-
'value and an entry YAML read as a number or a map are the usual ones.');
|
|
620
|
-
case 'unreadable-manifest':
|
|
621
|
-
return (`pnpm-workspace.yaml at ${monorepoRoot} is not valid YAML, so the matcher does not receive anything that it declares. ` +
|
|
622
|
-
'Repair the syntax error and run the command again. An unterminated quoted string and a mis-indented ' +
|
|
623
|
-
'entry are the usual ones.');
|
|
624
|
-
default: {
|
|
625
|
-
const unhandledCause = cause;
|
|
626
|
-
throw new Error(`Unhandled empty-workspace cause: ${String(unhandledCause)}`);
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
function describeDeclaredPatterns(patterns) {
|
|
631
|
-
const quotablePatterns = patterns.filter((pattern) => pattern.trim() !== '');
|
|
632
|
-
const emptiedCount = patterns.length - quotablePatterns.length;
|
|
633
|
-
if (emptiedCount === 0) {
|
|
634
|
-
return `declares ${renderQuotedList(patterns)}`;
|
|
635
|
-
}
|
|
636
|
-
const emptiedClause = `${emptiedCount} ${emptiedCount === 1 ? 'entry' : 'entries'} that YAML left empty`;
|
|
637
|
-
return quotablePatterns.length === 0
|
|
638
|
-
? `declares ${emptiedClause}`
|
|
639
|
-
: `declares ${renderQuotedList(quotablePatterns)}, beside ${emptiedClause}`;
|
|
640
|
-
}
|
|
641
|
-
function formatFilterRejection(pattern) {
|
|
642
|
-
return `-F/--filter did not match any workspace: \`${pattern}\`.`;
|
|
643
|
-
}
|
|
644
|
-
function suggestWorkspaceNames(pattern, names) {
|
|
645
|
-
if (names.length === 0) {
|
|
646
|
-
return '';
|
|
647
|
-
}
|
|
648
|
-
const containingNames = names.filter((name) => name.toLowerCase().includes(pattern.toLowerCase()));
|
|
649
|
-
if (containingNames.length > 0) {
|
|
650
|
-
return ` Did you mean ${renderQuotedList(containingNames)}?`;
|
|
651
|
-
}
|
|
652
|
-
const closestName = findClosestName(pattern, names);
|
|
653
|
-
if (closestName !== undefined) {
|
|
654
|
-
return ` Did you mean \`${closestName}\`?`;
|
|
655
|
-
}
|
|
656
|
-
return ` The workspace declares ${renderQuotedList(names)}.`;
|
|
657
|
-
}
|
|
658
|
-
function renderQuotedList(items) {
|
|
659
|
-
const shownItems = items
|
|
660
|
-
.slice(0, LIST_CEILING)
|
|
661
|
-
.map((item) => `\`${item}\``)
|
|
662
|
-
.join(', ');
|
|
663
|
-
const remainder = items.length - LIST_CEILING;
|
|
664
|
-
return remainder > 0 ? `${shownItems}, and ${remainder} more` : shownItems;
|
|
665
|
-
}
|
|
666
|
-
function findNoOpReason(resolvedCommand, isEmptiedByWorkspace) {
|
|
667
|
-
if (isEmptiedByWorkspace) {
|
|
668
|
-
return 'empty-workspace';
|
|
669
|
-
}
|
|
670
|
-
if (resolvedCommand === '') {
|
|
671
|
-
return 'empty-override';
|
|
672
|
-
}
|
|
673
|
-
if (resolvedCommand === ':') {
|
|
674
|
-
return 'noop-override';
|
|
675
|
-
}
|
|
676
|
-
return undefined;
|
|
677
|
-
}
|
|
678
|
-
function assertNoSelfReference(options) {
|
|
679
|
-
const { anchorDir, command, isReading, isWorkspaceRoot, monorepoRoot, registry } = options;
|
|
680
|
-
if (isReading) {
|
|
681
|
-
return;
|
|
682
|
-
}
|
|
683
|
-
const script = findChainedSelfReference(anchorDir, command);
|
|
684
|
-
if (script === undefined) {
|
|
685
|
-
return;
|
|
686
|
-
}
|
|
687
|
-
const remedy = formatSelfReferenceRemedy({ command, isWorkspaceRoot, monorepoRoot, registry, script });
|
|
688
|
-
const site = path.relative(monorepoRoot, resolvePackageJsonPath(anchorDir));
|
|
689
|
-
throw new UserError(`${site}: \`scripts.${command}\` re-invokes \`nmr ${command}\` (\`${escapeControlCharacters(script)}\`), ` +
|
|
690
|
-
`so nmr cannot run the steps that it chains. ${remedy}`);
|
|
691
|
-
}
|
|
692
|
-
function hasRunnableHook(hookName, registry, anchorDir, isWorkspaceRoot) {
|
|
693
|
-
if (findChainedSelfReference(anchorDir, hookName) !== undefined) {
|
|
694
|
-
return true;
|
|
695
|
-
}
|
|
696
|
-
const resolvedScript = resolveScript(hookName, registry, anchorDir, isWorkspaceRoot);
|
|
697
|
-
if (!resolvedScript)
|
|
698
|
-
return false;
|
|
699
|
-
const chain = renderChain(resolvedScript.steps);
|
|
700
|
-
return chain !== '' && chain !== ':';
|
|
701
|
-
}
|
|
702
|
-
async function lookUpRecordedPass(options) {
|
|
703
|
-
const { anchorDir, buildOutput, command, env, key, monorepoRoot, stderr } = options;
|
|
704
|
-
const entry = await readCheckCacheEntry({ anchorDir, command, monorepoRoot });
|
|
705
|
-
if (entry === undefined) {
|
|
706
|
-
writeDebugNote(`running ${command}: no pass recorded for this scope`, env, stderr);
|
|
707
|
-
return undefined;
|
|
708
|
-
}
|
|
709
|
-
if (entry.key !== key) {
|
|
710
|
-
writeDebugNote(`running ${command}: the tree or its inputs changed since the last pass`, env, stderr);
|
|
711
|
-
return undefined;
|
|
712
|
-
}
|
|
713
|
-
const [missingPackage] = buildOutput.missingPackages;
|
|
714
|
-
if (missingPackage !== undefined) {
|
|
715
|
-
writeDebugNote(`running ${command}: ${missingPackage} does not have build output`, env, stderr);
|
|
716
|
-
return undefined;
|
|
717
|
-
}
|
|
718
|
-
const stalePackage = findStaleBuildOutput(entry.buildDigests, buildOutput.digests);
|
|
719
|
-
if (stalePackage !== undefined) {
|
|
720
|
-
writeDebugNote(`running ${command}: ${stalePackage}'s build output came from a different tree`, env, stderr);
|
|
721
|
-
return undefined;
|
|
722
|
-
}
|
|
723
|
-
return {
|
|
724
|
-
ageMs: Math.max(0, Date.now() - Date.parse(entry.recordedAt)),
|
|
725
|
-
entry,
|
|
726
|
-
savedMs: entry.durationMs,
|
|
727
|
-
...(entry.retention?.key === options.retentionKey && { replay: entry.retention.replay }),
|
|
728
|
-
};
|
|
729
|
-
}
|
|
730
|
-
function openGate(options) {
|
|
731
|
-
const { command, config, env, monorepoRoot, passthrough, stderr, style } = options;
|
|
732
|
-
if (!isCacheableCommand(config.checkCache, command)) {
|
|
733
|
-
return undefined;
|
|
734
|
-
}
|
|
735
|
-
if (passthrough.includes('--no-cache')) {
|
|
736
|
-
stderr.write(`${formatMisplacedNoCacheWarning(command, style)}\n`);
|
|
737
|
-
}
|
|
738
|
-
if (passthrough.length > 0) {
|
|
739
|
-
writeDebugNote(`gate disabled: ${command} was passed arguments`, env, stderr);
|
|
740
|
-
return undefined;
|
|
741
|
-
}
|
|
742
|
-
const snapshot = resolveTreeSnapshot({ monorepoRoot, env });
|
|
743
|
-
if (!snapshot.ok) {
|
|
744
|
-
writeDebugNote(`gate disabled: ${snapshot.reason}`, env, stderr);
|
|
745
|
-
return undefined;
|
|
746
|
-
}
|
|
747
|
-
return snapshot.snapshot;
|
|
748
|
-
}
|
|
749
|
-
function parseArgs(args) {
|
|
750
|
-
const parsedArgs = {
|
|
751
|
-
isWorkspaceRoot: false,
|
|
752
|
-
quiet: false,
|
|
753
|
-
recursive: false,
|
|
754
|
-
shouldBypassCache: false,
|
|
755
|
-
shouldBypassLock: false,
|
|
756
|
-
shouldEmitJson: false,
|
|
757
|
-
shouldPrintLog: false,
|
|
758
|
-
shouldRunLocally: false,
|
|
759
|
-
shouldShowHelp: false,
|
|
760
|
-
shouldShowVersion: false,
|
|
761
|
-
passthrough: [],
|
|
762
|
-
};
|
|
763
|
-
let index = 0;
|
|
764
|
-
while (index < args.length) {
|
|
765
|
-
const arg = args[index];
|
|
766
|
-
if (arg === undefined)
|
|
767
|
-
break;
|
|
768
|
-
if (FILTER_FLAGS.has(arg)) {
|
|
769
|
-
index++;
|
|
770
|
-
const filterValue = args[index];
|
|
771
|
-
if (!filterValue) {
|
|
772
|
-
return { ok: false, error: '-F/--filter requires a pattern argument' };
|
|
773
|
-
}
|
|
774
|
-
parsedArgs.filter = filterValue;
|
|
775
|
-
index++;
|
|
776
|
-
continue;
|
|
777
|
-
}
|
|
778
|
-
const booleanFlag = BOOLEAN_FLAGS.get(arg);
|
|
779
|
-
if (booleanFlag !== undefined) {
|
|
780
|
-
parsedArgs[booleanFlag] = true;
|
|
781
|
-
index++;
|
|
782
|
-
continue;
|
|
783
|
-
}
|
|
784
|
-
const styleArgument = readOutputStyleArgument(args, index);
|
|
785
|
-
if (styleArgument !== undefined) {
|
|
786
|
-
if (!styleArgument.ok) {
|
|
787
|
-
return styleArgument;
|
|
788
|
-
}
|
|
789
|
-
parsedArgs.outputStyle = styleArgument.value;
|
|
790
|
-
index += styleArgument.consumedCount;
|
|
791
|
-
continue;
|
|
792
|
-
}
|
|
793
|
-
const delegateArgument = readDelegateArgument(args, index);
|
|
794
|
-
if (delegateArgument !== undefined) {
|
|
795
|
-
if (!delegateArgument.ok) {
|
|
796
|
-
return delegateArgument;
|
|
797
|
-
}
|
|
798
|
-
parsedArgs.delegateHost = delegateArgument.value;
|
|
799
|
-
index += delegateArgument.consumedCount;
|
|
800
|
-
continue;
|
|
801
|
-
}
|
|
802
|
-
parsedArgs.command = arg;
|
|
803
|
-
parsedArgs.passthrough = args.slice(index + 1);
|
|
804
|
-
break;
|
|
805
|
-
}
|
|
806
|
-
const error = findArgError(parsedArgs);
|
|
807
|
-
return error === undefined ? { ok: true, parsedArgs } : { ok: false, error };
|
|
808
|
-
}
|
|
809
|
-
async function reportRecording(plan) {
|
|
810
|
-
const { anchorDir, command, context, env, scope, stderr, stdout, styles } = plan.invocation;
|
|
811
|
-
const { config, monorepoRoot } = context;
|
|
812
|
-
const lookup = await resolveRecording({
|
|
813
|
-
anchorDir,
|
|
814
|
-
command,
|
|
815
|
-
currentIdentity: {
|
|
816
|
-
commandString: plan.commandString,
|
|
817
|
-
nmrVersion: VERSION,
|
|
818
|
-
nodeVersion: CURRENT_RUNTIME.nodeVersion,
|
|
819
|
-
treeHash: plan.snapshot?.hash,
|
|
820
|
-
},
|
|
821
|
-
isCacheable: isCacheableCommand(config.checkCache, command),
|
|
822
|
-
key: plan.key,
|
|
823
|
-
monorepoRoot,
|
|
824
|
-
});
|
|
825
|
-
if (!lookup.ok) {
|
|
826
|
-
stderr.write(`${renderRefusal({ command, refusal: lookup.refusal, scope, style: styles.stderr })}\n`);
|
|
827
|
-
return { exitCode: env[RUN_IF_PRESENT_ENV_VAR] === '1' ? 0 : 1 };
|
|
828
|
-
}
|
|
829
|
-
stdout.write(renderRecording({ command, recording: lookup.recording, scope, style: styles.stdout }));
|
|
830
|
-
return { exitCode: 0 };
|
|
831
|
-
}
|
|
832
|
-
function readPresentation(options) {
|
|
833
|
-
const { env } = options;
|
|
834
|
-
const verbosity = readVerbosityEnv(env);
|
|
835
|
-
if (!verbosity.ok) {
|
|
836
|
-
return verbosity;
|
|
837
|
-
}
|
|
838
|
-
const format = readReportFormatEnv(env);
|
|
839
|
-
if (!format.ok) {
|
|
840
|
-
return format;
|
|
841
|
-
}
|
|
842
|
-
const { invalid: invalidStyle, styles } = resolveOutputStyles(options);
|
|
843
|
-
if (invalidStyle !== undefined) {
|
|
844
|
-
return { ok: false, error: describeInvalidOutputStyle(invalidStyle) };
|
|
845
|
-
}
|
|
846
|
-
return { ok: true, format: format.format, styles, verbosity: verbosity.verbosity };
|
|
847
|
-
}
|
|
848
|
-
function readDelegateArgument(args, index) {
|
|
849
|
-
return readFlagValue(args, index, DELEGATE_FLAG, 'a host argument');
|
|
850
|
-
}
|
|
851
|
-
function readFlagValue(args, index, flag, requirement) {
|
|
852
|
-
const arg = args[index] ?? '';
|
|
853
|
-
const assignment = arg.startsWith(`${flag}=`) ? arg.slice(flag.length + 1) : undefined;
|
|
854
|
-
if (arg !== flag && assignment === undefined) {
|
|
855
|
-
return undefined;
|
|
856
|
-
}
|
|
857
|
-
const value = assignment ?? args[index + 1];
|
|
858
|
-
if (!value) {
|
|
859
|
-
return { ok: false, error: `${flag} requires ${requirement}` };
|
|
860
|
-
}
|
|
861
|
-
return { ok: true, value, consumedCount: assignment === undefined ? 2 : 1 };
|
|
862
|
-
}
|
|
863
|
-
function readOutputStyleArgument(args, index) {
|
|
864
|
-
return readFlagValue(args, index, OUTPUT_STYLE_FLAG, 'a value argument: auto, plain, or rich');
|
|
865
|
-
}
|
|
866
|
-
function readRunnableSteps(resolvedScript, workspacePackageDirs) {
|
|
867
|
-
if (workspacePackageDirs.length > 0) {
|
|
868
|
-
return { isEmptiedByWorkspace: false, steps: resolvedScript.steps };
|
|
869
|
-
}
|
|
870
|
-
const steps = dropRecursiveSteps(resolvedScript.steps);
|
|
871
|
-
return { isEmptiedByWorkspace: steps.length === 0 && resolvedScript.steps.length > 0, steps };
|
|
872
|
-
}
|
|
873
|
-
function reportNmrCrossing(options) {
|
|
874
|
-
const crossingStep = findNmrCrossing(options.resolvedScript.steps);
|
|
875
|
-
if (options.isReading || crossingStep === undefined) {
|
|
876
|
-
return;
|
|
877
|
-
}
|
|
878
|
-
const warning = formatNmrCrossingWarning({
|
|
879
|
-
crossingStep,
|
|
880
|
-
isWorkspaceRoot: options.isWorkspaceRoot,
|
|
881
|
-
monorepoRoot: options.monorepoRoot,
|
|
882
|
-
origin: describeOrigin(options.resolvedScript.origin, options.config, options.shouldUseRoot),
|
|
883
|
-
registry: options.registry,
|
|
884
|
-
style: options.style,
|
|
885
|
-
});
|
|
886
|
-
options.stderr.write(`${warning}\n`);
|
|
887
|
-
}
|
|
888
|
-
function reportVerdict(verdict, stdout, format, style) {
|
|
889
|
-
if (isHookName(verdict.command)) {
|
|
890
|
-
return;
|
|
891
|
-
}
|
|
892
|
-
writeVerdict(verdict, stdout, format, style);
|
|
893
|
-
}
|
|
894
|
-
function resolveReportingVerbosity(options) {
|
|
895
|
-
const { format, ...ladder } = options;
|
|
896
|
-
return format === 'json' ? 'quiet' : resolveVerbosity(ladder);
|
|
897
|
-
}
|
|
898
|
-
async function readGate(plan) {
|
|
899
|
-
const { key, snapshot } = plan;
|
|
900
|
-
const { context, env, stderr, stdout } = plan.invocation;
|
|
901
|
-
if (key === undefined || snapshot === undefined) {
|
|
902
|
-
return undefined;
|
|
903
|
-
}
|
|
904
|
-
const quiet = plan.runOptions.quiet === true;
|
|
905
|
-
return {
|
|
906
|
-
buildOutputBefore: await readBuildOutputState(context.monorepoRoot, context.config),
|
|
907
|
-
key,
|
|
908
|
-
retentionKey: computeRetentionKey({
|
|
909
|
-
channels: { stderr: resolveChannel(stderr, quiet), stdout: resolveChannel(stdout, quiet) },
|
|
910
|
-
env,
|
|
911
|
-
passKey: key,
|
|
912
|
-
}),
|
|
913
|
-
snapshot,
|
|
914
|
-
};
|
|
915
|
-
}
|
|
916
|
-
async function recallPass(plan, gate) {
|
|
917
|
-
const { anchorDir, command, context, env, stderr } = plan.invocation;
|
|
918
|
-
const { monorepoRoot } = context;
|
|
919
|
-
const recalledPass = await lookUpRecordedPass({
|
|
920
|
-
anchorDir,
|
|
921
|
-
buildOutput: gate.buildOutputBefore,
|
|
922
|
-
command,
|
|
923
|
-
env,
|
|
924
|
-
key: gate.key,
|
|
925
|
-
monorepoRoot,
|
|
926
|
-
retentionKey: gate.retentionKey,
|
|
927
|
-
stderr,
|
|
928
|
-
});
|
|
929
|
-
if (recalledPass === undefined) {
|
|
930
|
-
return undefined;
|
|
931
|
-
}
|
|
932
|
-
const { entry, ...recall } = recalledPass;
|
|
933
|
-
if (recall.replay !== undefined) {
|
|
934
|
-
await certifyRetention({ anchorDir, command, entry, env, monorepoRoot, runId: plan.runId, stderr });
|
|
935
|
-
}
|
|
936
|
-
return { exitCode: 0, outcome: { outcome: 'recalled', ...recall } };
|
|
937
|
-
}
|
|
938
|
-
async function recordPass(options) {
|
|
939
|
-
const { anchorDir, command, env, monorepoRoot, snapshot, stderr } = options;
|
|
940
|
-
const currentRead = resolveTreeSnapshot({ monorepoRoot, env: {} });
|
|
941
|
-
if (!currentRead.ok) {
|
|
942
|
-
writeDebugNote(`not recording ${command}: ${currentRead.reason}`, env, stderr);
|
|
943
|
-
return;
|
|
944
|
-
}
|
|
945
|
-
if (currentRead.snapshot.hash !== snapshot.hash) {
|
|
946
|
-
writeDebugNote(`not recording ${command}: the tree changed while it ran`, env, stderr);
|
|
947
|
-
return;
|
|
948
|
-
}
|
|
949
|
-
const output = await readBuildOutputState(monorepoRoot, options.config);
|
|
950
|
-
const [missingPackage] = output.missingPackages;
|
|
951
|
-
if (missingPackage !== undefined) {
|
|
952
|
-
writeDebugNote(`not recording ${command}: ${missingPackage} does not have build output`, env, stderr);
|
|
953
|
-
return;
|
|
954
|
-
}
|
|
955
|
-
const changedPackage = findStaleBuildOutput(options.buildOutputBefore.digests, output.digests);
|
|
956
|
-
if (changedPackage !== undefined) {
|
|
957
|
-
writeDebugNote(`not recording ${command}: ${changedPackage}'s build output changed while it ran`, env, stderr);
|
|
958
|
-
return;
|
|
959
|
-
}
|
|
960
|
-
const retention = await composeRetention({
|
|
961
|
-
anchorDir,
|
|
962
|
-
command,
|
|
963
|
-
key: options.retentionKey,
|
|
964
|
-
monorepoRoot,
|
|
965
|
-
retainedOutput: options.retainedOutput,
|
|
966
|
-
runId: options.runId,
|
|
967
|
-
steps: options.ownSteps,
|
|
968
|
-
treeHash: snapshot.hash,
|
|
969
|
-
});
|
|
970
|
-
const ref = { anchorDir, command, monorepoRoot };
|
|
971
|
-
const transcript = options.retainedOutput === undefined ? undefined : composeTranscript(options.retainedOutput);
|
|
972
|
-
try {
|
|
973
|
-
await recordTranscript(ref, transcript);
|
|
974
|
-
await writeCheckCacheEntry({
|
|
975
|
-
...ref,
|
|
976
|
-
entry: {
|
|
977
|
-
key: options.key,
|
|
978
|
-
treeHash: snapshot.hash,
|
|
979
|
-
headSha: snapshot.headSha,
|
|
980
|
-
commandString: options.commandString,
|
|
981
|
-
nmrVersion: VERSION,
|
|
982
|
-
nodeVersion: CURRENT_RUNTIME.nodeVersion,
|
|
983
|
-
durationMs: options.durationMs,
|
|
984
|
-
recordedAt: new Date().toISOString(),
|
|
985
|
-
buildDigests: output.digests,
|
|
986
|
-
...(retention !== undefined && { retention }),
|
|
987
|
-
},
|
|
988
|
-
});
|
|
989
|
-
}
|
|
990
|
-
catch (error) {
|
|
991
|
-
try {
|
|
992
|
-
await recordTranscript(ref, undefined);
|
|
993
|
-
}
|
|
994
|
-
catch {
|
|
995
|
-
}
|
|
996
|
-
writeDebugNote(`could not record ${command}: ${describeError(error)}`, env, stderr);
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
function resolveCacheKey(options) {
|
|
1000
|
-
const { env, snapshot, stderr, substitution } = options;
|
|
1001
|
-
if (snapshot === undefined) {
|
|
1002
|
-
return undefined;
|
|
1003
|
-
}
|
|
1004
|
-
if (substitution !== undefined) {
|
|
1005
|
-
writeDebugNote(`gate disabled: devBin substituted \`${substitution}\``, env, stderr);
|
|
1006
|
-
return undefined;
|
|
1007
|
-
}
|
|
1008
|
-
const result = computeCacheKey({
|
|
1009
|
-
anchorDir: options.anchorDir,
|
|
1010
|
-
command: options.command,
|
|
1011
|
-
commandString: options.commandString,
|
|
1012
|
-
env,
|
|
1013
|
-
monorepoRoot: options.monorepoRoot,
|
|
1014
|
-
nmrVersion: VERSION,
|
|
1015
|
-
snapshot,
|
|
1016
|
-
});
|
|
1017
|
-
if (!result.ok) {
|
|
1018
|
-
writeDebugNote(`gate disabled: ${result.reason}`, env, stderr);
|
|
1019
|
-
return undefined;
|
|
1020
|
-
}
|
|
1021
|
-
return result.key;
|
|
1022
|
-
}
|
|
1023
|
-
async function runDelegation(options) {
|
|
1024
|
-
const { context, delegation, parsedArgs, runOptions, stderr } = options;
|
|
1025
|
-
const selection = parsedArgs.filter === undefined ? undefined : readFilterSelection(parsedArgs.filter, context.monorepoRoot);
|
|
1026
|
-
const refusal = findEmptySelectionRefusal({ context, parsedArgs, selection });
|
|
1027
|
-
if (refusal !== undefined) {
|
|
1028
|
-
reportError(refusal, stderr);
|
|
1029
|
-
return { exitCode: 1 };
|
|
1030
|
-
}
|
|
1031
|
-
const shouldWithholdInput = parsedArgs.filter === undefined || selection === 'multiple';
|
|
1032
|
-
const step = shouldWithholdInput ? { ...delegation.step, shouldWithholdInput } : delegation.step;
|
|
1033
|
-
return runSteps([step], context.monorepoRoot, { ...runOptions, env: delegation.env });
|
|
1034
|
-
}
|
|
1035
|
-
async function runGated(plan) {
|
|
1036
|
-
const { commandString, key } = plan;
|
|
1037
|
-
const { anchorDir, command, context, env, stderr, stdout } = plan.invocation;
|
|
1038
|
-
const { config, monorepoRoot } = context;
|
|
1039
|
-
let gate = await readGate(plan);
|
|
1040
|
-
if (gate !== undefined && !plan.shouldBypassCache) {
|
|
1041
|
-
const recalled = await recallPass(plan, gate);
|
|
1042
|
-
if (recalled !== undefined) {
|
|
1043
|
-
return recalled;
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
const lock = await takeMachineLock(plan.invocation);
|
|
1047
|
-
try {
|
|
1048
|
-
if (lock?.waited === true && key !== undefined) {
|
|
1049
|
-
gate = await readGate(plan);
|
|
1050
|
-
if (gate !== undefined && !plan.shouldBypassCache) {
|
|
1051
|
-
const recalled = await recallPass(plan, gate);
|
|
1052
|
-
if (recalled !== undefined) {
|
|
1053
|
-
return recalled;
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
if (plan.overrideNotice !== undefined) {
|
|
1058
|
-
stdout.write(plan.overrideNotice);
|
|
1059
|
-
}
|
|
1060
|
-
const runOptions = { ...plan.runOptions, env: addLockEnv(plan.runOptions.env ?? env, lock) };
|
|
1061
|
-
const startedAtMs = Date.now();
|
|
1062
|
-
const { exitCode, retainedOutput } = await runSteps(plan.steps, anchorDir, runOptions);
|
|
1063
|
-
const durationMs = Date.now() - startedAtMs;
|
|
1064
|
-
if (exitCode === 0 && gate !== undefined) {
|
|
1065
|
-
await recordPass({
|
|
1066
|
-
anchorDir,
|
|
1067
|
-
buildOutputBefore: gate.buildOutputBefore,
|
|
1068
|
-
command,
|
|
1069
|
-
commandString,
|
|
1070
|
-
config,
|
|
1071
|
-
durationMs,
|
|
1072
|
-
env,
|
|
1073
|
-
key: gate.key,
|
|
1074
|
-
monorepoRoot,
|
|
1075
|
-
ownSteps: plan.ownSteps,
|
|
1076
|
-
retainedOutput,
|
|
1077
|
-
retentionKey: gate.retentionKey,
|
|
1078
|
-
runId: plan.runId,
|
|
1079
|
-
snapshot: gate.snapshot,
|
|
1080
|
-
stderr,
|
|
1081
|
-
});
|
|
1082
|
-
}
|
|
1083
|
-
return {
|
|
1084
|
-
exitCode,
|
|
1085
|
-
outcome: exitCode === 0 ? { outcome: 'passed', durationMs } : { outcome: 'failed', durationMs, exitCode },
|
|
1086
|
-
};
|
|
211
|
+
const { exitCode, host, outcome } = await runGated(plan);
|
|
212
|
+
if (outcome !== undefined) {
|
|
213
|
+
reportVerdict({ command, ...(host !== undefined && { host }), scope, ...outcome }, stdout, format, styles.stdout);
|
|
1087
214
|
}
|
|
1088
|
-
|
|
1089
|
-
await lock?.release();
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
function formatMisplacedFlagHint(args, passthrough, style) {
|
|
1093
|
-
const { flagNames, movedTokens, remainingTokens } = splitMisplacedFlags(passthrough);
|
|
1094
|
-
if (flagNames.length === 0) {
|
|
1095
|
-
return undefined;
|
|
1096
|
-
}
|
|
1097
|
-
const commandIndex = args.length - passthrough.length - 1;
|
|
1098
|
-
const correctedTokens = [
|
|
1099
|
-
'nmr',
|
|
1100
|
-
...args.slice(0, commandIndex),
|
|
1101
|
-
...movedTokens,
|
|
1102
|
-
...args.slice(commandIndex, commandIndex + 1),
|
|
1103
|
-
...remainingTokens,
|
|
1104
|
-
];
|
|
1105
|
-
const subject = flagNames.length === 1 ? 'is an nmr flag' : 'are nmr flags';
|
|
1106
|
-
return (`${STATUS_GLYPHS[style].warning.text} ${renderQuotedList(flagNames)} ${subject}; ` +
|
|
1107
|
-
`nmr reads its flags only before the command: \`${correctedTokens.map(quoteToken).join(' ')}\``);
|
|
1108
|
-
}
|
|
1109
|
-
function isDefinedInAnyScope(command, context) {
|
|
1110
|
-
const registries = [buildRootRegistry(context.config, context.monorepoRoot), buildWorkspaceRegistry(context.config)];
|
|
1111
|
-
if (registries.some((registry) => Object.hasOwn(registry, command))) {
|
|
1112
|
-
return true;
|
|
1113
|
-
}
|
|
1114
|
-
return [context.monorepoRoot, ...context.workspacePackageDirs].some((dir) => Object.hasOwn(readPackageJsonScripts(dir) ?? {}, command));
|
|
1115
|
-
}
|
|
1116
|
-
function isDelegationFlag(arg) {
|
|
1117
|
-
return arg === DELEGATE_FLAG || arg === NO_DELEGATE_FLAG || arg.startsWith(`${DELEGATE_FLAG}=`);
|
|
215
|
+
return { exitCode };
|
|
1118
216
|
}
|
|
1119
217
|
function isTopLevelInvocation(env) {
|
|
1120
218
|
const runId = env[RUN_ID_ENV_VAR];
|
|
1121
219
|
return runId === undefined || runId === '';
|
|
1122
220
|
}
|
|
1123
|
-
async function takeMachineLock(invocation) {
|
|
1124
|
-
const { anchorDir, command, env, parsedArgs, stderr, styles } = invocation;
|
|
1125
|
-
if (!HEAVY_COMMANDS.has(command) || parsedArgs.shouldPrintLog) {
|
|
1126
|
-
return undefined;
|
|
1127
|
-
}
|
|
1128
|
-
if (parsedArgs.passthrough.includes('--no-lock')) {
|
|
1129
|
-
stderr.write(`${formatMisplacedNoLockWarning(command, styles.stderr)}\n`);
|
|
1130
|
-
}
|
|
1131
|
-
return acquireMachineLock({
|
|
1132
|
-
command,
|
|
1133
|
-
dir: anchorDir,
|
|
1134
|
-
env: parsedArgs.shouldBypassLock ? { ...env, [NO_LOCK_ENV_VAR]: '1' } : env,
|
|
1135
|
-
stderr,
|
|
1136
|
-
});
|
|
1137
|
-
}
|
|
1138
|
-
function readMisplacedFlagLength(args, index) {
|
|
1139
|
-
const arg = args[index] ?? '';
|
|
1140
|
-
if (FILTER_FLAGS.has(arg)) {
|
|
1141
|
-
return (args[index + 1] ?? '') === '' ? undefined : 2;
|
|
1142
|
-
}
|
|
1143
|
-
const booleanFlag = BOOLEAN_FLAGS.get(arg);
|
|
1144
|
-
if (booleanFlag !== undefined) {
|
|
1145
|
-
return booleanFlag === 'shouldShowHelp' || booleanFlag === 'shouldShowVersion' ? undefined : 1;
|
|
1146
|
-
}
|
|
1147
|
-
const valueArgument = readOutputStyleArgument(args, index) ?? readDelegateArgument(args, index);
|
|
1148
|
-
return valueArgument?.ok === true ? valueArgument.consumedCount : undefined;
|
|
1149
|
-
}
|
|
1150
|
-
function splitMisplacedFlags(passthrough) {
|
|
1151
|
-
const flagNames = new Set();
|
|
1152
|
-
const movedTokens = [];
|
|
1153
|
-
const remainingTokens = [];
|
|
1154
|
-
let index = 0;
|
|
1155
|
-
while (index < passthrough.length) {
|
|
1156
|
-
const arg = passthrough[index] ?? '';
|
|
1157
|
-
if (arg === '--') {
|
|
1158
|
-
remainingTokens.push(...passthrough.slice(index));
|
|
1159
|
-
break;
|
|
1160
|
-
}
|
|
1161
|
-
const flagLength = readMisplacedFlagLength(passthrough, index);
|
|
1162
|
-
if (flagLength === undefined) {
|
|
1163
|
-
remainingTokens.push(arg);
|
|
1164
|
-
index++;
|
|
1165
|
-
continue;
|
|
1166
|
-
}
|
|
1167
|
-
flagNames.add(arg.split('=', 1)[0] ?? arg);
|
|
1168
|
-
movedTokens.push(...passthrough.slice(index, index + flagLength));
|
|
1169
|
-
index += flagLength;
|
|
1170
|
-
}
|
|
1171
|
-
return { flagNames: [...flagNames], movedTokens, remainingTokens };
|
|
1172
|
-
}
|
|
1173
|
-
function wrapWithHooks(command, mainSteps, registry, anchorDir, isWorkspaceRoot) {
|
|
1174
|
-
const steps = [];
|
|
1175
|
-
if (hasRunnableHook(`${command}:pre`, registry, anchorDir, isWorkspaceRoot)) {
|
|
1176
|
-
steps.push(composeNmrStep(`${command}:pre`, isWorkspaceRoot));
|
|
1177
|
-
}
|
|
1178
|
-
steps.push(...mainSteps);
|
|
1179
|
-
if (hasRunnableHook(`${command}:post`, registry, anchorDir, isWorkspaceRoot)) {
|
|
1180
|
-
steps.push(composeNmrStep(`${command}:post`, isWorkspaceRoot));
|
|
1181
|
-
}
|
|
1182
|
-
return steps;
|
|
1183
|
-
}
|