@williamthorsen/nmr 0.37.0 → 0.39.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 +114 -0
- package/.readyup/kits/default.js +92 -87
- package/.readyup/manifest.json +8 -8
- package/CHANGELOG.md +89 -0
- package/README.md +37 -28
- package/agents/guidance/rulebooks/nmr.md +2 -2
- package/dist/esm/check-cache.d.ts +4 -3
- package/dist/esm/check-cache.js +28 -28
- package/dist/esm/cli-build.js +3 -0
- package/dist/esm/cli-clean.js +3 -1
- package/dist/esm/cli-ensure-prepublish-hooks.js +7 -5
- package/dist/esm/cli-report-catalog.js +2 -1
- package/dist/esm/cli-report-overrides.js +2 -1
- package/dist/esm/commands/build-output.d.ts +2 -2
- package/dist/esm/commands/build-output.js +9 -9
- package/dist/esm/commands/build.d.ts +6 -1
- package/dist/esm/commands/build.js +73 -72
- package/dist/esm/commands/clean.d.ts +3 -2
- package/dist/esm/commands/clean.js +25 -24
- package/dist/esm/commands/ensure-prepublish-hooks.d.ts +5 -4
- package/dist/esm/commands/ensure-prepublish-hooks.js +44 -42
- package/dist/esm/commands/fmt.js +33 -33
- package/dist/esm/commands/report-catalog.d.ts +2 -1
- package/dist/esm/commands/report-catalog.js +14 -11
- package/dist/esm/commands/report-overrides.d.ts +2 -1
- package/dist/esm/commands/report-overrides.js +11 -10
- package/dist/esm/config.js +39 -29
- package/dist/esm/context.js +8 -8
- package/dist/esm/default-scripts.d.ts +2 -2
- package/dist/esm/default-scripts.js +5 -5
- package/dist/esm/glyphs.d.ts +9 -0
- package/dist/esm/glyphs.js +9 -0
- package/dist/esm/help.d.ts +2 -2
- package/dist/esm/help.js +36 -30
- package/dist/esm/helpers/clampToBytes.js +3 -3
- package/dist/esm/helpers/createBoundedBuffer.d.ts +1 -1
- package/dist/esm/helpers/createBoundedBuffer.js +2 -2
- package/dist/esm/helpers/duration.d.ts +2 -2
- package/dist/esm/helpers/duration.js +7 -7
- package/dist/esm/helpers/filter-selection.js +12 -12
- package/dist/esm/helpers/findClosestName.js +15 -15
- package/dist/esm/helpers/package-json.d.ts +2 -2
- package/dist/esm/helpers/package-json.js +26 -25
- package/dist/esm/helpers/transcript.d.ts +2 -2
- package/dist/esm/helpers/transcript.js +14 -14
- package/dist/esm/output-style.d.ts +41 -0
- package/dist/esm/output-style.js +17 -0
- package/dist/esm/prettier.js +5 -5
- package/dist/esm/recording.d.ts +8 -5
- package/dist/esm/recording.js +27 -17
- package/dist/esm/report-format.d.ts +1 -1
- package/dist/esm/report-format.js +7 -7
- package/dist/esm/resolveBinStyles.d.ts +12 -0
- package/dist/esm/resolveBinStyles.js +16 -0
- package/dist/esm/resolver.d.ts +2 -2
- package/dist/esm/resolver.js +16 -16
- package/dist/esm/runCli.js +309 -221
- package/dist/esm/runner.d.ts +1 -1
- package/dist/esm/runner.js +18 -18
- package/dist/esm/steps.d.ts +17 -5
- package/dist/esm/steps.js +37 -28
- package/dist/esm/tests.d.ts +7 -7
- package/dist/esm/tests.js +20 -8
- package/dist/esm/tiers.d.ts +1 -1
- package/dist/esm/tiers.js +8 -8
- package/dist/esm/verbosity.d.ts +1 -1
- package/dist/esm/verbosity.js +7 -7
- package/dist/esm/verdict.d.ts +15 -4
- package/dist/esm/verdict.js +78 -59
- package/dist/esm/vitest-source-resolution.js +48 -46
- package/dist/esm/vitest.d.ts +2 -2
- package/dist/esm/vitest.js +74 -25
- package/dist/esm/workspace.d.ts +2 -18
- package/dist/esm/workspace.js +9 -67
- package/docs/check-cache.md +5 -5
- package/docs/reporting.md +20 -2
- package/docs/scripts.md +2 -2
- package/docs/vitest.md +6 -6
- package/docs/workspace.md +24 -1
- package/package.json +9 -10
- package/dist/esm/helpers/workspace-patterns.d.ts +0 -8
- package/dist/esm/helpers/workspace-patterns.js +0 -29
package/dist/esm/recording.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
+
import { formatGlyphLine, STATUS_GLYPHS } from '@williamthorsen/nmr-core';
|
|
1
2
|
import { readCheckCacheEntry, readTranscript } from "./check-cache.js";
|
|
3
|
+
import { NMR_GLYPHS } from "./glyphs.js";
|
|
2
4
|
import { formatDuration } from "./helpers/duration.js";
|
|
3
5
|
export function renderRecording(options) {
|
|
4
6
|
const { entry } = options.recording;
|
|
5
7
|
const age = formatDuration(Math.max(0, Date.now() - Date.parse(entry.recordedAt)));
|
|
6
|
-
const header =
|
|
7
|
-
`ran in ${formatDuration(entry.durationMs)}
|
|
8
|
+
const header = formatGlyphLine(NMR_GLYPHS, options.style, 'recording', `${options.scope}: ${options.command} — recorded ${entry.recordedAt} (${age} ago), ` +
|
|
9
|
+
`ran in ${formatDuration(entry.durationMs)}`) + `\n$ ${entry.commandString}\n\n`;
|
|
8
10
|
return `${header}${appendNewline(renderBody(options.recording))}`;
|
|
9
11
|
}
|
|
10
12
|
export function renderRefusal(options) {
|
|
11
|
-
|
|
13
|
+
const { text } = STATUS_GLYPHS[options.style].warning;
|
|
14
|
+
return `${text} ${options.scope}: ${options.command}: no recording; ${describeRefusal(options.command, options.refusal)}`;
|
|
12
15
|
}
|
|
13
16
|
export async function resolveRecording(options) {
|
|
14
17
|
const { anchorDir, command, monorepoRoot } = options;
|
|
@@ -24,7 +27,10 @@ export async function resolveRecording(options) {
|
|
|
24
27
|
}
|
|
25
28
|
const ageMs = Math.max(0, Date.now() - Date.parse(entry.recordedAt));
|
|
26
29
|
if (entry.key !== options.key) {
|
|
27
|
-
return {
|
|
30
|
+
return {
|
|
31
|
+
ok: false,
|
|
32
|
+
refusal: { kind: 'mismatched', ageMs, difference: findKeyDifference(entry, options.currentIdentity) },
|
|
33
|
+
};
|
|
28
34
|
}
|
|
29
35
|
const transcript = await readTranscript({ anchorDir, command, monorepoRoot });
|
|
30
36
|
if (transcript === undefined && (entry.retention?.replay.length ?? 0) === 0) {
|
|
@@ -42,14 +48,14 @@ function describeDifference(difference) {
|
|
|
42
48
|
case 'command-string':
|
|
43
49
|
return 'over a command chain this is not';
|
|
44
50
|
case 'nmr-version':
|
|
45
|
-
return `under nmr ${difference.
|
|
51
|
+
return `under nmr ${difference.recordedVersion}, not ${difference.currentVersion}`;
|
|
46
52
|
case 'node-version':
|
|
47
|
-
return `under Node ${difference.
|
|
53
|
+
return `under Node ${difference.recordedVersion}, not ${difference.currentVersion}`;
|
|
48
54
|
case 'other':
|
|
49
55
|
return 'under an install or environment this run does not share';
|
|
50
56
|
default: {
|
|
51
|
-
const
|
|
52
|
-
throw new Error(`Unhandled key difference: ${JSON.stringify(
|
|
57
|
+
const unhandledDifference = difference;
|
|
58
|
+
throw new Error(`Unhandled key difference: ${JSON.stringify(unhandledDifference)}`);
|
|
53
59
|
}
|
|
54
60
|
}
|
|
55
61
|
}
|
|
@@ -66,23 +72,27 @@ function describeRefusal(command, refusal) {
|
|
|
66
72
|
case 'no-output':
|
|
67
73
|
return `the pass ${formatDuration(refusal.ageMs)} ago retained none, as a run printing nothing or writing to a terminal does`;
|
|
68
74
|
default: {
|
|
69
|
-
const
|
|
70
|
-
throw new Error(`Unhandled refusal: ${JSON.stringify(
|
|
75
|
+
const unhandledRefusal = refusal;
|
|
76
|
+
throw new Error(`Unhandled refusal: ${JSON.stringify(unhandledRefusal)}`);
|
|
71
77
|
}
|
|
72
78
|
}
|
|
73
79
|
}
|
|
74
|
-
function findKeyDifference(entry,
|
|
75
|
-
if (
|
|
80
|
+
function findKeyDifference(entry, currentIdentity) {
|
|
81
|
+
if (currentIdentity.treeHash !== undefined && entry.treeHash !== currentIdentity.treeHash) {
|
|
76
82
|
return { ingredient: 'tree' };
|
|
77
83
|
}
|
|
78
|
-
if (entry.commandString !==
|
|
84
|
+
if (entry.commandString !== currentIdentity.commandString) {
|
|
79
85
|
return { ingredient: 'command-string' };
|
|
80
86
|
}
|
|
81
|
-
if (entry.nmrVersion !==
|
|
82
|
-
return { ingredient: 'nmr-version',
|
|
87
|
+
if (entry.nmrVersion !== currentIdentity.nmrVersion) {
|
|
88
|
+
return { ingredient: 'nmr-version', currentVersion: currentIdentity.nmrVersion, recordedVersion: entry.nmrVersion };
|
|
83
89
|
}
|
|
84
|
-
if (entry.nodeVersion !==
|
|
85
|
-
return {
|
|
90
|
+
if (entry.nodeVersion !== currentIdentity.nodeVersion) {
|
|
91
|
+
return {
|
|
92
|
+
ingredient: 'node-version',
|
|
93
|
+
currentVersion: currentIdentity.nodeVersion,
|
|
94
|
+
recordedVersion: entry.nodeVersion,
|
|
95
|
+
};
|
|
86
96
|
}
|
|
87
97
|
return { ingredient: 'other' };
|
|
88
98
|
}
|
|
@@ -47,5 +47,5 @@ export type ReportFormatRead = {
|
|
|
47
47
|
export declare function resolveReportFormat(options: ResolveReportFormatOptions): ReportFormat;
|
|
48
48
|
export interface ResolveReportFormatOptions {
|
|
49
49
|
envFormat: ReportFormat | undefined;
|
|
50
|
-
|
|
50
|
+
hasJsonFlag: boolean;
|
|
51
51
|
}
|
|
@@ -6,20 +6,20 @@ export function isReportFormat(value) {
|
|
|
6
6
|
return names.includes(value);
|
|
7
7
|
}
|
|
8
8
|
export function readReportFormatEnv(env) {
|
|
9
|
-
const
|
|
10
|
-
if (
|
|
9
|
+
const rawValue = env[REPORT_FORMAT_ENV_VAR];
|
|
10
|
+
if (rawValue === undefined || rawValue === '') {
|
|
11
11
|
return { ok: true };
|
|
12
12
|
}
|
|
13
|
-
if (!isReportFormat(
|
|
14
|
-
return { ok: false, error: formatReportFormatRejection(REPORT_FORMAT_ENV_VAR,
|
|
13
|
+
if (!isReportFormat(rawValue)) {
|
|
14
|
+
return { ok: false, error: formatReportFormatRejection(REPORT_FORMAT_ENV_VAR, rawValue) };
|
|
15
15
|
}
|
|
16
|
-
return { ok: true, format:
|
|
16
|
+
return { ok: true, format: rawValue };
|
|
17
17
|
}
|
|
18
18
|
export const REPORT_FORMAT_ENV_VAR = 'NMR_REPORT_FORMAT';
|
|
19
19
|
export const REPORT_FORMATS = ['text', 'json'];
|
|
20
20
|
export function resolveReportFormat(options) {
|
|
21
|
-
const { envFormat,
|
|
22
|
-
if (
|
|
21
|
+
const { envFormat, hasJsonFlag } = options;
|
|
22
|
+
if (hasJsonFlag)
|
|
23
23
|
return 'json';
|
|
24
24
|
return envFormat ?? 'text';
|
|
25
25
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type StreamStyles } from '@williamthorsen/nmr-core';
|
|
2
|
+
/**
|
|
3
|
+
* Resolves the style of the running process's own output streams, and rejects a variable naming no style.
|
|
4
|
+
*
|
|
5
|
+
* What a standalone bin calls. A bin carries no `--output-style` of its own: the flag is `nmr`'s, and what
|
|
6
|
+
* reaches a bin is the variable that `nmr <command>` exports to it, so a bin run through nmr renders as nmr
|
|
7
|
+
* does and one run on its own detects its streams.
|
|
8
|
+
*
|
|
9
|
+
* Throws rather than exiting, so the bin's own boundary reports the message and sets the exit code, as it does
|
|
10
|
+
* for every other failure in what the caller declared.
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolveBinStyles(): StreamStyles;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import { describeInvalidOutputStyle } from '@williamthorsen/nmr-core';
|
|
3
|
+
import { resolveOutputStyles } from "./output-style.js";
|
|
4
|
+
import { UserError } from "./UserError.js";
|
|
5
|
+
export function resolveBinStyles() {
|
|
6
|
+
const { invalid: invalidStyle, styles } = resolveOutputStyles({
|
|
7
|
+
env: process.env,
|
|
8
|
+
flagValue: undefined,
|
|
9
|
+
stderrIsTty: process.stderr.isTTY,
|
|
10
|
+
stdoutIsTty: process.stdout.isTTY,
|
|
11
|
+
});
|
|
12
|
+
if (invalidStyle !== undefined) {
|
|
13
|
+
throw new UserError(describeInvalidOutputStyle(invalidStyle));
|
|
14
|
+
}
|
|
15
|
+
return styles;
|
|
16
|
+
}
|
package/dist/esm/resolver.d.ts
CHANGED
|
@@ -38,7 +38,7 @@ export interface ResolvedScript {
|
|
|
38
38
|
* A bare string element and the `{ run }` spec compose the same step. The spec's only addition is the
|
|
39
39
|
* declaration of what the step does with the invocation's trailing arguments, which position cannot carry.
|
|
40
40
|
*/
|
|
41
|
-
export declare function expandScript(script: ScriptValue,
|
|
41
|
+
export declare function expandScript(script: ScriptValue, isWorkspaceRoot: boolean): readonly Step[];
|
|
42
42
|
/**
|
|
43
43
|
* Returns a description of a script for help output.
|
|
44
44
|
*/
|
|
@@ -87,4 +87,4 @@ export declare function isSelfReferential(script: string, commandName: string, p
|
|
|
87
87
|
* Returns undefined if the command is not found in the registry. A package.json override of `""` (indicating
|
|
88
88
|
* skip) resolves to a single opaque step carrying it, which renders back to the empty string.
|
|
89
89
|
*/
|
|
90
|
-
export declare function resolveScript(commandName: string, registry: ScriptRegistry, packageDir: string | undefined,
|
|
90
|
+
export declare function resolveScript(commandName: string, registry: ScriptRegistry, packageDir: string | undefined, isWorkspaceRoot: boolean): ResolvedScript | undefined;
|
package/dist/esm/resolver.js
CHANGED
|
@@ -12,13 +12,13 @@ export function applyDevBin(command, devBin, monorepoRoot) {
|
|
|
12
12
|
}
|
|
13
13
|
const spaceIndex = command.indexOf(' ');
|
|
14
14
|
const firstToken = spaceIndex === -1 ? command : command.slice(0, spaceIndex);
|
|
15
|
-
const
|
|
15
|
+
const restOfCommand = spaceIndex === -1 ? '' : command.slice(spaceIndex);
|
|
16
16
|
const replacement = devBin[firstToken];
|
|
17
17
|
if (replacement === undefined) {
|
|
18
18
|
return command;
|
|
19
19
|
}
|
|
20
20
|
const resolvedReplacement = resolveReplacementPaths(replacement, monorepoRoot);
|
|
21
|
-
return resolvedReplacement +
|
|
21
|
+
return resolvedReplacement + restOfCommand;
|
|
22
22
|
}
|
|
23
23
|
export function applyDevBinToSteps(steps, devBin, monorepoRoot) {
|
|
24
24
|
if (!devBin) {
|
|
@@ -40,13 +40,13 @@ function resolveReplacementPaths(replacement, monorepoRoot) {
|
|
|
40
40
|
})
|
|
41
41
|
.join(' ');
|
|
42
42
|
}
|
|
43
|
-
export function expandScript(script,
|
|
43
|
+
export function expandScript(script, isWorkspaceRoot) {
|
|
44
44
|
if (typeof script === 'string') {
|
|
45
45
|
return [{ kind: 'opaque', command: script }];
|
|
46
46
|
}
|
|
47
47
|
return script.map((element) => typeof element === 'string'
|
|
48
|
-
? composeNmrStep(element,
|
|
49
|
-
: composeNmrStep(element.run,
|
|
48
|
+
? composeNmrStep(element, isWorkspaceRoot)
|
|
49
|
+
: composeNmrStep(element.run, isWorkspaceRoot, element.shouldDeclineArguments));
|
|
50
50
|
}
|
|
51
51
|
export function describeScript(script) {
|
|
52
52
|
return typeof script === 'string' ? script : `[${script.map(describeElement).join(', ')}]`;
|
|
@@ -55,13 +55,13 @@ function describeElement(element) {
|
|
|
55
55
|
if (typeof element === 'string') {
|
|
56
56
|
return element;
|
|
57
57
|
}
|
|
58
|
-
return element.
|
|
58
|
+
return element.shouldDeclineArguments === true ? `${element.run} (no args)` : element.run;
|
|
59
59
|
}
|
|
60
60
|
export function readPackageJsonScripts(packageDir) {
|
|
61
61
|
const file = resolvePackageJsonPath(packageDir);
|
|
62
|
-
let
|
|
62
|
+
let rawText;
|
|
63
63
|
try {
|
|
64
|
-
|
|
64
|
+
rawText = readFileSync(file, 'utf8');
|
|
65
65
|
}
|
|
66
66
|
catch (error) {
|
|
67
67
|
if (hasErrnoCode(error, 'ENOENT')) {
|
|
@@ -69,10 +69,10 @@ export function readPackageJsonScripts(packageDir) {
|
|
|
69
69
|
}
|
|
70
70
|
throw error;
|
|
71
71
|
}
|
|
72
|
-
const
|
|
73
|
-
if (!isObject(
|
|
72
|
+
const parsedManifest = parsePackageJson(rawText, file);
|
|
73
|
+
if (!isObject(parsedManifest))
|
|
74
74
|
return undefined;
|
|
75
|
-
const scripts =
|
|
75
|
+
const scripts = parsedManifest['scripts'];
|
|
76
76
|
if (!isObject(scripts))
|
|
77
77
|
return undefined;
|
|
78
78
|
return readScriptRecord(packageDir, scripts);
|
|
@@ -108,15 +108,15 @@ export function findChainedSelfReference(packageDir, commandName) {
|
|
|
108
108
|
export function isSelfReferential(script, commandName, packageDir) {
|
|
109
109
|
return readSelfReference({ anchoredAtRoot: isMonorepoRoot(packageDir), commandName, script }) !== undefined;
|
|
110
110
|
}
|
|
111
|
-
export function resolveScript(commandName, registry, packageDir,
|
|
111
|
+
export function resolveScript(commandName, registry, packageDir, isWorkspaceRoot) {
|
|
112
112
|
if (packageDir) {
|
|
113
113
|
const pkgScripts = readPackageJsonScripts(packageDir);
|
|
114
114
|
if (pkgScripts && Object.hasOwn(pkgScripts, commandName)) {
|
|
115
|
-
const
|
|
116
|
-
if (
|
|
115
|
+
const overrideCommand = pkgScripts[commandName];
|
|
116
|
+
if (overrideCommand !== undefined && !isSelfReferential(overrideCommand, commandName, packageDir)) {
|
|
117
117
|
return {
|
|
118
118
|
origin: { tier: 'package', file: resolvePackageJsonPath(packageDir), key: commandName },
|
|
119
|
-
steps: [{ kind: 'opaque', command:
|
|
119
|
+
steps: [{ kind: 'opaque', command: overrideCommand }],
|
|
120
120
|
};
|
|
121
121
|
}
|
|
122
122
|
}
|
|
@@ -128,5 +128,5 @@ export function resolveScript(commandName, registry, packageDir, workspaceRoot)
|
|
|
128
128
|
if (registryEntry === undefined) {
|
|
129
129
|
return undefined;
|
|
130
130
|
}
|
|
131
|
-
return { origin: { tier: 'registry', key: commandName }, steps: expandScript(registryEntry,
|
|
131
|
+
return { origin: { tier: 'registry', key: commandName }, steps: expandScript(registryEntry, isWorkspaceRoot) };
|
|
132
132
|
}
|