@williamthorsen/nmr 0.38.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 +84 -0
- package/.readyup/kits/default.js +84 -79
- package/.readyup/manifest.json +5 -5
- package/CHANGELOG.md +70 -0
- package/README.md +37 -11
- package/dist/esm/check-cache.d.ts +2 -2
- 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 +2 -2
- package/dist/esm/commands/build-output.js +9 -9
- package/dist/esm/commands/build.js +65 -63
- package/dist/esm/commands/clean.js +11 -11
- package/dist/esm/commands/ensure-prepublish-hooks.d.ts +2 -2
- package/dist/esm/commands/ensure-prepublish-hooks.js +41 -41
- package/dist/esm/commands/fmt.js +33 -33
- package/dist/esm/commands/report-catalog.js +9 -7
- package/dist/esm/commands/report-overrides.js +7 -7
- package/dist/esm/config.js +25 -25
- package/dist/esm/context.js +8 -8
- package/dist/esm/help.d.ts +2 -2
- package/dist/esm/help.js +21 -21
- 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/prettier.js +5 -5
- package/dist/esm/recording.d.ts +5 -5
- package/dist/esm/recording.js +21 -14
- package/dist/esm/report-format.d.ts +1 -1
- package/dist/esm/report-format.js +7 -7
- package/dist/esm/resolveBinStyles.js +3 -3
- package/dist/esm/resolver.d.ts +2 -2
- package/dist/esm/resolver.js +15 -15
- package/dist/esm/runCli.js +252 -186
- package/dist/esm/runner.d.ts +1 -1
- package/dist/esm/runner.js +18 -18
- package/dist/esm/steps.d.ts +13 -1
- package/dist/esm/steps.js +32 -27
- 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 +9 -2
- package/dist/esm/verdict.js +63 -50
- 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/reporting.md +6 -2
- package/docs/vitest.md +6 -6
- package/docs/workspace.md +24 -1
- package/package.json +3 -4
- package/dist/esm/helpers/workspace-patterns.d.ts +0 -8
- package/dist/esm/helpers/workspace-patterns.js +0 -29
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export function formatDuration(
|
|
2
|
-
const total = Math.max(0,
|
|
1
|
+
export function formatDuration(durationMs) {
|
|
2
|
+
const total = Math.max(0, durationMs);
|
|
3
3
|
if (total < MILLISECONDS_PER_MINUTE) {
|
|
4
4
|
return `${truncateToTenth(total)}s`;
|
|
5
5
|
}
|
|
@@ -10,11 +10,11 @@ export function formatDuration(milliseconds) {
|
|
|
10
10
|
}
|
|
11
11
|
return joinUnits(`${Math.floor(totalMinutes / MINUTES_PER_HOUR)}h`, totalMinutes % MINUTES_PER_HOUR, 'm');
|
|
12
12
|
}
|
|
13
|
-
export function formatSaving(
|
|
14
|
-
if (!Number.isFinite(
|
|
13
|
+
export function formatSaving(durationMs) {
|
|
14
|
+
if (!Number.isFinite(durationMs) || durationMs < MILLISECONDS_PER_SECOND) {
|
|
15
15
|
return undefined;
|
|
16
16
|
}
|
|
17
|
-
return `saved ~${formatDuration(
|
|
17
|
+
return `saved ~${formatDuration(durationMs)}`;
|
|
18
18
|
}
|
|
19
19
|
const MILLISECONDS_PER_MINUTE = 60_000;
|
|
20
20
|
const MILLISECONDS_PER_SECOND = 1_000;
|
|
@@ -23,6 +23,6 @@ const SECONDS_PER_MINUTE = 60;
|
|
|
23
23
|
function joinUnits(major, minor, minorUnit) {
|
|
24
24
|
return minor === 0 ? major : `${major} ${minor}${minorUnit}`;
|
|
25
25
|
}
|
|
26
|
-
function truncateToTenth(
|
|
27
|
-
return Math.floor(
|
|
26
|
+
function truncateToTenth(durationMs) {
|
|
27
|
+
return Math.floor(durationMs / 100) / 10;
|
|
28
28
|
}
|
|
@@ -5,31 +5,31 @@ import process from 'node:process';
|
|
|
5
5
|
import { isObject } from "./type-guards.js";
|
|
6
6
|
const SELECTION_MARKER = 'nmr-selected-scope';
|
|
7
7
|
export function readFilterSelection(pattern, monorepoRoot) {
|
|
8
|
-
const
|
|
8
|
+
const listingRun = spawnSync('pnpm', ['ls', '--filter', pattern, '--depth', '-1', '--json'], {
|
|
9
9
|
cwd: monorepoRoot,
|
|
10
10
|
encoding: 'utf8',
|
|
11
11
|
});
|
|
12
|
-
const
|
|
13
|
-
return
|
|
12
|
+
const probeReading = interpretSelectionProbe({ error: listingRun.error, status: listingRun.status, stdout: listingRun.stdout }, monorepoRoot);
|
|
13
|
+
return probeReading === 'root-only' ? readDelegateSelection(pattern, monorepoRoot) : probeReading;
|
|
14
14
|
}
|
|
15
15
|
export function interpretSelectionProbe(probe, monorepoRoot) {
|
|
16
16
|
if (probe.error !== undefined || probe.status !== 0) {
|
|
17
17
|
return 'unresolved';
|
|
18
18
|
}
|
|
19
|
-
let
|
|
19
|
+
let parsedListing;
|
|
20
20
|
try {
|
|
21
|
-
|
|
21
|
+
parsedListing = JSON.parse(probe.stdout);
|
|
22
22
|
}
|
|
23
23
|
catch {
|
|
24
24
|
return 'unresolved';
|
|
25
25
|
}
|
|
26
|
-
if (!Array.isArray(
|
|
26
|
+
if (!Array.isArray(parsedListing)) {
|
|
27
27
|
return 'unresolved';
|
|
28
28
|
}
|
|
29
|
-
if (
|
|
29
|
+
if (parsedListing.length === 0) {
|
|
30
30
|
return 'empty';
|
|
31
31
|
}
|
|
32
|
-
const packageCount =
|
|
32
|
+
const packageCount = parsedListing.filter((entry) => !isRootEntry(entry, monorepoRoot)).length;
|
|
33
33
|
if (packageCount === 0) {
|
|
34
34
|
return 'root-only';
|
|
35
35
|
}
|
|
@@ -45,9 +45,9 @@ function isRootEntry(entry, monorepoRoot) {
|
|
|
45
45
|
if (!isObject(entry) || typeof entry['path'] !== 'string') {
|
|
46
46
|
return false;
|
|
47
47
|
}
|
|
48
|
-
return
|
|
48
|
+
return resolveRealPath(entry['path']) === resolveRealPath(monorepoRoot);
|
|
49
49
|
}
|
|
50
|
-
function
|
|
50
|
+
function resolveRealPath(target) {
|
|
51
51
|
try {
|
|
52
52
|
return realpathSync(target);
|
|
53
53
|
}
|
|
@@ -56,6 +56,6 @@ function readRealPath(target) {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
function readDelegateSelection(pattern, monorepoRoot) {
|
|
59
|
-
const
|
|
60
|
-
return interpretDelegateProbe({ error:
|
|
59
|
+
const probeRun = spawnSync('pnpm', ['--filter', pattern, 'exec', process.execPath, '--eval', `process.stdout.write('${SELECTION_MARKER}')`], { cwd: monorepoRoot, encoding: 'utf8' });
|
|
60
|
+
return interpretDelegateProbe({ error: probeRun.error, status: probeRun.status, stdout: probeRun.stdout });
|
|
61
61
|
}
|
|
@@ -1,33 +1,33 @@
|
|
|
1
1
|
export function findClosestName(name, candidates) {
|
|
2
2
|
const ceiling = Math.max(1, Math.floor(name.length / 4));
|
|
3
|
-
let
|
|
4
|
-
let
|
|
3
|
+
let closestName;
|
|
4
|
+
let shortestDistance = Infinity;
|
|
5
5
|
for (const candidate of candidates) {
|
|
6
6
|
const distance = measureDistance(name, candidate);
|
|
7
|
-
if (distance <
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
if (distance < shortestDistance) {
|
|
8
|
+
shortestDistance = distance;
|
|
9
|
+
closestName = candidate;
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
|
-
return
|
|
12
|
+
return shortestDistance <= ceiling ? closestName : undefined;
|
|
13
13
|
}
|
|
14
14
|
function measureDistance(first, second) {
|
|
15
15
|
if (first.length === 0 || second.length === 0) {
|
|
16
16
|
return Math.max(first.length, second.length);
|
|
17
17
|
}
|
|
18
|
-
let
|
|
19
|
-
let
|
|
18
|
+
let previousRow = Array.from({ length: second.length + 1 }, (_unused, column) => column);
|
|
19
|
+
let currentRow = Array.from({ length: second.length + 1 }, () => 0);
|
|
20
20
|
for (let row = 1; row <= first.length; row += 1) {
|
|
21
|
-
|
|
21
|
+
currentRow[0] = row;
|
|
22
22
|
for (let column = 1; column <= second.length; column += 1) {
|
|
23
|
-
const substitution = readCell(
|
|
24
|
-
|
|
23
|
+
const substitution = readCell(previousRow, column - 1) + (first[row - 1] === second[column - 1] ? 0 : 1);
|
|
24
|
+
currentRow[column] = Math.min(readCell(currentRow, column - 1) + 1, readCell(previousRow, column) + 1, substitution);
|
|
25
25
|
}
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
const completedRow = currentRow;
|
|
27
|
+
currentRow = previousRow;
|
|
28
|
+
previousRow = completedRow;
|
|
29
29
|
}
|
|
30
|
-
return readCell(
|
|
30
|
+
return readCell(previousRow, second.length);
|
|
31
31
|
}
|
|
32
32
|
function readCell(row, index) {
|
|
33
33
|
const value = row[index];
|
|
@@ -25,14 +25,14 @@ export declare function readPackageJson(dir: string): PackageJson;
|
|
|
25
25
|
/**
|
|
26
26
|
* Returns the `pnpm.overrides` block a package.json declares, every key it holds and each value as written.
|
|
27
27
|
*/
|
|
28
|
-
export declare function getPnpmOverrides(
|
|
28
|
+
export declare function getPnpmOverrides(packageJson: PackageJson): Record<string, unknown> | undefined;
|
|
29
29
|
/**
|
|
30
30
|
* Parses a `package.json`'s text, rejecting what does not parse.
|
|
31
31
|
*
|
|
32
32
|
* A file the user wrote by hand is theirs to fix, so the failure names it rather than reporting the parser's
|
|
33
33
|
* own stack.
|
|
34
34
|
*/
|
|
35
|
-
export declare function parsePackageJson(
|
|
35
|
+
export declare function parsePackageJson(rawText: string, file: string): unknown;
|
|
36
36
|
/**
|
|
37
37
|
* Narrows a `scripts` record to strings, rejecting any value that is not one.
|
|
38
38
|
*
|
|
@@ -14,45 +14,46 @@ export const DEPENDENCY_FIELDS = [
|
|
|
14
14
|
];
|
|
15
15
|
export function readPackageJson(dir) {
|
|
16
16
|
const file = resolvePackageJsonPath(dir);
|
|
17
|
-
const
|
|
18
|
-
if (!isObject(
|
|
17
|
+
const parsedManifest = parsePackageJson(readFileSync(file, 'utf8'), file);
|
|
18
|
+
if (!isObject(parsedManifest)) {
|
|
19
19
|
throw new UserError(`Invalid package.json in ${dir}: expected an object`);
|
|
20
20
|
}
|
|
21
|
-
const
|
|
22
|
-
if (typeof
|
|
23
|
-
|
|
24
|
-
if (
|
|
25
|
-
|
|
26
|
-
if (typeof
|
|
27
|
-
|
|
28
|
-
if (typeof
|
|
29
|
-
|
|
30
|
-
if (isObject(parsed['scripts'])) {
|
|
31
|
-
pkg.scripts = readScriptRecord(dir, parsed['scripts']);
|
|
21
|
+
const packageJson = {};
|
|
22
|
+
if (typeof parsedManifest['name'] === 'string')
|
|
23
|
+
packageJson.name = parsedManifest['name'];
|
|
24
|
+
if (parsedManifest['private'] === true)
|
|
25
|
+
packageJson.private = true;
|
|
26
|
+
if (typeof parsedManifest['version'] === 'string')
|
|
27
|
+
packageJson.version = parsedManifest['version'];
|
|
28
|
+
if (typeof parsedManifest['packageManager'] === 'string') {
|
|
29
|
+
packageJson.packageManager = parsedManifest['packageManager'];
|
|
32
30
|
}
|
|
33
|
-
if (isObject(
|
|
34
|
-
|
|
31
|
+
if (isObject(parsedManifest['scripts'])) {
|
|
32
|
+
packageJson.scripts = readScriptRecord(dir, parsedManifest['scripts']);
|
|
33
|
+
}
|
|
34
|
+
if (isObject(parsedManifest['pnpm'])) {
|
|
35
|
+
const pnpm = parsedManifest['pnpm'];
|
|
35
36
|
if (isObject(pnpm['overrides'])) {
|
|
36
|
-
|
|
37
|
+
packageJson.pnpm = { overrides: pnpm['overrides'] };
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
for (const field of DEPENDENCY_FIELDS) {
|
|
40
|
-
const
|
|
41
|
-
if (isObject(
|
|
42
|
-
|
|
41
|
+
const declaredDependencies = parsedManifest[field];
|
|
42
|
+
if (isObject(declaredDependencies)) {
|
|
43
|
+
packageJson[field] = readStringValues(declaredDependencies);
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
|
-
return
|
|
46
|
+
return packageJson;
|
|
46
47
|
}
|
|
47
|
-
export function getPnpmOverrides(
|
|
48
|
-
if (!isObject(
|
|
48
|
+
export function getPnpmOverrides(packageJson) {
|
|
49
|
+
if (!isObject(packageJson.pnpm))
|
|
49
50
|
return undefined;
|
|
50
|
-
const overrides =
|
|
51
|
+
const overrides = packageJson.pnpm.overrides;
|
|
51
52
|
return isObject(overrides) ? overrides : undefined;
|
|
52
53
|
}
|
|
53
|
-
export function parsePackageJson(
|
|
54
|
+
export function parsePackageJson(rawText, file) {
|
|
54
55
|
try {
|
|
55
|
-
return JSON.parse(
|
|
56
|
+
return JSON.parse(rawText);
|
|
56
57
|
}
|
|
57
58
|
catch (error) {
|
|
58
59
|
throw new UserError(`Invalid package.json at ${file}: ${describeError(error)}`);
|
|
@@ -16,7 +16,7 @@ export declare const TRANSCRIPT_LIMIT_BYTES = 262144;
|
|
|
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
21
|
* Composes what a pass persists from what its command wrote, or `undefined` where it wrote nothing worth
|
|
22
22
|
* keeping.
|
|
@@ -25,6 +25,6 @@ export declare function cleanTranscript(raw: string): string;
|
|
|
25
25
|
* failure: nmr reads each through a pipe of its own, so their true interleaving is not among the facts it
|
|
26
26
|
* holds. A stream carrying nothing contributes neither content nor marker.
|
|
27
27
|
*/
|
|
28
|
-
export declare function composeTranscript(
|
|
28
|
+
export declare function composeTranscript(retainedOutput: RetainedOutput): string | undefined;
|
|
29
29
|
/** Renders the line that stands in for dropped bytes, in nmr's voice so 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');
|
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
|
@@ -28,12 +28,12 @@ export type KeyDifference = {
|
|
|
28
28
|
ingredient: 'command-string';
|
|
29
29
|
} | {
|
|
30
30
|
ingredient: 'nmr-version';
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
currentVersion: string;
|
|
32
|
+
recordedVersion: string;
|
|
33
33
|
} | {
|
|
34
34
|
ingredient: 'node-version';
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
currentVersion: string;
|
|
36
|
+
recordedVersion: string;
|
|
37
37
|
} | {
|
|
38
38
|
ingredient: 'other';
|
|
39
39
|
};
|
|
@@ -99,7 +99,7 @@ export declare function renderRefusal(options: {
|
|
|
99
99
|
export declare function resolveRecording(options: {
|
|
100
100
|
anchorDir: string;
|
|
101
101
|
command: string;
|
|
102
|
-
|
|
102
|
+
currentIdentity: RunIdentity;
|
|
103
103
|
isCacheable: boolean;
|
|
104
104
|
key: string | undefined;
|
|
105
105
|
monorepoRoot: string;
|
package/dist/esm/recording.js
CHANGED
|
@@ -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) {
|
|
@@ -45,14 +48,14 @@ function describeDifference(difference) {
|
|
|
45
48
|
case 'command-string':
|
|
46
49
|
return 'over a command chain 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
55
|
return 'under an install or environment 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
|
}
|
|
@@ -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
|
}
|
|
@@ -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
|
}
|
|
@@ -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
|
}
|
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(', ')}]`;
|
|
@@ -59,9 +59,9 @@ function describeElement(element) {
|
|
|
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
|
}
|