@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/help.js
CHANGED
|
@@ -1,35 +1,41 @@
|
|
|
1
1
|
import { isHookName } from "./helpers/hook-name.js";
|
|
2
|
+
import { OUTPUT_STYLE_ENV_VAR, OUTPUT_STYLE_FLAG } from "./output-style.js";
|
|
2
3
|
import { buildRootRegistry, buildWorkspaceRegistry, describeScript, isSelfReferential, readPackageJsonScripts, } from "./resolver.js";
|
|
3
|
-
export function generateHelp(config, packageDir,
|
|
4
|
+
export function generateHelp(config, packageDir, shouldUseRoot) {
|
|
4
5
|
const lines = [
|
|
5
6
|
'Usage: nmr [flags] <command> [args...]',
|
|
6
7
|
'',
|
|
7
8
|
'Flags:',
|
|
8
|
-
' -F, --filter <pattern>
|
|
9
|
-
' -R, --recursive
|
|
10
|
-
' -w, --workspace-root
|
|
11
|
-
" -q, --quiet
|
|
12
|
-
' --json
|
|
13
|
-
' --log
|
|
14
|
-
' --no-cache
|
|
15
|
-
|
|
16
|
-
'
|
|
9
|
+
' -F, --filter <pattern> Run command in packages whose manifest name matches',
|
|
10
|
+
' -R, --recursive Run command in all packages',
|
|
11
|
+
' -w, --workspace-root Use root scripts, running at the monorepo root',
|
|
12
|
+
" -q, --quiet Suppress command output, keeping nmr's verdicts",
|
|
13
|
+
' --json Report one JSON object per command, withholding command output',
|
|
14
|
+
' --log Print the recorded run instead of running (up to 256 KiB)',
|
|
15
|
+
' --no-cache Run even if this tree already passed; record the result',
|
|
16
|
+
` ${OUTPUT_STYLE_FLAG} <style> Output style: auto (default), plain, or rich`,
|
|
17
|
+
' -?, --help Show this help',
|
|
18
|
+
' -V, --version Show version number',
|
|
19
|
+
'',
|
|
20
|
+
'Environment:',
|
|
21
|
+
` ${OUTPUT_STYLE_ENV_VAR} Output style: auto (default), plain, or rich. auto prints`,
|
|
22
|
+
' plain, without emoji, when CI is set or the stream is not a terminal.',
|
|
17
23
|
'',
|
|
18
24
|
'Workspace commands:',
|
|
19
25
|
];
|
|
20
26
|
const overrides = packageDir === undefined ? {} : collectOverrides(packageDir);
|
|
21
27
|
let hadOverride = false;
|
|
22
28
|
const workspaceRegistry = filterHooks(buildWorkspaceRegistry(config));
|
|
23
|
-
const
|
|
24
|
-
if (
|
|
29
|
+
const workspaceMarkedNames = !shouldUseRoot ? applyOverrides(workspaceRegistry, overrides) : new Set();
|
|
30
|
+
if (workspaceMarkedNames.size > 0)
|
|
25
31
|
hadOverride = true;
|
|
26
|
-
formatRegistry(workspaceRegistry,
|
|
32
|
+
formatRegistry(workspaceRegistry, workspaceMarkedNames, lines);
|
|
27
33
|
lines.push('', 'Root commands:');
|
|
28
34
|
const rootRegistry = filterHooks(buildRootRegistry(config));
|
|
29
|
-
const
|
|
30
|
-
if (
|
|
35
|
+
const rootMarkedNames = shouldUseRoot ? applyOverrides(rootRegistry, overrides) : new Set();
|
|
36
|
+
if (rootMarkedNames.size > 0)
|
|
31
37
|
hadOverride = true;
|
|
32
|
-
formatRegistry(rootRegistry,
|
|
38
|
+
formatRegistry(rootRegistry, rootMarkedNames, lines);
|
|
33
39
|
if (hadOverride) {
|
|
34
40
|
lines.push('', '* Overridden by package.json');
|
|
35
41
|
}
|
|
@@ -39,43 +45,43 @@ function collectOverrides(packageDir) {
|
|
|
39
45
|
const scripts = readPackageJsonScripts(packageDir);
|
|
40
46
|
if (!scripts)
|
|
41
47
|
return {};
|
|
42
|
-
const
|
|
48
|
+
const candidateOverrides = {};
|
|
43
49
|
for (const [name, value] of Object.entries(scripts)) {
|
|
44
50
|
if (isHookName(name))
|
|
45
51
|
continue;
|
|
46
52
|
if (isSelfReferential(value, name, packageDir))
|
|
47
53
|
continue;
|
|
48
|
-
|
|
54
|
+
candidateOverrides[name] = value;
|
|
49
55
|
}
|
|
50
|
-
return
|
|
56
|
+
return candidateOverrides;
|
|
51
57
|
}
|
|
52
58
|
function applyOverrides(registry, overrides) {
|
|
53
|
-
const
|
|
59
|
+
const markedNames = new Set();
|
|
54
60
|
for (const [name, value] of Object.entries(overrides)) {
|
|
55
61
|
if (!Object.hasOwn(registry, name)) {
|
|
56
62
|
continue;
|
|
57
63
|
}
|
|
58
64
|
registry[name] = value;
|
|
59
|
-
|
|
65
|
+
markedNames.add(name);
|
|
60
66
|
}
|
|
61
|
-
return
|
|
67
|
+
return markedNames;
|
|
62
68
|
}
|
|
63
69
|
function filterHooks(registry) {
|
|
64
|
-
const
|
|
70
|
+
const hooklessRegistry = {};
|
|
65
71
|
for (const [key, value] of Object.entries(registry)) {
|
|
66
72
|
if (!isHookName(key))
|
|
67
|
-
|
|
73
|
+
hooklessRegistry[key] = value;
|
|
68
74
|
}
|
|
69
|
-
return
|
|
75
|
+
return hooklessRegistry;
|
|
70
76
|
}
|
|
71
|
-
function formatRegistry(registry,
|
|
77
|
+
function formatRegistry(registry, markedNames, lines) {
|
|
72
78
|
const keys = Object.keys(registry);
|
|
73
79
|
if (keys.length === 0)
|
|
74
80
|
return;
|
|
75
|
-
const
|
|
76
|
-
const
|
|
81
|
+
const maxKeyLength = Math.max(...keys.map((k) => k.length));
|
|
82
|
+
const padWidth = Math.max(maxKeyLength + 1 + 2, 20);
|
|
77
83
|
for (const [key, value] of Object.entries(registry)) {
|
|
78
|
-
const marker =
|
|
79
|
-
lines.push(` ${(key + marker).padEnd(
|
|
84
|
+
const marker = markedNames.has(key) ? '*' : ' ';
|
|
85
|
+
lines.push(` ${(key + marker).padEnd(padWidth)} ${describeScript(value)}`);
|
|
80
86
|
}
|
|
81
87
|
}
|
|
@@ -7,14 +7,14 @@ export function clampToBytes(value, budgetBytes) {
|
|
|
7
7
|
if (budget < 0) {
|
|
8
8
|
return '';
|
|
9
9
|
}
|
|
10
|
-
let
|
|
10
|
+
let keptText = '';
|
|
11
11
|
let bytes = 0;
|
|
12
12
|
for (const character of value) {
|
|
13
13
|
const size = Buffer.byteLength(character);
|
|
14
14
|
if (bytes + size > budget)
|
|
15
15
|
break;
|
|
16
|
-
|
|
16
|
+
keptText += character;
|
|
17
17
|
bytes += size;
|
|
18
18
|
}
|
|
19
|
-
return `${
|
|
19
|
+
return `${keptText}${TRUNCATION_MARK}`;
|
|
20
20
|
}
|
|
@@ -6,7 +6,7 @@ export interface BoundedBuffer {
|
|
|
6
6
|
/** Retains a chunk, dropping bytes from the middle once both ends are full. */
|
|
7
7
|
append: (chunk: Buffer) => void;
|
|
8
8
|
/** Renders the retained bytes, with a marker naming the dropped count standing in for what was dropped. */
|
|
9
|
-
|
|
9
|
+
readBuffer: () => Buffer;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* Creates an accumulator that retains the first and last bytes it is fed and drops the middle.
|
|
@@ -40,7 +40,7 @@ export function createBoundedBuffer(options = {}) {
|
|
|
40
40
|
tailOffset = (tailOffset + chunk.length) % tailLimitBytes;
|
|
41
41
|
tailBytes = Math.min(tailLimitBytes, tailBytes + chunk.length);
|
|
42
42
|
}
|
|
43
|
-
function
|
|
43
|
+
function readBuffer() {
|
|
44
44
|
const headBuffer = Buffer.concat(headChunks);
|
|
45
45
|
if (tailRing === undefined) {
|
|
46
46
|
return headBuffer;
|
|
@@ -53,5 +53,5 @@ export function createBoundedBuffer(options = {}) {
|
|
|
53
53
|
}
|
|
54
54
|
return Buffer.concat([headBuffer, Buffer.from(formatElisionMarker(elidedBytes)), tailBuffer]);
|
|
55
55
|
}
|
|
56
|
-
return { append,
|
|
56
|
+
return { append, readBuffer };
|
|
57
57
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* `1m 30s`, where rounding would claim `2m`. Below a minute the tenth is kept, since that is the scale at which
|
|
6
6
|
* one check's runtime differs from another's; a whole number sheds the `.0` rather than reading `12.0s`.
|
|
7
7
|
*/
|
|
8
|
-
export declare function formatDuration(
|
|
8
|
+
export declare function formatDuration(durationMs: number): string;
|
|
9
9
|
/**
|
|
10
10
|
* Renders the clause a skip spends on the time it saved, or `undefined` when there is no saving worth naming.
|
|
11
11
|
* A skip that can say what it saved earns the clause; one that cannot says nothing, and the caller drops it
|
|
@@ -14,4 +14,4 @@ export declare function formatDuration(milliseconds: number): string;
|
|
|
14
14
|
* Guards its own input rather than trusting the caller's, because the clause is shared: a non-finite duration
|
|
15
15
|
* compares false against the threshold, and would reach a reader as `saved ~NaNs`.
|
|
16
16
|
*/
|
|
17
|
-
export declare function formatSaving(
|
|
17
|
+
export declare function formatSaving(durationMs: number): string | undefined;
|
|
@@ -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');
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Writable } from 'node:stream';
|
|
2
|
+
import { type StreamStyleResolution } from '@williamthorsen/nmr-core';
|
|
3
|
+
/**
|
|
4
|
+
* Reports whether a stream is backed by a terminal, which is what detection reads where no source names a
|
|
5
|
+
* style. A stream carrying no `isTTY`, which is the one a test injects, is not one.
|
|
6
|
+
*/
|
|
7
|
+
export declare function isTerminalStream(stream: Writable): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Carries the resolved style down the spawned chain, so the flag reaches every process rather than the first
|
|
10
|
+
* and a child writing to a pipe renders as its parent does rather than detecting plain on its own descriptor.
|
|
11
|
+
*
|
|
12
|
+
* Deliberately not a keyed variable, on neither the pass key nor the retention key: it changes how a run
|
|
13
|
+
* renders and never what a command concludes, so folding it into either would stop a plain run from hitting a
|
|
14
|
+
* pass a rich one recorded.
|
|
15
|
+
*/
|
|
16
|
+
export declare const OUTPUT_STYLE_ENV_VAR = "NMR_OUTPUT_STYLE";
|
|
17
|
+
/**
|
|
18
|
+
* The flag naming a style for one invocation, which outranks `OUTPUT_STYLE_ENV_VAR`.
|
|
19
|
+
*
|
|
20
|
+
* `nmr` alone carries it. A standalone bin takes the variable, which `nmr <command>` exports to it, so adding
|
|
21
|
+
* a style to a bin does not mean adding a flag to each bin's own parser.
|
|
22
|
+
*/
|
|
23
|
+
export declare const OUTPUT_STYLE_FLAG = "--output-style";
|
|
24
|
+
/**
|
|
25
|
+
* Resolves the style of each output stream: the flag, then `NMR_OUTPUT_STYLE`, then detection from `CI`, the
|
|
26
|
+
* stream's terminal state, and `TERM`.
|
|
27
|
+
*
|
|
28
|
+
* Never throws. A value naming no style is returned in `invalid`, for the caller to report with
|
|
29
|
+
* `describeInvalidOutputStyle`, because the caller has to render that complaint in some style.
|
|
30
|
+
*
|
|
31
|
+
* The flag's raw value is handed to nmr-core as an assignment rather than narrowed here, so nmr rejects a
|
|
32
|
+
* misspelling in the words its siblings use and no second ladder can come to name a different one.
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveOutputStyles(options: ResolveOutputStylesOptions): StreamStyleResolution;
|
|
35
|
+
export interface ResolveOutputStylesOptions {
|
|
36
|
+
env: NodeJS.ProcessEnv;
|
|
37
|
+
/** The value the flag was given, absent where the invocation carries none, as a bin's invocation always does. */
|
|
38
|
+
flagValue?: string | undefined;
|
|
39
|
+
stderrIsTty: boolean;
|
|
40
|
+
stdoutIsTty: boolean;
|
|
41
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { resolveStreamStyles } from '@williamthorsen/nmr-core';
|
|
2
|
+
export function isTerminalStream(stream) {
|
|
3
|
+
return 'isTTY' in stream && stream.isTTY === true;
|
|
4
|
+
}
|
|
5
|
+
export const OUTPUT_STYLE_ENV_VAR = 'NMR_OUTPUT_STYLE';
|
|
6
|
+
export const OUTPUT_STYLE_FLAG = '--output-style';
|
|
7
|
+
export function resolveOutputStyles(options) {
|
|
8
|
+
const { env, flagValue, stderrIsTty, stdoutIsTty } = options;
|
|
9
|
+
return resolveStreamStyles({
|
|
10
|
+
argv: flagValue === undefined ? [] : [`${OUTPUT_STYLE_FLAG}=${flagValue}`],
|
|
11
|
+
env,
|
|
12
|
+
envVar: OUTPUT_STYLE_ENV_VAR,
|
|
13
|
+
flag: OUTPUT_STYLE_FLAG,
|
|
14
|
+
stderrIsTty,
|
|
15
|
+
stdoutIsTty,
|
|
16
|
+
});
|
|
17
|
+
}
|
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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type OutputStyle } from '@williamthorsen/nmr-core';
|
|
1
2
|
import { type CheckCacheEntry } from './check-cache.js';
|
|
2
3
|
/**
|
|
3
4
|
* What this invocation would be recorded under, insofar as an entry records the same facts. A mismatch is read
|
|
@@ -27,12 +28,12 @@ export type KeyDifference = {
|
|
|
27
28
|
ingredient: 'command-string';
|
|
28
29
|
} | {
|
|
29
30
|
ingredient: 'nmr-version';
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
currentVersion: string;
|
|
32
|
+
recordedVersion: string;
|
|
32
33
|
} | {
|
|
33
34
|
ingredient: 'node-version';
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
currentVersion: string;
|
|
36
|
+
recordedVersion: string;
|
|
36
37
|
} | {
|
|
37
38
|
ingredient: 'other';
|
|
38
39
|
};
|
|
@@ -75,6 +76,7 @@ export declare function renderRecording(options: {
|
|
|
75
76
|
command: string;
|
|
76
77
|
recording: Recording;
|
|
77
78
|
scope: string;
|
|
79
|
+
style: OutputStyle;
|
|
78
80
|
}): string;
|
|
79
81
|
/**
|
|
80
82
|
* Renders a refusal on the one line a fan-out can attribute, in the grammar a verdict uses: the scope, the
|
|
@@ -84,6 +86,7 @@ export declare function renderRefusal(options: {
|
|
|
84
86
|
command: string;
|
|
85
87
|
refusal: RecordingRefusal;
|
|
86
88
|
scope: string;
|
|
89
|
+
style: OutputStyle;
|
|
87
90
|
}): string;
|
|
88
91
|
/**
|
|
89
92
|
* Resolves what one scope has to show for one command.
|
|
@@ -96,7 +99,7 @@ export declare function renderRefusal(options: {
|
|
|
96
99
|
export declare function resolveRecording(options: {
|
|
97
100
|
anchorDir: string;
|
|
98
101
|
command: string;
|
|
99
|
-
|
|
102
|
+
currentIdentity: RunIdentity;
|
|
100
103
|
isCacheable: boolean;
|
|
101
104
|
key: string | undefined;
|
|
102
105
|
monorepoRoot: string;
|