@williamthorsen/nmr 0.38.0 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.meta/changelog.json +212 -4
- package/.readyup/kits/default.js +124 -104
- package/.readyup/manifest.json +12 -7
- package/CHANGELOG.md +108 -0
- package/README.md +41 -47
- package/agents/README.md +8 -8
- package/agents/guidance/rulebooks/nmr.md +8 -8
- package/bin/ensure-prepublish-hooks.js +1 -1
- package/bin/nmr-clean.js +1 -1
- package/bin/nmr-compile.js +1 -1
- package/bin/nmr-fmt.js +1 -1
- package/bin/nmr-report-catalog.js +1 -1
- package/bin/nmr-report-overrides.js +1 -1
- package/bin/nmr-taze.js +1 -1
- package/bin/nmr.js +1 -1
- package/dist/esm/UserError.d.ts +1 -2
- package/dist/esm/check-cache.d.ts +61 -63
- package/dist/esm/check-cache.js +24 -24
- package/dist/esm/cli-ensure-prepublish-hooks.js +4 -4
- package/dist/esm/commands/build-output.d.ts +37 -38
- package/dist/esm/commands/build-output.js +9 -9
- package/dist/esm/commands/build.d.ts +13 -14
- package/dist/esm/commands/build.js +67 -65
- package/dist/esm/commands/clean.d.ts +6 -6
- package/dist/esm/commands/clean.js +11 -11
- package/dist/esm/commands/ensure-prepublish-hooks.d.ts +7 -7
- package/dist/esm/commands/ensure-prepublish-hooks.js +41 -41
- package/dist/esm/commands/fmt.d.ts +8 -8
- package/dist/esm/commands/fmt.js +33 -33
- package/dist/esm/commands/report-catalog.d.ts +5 -7
- package/dist/esm/commands/report-catalog.js +10 -8
- package/dist/esm/commands/report-overrides.d.ts +0 -2
- package/dist/esm/commands/report-overrides.js +8 -8
- package/dist/esm/commands/taze.d.ts +9 -8
- package/dist/esm/config.d.ts +5 -5
- package/dist/esm/config.js +27 -27
- package/dist/esm/context.d.ts +1 -2
- package/dist/esm/context.js +8 -8
- package/dist/esm/default-scripts.d.ts +5 -6
- package/dist/esm/defineConfig.d.ts +1 -1
- package/dist/esm/git-ignored-paths.d.ts +9 -0
- package/dist/esm/git-ignored-paths.js +10 -0
- package/dist/esm/glyphs.d.ts +3 -3
- package/dist/esm/help.d.ts +4 -4
- package/dist/esm/help.js +21 -21
- package/dist/esm/helpers/clampToBytes.d.ts +4 -5
- package/dist/esm/helpers/clampToBytes.js +3 -3
- package/dist/esm/helpers/config-path.d.ts +0 -2
- package/dist/esm/helpers/createBoundedBuffer.d.ts +3 -3
- package/dist/esm/helpers/createBoundedBuffer.js +2 -2
- package/dist/esm/helpers/deriveExcerpt.d.ts +4 -5
- package/dist/esm/helpers/duration.d.ts +10 -11
- package/dist/esm/helpers/duration.js +7 -7
- package/dist/esm/helpers/filter-selection.d.ts +19 -19
- package/dist/esm/helpers/filter-selection.js +12 -12
- package/dist/esm/helpers/findClosestName.d.ts +1 -1
- package/dist/esm/helpers/findClosestName.js +15 -15
- package/dist/esm/helpers/hook-name.d.ts +2 -5
- package/dist/esm/helpers/package-json.d.ts +11 -10
- package/dist/esm/helpers/package-json.js +26 -25
- package/dist/esm/helpers/readStringValues.d.ts +2 -3
- package/dist/esm/helpers/reportClosing.d.ts +8 -9
- package/dist/esm/helpers/transcript.d.ts +15 -15
- package/dist/esm/helpers/transcript.js +14 -14
- package/dist/esm/helpers/type-guards.d.ts +1 -0
- package/dist/esm/output-style.d.ts +12 -13
- package/dist/esm/prettier.d.ts +2 -2
- package/dist/esm/prettier.js +5 -5
- package/dist/esm/recording.d.ts +25 -20
- package/dist/esm/recording.js +28 -21
- package/dist/esm/replay-assembly.d.ts +10 -10
- package/dist/esm/report-format.d.ts +15 -20
- package/dist/esm/report-format.js +7 -7
- package/dist/esm/reportCliFailure.d.ts +2 -3
- package/dist/esm/resolve-scripts.d.ts +4 -5
- package/dist/esm/resolveBinStyles.d.ts +4 -5
- package/dist/esm/resolveBinStyles.js +3 -3
- package/dist/esm/resolver.d.ts +16 -20
- package/dist/esm/resolver.js +15 -15
- package/dist/esm/runCli.d.ts +6 -6
- package/dist/esm/runCli.js +261 -195
- package/dist/esm/runner.d.ts +20 -20
- package/dist/esm/runner.js +18 -18
- package/dist/esm/steps.d.ts +51 -38
- package/dist/esm/steps.js +32 -27
- package/dist/esm/taze.d.ts +1 -1
- package/dist/esm/tests.d.ts +23 -18
- package/dist/esm/tests.js +21 -9
- package/dist/esm/tiers.d.ts +28 -24
- package/dist/esm/tiers.js +13 -9
- package/dist/esm/types.d.ts +5 -5
- package/dist/esm/verbosity.d.ts +23 -21
- package/dist/esm/verbosity.js +7 -7
- package/dist/esm/verdict.d.ts +39 -32
- package/dist/esm/verdict.js +63 -50
- package/dist/esm/vitest-source-resolution.d.ts +11 -11
- package/dist/esm/vitest-source-resolution.js +49 -47
- package/dist/esm/vitest.d.ts +27 -26
- package/dist/esm/vitest.js +90 -33
- package/dist/esm/workspace.d.ts +6 -22
- package/dist/esm/workspace.js +9 -67
- package/docs/check-cache.md +57 -57
- package/docs/prettier.md +10 -10
- package/docs/reporting.md +48 -44
- package/docs/scripts.md +29 -29
- package/docs/upgrades.md +18 -18
- package/docs/utilities.md +13 -13
- package/docs/vitest.md +68 -66
- package/docs/workspace.md +27 -4
- package/package.json +6 -6
- package/dist/esm/helpers/workspace-patterns.d.ts +0 -8
- package/dist/esm/helpers/workspace-patterns.js +0 -29
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
import type { RetainedOutput } from '../runner.js';
|
|
2
2
|
/**
|
|
3
|
-
* The bound on what one recorded pass persists, and
|
|
3
|
+
* The bound on what one recorded pass persists, and therefore on what `nmr --log` can print back.
|
|
4
4
|
*
|
|
5
|
-
* A quantity of its own, distinct from the two that bound the same bytes elsewhere: the in-memory copy a
|
|
6
|
-
* command's capture
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* A quantity of its own, distinct from the two that bound the same bytes elsewhere: the in-memory copy kept by a
|
|
6
|
+
* command's capture is bounded per stream, for a failure that this run may still have to report, and a verdict
|
|
7
|
+
* line is bounded at the size of an atomic pipe write. This one bounds what a cache directory accumulates over
|
|
8
|
+
* every scope and command recorded in a repo.
|
|
9
9
|
*/
|
|
10
10
|
export declare const TRANSCRIPT_LIMIT_BYTES = 262144;
|
|
11
11
|
/**
|
|
12
|
-
* Reduces a command's raw output to the form nmr persists: escape sequences dropped, and a line
|
|
13
|
-
*
|
|
12
|
+
* Reduces a command's raw output to the form that nmr persists: escape sequences dropped, and a line redrawn by
|
|
13
|
+
* the command reduced to what a reader was left looking at.
|
|
14
14
|
*
|
|
15
|
-
* Applied where the bytes are stored rather than where they are captured. The copy a quiet run writes to
|
|
15
|
+
* Applied where the bytes are stored rather than where they are captured. The copy that a quiet run writes to
|
|
16
16
|
* stderr on failure is the command's own output going to a stream that may be a terminal, and stripping its
|
|
17
17
|
* color there would be a loss.
|
|
18
18
|
*/
|
|
19
|
-
export declare function cleanTranscript(
|
|
19
|
+
export declare function cleanTranscript(rawText: string): string;
|
|
20
20
|
/**
|
|
21
|
-
* Composes what a pass persists from what its command wrote, or `undefined`
|
|
21
|
+
* Composes what a pass persists from what its command wrote, or `undefined` when it wrote nothing worth
|
|
22
22
|
* keeping.
|
|
23
23
|
*
|
|
24
|
-
* The streams follow one another rather than interleaving, in the order a quiet run already
|
|
25
|
-
* failure: nmr reads each through a pipe of its own, so
|
|
26
|
-
*
|
|
24
|
+
* The streams follow one another rather than interleaving, in the order in which a quiet run already prints them
|
|
25
|
+
* on failure: nmr reads each through a pipe of its own, so it does not know their true interleaving. An empty
|
|
26
|
+
* stream contributes neither content nor marker.
|
|
27
27
|
*/
|
|
28
|
-
export declare function composeTranscript(
|
|
29
|
-
/** Renders the line that stands in for dropped bytes, in nmr's voice so it is not mistaken for the tool's. */
|
|
28
|
+
export declare function composeTranscript(retainedOutput: RetainedOutput): string | undefined;
|
|
29
|
+
/** Renders the line that stands in for dropped bytes, in nmr's voice so that it is not mistaken for the tool's. */
|
|
30
30
|
export declare function formatElisionMarker(byteCount: number): string;
|
|
@@ -2,15 +2,15 @@ export const TRANSCRIPT_LIMIT_BYTES = 262_144;
|
|
|
2
2
|
const ANSI_PATTERN = /\u{1B}(?:\[[\u{30}-\u{3F}]*[\u{20}-\u{2F}]*[\u{40}-\u{7E}]|\][^\u{7}\u{1B}]*(?:\u{7}|\u{1B}\\))/gu;
|
|
3
3
|
const END_LIMIT_BYTES = TRANSCRIPT_LIMIT_BYTES / 2;
|
|
4
4
|
const STREAM_MARKER = '\n… nmr: stderr …\n';
|
|
5
|
-
export function cleanTranscript(
|
|
6
|
-
return
|
|
5
|
+
export function cleanTranscript(rawText) {
|
|
6
|
+
return rawText
|
|
7
7
|
.replaceAll(ANSI_PATTERN, '')
|
|
8
8
|
.split('\n')
|
|
9
9
|
.map((line) => renderCarriageReturns(line))
|
|
10
10
|
.join('\n');
|
|
11
11
|
}
|
|
12
|
-
export function composeTranscript(
|
|
13
|
-
const streams = [
|
|
12
|
+
export function composeTranscript(retainedOutput) {
|
|
13
|
+
const streams = [retainedOutput.stdout, retainedOutput.stderr]
|
|
14
14
|
.map((stream) => cleanTranscript(stream.toString('utf8')))
|
|
15
15
|
.filter((stream) => stream.trim() !== '');
|
|
16
16
|
if (streams.length === 0) {
|
|
@@ -22,19 +22,19 @@ export function formatElisionMarker(byteCount) {
|
|
|
22
22
|
const unit = byteCount === 1 ? 'byte' : 'bytes';
|
|
23
23
|
return `\n… nmr elided ${byteCount.toLocaleString('en-US')} ${unit} …\n`;
|
|
24
24
|
}
|
|
25
|
-
function alignEnd(buffer,
|
|
26
|
-
let
|
|
27
|
-
while (
|
|
28
|
-
|
|
25
|
+
function alignEnd(buffer, endOffset) {
|
|
26
|
+
let alignedOffset = Math.min(buffer.length, endOffset);
|
|
27
|
+
while (alignedOffset > 0 && isContinuationByte(buffer[alignedOffset])) {
|
|
28
|
+
alignedOffset--;
|
|
29
29
|
}
|
|
30
|
-
return
|
|
30
|
+
return alignedOffset;
|
|
31
31
|
}
|
|
32
|
-
function alignStart(buffer,
|
|
33
|
-
let
|
|
34
|
-
while (
|
|
35
|
-
|
|
32
|
+
function alignStart(buffer, startOffset) {
|
|
33
|
+
let alignedOffset = Math.max(0, startOffset);
|
|
34
|
+
while (alignedOffset < buffer.length && isContinuationByte(buffer[alignedOffset])) {
|
|
35
|
+
alignedOffset++;
|
|
36
36
|
}
|
|
37
|
-
return
|
|
37
|
+
return alignedOffset;
|
|
38
38
|
}
|
|
39
39
|
function clampTranscript(transcript) {
|
|
40
40
|
const buffer = Buffer.from(transcript, 'utf8');
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** Narrows an unknown value to a plain object, excluding `null` and arrays. */
|
|
1
2
|
export declare function isObject(value: unknown): value is Record<string, unknown>;
|
|
2
3
|
/** Narrows an unknown value to a record of plain strings. */
|
|
3
4
|
export declare function isStringRecord(value: unknown): value is Record<string, string>;
|
|
@@ -1,40 +1,39 @@
|
|
|
1
1
|
import type { Writable } from 'node:stream';
|
|
2
2
|
import { type StreamStyleResolution } from '@williamthorsen/nmr-core';
|
|
3
3
|
/**
|
|
4
|
-
* Reports whether a stream is backed by a terminal, which
|
|
5
|
-
* style. A stream
|
|
4
|
+
* Reports whether a stream is backed by a terminal, which detection reads when the flag and `NMR_OUTPUT_STYLE`
|
|
5
|
+
* do not name a style. A stream without an `isTTY` property is not one.
|
|
6
6
|
*/
|
|
7
7
|
export declare function isTerminalStream(stream: Writable): boolean;
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
* and a child writing to a pipe renders as its parent does rather than detecting plain on its own
|
|
9
|
+
* Passes the resolved style down the spawned chain, so that the flag's value is available to every process rather
|
|
10
|
+
* than the first and a child writing to a pipe renders as its parent does rather than detecting plain on its own
|
|
11
|
+
* descriptor.
|
|
11
12
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* pass a rich one recorded.
|
|
13
|
+
* Neither the pass key nor the retention key includes it: It changes how a run renders and never what a command
|
|
14
|
+
* concludes, so a plain run can reuse a pass that a rich run recorded.
|
|
15
15
|
*/
|
|
16
16
|
export declare const OUTPUT_STYLE_ENV_VAR = "NMR_OUTPUT_STYLE";
|
|
17
17
|
/**
|
|
18
18
|
* The flag naming a style for one invocation, which outranks `OUTPUT_STYLE_ENV_VAR`.
|
|
19
19
|
*
|
|
20
|
-
* `nmr`
|
|
21
|
-
* a style to a bin does not mean adding a flag to each bin's own parser.
|
|
20
|
+
* Only `nmr` accepts it. A standalone bin reads the variable, which `nmr <command>` exports to it.
|
|
22
21
|
*/
|
|
23
22
|
export declare const OUTPUT_STYLE_FLAG = "--output-style";
|
|
24
23
|
/**
|
|
25
24
|
* Resolves the style of each output stream: the flag, then `NMR_OUTPUT_STYLE`, then detection from `CI`, the
|
|
26
25
|
* stream's terminal state, and `TERM`.
|
|
27
26
|
*
|
|
28
|
-
* Never throws. A value
|
|
27
|
+
* Never throws. A value that does not name any style is returned in `invalid`, for the caller to report with
|
|
29
28
|
* `describeInvalidOutputStyle`, because the caller has to render that complaint in some style.
|
|
30
29
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
30
|
+
* Passes the flag's raw value to nmr-core unnarrowed, so that nmr rejects a misspelling in the same words as
|
|
31
|
+
* its sibling CLIs.
|
|
33
32
|
*/
|
|
34
33
|
export declare function resolveOutputStyles(options: ResolveOutputStylesOptions): StreamStyleResolution;
|
|
35
34
|
export interface ResolveOutputStylesOptions {
|
|
36
35
|
env: NodeJS.ProcessEnv;
|
|
37
|
-
/** The value the flag
|
|
36
|
+
/** The value given to the flag, absent when the invocation omits the flag, as a bin's invocation always does. */
|
|
38
37
|
flagValue?: string | undefined;
|
|
39
38
|
stderrIsTty: boolean;
|
|
40
39
|
stdoutIsTty: boolean;
|
package/dist/esm/prettier.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Config } from 'prettier';
|
|
2
2
|
export interface PrettierConfigOptions extends Omit<Config, 'overrides' | 'plugins'> {
|
|
3
|
-
/** Appended to the plugins this config
|
|
3
|
+
/** Appended to the plugins registered by this config. */
|
|
4
4
|
additionalPlugins?: NonNullable<Config['plugins']>;
|
|
5
|
-
/** Appended to the overrides this config
|
|
5
|
+
/** Appended to the overrides declared by this config, so a later entry wins over nmr's own. */
|
|
6
6
|
additionalOverrides?: NonNullable<Config['overrides']>;
|
|
7
7
|
/** Replacing the override list is inexpressible; append through `additionalOverrides`. */
|
|
8
8
|
overrides?: never;
|
package/dist/esm/prettier.js
CHANGED
|
@@ -57,23 +57,23 @@ export function definePrettierConfig(options = {}) {
|
|
|
57
57
|
function buildShellPlugin() {
|
|
58
58
|
return {
|
|
59
59
|
...shPlugin,
|
|
60
|
-
languages: shPlugin.languages.filter(isInferredLanguage).map(
|
|
60
|
+
languages: shPlugin.languages.filter(isInferredLanguage).map(dropExcludedFilenames),
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
63
|
function isInferredLanguage(language) {
|
|
64
64
|
return INFERRED_LANGUAGES.has(language.name);
|
|
65
65
|
}
|
|
66
|
-
function
|
|
66
|
+
function dropExcludedFilenames(language) {
|
|
67
67
|
if (language.filenames === undefined)
|
|
68
68
|
return language;
|
|
69
69
|
return { ...language, filenames: language.filenames.filter((name) => !EXCLUDED_FILENAMES.has(name)) };
|
|
70
70
|
}
|
|
71
71
|
function assertNoReplacement(options) {
|
|
72
72
|
if ('overrides' in options)
|
|
73
|
-
throw
|
|
73
|
+
throw buildOwnedKeyError('overrides', 'additionalOverrides');
|
|
74
74
|
if ('plugins' in options)
|
|
75
|
-
throw
|
|
75
|
+
throw buildOwnedKeyError('plugins', 'additionalPlugins');
|
|
76
76
|
}
|
|
77
|
-
function
|
|
77
|
+
function buildOwnedKeyError(key, seam) {
|
|
78
78
|
return new TypeError(`definePrettierConfig: \`${key}\` is owned by this config. Use \`${seam}\` to append to it.`);
|
|
79
79
|
}
|
package/dist/esm/recording.d.ts
CHANGED
|
@@ -2,16 +2,22 @@ import { type OutputStyle } from '@williamthorsen/nmr-core';
|
|
|
2
2
|
import { type CheckCacheEntry } from './check-cache.js';
|
|
3
3
|
/**
|
|
4
4
|
* What this invocation would be recorded under, insofar as an entry records the same facts. A mismatch is read
|
|
5
|
-
* against these
|
|
5
|
+
* against these so that a refusal names the ingredient that moved rather than blaming the tree for all of them.
|
|
6
6
|
*/
|
|
7
7
|
export interface RunIdentity {
|
|
8
8
|
commandString: string;
|
|
9
9
|
nmrVersion: string;
|
|
10
10
|
nodeVersion: string;
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Absent when this invocation did not take a snapshot, which leaves the tree unattributable rather than assumed
|
|
13
|
+
* equal.
|
|
14
|
+
*/
|
|
12
15
|
treeHash: string | undefined;
|
|
13
16
|
}
|
|
14
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* A recorded pass and what it left a reader: its command's own transcript, or the assembly that a composite
|
|
19
|
+
* stores.
|
|
20
|
+
*/
|
|
15
21
|
export interface Recording {
|
|
16
22
|
entry: CheckCacheEntry;
|
|
17
23
|
/** What the command wrote, absent on a composite, which retains nothing of its own. */
|
|
@@ -19,8 +25,8 @@ export interface Recording {
|
|
|
19
25
|
}
|
|
20
26
|
/**
|
|
21
27
|
* Which of the pass key's ingredients a recorded pass and this invocation disagree on, insofar as an entry
|
|
22
|
-
* records enough to tell:
|
|
23
|
-
* environment variables the key folds in.
|
|
28
|
+
* records enough to tell: The residual covers what it does not, which is the install, the platform, and the
|
|
29
|
+
* environment variables that the key folds in.
|
|
24
30
|
*/
|
|
25
31
|
export type KeyDifference = {
|
|
26
32
|
ingredient: 'tree';
|
|
@@ -28,12 +34,12 @@ export type KeyDifference = {
|
|
|
28
34
|
ingredient: 'command-string';
|
|
29
35
|
} | {
|
|
30
36
|
ingredient: 'nmr-version';
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
currentVersion: string;
|
|
38
|
+
recordedVersion: string;
|
|
33
39
|
} | {
|
|
34
40
|
ingredient: 'node-version';
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
currentVersion: string;
|
|
42
|
+
recordedVersion: string;
|
|
37
43
|
} | {
|
|
38
44
|
ingredient: 'other';
|
|
39
45
|
};
|
|
@@ -48,12 +54,12 @@ export type RecordingLookup = {
|
|
|
48
54
|
/**
|
|
49
55
|
* Why there is nothing to print. Each is a separate answer to a reader asking for the last run's output, and
|
|
50
56
|
* each names a different next move: run the command, run it on this tree, run it through a pipe, or stop
|
|
51
|
-
* asking for a command
|
|
57
|
+
* asking for a command that the check-result cache does not record.
|
|
52
58
|
*/
|
|
53
59
|
export type RecordingRefusal = {
|
|
54
60
|
kind: 'uncacheable';
|
|
55
61
|
} | {
|
|
56
|
-
kind: '
|
|
62
|
+
kind: 'cache-inapplicable';
|
|
57
63
|
} | {
|
|
58
64
|
kind: 'unrecorded';
|
|
59
65
|
} | {
|
|
@@ -68,9 +74,9 @@ export type RecordingRefusal = {
|
|
|
68
74
|
* Renders a recording as the reader sees it: a header dating what follows, the command string that produced
|
|
69
75
|
* it, and then the run's own bytes.
|
|
70
76
|
*
|
|
71
|
-
* The header
|
|
72
|
-
*
|
|
73
|
-
*
|
|
77
|
+
* The header presents the body as a recording rather than as this invocation's output, which lets nmr show a
|
|
78
|
+
* reader at a terminal what a piped run wrote. The command string is the whole chain, hooks included, so the
|
|
79
|
+
* header names what earned the pass and not merely what was typed.
|
|
74
80
|
*/
|
|
75
81
|
export declare function renderRecording(options: {
|
|
76
82
|
command: string;
|
|
@@ -79,8 +85,8 @@ export declare function renderRecording(options: {
|
|
|
79
85
|
style: OutputStyle;
|
|
80
86
|
}): string;
|
|
81
87
|
/**
|
|
82
|
-
* Renders a refusal on the one line a fan-out can attribute, in the grammar a verdict
|
|
83
|
-
* command, and what is missing.
|
|
88
|
+
* Renders a refusal on the one line that a fan-out can attribute, in the grammar used by a verdict: the scope,
|
|
89
|
+
* the command, and what is missing.
|
|
84
90
|
*/
|
|
85
91
|
export declare function renderRefusal(options: {
|
|
86
92
|
command: string;
|
|
@@ -92,14 +98,13 @@ export declare function renderRefusal(options: {
|
|
|
92
98
|
* Resolves what one scope has to show for one command.
|
|
93
99
|
*
|
|
94
100
|
* Admitted on the pass key alone, so `--log` prints exactly what a skip would have recalled and never a
|
|
95
|
-
* recording of some other tree. The retention key is
|
|
96
|
-
*
|
|
97
|
-
* does not.
|
|
101
|
+
* recording of some other tree. The retention key is not consulted: It certifies that a recording describes
|
|
102
|
+
* this presentation environment, which a replayed excerpt needs and a dated recording does not.
|
|
98
103
|
*/
|
|
99
104
|
export declare function resolveRecording(options: {
|
|
100
105
|
anchorDir: string;
|
|
101
106
|
command: string;
|
|
102
|
-
|
|
107
|
+
currentIdentity: RunIdentity;
|
|
103
108
|
isCacheable: boolean;
|
|
104
109
|
key: string | undefined;
|
|
105
110
|
monorepoRoot: string;
|
package/dist/esm/recording.js
CHANGED
|
@@ -19,7 +19,7 @@ export async function resolveRecording(options) {
|
|
|
19
19
|
return { ok: false, refusal: { kind: 'uncacheable' } };
|
|
20
20
|
}
|
|
21
21
|
if (options.key === undefined) {
|
|
22
|
-
return { ok: false, refusal: { kind: '
|
|
22
|
+
return { ok: false, refusal: { kind: 'cache-inapplicable' } };
|
|
23
23
|
}
|
|
24
24
|
const entry = await readCheckCacheEntry({ anchorDir, command, monorepoRoot });
|
|
25
25
|
if (entry === undefined) {
|
|
@@ -27,7 +27,10 @@ export async function resolveRecording(options) {
|
|
|
27
27
|
}
|
|
28
28
|
const ageMs = Math.max(0, Date.now() - Date.parse(entry.recordedAt));
|
|
29
29
|
if (entry.key !== options.key) {
|
|
30
|
-
return {
|
|
30
|
+
return {
|
|
31
|
+
ok: false,
|
|
32
|
+
refusal: { kind: 'mismatched', ageMs, difference: findKeyDifference(entry, options.currentIdentity) },
|
|
33
|
+
};
|
|
31
34
|
}
|
|
32
35
|
const transcript = await readTranscript({ anchorDir, command, monorepoRoot });
|
|
33
36
|
if (transcript === undefined && (entry.retention?.replay.length ?? 0) === 0) {
|
|
@@ -41,27 +44,27 @@ function appendNewline(body) {
|
|
|
41
44
|
function describeDifference(difference) {
|
|
42
45
|
switch (difference.ingredient) {
|
|
43
46
|
case 'tree':
|
|
44
|
-
return 'on a tree this is not';
|
|
47
|
+
return 'on a tree that this is not';
|
|
45
48
|
case 'command-string':
|
|
46
|
-
return 'over a command chain this is not';
|
|
49
|
+
return 'over a command chain that this is not';
|
|
47
50
|
case 'nmr-version':
|
|
48
|
-
return `under nmr ${difference.
|
|
51
|
+
return `under nmr ${difference.recordedVersion}, not ${difference.currentVersion}`;
|
|
49
52
|
case 'node-version':
|
|
50
|
-
return `under Node ${difference.
|
|
53
|
+
return `under Node ${difference.recordedVersion}, not ${difference.currentVersion}`;
|
|
51
54
|
case 'other':
|
|
52
|
-
return 'under an install or environment this run does not share';
|
|
55
|
+
return 'under an install or environment that this run does not share';
|
|
53
56
|
default: {
|
|
54
|
-
const
|
|
55
|
-
throw new Error(`Unhandled key difference: ${JSON.stringify(
|
|
57
|
+
const unhandledDifference = difference;
|
|
58
|
+
throw new Error(`Unhandled key difference: ${JSON.stringify(unhandledDifference)}`);
|
|
56
59
|
}
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
62
|
function describeRefusal(command, refusal) {
|
|
60
63
|
switch (refusal.kind) {
|
|
61
64
|
case 'uncacheable':
|
|
62
|
-
return `\`${command}\` is outside the check-result cache, so
|
|
63
|
-
case '
|
|
64
|
-
return 'the check-result cache
|
|
65
|
+
return `\`${command}\` is outside the check-result cache, so the cache does not record any run of it`;
|
|
66
|
+
case 'cache-inapplicable':
|
|
67
|
+
return 'the check-result cache does not apply here (NMR_DEBUG=1 reports why)';
|
|
65
68
|
case 'unrecorded':
|
|
66
69
|
return 'nothing has recorded a pass for this scope';
|
|
67
70
|
case 'mismatched':
|
|
@@ -69,23 +72,27 @@ function describeRefusal(command, refusal) {
|
|
|
69
72
|
case 'no-output':
|
|
70
73
|
return `the pass ${formatDuration(refusal.ageMs)} ago retained none, as a run printing nothing or writing to a terminal does`;
|
|
71
74
|
default: {
|
|
72
|
-
const
|
|
73
|
-
throw new Error(`Unhandled refusal: ${JSON.stringify(
|
|
75
|
+
const unhandledRefusal = refusal;
|
|
76
|
+
throw new Error(`Unhandled refusal: ${JSON.stringify(unhandledRefusal)}`);
|
|
74
77
|
}
|
|
75
78
|
}
|
|
76
79
|
}
|
|
77
|
-
function findKeyDifference(entry,
|
|
78
|
-
if (
|
|
80
|
+
function findKeyDifference(entry, currentIdentity) {
|
|
81
|
+
if (currentIdentity.treeHash !== undefined && entry.treeHash !== currentIdentity.treeHash) {
|
|
79
82
|
return { ingredient: 'tree' };
|
|
80
83
|
}
|
|
81
|
-
if (entry.commandString !==
|
|
84
|
+
if (entry.commandString !== currentIdentity.commandString) {
|
|
82
85
|
return { ingredient: 'command-string' };
|
|
83
86
|
}
|
|
84
|
-
if (entry.nmrVersion !==
|
|
85
|
-
return { ingredient: 'nmr-version',
|
|
87
|
+
if (entry.nmrVersion !== currentIdentity.nmrVersion) {
|
|
88
|
+
return { ingredient: 'nmr-version', currentVersion: currentIdentity.nmrVersion, recordedVersion: entry.nmrVersion };
|
|
86
89
|
}
|
|
87
|
-
if (entry.nodeVersion !==
|
|
88
|
-
return {
|
|
90
|
+
if (entry.nodeVersion !== currentIdentity.nodeVersion) {
|
|
91
|
+
return {
|
|
92
|
+
ingredient: 'node-version',
|
|
93
|
+
currentVersion: currentIdentity.nodeVersion,
|
|
94
|
+
recordedVersion: entry.nodeVersion,
|
|
95
|
+
};
|
|
89
96
|
}
|
|
90
97
|
return { ingredient: 'other' };
|
|
91
98
|
}
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { type ReplayLine } from './check-cache.js';
|
|
2
2
|
import { type Step } from './steps.js';
|
|
3
3
|
/**
|
|
4
|
-
* Assembles what a composite's skip replays: the excerpts its constituents
|
|
5
|
-
* steps name them.
|
|
4
|
+
* Assembles what a composite's skip replays: the excerpts recorded by its constituents, in the order in which
|
|
5
|
+
* its own steps name them.
|
|
6
6
|
*
|
|
7
|
-
* A constituent that is itself a composite
|
|
7
|
+
* A constituent that is itself a composite has a flat, attributed list of its own, so splicing one in keeps
|
|
8
8
|
* the assembly flat and a skipped `ci` replays a package's `test` rather than one opaque `check:strict`
|
|
9
|
-
* line. Nothing is computed and nothing is inferred:
|
|
10
|
-
* the assembly.
|
|
9
|
+
* line. Nothing is computed and nothing is inferred: A constituent without an admissible entry is absent
|
|
10
|
+
* from the assembly.
|
|
11
11
|
*
|
|
12
|
-
* A constituent is looked up at the scope its own process anchors
|
|
13
|
-
* root for an element
|
|
12
|
+
* A constituent is looked up at the scope at which its own process anchors: the composite's anchor, or the
|
|
13
|
+
* monorepo root for an element that passes `-w`, which is how a package-scoped composite finds a root command's entry.
|
|
14
14
|
*
|
|
15
|
-
* An entry is admissible
|
|
16
|
-
* The witness is what a run stamps on an excerpt it records and on one it recalls and replays; the
|
|
17
|
-
*
|
|
15
|
+
* An entry is admissible when the run that certified it is this one and the tree that it describes is this one.
|
|
16
|
+
* The witness is what a run stamps on an excerpt that it records and on one that it recalls and replays; the
|
|
17
|
+
* tree hash bounds an identity carried by a process out of the run that issued it.
|
|
18
18
|
*/
|
|
19
19
|
export declare function assembleReplay(options: {
|
|
20
20
|
anchorDir: string;
|
|
@@ -1,32 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Renders the rejection of a value off the format ladder, naming where it was written. Every source that
|
|
3
|
-
* accepts a format renders through this, so
|
|
3
|
+
* accepts a format renders through this, so two of them can never come to name different ladders.
|
|
4
4
|
*/
|
|
5
5
|
export declare function formatReportFormatRejection(source: string, value: string): string;
|
|
6
6
|
/** Narrows a raw value to a point on the format ladder. */
|
|
7
7
|
export declare function isReportFormat(value: string): value is ReportFormat;
|
|
8
8
|
/**
|
|
9
|
-
* Reads the format the environment names. An unrecognized value
|
|
10
|
-
*
|
|
9
|
+
* Reads the format that the environment names. An unrecognized value is returned as an error, which keeps a
|
|
10
|
+
* misspelling from going unnoticed for the life of the shell.
|
|
11
11
|
*
|
|
12
|
-
* An unset or empty variable
|
|
13
|
-
* with the one the loudness ladder performs and leaves room for a level below the environment.
|
|
12
|
+
* An unset or empty variable does not name a format, and `resolveReportFormat` supplies the default.
|
|
14
13
|
*/
|
|
15
14
|
export declare function readReportFormatEnv(env: NodeJS.ProcessEnv): ReportFormatRead;
|
|
16
15
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* Passes the resolved format down the spawned chain, so that `--json` takes effect in every process and not only
|
|
17
|
+
* the first. It is not a keyed variable: It changes how a run reports and never what a command concludes, so a
|
|
18
|
+
* machine-readable run recalls a pass that a prose run recorded.
|
|
20
19
|
*/
|
|
21
20
|
export declare const REPORT_FORMAT_ENV_VAR = "NMR_REPORT_FORMAT";
|
|
22
21
|
/**
|
|
23
|
-
* The points on the format ladder.
|
|
24
|
-
*
|
|
22
|
+
* The points on the format ladder. `text` is a named point, so that one invocation can override a format
|
|
23
|
+
* exported for the shell in either direction.
|
|
25
24
|
*/
|
|
26
25
|
export declare const REPORT_FORMATS: readonly ["text", "json"];
|
|
27
|
-
/** How nmr renders its own verdicts, which is never the output of the commands it runs. */
|
|
26
|
+
/** How nmr renders its own verdicts, which is never the output of the commands that it runs. */
|
|
28
27
|
export type ReportFormat = (typeof REPORT_FORMATS)[number];
|
|
29
|
-
/** The format the environment names, if any, or the message naming why its value could not be read. */
|
|
28
|
+
/** The format that the environment names, if any, or the message naming why its value could not be read. */
|
|
30
29
|
export type ReportFormatRead = {
|
|
31
30
|
ok: true;
|
|
32
31
|
format?: ReportFormat;
|
|
@@ -35,17 +34,13 @@ export type ReportFormatRead = {
|
|
|
35
34
|
error: string;
|
|
36
35
|
};
|
|
37
36
|
/**
|
|
38
|
-
* Resolves the format this process reports
|
|
37
|
+
* Resolves the format in which this process reports: the flag, then the environment, then `text`.
|
|
39
38
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* teaches; either is additive later.
|
|
43
|
-
*
|
|
44
|
-
* Total by construction. The environment was validated as it was read, so no rejection is left for this to
|
|
45
|
-
* report and the whole ladder reads in one place.
|
|
39
|
+
* Total by construction. The environment was validated as it was read, so this does not have any rejection left
|
|
40
|
+
* to report and the whole ladder reads in one place.
|
|
46
41
|
*/
|
|
47
42
|
export declare function resolveReportFormat(options: ResolveReportFormatOptions): ReportFormat;
|
|
48
43
|
export interface ResolveReportFormatOptions {
|
|
49
44
|
envFormat: ReportFormat | undefined;
|
|
50
|
-
|
|
45
|
+
hasJsonFlag: boolean;
|
|
51
46
|
}
|
|
@@ -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
|
}
|
|
@@ -3,8 +3,7 @@ import type { Writable } from 'node:stream';
|
|
|
3
3
|
* Writes a failure to the CLI's error stream: as its message alone when the user's own declaration caused it,
|
|
4
4
|
* and with its stack otherwise.
|
|
5
5
|
*
|
|
6
|
-
* A stack records where nmr noticed a problem, which says nothing about the file a user has to edit; for
|
|
7
|
-
* own fault it is where a report has to start.
|
|
8
|
-
* non-`Error` alike.
|
|
6
|
+
* A stack records where nmr noticed a problem, which says nothing about the file that a user has to edit; for
|
|
7
|
+
* nmr's own fault it is where a report has to start.
|
|
9
8
|
*/
|
|
10
9
|
export declare function reportCliFailure(error: unknown, stderr: Writable): void;
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { type ScriptRegistry } from './default-scripts.js';
|
|
2
2
|
export type { ScriptRegistry, ScriptValue, StepSpec } from './default-scripts.js';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* A function rather than the exported table itself: callers merge overrides into the result, and `generateHelp`
|
|
7
|
-
* rewrites entries in place, so each caller needs a copy the module does not share.
|
|
4
|
+
* Returns a shallow copy of the default workspace scripts, whose entries a caller may replace without changing the
|
|
5
|
+
* defaults.
|
|
8
6
|
*/
|
|
9
7
|
export declare function getDefaultWorkspaceScripts(): ScriptRegistry;
|
|
10
8
|
/**
|
|
11
|
-
*
|
|
9
|
+
* Returns a shallow copy of the default root scripts, whose entries a caller may replace without changing the
|
|
10
|
+
* defaults.
|
|
12
11
|
*/
|
|
13
12
|
export declare function getDefaultRootScripts(): ScriptRegistry;
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { type StreamStyles } from '@williamthorsen/nmr-core';
|
|
2
2
|
/**
|
|
3
|
-
* Resolves the style of the running process's own output streams, and rejects a variable
|
|
3
|
+
* Resolves the style of the running process's own output streams, and rejects a variable that does not name any
|
|
4
|
+
* style.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
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.
|
|
6
|
+
* Each standalone bin calls this and does not pass a flag value, because only `nmr` accepts `OUTPUT_STYLE_FLAG`.
|
|
8
7
|
*
|
|
9
|
-
* Throws rather than exiting, so the bin's own boundary reports the message and sets the exit code, as it does
|
|
8
|
+
* Throws rather than exiting, so that the bin's own boundary reports the message and sets the exit code, as it does
|
|
10
9
|
* for every other failure in what the caller declared.
|
|
11
10
|
*/
|
|
12
11
|
export declare function resolveBinStyles(): StreamStyles;
|
|
@@ -3,14 +3,14 @@ import { describeInvalidOutputStyle } from '@williamthorsen/nmr-core';
|
|
|
3
3
|
import { resolveOutputStyles } from "./output-style.js";
|
|
4
4
|
import { UserError } from "./UserError.js";
|
|
5
5
|
export function resolveBinStyles() {
|
|
6
|
-
const { invalid, styles } = resolveOutputStyles({
|
|
6
|
+
const { invalid: invalidStyle, styles } = resolveOutputStyles({
|
|
7
7
|
env: process.env,
|
|
8
8
|
flagValue: undefined,
|
|
9
9
|
stderrIsTty: process.stderr.isTTY,
|
|
10
10
|
stdoutIsTty: process.stdout.isTTY,
|
|
11
11
|
});
|
|
12
|
-
if (
|
|
13
|
-
throw new UserError(describeInvalidOutputStyle(
|
|
12
|
+
if (invalidStyle !== undefined) {
|
|
13
|
+
throw new UserError(describeInvalidOutputStyle(invalidStyle));
|
|
14
14
|
}
|
|
15
15
|
return styles;
|
|
16
16
|
}
|