@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.
Files changed (82) hide show
  1. package/.meta/changelog.json +114 -0
  2. package/.readyup/kits/default.js +92 -87
  3. package/.readyup/manifest.json +8 -8
  4. package/CHANGELOG.md +89 -0
  5. package/README.md +37 -28
  6. package/agents/guidance/rulebooks/nmr.md +2 -2
  7. package/dist/esm/check-cache.d.ts +4 -3
  8. package/dist/esm/check-cache.js +28 -28
  9. package/dist/esm/cli-build.js +3 -0
  10. package/dist/esm/cli-clean.js +3 -1
  11. package/dist/esm/cli-ensure-prepublish-hooks.js +7 -5
  12. package/dist/esm/cli-report-catalog.js +2 -1
  13. package/dist/esm/cli-report-overrides.js +2 -1
  14. package/dist/esm/commands/build-output.d.ts +2 -2
  15. package/dist/esm/commands/build-output.js +9 -9
  16. package/dist/esm/commands/build.d.ts +6 -1
  17. package/dist/esm/commands/build.js +73 -72
  18. package/dist/esm/commands/clean.d.ts +3 -2
  19. package/dist/esm/commands/clean.js +25 -24
  20. package/dist/esm/commands/ensure-prepublish-hooks.d.ts +5 -4
  21. package/dist/esm/commands/ensure-prepublish-hooks.js +44 -42
  22. package/dist/esm/commands/fmt.js +33 -33
  23. package/dist/esm/commands/report-catalog.d.ts +2 -1
  24. package/dist/esm/commands/report-catalog.js +14 -11
  25. package/dist/esm/commands/report-overrides.d.ts +2 -1
  26. package/dist/esm/commands/report-overrides.js +11 -10
  27. package/dist/esm/config.js +39 -29
  28. package/dist/esm/context.js +8 -8
  29. package/dist/esm/default-scripts.d.ts +2 -2
  30. package/dist/esm/default-scripts.js +5 -5
  31. package/dist/esm/glyphs.d.ts +9 -0
  32. package/dist/esm/glyphs.js +9 -0
  33. package/dist/esm/help.d.ts +2 -2
  34. package/dist/esm/help.js +36 -30
  35. package/dist/esm/helpers/clampToBytes.js +3 -3
  36. package/dist/esm/helpers/createBoundedBuffer.d.ts +1 -1
  37. package/dist/esm/helpers/createBoundedBuffer.js +2 -2
  38. package/dist/esm/helpers/duration.d.ts +2 -2
  39. package/dist/esm/helpers/duration.js +7 -7
  40. package/dist/esm/helpers/filter-selection.js +12 -12
  41. package/dist/esm/helpers/findClosestName.js +15 -15
  42. package/dist/esm/helpers/package-json.d.ts +2 -2
  43. package/dist/esm/helpers/package-json.js +26 -25
  44. package/dist/esm/helpers/transcript.d.ts +2 -2
  45. package/dist/esm/helpers/transcript.js +14 -14
  46. package/dist/esm/output-style.d.ts +41 -0
  47. package/dist/esm/output-style.js +17 -0
  48. package/dist/esm/prettier.js +5 -5
  49. package/dist/esm/recording.d.ts +8 -5
  50. package/dist/esm/recording.js +27 -17
  51. package/dist/esm/report-format.d.ts +1 -1
  52. package/dist/esm/report-format.js +7 -7
  53. package/dist/esm/resolveBinStyles.d.ts +12 -0
  54. package/dist/esm/resolveBinStyles.js +16 -0
  55. package/dist/esm/resolver.d.ts +2 -2
  56. package/dist/esm/resolver.js +16 -16
  57. package/dist/esm/runCli.js +309 -221
  58. package/dist/esm/runner.d.ts +1 -1
  59. package/dist/esm/runner.js +18 -18
  60. package/dist/esm/steps.d.ts +17 -5
  61. package/dist/esm/steps.js +37 -28
  62. package/dist/esm/tests.d.ts +7 -7
  63. package/dist/esm/tests.js +20 -8
  64. package/dist/esm/tiers.d.ts +1 -1
  65. package/dist/esm/tiers.js +8 -8
  66. package/dist/esm/verbosity.d.ts +1 -1
  67. package/dist/esm/verbosity.js +7 -7
  68. package/dist/esm/verdict.d.ts +15 -4
  69. package/dist/esm/verdict.js +78 -59
  70. package/dist/esm/vitest-source-resolution.js +48 -46
  71. package/dist/esm/vitest.d.ts +2 -2
  72. package/dist/esm/vitest.js +74 -25
  73. package/dist/esm/workspace.d.ts +2 -18
  74. package/dist/esm/workspace.js +9 -67
  75. package/docs/check-cache.md +5 -5
  76. package/docs/reporting.md +20 -2
  77. package/docs/scripts.md +2 -2
  78. package/docs/vitest.md +6 -6
  79. package/docs/workspace.md +24 -1
  80. package/package.json +9 -10
  81. package/dist/esm/helpers/workspace-patterns.d.ts +0 -8
  82. package/dist/esm/helpers/workspace-patterns.js +0 -29
@@ -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 = `📼 ${options.scope}: ${options.command} — recorded ${entry.recordedAt} (${age} ago), ` +
7
- `ran in ${formatDuration(entry.durationMs)}\n$ ${entry.commandString}\n\n`;
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
- return `📭 ${options.scope}: ${options.command}: no recording; ${describeRefusal(options.command, options.refusal)}`;
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 { ok: false, refusal: { kind: 'mismatched', ageMs, difference: findKeyDifference(entry, options.current) } };
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.recorded}, not ${difference.current}`;
51
+ return `under nmr ${difference.recordedVersion}, not ${difference.currentVersion}`;
46
52
  case 'node-version':
47
- return `under Node ${difference.recorded}, not ${difference.current}`;
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 unhandled = difference;
52
- throw new Error(`Unhandled key difference: ${JSON.stringify(unhandled)}`);
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 unhandled = refusal;
70
- throw new Error(`Unhandled refusal: ${JSON.stringify(unhandled)}`);
75
+ const unhandledRefusal = refusal;
76
+ throw new Error(`Unhandled refusal: ${JSON.stringify(unhandledRefusal)}`);
71
77
  }
72
78
  }
73
79
  }
74
- function findKeyDifference(entry, current) {
75
- if (current.treeHash !== undefined && entry.treeHash !== current.treeHash) {
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 !== current.commandString) {
84
+ if (entry.commandString !== currentIdentity.commandString) {
79
85
  return { ingredient: 'command-string' };
80
86
  }
81
- if (entry.nmrVersion !== current.nmrVersion) {
82
- return { ingredient: 'nmr-version', current: current.nmrVersion, recorded: entry.nmrVersion };
87
+ if (entry.nmrVersion !== currentIdentity.nmrVersion) {
88
+ return { ingredient: 'nmr-version', currentVersion: currentIdentity.nmrVersion, recordedVersion: entry.nmrVersion };
83
89
  }
84
- if (entry.nodeVersion !== current.nodeVersion) {
85
- return { ingredient: 'node-version', current: current.nodeVersion, recorded: entry.nodeVersion };
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
- jsonFlag: boolean;
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 raw = env[REPORT_FORMAT_ENV_VAR];
10
- if (raw === undefined || raw === '') {
9
+ const rawValue = env[REPORT_FORMAT_ENV_VAR];
10
+ if (rawValue === undefined || rawValue === '') {
11
11
  return { ok: true };
12
12
  }
13
- if (!isReportFormat(raw)) {
14
- return { ok: false, error: formatReportFormatRejection(REPORT_FORMAT_ENV_VAR, raw) };
13
+ if (!isReportFormat(rawValue)) {
14
+ return { ok: false, error: formatReportFormatRejection(REPORT_FORMAT_ENV_VAR, rawValue) };
15
15
  }
16
- return { ok: true, format: raw };
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, jsonFlag } = options;
22
- if (jsonFlag)
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
+ }
@@ -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, workspaceRoot: boolean): readonly Step[];
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, workspaceRoot: boolean): ResolvedScript | undefined;
90
+ export declare function resolveScript(commandName: string, registry: ScriptRegistry, packageDir: string | undefined, isWorkspaceRoot: boolean): ResolvedScript | undefined;
@@ -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 rest = spaceIndex === -1 ? '' : command.slice(spaceIndex);
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 + rest;
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, workspaceRoot) {
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, workspaceRoot)
49
- : composeNmrStep(element.run, workspaceRoot, element.declinesArgs));
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.declinesArgs === true ? `${element.run} (no args)` : element.run;
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 raw;
62
+ let rawText;
63
63
  try {
64
- raw = readFileSync(file, 'utf8');
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 parsed = parsePackageJson(raw, file);
73
- if (!isObject(parsed))
72
+ const parsedManifest = parsePackageJson(rawText, file);
73
+ if (!isObject(parsedManifest))
74
74
  return undefined;
75
- const scripts = parsed['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, workspaceRoot) {
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 override = pkgScripts[commandName];
116
- if (override !== undefined && !isSelfReferential(override, commandName, packageDir)) {
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: override }],
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, workspaceRoot) };
131
+ return { origin: { tier: 'registry', key: commandName }, steps: expandScript(registryEntry, isWorkspaceRoot) };
132
132
  }