@williamthorsen/nmr 0.48.0 → 0.49.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 +158 -0
- package/.readyup/kits/default.js +1527 -1460
- package/.readyup/manifest.json +96 -11
- package/CHANGELOG.md +41 -0
- package/README.md +36 -8
- package/agents/guidance/rulebooks/nmr.md +2 -2
- package/dist/esm/check-cache-build-output.d.ts +24 -0
- package/dist/esm/check-cache-build-output.js +49 -0
- package/dist/esm/check-cache-delegation.d.ts +27 -0
- package/dist/esm/check-cache-delegation.js +92 -0
- package/dist/esm/check-cache-entries.d.ts +109 -0
- package/dist/esm/check-cache-entries.js +87 -0
- package/dist/esm/check-cache-gate.d.ts +64 -0
- package/dist/esm/check-cache-gate.js +226 -0
- package/dist/esm/check-cache-key.d.ts +47 -0
- package/dist/esm/check-cache-key.js +77 -0
- package/dist/esm/check-cache-record.d.ts +69 -0
- package/dist/esm/check-cache-record.js +167 -0
- package/dist/esm/check-cache.d.ts +8 -176
- package/dist/esm/check-cache.js +9 -214
- package/dist/esm/child-env.d.ts +57 -0
- package/dist/esm/child-env.js +59 -0
- package/dist/esm/child-process.d.ts +55 -0
- package/dist/esm/child-process.js +112 -0
- package/dist/esm/commands/build.d.ts +0 -16
- package/dist/esm/commands/build.js +9 -214
- package/dist/esm/commands/clean.js +3 -2
- package/dist/esm/commands/compiler.d.ts +18 -0
- package/dist/esm/commands/compiler.js +49 -0
- package/dist/esm/commands/fmt.d.ts +0 -39
- package/dist/esm/commands/fmt.js +4 -81
- package/dist/esm/commands/format-targets.d.ts +39 -0
- package/dist/esm/commands/format-targets.js +83 -0
- package/dist/esm/commands/rewrite-specifiers.d.ts +17 -0
- package/dist/esm/commands/rewrite-specifiers.js +122 -0
- package/dist/esm/commands/tsconfig-chain.d.ts +8 -0
- package/dist/esm/commands/tsconfig-chain.js +63 -0
- package/dist/esm/config-check-cache.d.ts +17 -0
- package/dist/esm/config-check-cache.js +63 -0
- package/dist/esm/config-validation.d.ts +20 -0
- package/dist/esm/config-validation.js +236 -0
- package/dist/esm/config.js +4 -286
- package/dist/esm/crossing-diagnostics.d.ts +39 -0
- package/dist/esm/crossing-diagnostics.js +132 -0
- package/dist/esm/default-scripts.js +1 -1
- package/dist/esm/delegate-host.d.ts +43 -0
- package/dist/esm/delegate-host.js +178 -0
- package/dist/esm/delegation-target.d.ts +52 -0
- package/dist/esm/delegation-target.js +71 -0
- package/dist/esm/fan-out.d.ts +57 -0
- package/dist/esm/fan-out.js +146 -0
- package/dist/esm/glyphs.d.ts +1 -1
- package/dist/esm/help.js +9 -3
- package/dist/esm/helpers/deriveExcerpt.d.ts +9 -0
- package/dist/esm/helpers/deriveExcerpt.js +1 -1
- package/dist/esm/helpers/renderQuotedList.d.ts +2 -0
- package/dist/esm/helpers/renderQuotedList.js +9 -0
- package/dist/esm/hooks.d.ts +17 -0
- package/dist/esm/hooks.js +23 -0
- package/dist/esm/invocation-args.d.ts +40 -0
- package/dist/esm/invocation-args.js +178 -0
- package/dist/esm/invocation.d.ts +67 -0
- package/dist/esm/invocation.js +3 -0
- package/dist/esm/machine-lock.d.ts +2 -0
- package/dist/esm/machine-lock.js +1 -1
- package/dist/esm/output-channel.d.ts +23 -0
- package/dist/esm/output-channel.js +14 -0
- package/dist/esm/pass-manifest.d.ts +53 -0
- package/dist/esm/pass-manifest.js +89 -0
- package/dist/esm/presentation.d.ts +47 -0
- package/dist/esm/presentation.js +32 -0
- package/dist/esm/recording.d.ts +1 -1
- package/dist/esm/recording.js +2 -2
- package/dist/esm/remote-host-session.d.ts +62 -0
- package/dist/esm/remote-host-session.js +277 -0
- package/dist/esm/remote-probe-host.d.ts +22 -0
- package/dist/esm/remote-probe-host.js +50 -0
- package/dist/esm/remote-probe.d.ts +31 -0
- package/dist/esm/remote-probe.js +78 -0
- package/dist/esm/remote-run.d.ts +28 -73
- package/dist/esm/remote-run.js +103 -165
- package/dist/esm/remote-session.d.ts +40 -0
- package/dist/esm/remote-session.js +151 -0
- package/dist/esm/remote-snapshot.d.ts +21 -0
- package/dist/esm/remote-snapshot.js +80 -0
- package/dist/esm/replay-assembly.d.ts +2 -2
- package/dist/esm/replay-assembly.js +1 -1
- package/dist/esm/run-chain.d.ts +21 -0
- package/dist/esm/run-chain.js +108 -0
- package/dist/esm/runCli.d.ts +1 -9
- package/dist/esm/runCli.js +39 -1002
- package/dist/esm/runner.d.ts +1 -54
- package/dist/esm/runner.js +2 -125
- package/dist/esm/shell-syntax.d.ts +30 -0
- package/dist/esm/shell-syntax.js +120 -0
- package/dist/esm/steps.d.ts +0 -2
- package/dist/esm/steps.js +3 -119
- package/dist/esm/types.d.ts +5 -0
- package/dist/esm/user-config.d.ts +34 -0
- package/dist/esm/user-config.js +93 -0
- package/dist/esm/verbosity.d.ts +6 -0
- package/dist/esm/verbosity.js +7 -7
- package/dist/esm/verdict.d.ts +1 -1
- package/dist/esm/verdict.js +1 -1
- package/docs/check-cache.md +28 -12
- package/docs/delegate.md +96 -16
- package/docs/reporting.md +6 -1
- package/docs/scripts.md +2 -1
- package/package.json +3 -3
- /package/dist/esm/{prettier-default-config.d.ts → prettier-default.config.d.ts} +0 -0
- /package/dist/esm/{prettier-default-config.js → prettier-default.config.js} +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { StreamStyles } from '@williamthorsen/nmr-core';
|
|
2
|
+
import { type TreeSnapshot } from './check-cache.js';
|
|
3
|
+
import type { Invocation } from './invocation.js';
|
|
4
|
+
import { type MachineLock } from './machine-lock.js';
|
|
5
|
+
import { type ReportFormat } from './report-format.js';
|
|
6
|
+
import { type CommandVerbosity } from './verbosity.js';
|
|
7
|
+
/**
|
|
8
|
+
* Returns the environment that tells the processes below this one what the lock came to: They run under its hold,
|
|
9
|
+
* or, when the chain runs unlocked, they skip the lock.
|
|
10
|
+
*/
|
|
11
|
+
export declare function addLockEnv(env: NodeJS.ProcessEnv, lock: MachineLock | undefined): NodeJS.ProcessEnv;
|
|
12
|
+
/**
|
|
13
|
+
* Builds the environment that every process below this one inherits. The snapshot travels down to make a chain
|
|
14
|
+
* of nmr invocations gate on one observation of the tree; a bypass, to cover the whole chain rather than only the
|
|
15
|
+
* command next to which it was typed; the verbosity, to make each process suppress the output of the command
|
|
16
|
+
* that it runs rather than of the subtree beneath it; and the run's identity, to mark the excerpts that a run
|
|
17
|
+
* records at every scope as one run's.
|
|
18
|
+
*
|
|
19
|
+
* The verbosity is written in both modes, so that a chain's loudness is decided once at the top rather than
|
|
20
|
+
* re-derived at every link from an environment that a caller may have set.
|
|
21
|
+
*
|
|
22
|
+
* Trailing arguments bypass the cache for the whole chain, as `--no-cache` does. `openGate` disables this
|
|
23
|
+
* invocation's own gate for them, but the steps below it are separate nmr invocations that do not receive any
|
|
24
|
+
* arguments of their own, and without the bypass a narrowed command would serve part of its work from a recorded
|
|
25
|
+
* pass.
|
|
26
|
+
*
|
|
27
|
+
* `NMR_DELEGATE` is set empty: a command that runs here runs its whole chain here, so that a step or a `-F`/`-R`
|
|
28
|
+
* child of a local run passes itself on neither to the host that the variable names nor to the user config's.
|
|
29
|
+
*
|
|
30
|
+
* The style written down is the resolved one and never `auto`: a child on a pipe renders as this process does
|
|
31
|
+
* rather than detecting plain on its own descriptor. It is the style to which stdout resolved: the verdicts and
|
|
32
|
+
* nearly every other line that a child prints go there.
|
|
33
|
+
*/
|
|
34
|
+
export declare function buildChildEnv(options: {
|
|
35
|
+
env: NodeJS.ProcessEnv;
|
|
36
|
+
format: ReportFormat;
|
|
37
|
+
passthrough: readonly string[];
|
|
38
|
+
runId: string;
|
|
39
|
+
shouldBypassCache: boolean;
|
|
40
|
+
shouldBypassLock: boolean;
|
|
41
|
+
snapshot: TreeSnapshot | undefined;
|
|
42
|
+
styles: StreamStyles;
|
|
43
|
+
verbosity: CommandVerbosity;
|
|
44
|
+
}): NodeJS.ProcessEnv;
|
|
45
|
+
/**
|
|
46
|
+
* Returns the variables of a child environment that the host's nmr receives: those that decide how it reports, whether
|
|
47
|
+
* it bypasses its cache, whether it writes debug notes, and which run it belongs to. The rest describe this machine,
|
|
48
|
+
* such as the tree snapshot and the lock token, or would disable the host's lock on the strength of a local
|
|
49
|
+
* workaround, as an inherited `NMR_NO_LOCK` would; the `--no-lock` flag still reaches the host as an argument.
|
|
50
|
+
*/
|
|
51
|
+
export declare function buildRemoteEnv(childEnv: NodeJS.ProcessEnv): Record<string, string>;
|
|
52
|
+
/**
|
|
53
|
+
* Takes the machine lock for a heavy command that runs, or returns `undefined` for any other invocation. A delegated
|
|
54
|
+
* `--log` reads recordings and runs nothing. A `--no-lock` written after the command name is an argument to the
|
|
55
|
+
* command, so it is reported rather than read.
|
|
56
|
+
*/
|
|
57
|
+
export declare function takeMachineLock(invocation: Invocation): Promise<MachineLock | undefined>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { DEBUG_ENV_VAR, encodeTreeSnapshot, NO_CACHE_ENV_VAR, RUN_ID_ENV_VAR, TREE_SNAPSHOT_ENV_VAR, } from "./check-cache.js";
|
|
2
|
+
import { acquireMachineLock, formatMisplacedNoLockWarning, HEAVY_COMMANDS, LOCK_TOKEN_ENV_VAR, NO_LOCK_ENV_VAR, } from "./machine-lock.js";
|
|
3
|
+
import { OUTPUT_STYLE_ENV_VAR } from "./output-style.js";
|
|
4
|
+
import { DELEGATE_ENV_VAR } from "./remote-run.js";
|
|
5
|
+
import { REPORT_FORMAT_ENV_VAR } from "./report-format.js";
|
|
6
|
+
import { COMMAND_VERBOSITY_ENV_VAR } from "./verbosity.js";
|
|
7
|
+
export function addLockEnv(env, lock) {
|
|
8
|
+
if (lock?.token !== undefined) {
|
|
9
|
+
return { ...env, [LOCK_TOKEN_ENV_VAR]: lock.token };
|
|
10
|
+
}
|
|
11
|
+
if (lock?.isChainUnlocked === true) {
|
|
12
|
+
return { ...env, [NO_LOCK_ENV_VAR]: '1' };
|
|
13
|
+
}
|
|
14
|
+
return env;
|
|
15
|
+
}
|
|
16
|
+
export function buildChildEnv(options) {
|
|
17
|
+
const { env, format, passthrough, runId, shouldBypassCache, shouldBypassLock, snapshot, styles, verbosity } = options;
|
|
18
|
+
const bypassesCache = shouldBypassCache || passthrough.length > 0;
|
|
19
|
+
return {
|
|
20
|
+
...env,
|
|
21
|
+
[DELEGATE_ENV_VAR]: '',
|
|
22
|
+
...(snapshot !== undefined && { [TREE_SNAPSHOT_ENV_VAR]: encodeTreeSnapshot(snapshot) }),
|
|
23
|
+
...(bypassesCache && { [NO_CACHE_ENV_VAR]: '1' }),
|
|
24
|
+
...(shouldBypassLock && { [NO_LOCK_ENV_VAR]: '1' }),
|
|
25
|
+
[COMMAND_VERBOSITY_ENV_VAR]: verbosity,
|
|
26
|
+
[OUTPUT_STYLE_ENV_VAR]: styles.stdout,
|
|
27
|
+
[REPORT_FORMAT_ENV_VAR]: format,
|
|
28
|
+
[RUN_ID_ENV_VAR]: runId,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export function buildRemoteEnv(childEnv) {
|
|
32
|
+
return Object.fromEntries(REMOTE_ENV_VARS.flatMap((name) => {
|
|
33
|
+
const value = childEnv[name];
|
|
34
|
+
return value === undefined ? [] : [[name, value]];
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
export async function takeMachineLock(invocation) {
|
|
38
|
+
const { anchorDir, command, env, parsedArgs, stderr, styles } = invocation;
|
|
39
|
+
if (!HEAVY_COMMANDS.has(command) || parsedArgs.shouldPrintLog) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
if (parsedArgs.passthrough.includes('--no-lock')) {
|
|
43
|
+
stderr.write(`${formatMisplacedNoLockWarning(command, styles.stderr)}\n`);
|
|
44
|
+
}
|
|
45
|
+
return acquireMachineLock({
|
|
46
|
+
command,
|
|
47
|
+
dir: anchorDir,
|
|
48
|
+
env: parsedArgs.shouldBypassLock ? { ...env, [NO_LOCK_ENV_VAR]: '1' } : env,
|
|
49
|
+
stderr,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
const REMOTE_ENV_VARS = [
|
|
53
|
+
COMMAND_VERBOSITY_ENV_VAR,
|
|
54
|
+
DEBUG_ENV_VAR,
|
|
55
|
+
NO_CACHE_ENV_VAR,
|
|
56
|
+
OUTPUT_STYLE_ENV_VAR,
|
|
57
|
+
REPORT_FORMAT_ENV_VAR,
|
|
58
|
+
RUN_ID_ENV_VAR,
|
|
59
|
+
];
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type ChildProcess } from 'node:child_process';
|
|
2
|
+
import type { Writable } from 'node:stream';
|
|
3
|
+
import type { OutputChannels } from './output-channel.js';
|
|
4
|
+
export interface RunCommandOptions {
|
|
5
|
+
/** The channel on which each of the child's output streams runs. */
|
|
6
|
+
channels: OutputChannels;
|
|
7
|
+
/** When true, suppresses output on success and writes captured output to stderr on failure. */
|
|
8
|
+
quiet?: boolean;
|
|
9
|
+
/** Stream that subprocess stdout flows to in non-quiet mode. Defaults to `process.stdout`. */
|
|
10
|
+
stdout?: Writable;
|
|
11
|
+
/** Stream that subprocess stderr flows to (and that quiet-mode failure output is written to). Defaults to `process.stderr`. */
|
|
12
|
+
stderr?: Writable;
|
|
13
|
+
/** Environment for the subprocess. Defaults to `process.env`. */
|
|
14
|
+
env?: NodeJS.ProcessEnv;
|
|
15
|
+
}
|
|
16
|
+
export type RunCommandResult = CommandCompletion & {
|
|
17
|
+
/**
|
|
18
|
+
* Output retained while the command ran, elided in the middle when it overran the bound.
|
|
19
|
+
* `undefined` when the stream was handed to the child directly, or when capture stopped early
|
|
20
|
+
* and the retained copy would understate what the command produced.
|
|
21
|
+
*/
|
|
22
|
+
stdout: Buffer | undefined;
|
|
23
|
+
stderr: Buffer | undefined;
|
|
24
|
+
};
|
|
25
|
+
/** What to hand `spawn`: a whole command line for a shell to parse, or a file and the arguments that it receives. */
|
|
26
|
+
export interface SpawnSpec {
|
|
27
|
+
args: readonly string[];
|
|
28
|
+
file: string;
|
|
29
|
+
/** Whether the child reads nmr's own stdin or the null device. */
|
|
30
|
+
input: 'ignore' | 'inherit';
|
|
31
|
+
shouldUseShell: boolean;
|
|
32
|
+
}
|
|
33
|
+
/** A spawned child and a promise of how it ended. */
|
|
34
|
+
export interface StartedCommand {
|
|
35
|
+
child: ChildProcess;
|
|
36
|
+
result: Promise<RunCommandResult>;
|
|
37
|
+
}
|
|
38
|
+
/** Returns the exit code that a shell reports for a death by the signal. */
|
|
39
|
+
export declare function describeSignalExitCode(signal: NodeJS.Signals): number;
|
|
40
|
+
/** Spawns one child on the caller's channels, returning the child and a promise of how it ended. */
|
|
41
|
+
export declare function startSpawned({ args, file, input, shouldUseShell }: SpawnSpec, cwd: string | undefined, options: RunCommandOptions): StartedCommand;
|
|
42
|
+
/** How a command ended, and the exit code that reports that ending to nmr's own caller. */
|
|
43
|
+
type CommandCompletion = {
|
|
44
|
+
outcome: 'exited';
|
|
45
|
+
exitCode: number;
|
|
46
|
+
} | {
|
|
47
|
+
outcome: 'signaled';
|
|
48
|
+
exitCode: number;
|
|
49
|
+
signal: NodeJS.Signals;
|
|
50
|
+
} | {
|
|
51
|
+
outcome: 'spawn-failed';
|
|
52
|
+
exitCode: number;
|
|
53
|
+
error: Error;
|
|
54
|
+
};
|
|
55
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import { reportError } from '@williamthorsen/nmr-core';
|
|
5
|
+
import { createBoundedBuffer } from "./helpers/createBoundedBuffer.js";
|
|
6
|
+
const SIGNAL_EXIT_BASE = 128;
|
|
7
|
+
const PIPE_DRAIN_GRACE_MS = 2_000;
|
|
8
|
+
export function describeSignalExitCode(signal) {
|
|
9
|
+
return SIGNAL_EXIT_BASE + os.constants.signals[signal];
|
|
10
|
+
}
|
|
11
|
+
export function startSpawned({ args, file, input, shouldUseShell }, cwd, options) {
|
|
12
|
+
const quiet = options.quiet === true;
|
|
13
|
+
const stdout = options.stdout ?? process.stdout;
|
|
14
|
+
const stderr = options.stderr ?? process.stderr;
|
|
15
|
+
const env = options.env ?? process.env;
|
|
16
|
+
const child = spawn(file, args, {
|
|
17
|
+
shell: shouldUseShell,
|
|
18
|
+
stdio: [input, options.channels.stdout, options.channels.stderr],
|
|
19
|
+
cwd,
|
|
20
|
+
env,
|
|
21
|
+
});
|
|
22
|
+
return { child, result: collectResult(child, { quiet, stderr, stdout }) };
|
|
23
|
+
}
|
|
24
|
+
function awaitCompletion(child, stopReading) {
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
let exitCompletion;
|
|
27
|
+
let graceTimer;
|
|
28
|
+
let isSettled = false;
|
|
29
|
+
function settle(completion) {
|
|
30
|
+
if (isSettled)
|
|
31
|
+
return;
|
|
32
|
+
isSettled = true;
|
|
33
|
+
if (graceTimer !== undefined)
|
|
34
|
+
clearTimeout(graceTimer);
|
|
35
|
+
resolve(completion);
|
|
36
|
+
}
|
|
37
|
+
child.on('error', (error) => {
|
|
38
|
+
settle({ outcome: 'spawn-failed', exitCode: 1, error });
|
|
39
|
+
});
|
|
40
|
+
child.on('exit', (code, signal) => {
|
|
41
|
+
const exitDescription = describeExit(code, signal);
|
|
42
|
+
exitCompletion = exitDescription;
|
|
43
|
+
graceTimer = setTimeout(() => {
|
|
44
|
+
stopReading();
|
|
45
|
+
settle(exitDescription);
|
|
46
|
+
}, PIPE_DRAIN_GRACE_MS);
|
|
47
|
+
});
|
|
48
|
+
child.on('close', () => {
|
|
49
|
+
settle(exitCompletion ?? { outcome: 'exited', exitCode: 1 });
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function captureStream(source, destination) {
|
|
54
|
+
const retainedCopy = createBoundedBuffer();
|
|
55
|
+
let isComplete = true;
|
|
56
|
+
function stopReading() {
|
|
57
|
+
if (destination !== undefined)
|
|
58
|
+
source.unpipe(destination);
|
|
59
|
+
source.destroy();
|
|
60
|
+
}
|
|
61
|
+
function abandon() {
|
|
62
|
+
isComplete = false;
|
|
63
|
+
stopReading();
|
|
64
|
+
}
|
|
65
|
+
source.on('error', abandon);
|
|
66
|
+
if (destination !== undefined) {
|
|
67
|
+
destination.on('error', abandon);
|
|
68
|
+
source.pipe(destination, { end: false });
|
|
69
|
+
}
|
|
70
|
+
source.on('data', (chunk) => retainedCopy.append(chunk));
|
|
71
|
+
return {
|
|
72
|
+
readBuffer: () => (isComplete ? retainedCopy.readBuffer() : undefined),
|
|
73
|
+
detach: () => {
|
|
74
|
+
destination?.off('error', abandon);
|
|
75
|
+
},
|
|
76
|
+
stopReading,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
async function collectResult(child, { quiet, stderr, stdout }) {
|
|
80
|
+
const stdoutCapture = child.stdout === null ? undefined : captureStream(child.stdout, quiet ? undefined : stdout);
|
|
81
|
+
const stderrCapture = child.stderr === null ? undefined : captureStream(child.stderr, quiet ? undefined : stderr);
|
|
82
|
+
const commandCompletion = await awaitCompletion(child, () => {
|
|
83
|
+
stdoutCapture?.stopReading();
|
|
84
|
+
stderrCapture?.stopReading();
|
|
85
|
+
});
|
|
86
|
+
stdoutCapture?.detach();
|
|
87
|
+
stderrCapture?.detach();
|
|
88
|
+
const result = {
|
|
89
|
+
...commandCompletion,
|
|
90
|
+
stdout: stdoutCapture?.readBuffer(),
|
|
91
|
+
stderr: stderrCapture?.readBuffer(),
|
|
92
|
+
};
|
|
93
|
+
if (result.outcome === 'spawn-failed') {
|
|
94
|
+
reportError(result.error.message, stderr);
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
if (quiet && result.exitCode !== 0) {
|
|
98
|
+
writeBuffer(result.stdout, stderr);
|
|
99
|
+
writeBuffer(result.stderr, stderr);
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
function describeExit(code, signal) {
|
|
104
|
+
if (signal !== null) {
|
|
105
|
+
return { outcome: 'signaled', exitCode: describeSignalExitCode(signal), signal };
|
|
106
|
+
}
|
|
107
|
+
return { outcome: 'exited', exitCode: code ?? 1 };
|
|
108
|
+
}
|
|
109
|
+
function writeBuffer(buffer, destination) {
|
|
110
|
+
if (buffer !== undefined && buffer.length > 0)
|
|
111
|
+
destination.write(buffer);
|
|
112
|
+
}
|
|
@@ -37,19 +37,3 @@ export declare function buildPackage(packageDir: string, options: BuildPackageOp
|
|
|
37
37
|
* detected.
|
|
38
38
|
*/
|
|
39
39
|
export declare function computeBuildHash(packageDir: string, files: string[], emitConfig: object, toolchain: BuildToolchain): Promise<string>;
|
|
40
|
-
/**
|
|
41
|
-
* Loads the compiler API on which every other function in this module relies, reporting a missing `typescript` peer
|
|
42
|
-
* with the version that `nmr-compile` requires. `buildPackage` calls it first; a caller of another export calls it
|
|
43
|
-
* beforehand.
|
|
44
|
-
*
|
|
45
|
-
* @internal
|
|
46
|
-
*/
|
|
47
|
-
export declare function loadCompiler(loadTypeScript?: () => Promise<typeof TypeScript>): Promise<void>;
|
|
48
|
-
/**
|
|
49
|
-
* Resolves a package's full tsconfig `extends` chain, returning every config file in it (the leaf `tsconfig.json` and
|
|
50
|
-
* each base that it transitively extends, up to the repo root) as paths relative to `packageDir`.
|
|
51
|
-
* Emit is driven by the fully-resolved compiler options, so the base configs must be in the cache's hashed input
|
|
52
|
-
* set; otherwise a change to a base config would not bust the cache and stale output could be released. The
|
|
53
|
-
* relative paths give `computeBuildHash` a location-independent path string to fold into the digest.
|
|
54
|
-
*/
|
|
55
|
-
export declare function resolveTsconfigChain(packageDir: string, configFileName?: string): string[];
|
|
@@ -2,20 +2,15 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import { readFile, rename, rm } from 'node:fs/promises';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
-
import { formatGlyphLine,
|
|
5
|
+
import { formatGlyphLine, readCacheEntry, STATUS_GLYPHS, writeCacheEntry, } from '@williamthorsen/nmr-core';
|
|
6
6
|
import { glob } from 'glob';
|
|
7
7
|
import { NMR_GLYPHS } from "../glyphs.js";
|
|
8
8
|
import { resolveConfigPath } from "../helpers/config-path.js";
|
|
9
9
|
import { DEFAULT_ENTRY_GLOBS, DEFAULT_IGNORE_PATTERNS, DEFAULT_OUTDIR, hasExpectedBuildOutput, resolveBuildCachePath, resolveScratchDirs, resolveToolchainFingerprint, } from "./build-output.js";
|
|
10
|
+
import { assertSupportedTypeScript, formatDiagnostics, getCompiler, loadCompiler } from "./compiler.js";
|
|
11
|
+
import { isWithin, rewriteSpecifiers } from "./rewrite-specifiers.js";
|
|
12
|
+
import { resolveTsconfigChain } from "./tsconfig-chain.js";
|
|
10
13
|
const SOURCE_ROOT = 'src';
|
|
11
|
-
const MINIMUM_TYPESCRIPT_MAJOR = 5;
|
|
12
|
-
const MINIMUM_TYPESCRIPT_MINOR = 7;
|
|
13
|
-
const UNSUPPORTED_TYPESCRIPT_MAJOR = 7;
|
|
14
|
-
const SUPPORTED_TYPESCRIPT_RANGE = `>=${MINIMUM_TYPESCRIPT_MAJOR}.${MINIMUM_TYPESCRIPT_MINOR} <${UNSUPPORTED_TYPESCRIPT_MAJOR}`;
|
|
15
|
-
const INSTALL_TYPESCRIPT_GUIDANCE = "Install a version in that range as the 'typescript' peer dependency.";
|
|
16
|
-
const TS_EXTENSIONS = ['.ts', '.tsx'];
|
|
17
|
-
const JS_EXTENSION = '.js';
|
|
18
|
-
let ts;
|
|
19
14
|
export async function buildPackage(packageDir, options) {
|
|
20
15
|
await loadCompiler(options.loadTypeScript);
|
|
21
16
|
assertSupportedTypeScript();
|
|
@@ -34,7 +29,7 @@ export async function buildPackage(packageDir, options) {
|
|
|
34
29
|
...resolveTsconfigChain(packageDir),
|
|
35
30
|
];
|
|
36
31
|
const toolchain = {
|
|
37
|
-
compilerVersion:
|
|
32
|
+
compilerVersion: getCompiler().version,
|
|
38
33
|
fingerprint: await resolveToolchainFingerprint(packageDir),
|
|
39
34
|
};
|
|
40
35
|
const { changed, currentHash } = await detectBuildChanges(packageDir, [...entryPoints, ...dependencies], emitConfig, toolchain, cachePath, hasExpectedBuildOutput(packageDir, outdir, entryPoints), options.style);
|
|
@@ -60,40 +55,8 @@ export async function computeBuildHash(packageDir, files, emitConfig, toolchain)
|
|
|
60
55
|
hash.update(toolchain.fingerprint);
|
|
61
56
|
return hash.digest('hex');
|
|
62
57
|
}
|
|
63
|
-
export async function loadCompiler(loadTypeScript = importTypeScript) {
|
|
64
|
-
try {
|
|
65
|
-
ts = await loadTypeScript();
|
|
66
|
-
}
|
|
67
|
-
catch (error) {
|
|
68
|
-
if (hasErrnoCode(error, 'ERR_MODULE_NOT_FOUND') && error.message.includes("'typescript'")) {
|
|
69
|
-
throw new Error(`nmr-compile requires TypeScript ${SUPPORTED_TYPESCRIPT_RANGE}, but 'typescript' could not be resolved. ` +
|
|
70
|
-
INSTALL_TYPESCRIPT_GUIDANCE, { cause: error });
|
|
71
|
-
}
|
|
72
|
-
throw error;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
export function resolveTsconfigChain(packageDir, configFileName = 'tsconfig.json') {
|
|
76
|
-
const resolvedChain = [];
|
|
77
|
-
const seen = new Set();
|
|
78
|
-
function walk(configPath) {
|
|
79
|
-
const normalizedPath = path.resolve(configPath);
|
|
80
|
-
if (seen.has(normalizedPath)) {
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
seen.add(normalizedPath);
|
|
84
|
-
resolvedChain.push(normalizedPath);
|
|
85
|
-
const configFile = ts.readConfigFile(normalizedPath, (fileName) => ts.sys.readFile(fileName));
|
|
86
|
-
if (configFile.error) {
|
|
87
|
-
throw new Error(`nmr-compile: failed to read ${normalizedPath}.\n${formatDiagnostics([configFile.error])}`);
|
|
88
|
-
}
|
|
89
|
-
for (const entry of normalizeExtendsField(configFile.config)) {
|
|
90
|
-
walk(resolveExtendsTarget(entry, normalizedPath));
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
walk(path.resolve(packageDir, configFileName));
|
|
94
|
-
return resolvedChain.map((absolute) => path.relative(packageDir, absolute));
|
|
95
|
-
}
|
|
96
58
|
async function emitPackage(packageDir, entryPoints, outdir) {
|
|
59
|
+
const ts = getCompiler();
|
|
97
60
|
const compilerOptions = synthesizeCompilerOptions(packageDir, outdir);
|
|
98
61
|
const rootNames = entryPoints.map((entry) => path.resolve(packageDir, entry));
|
|
99
62
|
const sourceRoot = path.resolve(packageDir, SOURCE_ROOT);
|
|
@@ -127,6 +90,7 @@ function assertEmitSucceeded(result) {
|
|
|
127
90
|
}
|
|
128
91
|
}
|
|
129
92
|
function createCachingCompilerHost(compilerOptions) {
|
|
93
|
+
const ts = getCompiler();
|
|
130
94
|
const host = ts.createCompilerHost(compilerOptions);
|
|
131
95
|
const parsedFiles = new Map();
|
|
132
96
|
const readSourceFile = host.getSourceFile.bind(host);
|
|
@@ -179,6 +143,7 @@ async function swapIntoPlace(emitDir, scratchDirs) {
|
|
|
179
143
|
await discardScratchDirs(scratchDirs);
|
|
180
144
|
}
|
|
181
145
|
function synthesizeCompilerOptions(packageDir, outdir) {
|
|
146
|
+
const ts = getCompiler();
|
|
182
147
|
const configPath = path.join(packageDir, 'tsconfig.json');
|
|
183
148
|
const configFile = ts.readConfigFile(configPath, (fileName) => ts.sys.readFile(fileName));
|
|
184
149
|
if (configFile.error) {
|
|
@@ -204,6 +169,7 @@ function synthesizeCompilerOptions(packageDir, outdir) {
|
|
|
204
169
|
};
|
|
205
170
|
}
|
|
206
171
|
function writeStagedOutput(stagedFiles, emitDir, stagingDir) {
|
|
172
|
+
const ts = getCompiler();
|
|
207
173
|
for (const [fileName, file] of stagedFiles) {
|
|
208
174
|
if (!isWithin(emitDir, fileName)) {
|
|
209
175
|
throw new Error(`nmr-compile: the compiler emitted ${fileName}, which is outside the output directory ${emitDir}. ` +
|
|
@@ -212,86 +178,6 @@ function writeStagedOutput(stagedFiles, emitDir, stagingDir) {
|
|
|
212
178
|
ts.sys.writeFile(path.join(stagingDir, path.relative(emitDir, fileName)), file.text, file.writeByteOrderMark);
|
|
213
179
|
}
|
|
214
180
|
}
|
|
215
|
-
function rewriteSpecifiers(outputFile, text, compilerOptions, sourceRoot) {
|
|
216
|
-
if (!isRewritableOutput(outputFile)) {
|
|
217
|
-
return text;
|
|
218
|
-
}
|
|
219
|
-
const sourceFile = ts.createSourceFile(outputFile, text, ts.ScriptTarget.Latest, false, resolveScriptKind(outputFile));
|
|
220
|
-
const aliasPrefixes = collectAliasPrefixes(compilerOptions);
|
|
221
|
-
const sourceContainingFile = mapOutputToSource(outputFile, compilerOptions, sourceRoot);
|
|
222
|
-
const edits = [];
|
|
223
|
-
visitModuleSpecifiers(sourceFile, (literal) => {
|
|
224
|
-
const replacement = resolveSpecifierReplacement(literal.text, sourceContainingFile, compilerOptions, sourceRoot, aliasPrefixes);
|
|
225
|
-
if (replacement === undefined) {
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
const startOffset = literal.getStart(sourceFile);
|
|
229
|
-
const quote = text[startOffset] ?? '"';
|
|
230
|
-
edits.push({ startOffset, endOffset: literal.getEnd(), text: `${quote}${replacement}${quote}` });
|
|
231
|
-
});
|
|
232
|
-
if (edits.length === 0) {
|
|
233
|
-
return text;
|
|
234
|
-
}
|
|
235
|
-
const orderedEdits = edits.toSorted((a, b) => b.startOffset - a.startOffset);
|
|
236
|
-
let updatedText = text;
|
|
237
|
-
for (const edit of orderedEdits) {
|
|
238
|
-
updatedText = updatedText.slice(0, edit.startOffset) + edit.text + updatedText.slice(edit.endOffset);
|
|
239
|
-
}
|
|
240
|
-
return updatedText;
|
|
241
|
-
}
|
|
242
|
-
function resolveSpecifierReplacement(specifier, sourceContainingFile, compilerOptions, sourceRoot, aliasPrefixes) {
|
|
243
|
-
if (isRelativeSpecifier(specifier)) {
|
|
244
|
-
const rewrittenSpecifier = swapTypeScriptExtension(specifier);
|
|
245
|
-
return rewrittenSpecifier === specifier ? undefined : rewrittenSpecifier;
|
|
246
|
-
}
|
|
247
|
-
if (aliasPrefixes.every((prefix) => !(specifier === prefix || specifier.startsWith(prefix)))) {
|
|
248
|
-
return undefined;
|
|
249
|
-
}
|
|
250
|
-
const resolvedModule = ts.resolveModuleName(specifier, sourceContainingFile, compilerOptions, ts.sys).resolvedModule;
|
|
251
|
-
if (!resolvedModule) {
|
|
252
|
-
throw new Error(`nmr-compile: could not resolve aliased import '${specifier}' from ${sourceContainingFile}. ` +
|
|
253
|
-
`Verify the tsconfig 'paths' mapping and that the target file exists.`);
|
|
254
|
-
}
|
|
255
|
-
if (!isWithin(sourceRoot, resolvedModule.resolvedFileName)) {
|
|
256
|
-
const { paths: _paths, baseUrl: _baseUrl, rootDirs: _rootDirs, ...nodeResolutionOptions } = compilerOptions;
|
|
257
|
-
const bareResolvedModule = ts.resolveModuleName(specifier, sourceContainingFile, nodeResolutionOptions, ts.sys).resolvedModule;
|
|
258
|
-
if (bareResolvedModule) {
|
|
259
|
-
return undefined;
|
|
260
|
-
}
|
|
261
|
-
throw new Error(`nmr-compile: aliased import '${specifier}' from ${sourceContainingFile} resolves to ` +
|
|
262
|
-
`${resolvedModule.resolvedFileName}, outside the package source root ${sourceRoot}, and does not resolve ` +
|
|
263
|
-
`the way Node will at runtime, which ignores tsconfig 'paths', 'baseUrl', and 'rootDirs'. The ` +
|
|
264
|
-
`emitted specifier would fail at runtime; re-anchor the alias inside the package.`);
|
|
265
|
-
}
|
|
266
|
-
const relativeSpecifier = buildRelativeSpecifier(path.dirname(sourceContainingFile), resolvedModule.resolvedFileName);
|
|
267
|
-
return swapTypeScriptExtension(relativeSpecifier);
|
|
268
|
-
}
|
|
269
|
-
function visitModuleSpecifiers(sourceFile, visit) {
|
|
270
|
-
function walk(node) {
|
|
271
|
-
const specifier = getModuleSpecifier(node);
|
|
272
|
-
if (specifier !== undefined) {
|
|
273
|
-
visit(specifier);
|
|
274
|
-
}
|
|
275
|
-
ts.forEachChild(node, walk);
|
|
276
|
-
}
|
|
277
|
-
walk(sourceFile);
|
|
278
|
-
}
|
|
279
|
-
function getModuleSpecifier(node) {
|
|
280
|
-
if ((ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && node.moduleSpecifier !== undefined) {
|
|
281
|
-
return ts.isStringLiteralLike(node.moduleSpecifier) ? node.moduleSpecifier : undefined;
|
|
282
|
-
}
|
|
283
|
-
if (ts.isImportEqualsDeclaration(node) && ts.isExternalModuleReference(node.moduleReference)) {
|
|
284
|
-
return ts.isStringLiteralLike(node.moduleReference.expression) ? node.moduleReference.expression : undefined;
|
|
285
|
-
}
|
|
286
|
-
if (ts.isImportTypeNode(node) && ts.isLiteralTypeNode(node.argument)) {
|
|
287
|
-
return ts.isStringLiteralLike(node.argument.literal) ? node.argument.literal : undefined;
|
|
288
|
-
}
|
|
289
|
-
if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
290
|
-
const [first] = node.arguments;
|
|
291
|
-
return first !== undefined && ts.isStringLiteralLike(first) ? first : undefined;
|
|
292
|
-
}
|
|
293
|
-
return undefined;
|
|
294
|
-
}
|
|
295
181
|
async function detectBuildChanges(packageDir, files, emitConfig, toolchain, cachePath, isOutputPresent, style) {
|
|
296
182
|
const packageName = path.basename(packageDir);
|
|
297
183
|
const previousHash = await readCacheEntry(cachePath);
|
|
@@ -307,100 +193,9 @@ async function detectBuildChanges(packageDir, files, emitConfig, toolchain, cach
|
|
|
307
193
|
console.info(formatGlyphLine(NMR_GLYPHS, style, 'package', `${packageName}: Changes detected.`));
|
|
308
194
|
return { changed: true, currentHash };
|
|
309
195
|
}
|
|
310
|
-
async function importTypeScript() {
|
|
311
|
-
return await import('typescript');
|
|
312
|
-
}
|
|
313
|
-
function assertSupportedTypeScript() {
|
|
314
|
-
const versionMajorMinor = ts.versionMajorMinor;
|
|
315
|
-
const version = ts.version;
|
|
316
|
-
const [majorPart, minorPart] = typeof versionMajorMinor === 'string' ? versionMajorMinor.split('.', 2) : [];
|
|
317
|
-
const major = majorPart === undefined ? 0 : Number(majorPart);
|
|
318
|
-
const minor = minorPart === undefined ? 0 : Number(minorPart);
|
|
319
|
-
const tooOld = major < MINIMUM_TYPESCRIPT_MAJOR || (major === MINIMUM_TYPESCRIPT_MAJOR && minor < MINIMUM_TYPESCRIPT_MINOR);
|
|
320
|
-
if (tooOld || major >= UNSUPPORTED_TYPESCRIPT_MAJOR) {
|
|
321
|
-
throw new Error(`nmr-compile requires TypeScript ${SUPPORTED_TYPESCRIPT_RANGE}, but found ` +
|
|
322
|
-
`${typeof version === 'string' ? version : 'a version without the compiler API'}. ${INSTALL_TYPESCRIPT_GUIDANCE}`);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
function collectAliasPrefixes(compilerOptions) {
|
|
326
|
-
if (compilerOptions.paths === undefined) {
|
|
327
|
-
return [];
|
|
328
|
-
}
|
|
329
|
-
return Object.keys(compilerOptions.paths).map((pattern) => pattern.replace(/\*$/, ''));
|
|
330
|
-
}
|
|
331
196
|
function describeEmit(fileCount, outdir) {
|
|
332
197
|
if (fileCount === 0) {
|
|
333
198
|
return 'Did not emit any output.';
|
|
334
199
|
}
|
|
335
200
|
return `Compiled ${fileCount} files to ${outdir.replace(/\/+$/u, '')}.`;
|
|
336
201
|
}
|
|
337
|
-
function formatDiagnostics(diagnostics) {
|
|
338
|
-
return ts.formatDiagnosticsWithColorAndContext(diagnostics, {
|
|
339
|
-
getCurrentDirectory: () => process.cwd(),
|
|
340
|
-
getCanonicalFileName: (fileName) => fileName,
|
|
341
|
-
getNewLine: () => ts.sys.newLine,
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
function isRewritableOutput(file) {
|
|
345
|
-
return file.endsWith('.d.ts') || file.endsWith('.js');
|
|
346
|
-
}
|
|
347
|
-
function normalizeExtendsField(config) {
|
|
348
|
-
if (config === null || typeof config !== 'object' || !('extends' in config)) {
|
|
349
|
-
return [];
|
|
350
|
-
}
|
|
351
|
-
const extendsField = config.extends;
|
|
352
|
-
if (typeof extendsField === 'string') {
|
|
353
|
-
return [extendsField];
|
|
354
|
-
}
|
|
355
|
-
if (Array.isArray(extendsField)) {
|
|
356
|
-
return extendsField.filter((entry) => typeof entry === 'string');
|
|
357
|
-
}
|
|
358
|
-
return [];
|
|
359
|
-
}
|
|
360
|
-
function resolveExtendsTarget(extendsEntry, fromConfigPath) {
|
|
361
|
-
if (isRelativeSpecifier(extendsEntry) || path.isAbsolute(extendsEntry)) {
|
|
362
|
-
const base = path.resolve(path.dirname(fromConfigPath), extendsEntry);
|
|
363
|
-
if (ts.sys.fileExists(base)) {
|
|
364
|
-
return base;
|
|
365
|
-
}
|
|
366
|
-
const withJsonExtension = `${base}.json`;
|
|
367
|
-
if (ts.sys.fileExists(withJsonExtension)) {
|
|
368
|
-
return withJsonExtension;
|
|
369
|
-
}
|
|
370
|
-
throw new Error(`nmr-compile: ${fromConfigPath} extends '${extendsEntry}', which does not exist.`);
|
|
371
|
-
}
|
|
372
|
-
const resolvedPath = resolvePackageSpecifier(extendsEntry, fromConfigPath) ??
|
|
373
|
-
resolvePackageSpecifier(`${extendsEntry}/tsconfig.json`, fromConfigPath);
|
|
374
|
-
if (resolvedPath === undefined) {
|
|
375
|
-
throw new Error(`nmr-compile: ${fromConfigPath} extends '${extendsEntry}', which does not resolve to a file.`);
|
|
376
|
-
}
|
|
377
|
-
return resolvedPath;
|
|
378
|
-
}
|
|
379
|
-
function resolvePackageSpecifier(specifier, fromConfigPath) {
|
|
380
|
-
const { resolvedModule } = ts.resolveModuleName(specifier, fromConfigPath, { moduleResolution: ts.ModuleResolutionKind.NodeNext, resolveJsonModule: true }, ts.sys);
|
|
381
|
-
return resolvedModule?.resolvedFileName;
|
|
382
|
-
}
|
|
383
|
-
function isRelativeSpecifier(specifier) {
|
|
384
|
-
return specifier.startsWith('./') || specifier.startsWith('../');
|
|
385
|
-
}
|
|
386
|
-
function isWithin(parent, child) {
|
|
387
|
-
const relativePath = path.relative(parent, child);
|
|
388
|
-
return relativePath !== '' && !relativePath.startsWith('..') && !path.isAbsolute(relativePath);
|
|
389
|
-
}
|
|
390
|
-
function mapOutputToSource(outputFile, compilerOptions, sourceRoot) {
|
|
391
|
-
const outDir = compilerOptions.outDir ?? path.dirname(outputFile);
|
|
392
|
-
const relativeFromOut = path.relative(outDir, outputFile);
|
|
393
|
-
const withoutExtension = relativeFromOut.replace(/\.d\.ts$|\.js$/, '');
|
|
394
|
-
return path.join(sourceRoot, `${withoutExtension}.ts`);
|
|
395
|
-
}
|
|
396
|
-
function buildRelativeSpecifier(fromDir, targetFile) {
|
|
397
|
-
const relativePath = path.relative(fromDir, targetFile).split(path.sep).join('/');
|
|
398
|
-
return relativePath.startsWith('.') ? relativePath : `./${relativePath}`;
|
|
399
|
-
}
|
|
400
|
-
function resolveScriptKind(file) {
|
|
401
|
-
return file.endsWith('.d.ts') ? ts.ScriptKind.TS : ts.ScriptKind.JS;
|
|
402
|
-
}
|
|
403
|
-
function swapTypeScriptExtension(specifier) {
|
|
404
|
-
const extension = TS_EXTENSIONS.find((candidate) => specifier.endsWith(candidate));
|
|
405
|
-
return extension === undefined ? specifier : `${specifier.slice(0, -extension.length)}${JS_EXTENSION}`;
|
|
406
|
-
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { rm } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { formatGlyphLine } from '@williamthorsen/nmr-core';
|
|
4
|
-
import { removeCheckCache } from "../check-cache.js";
|
|
4
|
+
import { removeCheckCache } from "../check-cache-entries.js";
|
|
5
5
|
import { loadRootConfig } from "../config.js";
|
|
6
6
|
import { findContainingPackageDir } from "../context.js";
|
|
7
7
|
import { NMR_GLYPHS } from "../glyphs.js";
|
|
8
8
|
import { reportClosing } from "../helpers/reportClosing.js";
|
|
9
|
+
import { resolveChannel } from "../output-channel.js";
|
|
9
10
|
import { applyDevBin, buildWorkspaceRegistry, resolveScript } from "../resolver.js";
|
|
10
|
-
import {
|
|
11
|
+
import { runCommand } from "../runner.js";
|
|
11
12
|
import { renderChain } from "../steps.js";
|
|
12
13
|
import { findMonorepoRoot, getWorkspacePackageDirs } from "../workspace.js";
|
|
13
14
|
import { resolveBuildCachePath } from "./build-output.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type * as TypeScript from 'typescript';
|
|
2
|
+
/**
|
|
3
|
+
* Asserts the resolved `typescript` peer is new enough for `rewriteRelativeImportExtensions` and old enough to
|
|
4
|
+
* include the compiler API.
|
|
5
|
+
*/
|
|
6
|
+
export declare function assertSupportedTypeScript(): void;
|
|
7
|
+
/** Renders compiler diagnostics as colored text with source context. */
|
|
8
|
+
export declare function formatDiagnostics(diagnostics: readonly TypeScript.Diagnostic[]): string;
|
|
9
|
+
/** Returns the compiler API that `loadCompiler` loaded, throwing when it has not run. */
|
|
10
|
+
export declare function getCompiler(): typeof TypeScript;
|
|
11
|
+
/**
|
|
12
|
+
* Loads the compiler API on which every `nmr-compile` module relies, reporting a missing `typescript` peer with the
|
|
13
|
+
* version that `nmr-compile` requires. `buildPackage` calls it first; a caller of another compiler-dependent export
|
|
14
|
+
* calls it beforehand.
|
|
15
|
+
*
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
export declare function loadCompiler(loadTypeScript?: () => Promise<typeof TypeScript>): Promise<void>;
|