@williamthorsen/nmr 0.29.0 โ 0.31.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/.readyup/kits/default.js +33 -22
- package/.readyup/manifest.json +2 -2
- package/README.md +327 -78
- package/agents/guidance/rulebooks/nmr.md +4 -2
- package/bin/nmr-report-catalog.js +11 -0
- package/dist/esm/UserError.d.ts +3 -0
- package/dist/esm/UserError.js +6 -0
- package/dist/esm/check-cache.d.ts +36 -1
- package/dist/esm/check-cache.js +93 -24
- package/dist/esm/cli-build.js +2 -1
- package/dist/esm/cli-clean.js +2 -1
- package/dist/esm/cli-ensure-prepublish-hooks.js +2 -1
- package/dist/esm/cli-report-catalog.d.ts +1 -0
- package/dist/esm/cli-report-catalog.js +11 -0
- package/dist/esm/cli-report-overrides.js +2 -1
- package/dist/esm/cli-taze.js +2 -1
- package/dist/esm/cli.js +2 -1
- package/dist/esm/commands/clean.js +9 -5
- package/dist/esm/commands/report-catalog.d.ts +1 -0
- package/dist/esm/commands/report-catalog.js +34 -0
- package/dist/esm/commands/report-overrides.js +22 -7
- package/dist/esm/commands/taze.js +2 -1
- package/dist/esm/config.d.ts +1 -0
- package/dist/esm/config.js +81 -16
- package/dist/esm/default-scripts.d.ts +5 -1
- package/dist/esm/default-scripts.js +19 -16
- package/dist/esm/defineConfig.d.ts +1 -1
- package/dist/esm/help.js +3 -1
- package/dist/esm/helpers/clampToBytes.d.ts +2 -0
- package/dist/esm/helpers/clampToBytes.js +20 -0
- package/dist/esm/helpers/createBoundedBuffer.js +1 -4
- package/dist/esm/helpers/deriveExcerpt.d.ts +1 -0
- package/dist/esm/helpers/deriveExcerpt.js +30 -0
- package/dist/esm/helpers/duration.js +16 -9
- package/dist/esm/helpers/package-json.d.ts +11 -4
- package/dist/esm/helpers/package-json.js +51 -22
- package/dist/esm/helpers/readStringValues.d.ts +1 -0
- package/dist/esm/helpers/readStringValues.js +8 -0
- package/dist/esm/helpers/transcript.d.ts +5 -0
- package/dist/esm/helpers/transcript.js +59 -0
- package/dist/esm/recording.d.ts +65 -0
- package/dist/esm/recording.js +96 -0
- package/dist/esm/replay-assembly.d.ts +9 -0
- package/dist/esm/replay-assembly.js +26 -0
- package/dist/esm/report-format.d.ts +18 -0
- package/dist/esm/report-format.js +25 -0
- package/dist/esm/reportCliFailure.d.ts +2 -0
- package/dist/esm/reportCliFailure.js +11 -0
- package/dist/esm/resolve-scripts.d.ts +1 -1
- package/dist/esm/resolver.d.ts +13 -3
- package/dist/esm/resolver.js +34 -19
- package/dist/esm/runCli.d.ts +1 -0
- package/dist/esm/runCli.js +372 -70
- package/dist/esm/runner.d.ts +20 -1
- package/dist/esm/runner.js +72 -12
- package/dist/esm/scripts.d.ts +1 -1
- package/dist/esm/steps.d.ts +18 -0
- package/dist/esm/steps.js +144 -0
- package/dist/esm/types.d.ts +11 -2
- package/dist/esm/verbosity.d.ts +22 -0
- package/dist/esm/verbosity.js +37 -0
- package/dist/esm/verdict.d.ts +29 -0
- package/dist/esm/verdict.js +150 -0
- package/dist/esm/workspace.d.ts +2 -0
- package/dist/esm/workspace.js +20 -3
- package/package.json +12 -5
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
try {
|
|
3
|
+
await import('../dist/esm/cli-report-catalog.js');
|
|
4
|
+
} catch (error) {
|
|
5
|
+
if (error.code === 'ERR_MODULE_NOT_FOUND') {
|
|
6
|
+
process.stderr.write('nmr-report-catalog: build output not found โ run `pnpm run bootstrap` first\n');
|
|
7
|
+
} else {
|
|
8
|
+
process.stderr.write(`nmr-report-catalog: failed to load: ${error.message}\n`);
|
|
9
|
+
}
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Writable } from 'node:stream';
|
|
2
|
+
import type { OutputChannels } from './runner.js';
|
|
2
3
|
import type { CheckCacheConfig, NmrConfig } from './types.js';
|
|
3
4
|
export interface CheckCacheEntry {
|
|
4
5
|
key: string;
|
|
@@ -10,6 +11,22 @@ export interface CheckCacheEntry {
|
|
|
10
11
|
durationMs: number;
|
|
11
12
|
recordedAt: string;
|
|
12
13
|
buildDigests: Record<string, string>;
|
|
14
|
+
retention?: Retention;
|
|
15
|
+
}
|
|
16
|
+
export interface EntryRef {
|
|
17
|
+
anchorDir: string;
|
|
18
|
+
command: string;
|
|
19
|
+
monorepoRoot: string;
|
|
20
|
+
}
|
|
21
|
+
export interface ReplayLine {
|
|
22
|
+
command: string;
|
|
23
|
+
excerpt: string;
|
|
24
|
+
scope: string;
|
|
25
|
+
}
|
|
26
|
+
export interface Retention {
|
|
27
|
+
key: string;
|
|
28
|
+
replay: ReplayLine[];
|
|
29
|
+
runId: string;
|
|
13
30
|
}
|
|
14
31
|
export interface BuildOutputState {
|
|
15
32
|
missing: string[];
|
|
@@ -28,7 +45,17 @@ export declare const CURRENT_RUNTIME: RuntimeIdentity;
|
|
|
28
45
|
export declare const DEBUG_ENV_VAR = "NMR_DEBUG";
|
|
29
46
|
export declare const DEFAULT_CACHEABLE_COMMANDS: string[];
|
|
30
47
|
export declare const NO_CACHE_ENV_VAR = "NMR_NO_CACHE";
|
|
48
|
+
export declare const RUN_ID_ENV_VAR = "NMR_RUN_ID";
|
|
31
49
|
export declare const TREE_SNAPSHOT_ENV_VAR = "NMR_TREE_SNAPSHOT";
|
|
50
|
+
export declare function certifyRetention(options: {
|
|
51
|
+
anchorDir: string;
|
|
52
|
+
command: string;
|
|
53
|
+
entry: CheckCacheEntry;
|
|
54
|
+
env: NodeJS.ProcessEnv;
|
|
55
|
+
monorepoRoot: string;
|
|
56
|
+
runId: string;
|
|
57
|
+
stderr: Writable;
|
|
58
|
+
}): Promise<void>;
|
|
32
59
|
export declare function computeCacheKey(options: {
|
|
33
60
|
anchorDir: string;
|
|
34
61
|
command: string;
|
|
@@ -45,18 +72,26 @@ export declare function computeCacheKey(options: {
|
|
|
45
72
|
ok: false;
|
|
46
73
|
reason: string;
|
|
47
74
|
};
|
|
75
|
+
export declare function computeRetentionKey(options: {
|
|
76
|
+
channels: OutputChannels;
|
|
77
|
+
env: NodeJS.ProcessEnv;
|
|
78
|
+
passKey: string;
|
|
79
|
+
}): string;
|
|
48
80
|
export declare function encodeTreeSnapshot(snapshot: TreeSnapshot): string;
|
|
49
81
|
export declare function findStaleBuildOutput(earlier: Record<string, string>, later: Record<string, string>): string | undefined;
|
|
50
82
|
export declare function formatMisplacedNoCacheWarning(command: string): string;
|
|
51
|
-
export declare function
|
|
83
|
+
export declare function isCacheableCommand(checkCache: CheckCacheConfig | undefined, command: string): boolean;
|
|
52
84
|
export declare function readBuildOutputState(monorepoRoot: string, config: NmrConfig): Promise<BuildOutputState>;
|
|
53
85
|
export declare function readCheckCacheEntry(options: {
|
|
54
86
|
anchorDir: string;
|
|
55
87
|
command: string;
|
|
56
88
|
monorepoRoot: string;
|
|
57
89
|
}): Promise<CheckCacheEntry | undefined>;
|
|
90
|
+
export declare function readTranscript(ref: EntryRef): Promise<string | undefined>;
|
|
91
|
+
export declare function recordTranscript(ref: EntryRef, transcript: string | undefined): Promise<void>;
|
|
58
92
|
export declare function removeCheckCache(scopeDir: string): Promise<void>;
|
|
59
93
|
export declare function resolveCacheableCommands(checkCache: CheckCacheConfig | undefined): Set<string>;
|
|
94
|
+
export declare function resolveRunId(env: NodeJS.ProcessEnv): string;
|
|
60
95
|
export declare function resolveTreeSnapshot(options: {
|
|
61
96
|
monorepoRoot: string;
|
|
62
97
|
env: NodeJS.ProcessEnv;
|
package/dist/esm/check-cache.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import { readFileSync, realpathSync } from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import process from 'node:process';
|
|
5
|
-
import { hashWorkingTree, readHeadSha, readJsonCacheEntry, removeCacheDir, resolveCacheEntryPath, writeCacheEntry, } from '@williamthorsen/nmr-core';
|
|
5
|
+
import { hashWorkingTree, readCacheEntry, readHeadSha, readJsonCacheEntry, removeCacheDir, removeCacheEntry, resolveCacheEntryPath, writeCacheEntry, } from '@williamthorsen/nmr-core';
|
|
6
|
+
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
6
7
|
import { hasBuildOutput, readBuildDigest } from "./commands/build-output.js";
|
|
7
8
|
import { loadWorkspaceConfig } from "./config.js";
|
|
8
|
-
import {
|
|
9
|
+
import { isHookName } from "./helpers/hook-name.js";
|
|
9
10
|
import { isObject, isStringRecord } from "./helpers/type-guards.js";
|
|
10
11
|
import { getDefaultWorkspaceScripts } from "./resolve-scripts.js";
|
|
11
12
|
import { buildWorkspaceRegistry, resolveScript } from "./resolver.js";
|
|
13
|
+
import { renderChain } from "./steps.js";
|
|
12
14
|
import { getWorkspacePackageDirs } from "./workspace.js";
|
|
13
15
|
export const CURRENT_RUNTIME = {
|
|
14
16
|
arch: process.arch,
|
|
@@ -38,6 +40,7 @@ export const DEFAULT_CACHEABLE_COMMANDS = [
|
|
|
38
40
|
'typecheck',
|
|
39
41
|
];
|
|
40
42
|
export const NO_CACHE_ENV_VAR = 'NMR_NO_CACHE';
|
|
43
|
+
export const RUN_ID_ENV_VAR = 'NMR_RUN_ID';
|
|
41
44
|
export const TREE_SNAPSHOT_ENV_VAR = 'NMR_TREE_SNAPSHOT';
|
|
42
45
|
const BUILT_IN_COMPILE = 'nmr-compile';
|
|
43
46
|
const CACHE_TOOL = 'nmr-check';
|
|
@@ -47,7 +50,26 @@ const INSTALL_FINGERPRINT_FILES = [
|
|
|
47
50
|
];
|
|
48
51
|
const KEY_FORMAT = 'nmr-check-cache-v1';
|
|
49
52
|
const KEYED_ENV_VARS = ['LANG', 'LC_ALL', 'NODE_OPTIONS', 'TZ'];
|
|
53
|
+
const RETENTION_KEY_FORMAT = 'nmr-retention-v1';
|
|
54
|
+
const RETENTION_KEYED_ENV_VARS = ['CI', 'COLUMNS', 'FORCE_COLOR', 'NO_COLOR', 'TERM'];
|
|
50
55
|
const UNSAFE_SLUG_CHARACTERS = /[^\w.-]+/g;
|
|
56
|
+
export async function certifyRetention(options) {
|
|
57
|
+
const { entry, runId } = options;
|
|
58
|
+
if (entry.retention === undefined || entry.retention.runId === runId) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
await writeCheckCacheEntry({
|
|
63
|
+
anchorDir: options.anchorDir,
|
|
64
|
+
command: options.command,
|
|
65
|
+
monorepoRoot: options.monorepoRoot,
|
|
66
|
+
entry: { ...entry, retention: { ...entry.retention, runId } },
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
writeDebugNote(`could not certify ${options.command}: ${describeError(error)}`, options.env, options.stderr);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
51
73
|
export function computeCacheKey(options) {
|
|
52
74
|
const fingerprint = resolveInstallFingerprint(options.monorepoRoot);
|
|
53
75
|
if (!fingerprint.ok) {
|
|
@@ -67,16 +89,16 @@ export function computeCacheKey(options) {
|
|
|
67
89
|
runtime.arch,
|
|
68
90
|
fingerprint.fingerprint,
|
|
69
91
|
];
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
return
|
|
92
|
+
return { ok: true, key: digestParts([...parts, ...composeEnvParts(KEYED_ENV_VARS, options.env)]) };
|
|
93
|
+
}
|
|
94
|
+
export function computeRetentionKey(options) {
|
|
95
|
+
const parts = [
|
|
96
|
+
RETENTION_KEY_FORMAT,
|
|
97
|
+
options.passKey,
|
|
98
|
+
describeChannel(options.channels.stdout),
|
|
99
|
+
describeChannel(options.channels.stderr),
|
|
100
|
+
];
|
|
101
|
+
return digestParts([...parts, ...composeEnvParts(RETENTION_KEYED_ENV_VARS, options.env)]);
|
|
80
102
|
}
|
|
81
103
|
export function encodeTreeSnapshot(snapshot) {
|
|
82
104
|
return `${snapshot.hash} ${snapshot.headSha}`;
|
|
@@ -89,11 +111,8 @@ export function formatMisplacedNoCacheWarning(command) {
|
|
|
89
111
|
return (`โ ๏ธ --no-cache after the command name is passed to \`${command}\`, not read by nmr. ` +
|
|
90
112
|
`Did you mean \`nmr --no-cache ${command}\`?`);
|
|
91
113
|
}
|
|
92
|
-
export function
|
|
93
|
-
|
|
94
|
-
const saving = formatSaving(entry.durationMs);
|
|
95
|
-
const savingClause = saving === undefined ? '' : ` (${saving})`;
|
|
96
|
-
return `โญ๏ธ ${command}: passed ${age} ago on this tree${savingClause}.`;
|
|
114
|
+
export function isCacheableCommand(checkCache, command) {
|
|
115
|
+
return !isHookName(command) && checkCache?.enabled !== false && resolveCacheableCommands(checkCache).has(command);
|
|
97
116
|
}
|
|
98
117
|
export async function readBuildOutputState(monorepoRoot, config) {
|
|
99
118
|
const state = { missing: [], digests: {} };
|
|
@@ -125,7 +144,23 @@ export async function readBuildOutputState(monorepoRoot, config) {
|
|
|
125
144
|
return state;
|
|
126
145
|
}
|
|
127
146
|
export async function readCheckCacheEntry(options) {
|
|
128
|
-
|
|
147
|
+
const parsed = await readJsonCacheEntry(resolveEntryPath(options), isParsedCheckCacheEntry);
|
|
148
|
+
if (parsed === undefined) {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
const { retention, ...pass } = parsed;
|
|
152
|
+
return isRetention(retention) ? { ...pass, retention } : pass;
|
|
153
|
+
}
|
|
154
|
+
export async function readTranscript(ref) {
|
|
155
|
+
return readCacheEntry(resolveEntryPath(ref, '.log'));
|
|
156
|
+
}
|
|
157
|
+
export async function recordTranscript(ref, transcript) {
|
|
158
|
+
const entryPath = resolveEntryPath(ref, '.log');
|
|
159
|
+
if (transcript === undefined) {
|
|
160
|
+
await removeCacheEntry(entryPath);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
await writeCacheEntry(entryPath, transcript);
|
|
129
164
|
}
|
|
130
165
|
export async function removeCheckCache(scopeDir) {
|
|
131
166
|
await removeCacheDir({ tool: CACHE_TOOL, scopeDir });
|
|
@@ -138,6 +173,10 @@ export function resolveCacheableCommands(checkCache) {
|
|
|
138
173
|
}
|
|
139
174
|
return commands;
|
|
140
175
|
}
|
|
176
|
+
export function resolveRunId(env) {
|
|
177
|
+
const inherited = env[RUN_ID_ENV_VAR];
|
|
178
|
+
return inherited === undefined || inherited === '' ? randomUUID() : inherited;
|
|
179
|
+
}
|
|
141
180
|
export function resolveTreeSnapshot(options) {
|
|
142
181
|
const inherited = decodeTreeSnapshot(options.env[TREE_SNAPSHOT_ENV_VAR]);
|
|
143
182
|
if (inherited !== undefined && readHeadSha(options.monorepoRoot) === inherited.headSha) {
|
|
@@ -167,6 +206,12 @@ export function writeDebugNote(message, env, stderr) {
|
|
|
167
206
|
stderr.write(`nmr check-cache: ${message}\n`);
|
|
168
207
|
}
|
|
169
208
|
}
|
|
209
|
+
function composeEnvParts(names, env) {
|
|
210
|
+
return names.flatMap((name) => {
|
|
211
|
+
const value = env[name];
|
|
212
|
+
return [name, value === undefined ? 'unset' : 'set', value ?? ''];
|
|
213
|
+
});
|
|
214
|
+
}
|
|
170
215
|
function decodeTreeSnapshot(encoded) {
|
|
171
216
|
if (encoded === undefined) {
|
|
172
217
|
return undefined;
|
|
@@ -177,7 +222,18 @@ function decodeTreeSnapshot(encoded) {
|
|
|
177
222
|
}
|
|
178
223
|
return { hash, headSha };
|
|
179
224
|
}
|
|
180
|
-
function
|
|
225
|
+
function describeChannel(channel) {
|
|
226
|
+
return channel === 'pipe' ? 'pipe' : 'descriptor';
|
|
227
|
+
}
|
|
228
|
+
function digestParts(parts) {
|
|
229
|
+
const hash = createHash('sha256');
|
|
230
|
+
for (const part of parts) {
|
|
231
|
+
hash.update(part);
|
|
232
|
+
hash.update('\0');
|
|
233
|
+
}
|
|
234
|
+
return hash.digest('hex');
|
|
235
|
+
}
|
|
236
|
+
function isParsedCheckCacheEntry(value) {
|
|
181
237
|
if (!isObject(value)) {
|
|
182
238
|
return false;
|
|
183
239
|
}
|
|
@@ -192,19 +248,32 @@ function isCheckCacheEntry(value) {
|
|
|
192
248
|
}
|
|
193
249
|
function isProbeSubject(packageDir, registry) {
|
|
194
250
|
const build = resolveScript('build', registry, packageDir, false);
|
|
195
|
-
if (build === undefined || build.
|
|
251
|
+
if (build === undefined || build.origin.tier === 'package') {
|
|
196
252
|
return false;
|
|
197
253
|
}
|
|
198
254
|
const compile = resolveScript('compile', registry, packageDir, false);
|
|
199
|
-
return compile !== undefined && compile.
|
|
255
|
+
return compile !== undefined && renderChain(compile.steps) === BUILT_IN_COMPILE;
|
|
256
|
+
}
|
|
257
|
+
function isReplayLine(value) {
|
|
258
|
+
return (isObject(value) &&
|
|
259
|
+
typeof value['command'] === 'string' &&
|
|
260
|
+
typeof value['excerpt'] === 'string' &&
|
|
261
|
+
typeof value['scope'] === 'string');
|
|
262
|
+
}
|
|
263
|
+
function isRetention(value) {
|
|
264
|
+
return (isObject(value) &&
|
|
265
|
+
typeof value['key'] === 'string' &&
|
|
266
|
+
typeof value['runId'] === 'string' &&
|
|
267
|
+
Array.isArray(value['replay']) &&
|
|
268
|
+
value['replay'].every((line) => isReplayLine(line)));
|
|
200
269
|
}
|
|
201
|
-
function resolveEntryPath(options) {
|
|
270
|
+
function resolveEntryPath(options, extension = '.json') {
|
|
202
271
|
const anchorDir = path.resolve(options.anchorDir);
|
|
203
272
|
const ref = {
|
|
204
273
|
tool: CACHE_TOOL,
|
|
205
274
|
scopeDir: options.monorepoRoot,
|
|
206
275
|
slug: `${path.basename(anchorDir)}-${options.command.replaceAll(UNSAFE_SLUG_CHARACTERS, '-')}`,
|
|
207
|
-
extension
|
|
276
|
+
extension,
|
|
208
277
|
discriminators: [anchorDir, options.command],
|
|
209
278
|
};
|
|
210
279
|
return resolveCacheEntryPath(ref);
|
package/dist/esm/cli-build.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { reportError } from '@williamthorsen/nmr-core';
|
|
2
|
+
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
2
3
|
import { buildPackage } from "./commands/build.js";
|
|
3
4
|
import { loadWorkspaceConfig } from "./config.js";
|
|
4
5
|
try {
|
|
@@ -9,6 +10,6 @@ try {
|
|
|
9
10
|
});
|
|
10
11
|
}
|
|
11
12
|
catch (error) {
|
|
12
|
-
reportError(
|
|
13
|
+
reportError(describeError(error));
|
|
13
14
|
process.exitCode = 1;
|
|
14
15
|
}
|
package/dist/esm/cli-clean.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { reportError } from '@williamthorsen/nmr-core';
|
|
2
|
+
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
2
3
|
import { runClean } from "./commands/clean.js";
|
|
3
4
|
try {
|
|
4
5
|
await runClean(process.cwd());
|
|
5
6
|
}
|
|
6
7
|
catch (error) {
|
|
7
|
-
reportError(
|
|
8
|
+
reportError(describeError(error));
|
|
8
9
|
process.exitCode = 1;
|
|
9
10
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { parseArgsOrExit, reportError } from '@williamthorsen/nmr-core';
|
|
2
|
+
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
2
3
|
import { DEFAULT_HOOK, ensurePrepublishHooks } from "./commands/ensure-prepublish-hooks.js";
|
|
3
4
|
import { findMonorepoRoot } from "./workspace.js";
|
|
4
5
|
const flagSchema = {
|
|
@@ -38,6 +39,6 @@ try {
|
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
catch (error) {
|
|
41
|
-
reportError(
|
|
42
|
+
reportError(describeError(error));
|
|
42
43
|
process.exitCode = 1;
|
|
43
44
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import { reportError } from '@williamthorsen/nmr-core';
|
|
3
|
+
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
4
|
+
import { reportCatalog } from "./commands/report-catalog.js";
|
|
5
|
+
try {
|
|
6
|
+
reportCatalog(process.cwd());
|
|
7
|
+
}
|
|
8
|
+
catch (error) {
|
|
9
|
+
reportError(describeError(error));
|
|
10
|
+
process.exitCode = 1;
|
|
11
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { reportError } from '@williamthorsen/nmr-core';
|
|
2
|
+
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
2
3
|
import { reportOverrides } from "./commands/report-overrides.js";
|
|
3
4
|
import { findMonorepoRoot } from "./workspace.js";
|
|
4
5
|
try {
|
|
@@ -6,6 +7,6 @@ try {
|
|
|
6
7
|
reportOverrides(monorepoRoot);
|
|
7
8
|
}
|
|
8
9
|
catch (error) {
|
|
9
|
-
reportError(
|
|
10
|
+
reportError(describeError(error));
|
|
10
11
|
process.exitCode = 1;
|
|
11
12
|
}
|
package/dist/esm/cli-taze.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import process from 'node:process';
|
|
2
2
|
import { reportError } from '@williamthorsen/nmr-core';
|
|
3
|
+
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
3
4
|
import { runTaze } from "./commands/taze.js";
|
|
4
5
|
try {
|
|
5
6
|
process.exitCode = runTaze(process.argv.slice(2));
|
|
6
7
|
}
|
|
7
8
|
catch (error) {
|
|
8
|
-
reportError(
|
|
9
|
+
reportError(describeError(error));
|
|
9
10
|
process.exitCode = 1;
|
|
10
11
|
}
|
package/dist/esm/cli.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import process from 'node:process';
|
|
2
|
+
import { reportCliFailure } from "./reportCliFailure.js";
|
|
2
3
|
import { runCli } from "./runCli.js";
|
|
3
4
|
try {
|
|
4
5
|
const { exitCode } = await runCli({
|
|
@@ -11,6 +12,6 @@ try {
|
|
|
11
12
|
process.exitCode = exitCode;
|
|
12
13
|
}
|
|
13
14
|
catch (error) {
|
|
14
|
-
|
|
15
|
+
reportCliFailure(error, process.stderr);
|
|
15
16
|
process.exitCode = 1;
|
|
16
17
|
}
|
|
@@ -4,7 +4,8 @@ import { removeCheckCache } from "../check-cache.js";
|
|
|
4
4
|
import { loadRootConfig } from "../config.js";
|
|
5
5
|
import { findContainingPackageDir } from "../context.js";
|
|
6
6
|
import { applyDevBin, buildWorkspaceRegistry, resolveScript } from "../resolver.js";
|
|
7
|
-
import { runCommand } from "../runner.js";
|
|
7
|
+
import { resolveChannel, runCommand } from "../runner.js";
|
|
8
|
+
import { renderChain } from "../steps.js";
|
|
8
9
|
import { findMonorepoRoot, getWorkspacePackageDirs } from "../workspace.js";
|
|
9
10
|
import { resolveBuildCachePath } from "./build-output.js";
|
|
10
11
|
const CLEAN_ICON = '๐งน';
|
|
@@ -44,15 +45,18 @@ async function sweepWorkspace(monorepoRoot, workspacePackageDirs) {
|
|
|
44
45
|
const registry = buildWorkspaceRegistry(config);
|
|
45
46
|
for (const packageDir of workspacePackageDirs) {
|
|
46
47
|
const resolved = resolveScript(CLEAN_COMMAND, registry, packageDir, false);
|
|
47
|
-
|
|
48
|
+
const resolvedCommand = resolved === undefined ? undefined : renderChain(resolved.steps);
|
|
49
|
+
if (resolvedCommand === undefined || resolvedCommand === '') {
|
|
48
50
|
continue;
|
|
49
51
|
}
|
|
50
|
-
if (
|
|
52
|
+
if (resolvedCommand === BUILT_IN_CLEAN) {
|
|
51
53
|
await cleanPackage(packageDir);
|
|
52
54
|
continue;
|
|
53
55
|
}
|
|
54
|
-
const command = applyDevBin(
|
|
55
|
-
const { exitCode } = await runCommand(command, packageDir
|
|
56
|
+
const command = applyDevBin(resolvedCommand, config.devBin, monorepoRoot);
|
|
57
|
+
const { exitCode } = await runCommand(command, packageDir, {
|
|
58
|
+
channels: { stderr: resolveChannel(process.stderr, false), stdout: resolveChannel(process.stdout, false) },
|
|
59
|
+
});
|
|
56
60
|
if (exitCode !== 0) {
|
|
57
61
|
throw new Error(`nmr-clean: \`${command}\` failed in ${path.basename(packageDir)} with exit code ${exitCode}.`);
|
|
58
62
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function reportCatalog(cwd: string): void;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { findContainingPackageDir } from "../context.js";
|
|
2
|
+
import { DEPENDENCY_FIELDS, readPackageJson } from "../helpers/package-json.js";
|
|
3
|
+
import { findMonorepoRoot, getWorkspacePackageDirs } from "../workspace.js";
|
|
4
|
+
const CATALOG_PROTOCOL = 'catalog:';
|
|
5
|
+
export function reportCatalog(cwd) {
|
|
6
|
+
const monorepoRoot = findMonorepoRoot(cwd);
|
|
7
|
+
const packageDir = findContainingPackageDir(cwd, getWorkspacePackageDirs(monorepoRoot));
|
|
8
|
+
if (packageDir === undefined) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const dependencies = findCataloguedDependencies(packageDir);
|
|
12
|
+
if (dependencies.length === 0) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const subject = dependencies.length === 1 ? '1 dependency comes' : `${dependencies.length} dependencies come`;
|
|
16
|
+
console.warn(`๐ WARN: ${subject} from a catalog, which a package-scoped upgrade does not read:`);
|
|
17
|
+
for (const { name, specifier } of dependencies) {
|
|
18
|
+
console.warn(`- ${name} โ ${specifier}`);
|
|
19
|
+
}
|
|
20
|
+
console.warn(`Run \`nmr upgrade\` from ${monorepoRoot} to include them.`);
|
|
21
|
+
}
|
|
22
|
+
function findCataloguedDependencies(packageDir) {
|
|
23
|
+
const pkg = readPackageJson(packageDir);
|
|
24
|
+
const found = new Map();
|
|
25
|
+
for (const field of DEPENDENCY_FIELDS) {
|
|
26
|
+
const declared = Object.entries(pkg[field] ?? {});
|
|
27
|
+
for (const [name, specifier] of declared) {
|
|
28
|
+
if (specifier.startsWith(CATALOG_PROTOCOL) && !found.has(name)) {
|
|
29
|
+
found.set(name, specifier);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return [...found].map(([name, specifier]) => ({ name, specifier })).toSorted((a, b) => a.name.localeCompare(b.name));
|
|
34
|
+
}
|
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
import { getPnpmOverrides, readPackageJson } from "../helpers/package-json.js";
|
|
2
|
+
import { UserError } from "../UserError.js";
|
|
3
|
+
import { readWorkspaceOverrides } from "../workspace.js";
|
|
2
4
|
export function reportOverrides(monorepoRoot) {
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
const declared = listEntries(readWorkspaceOverrides(monorepoRoot));
|
|
6
|
+
if (declared.length > 0) {
|
|
7
|
+
console.warn('๐ WARN: pnpm overrides are active! Check whether these are still needed:');
|
|
8
|
+
for (const [name, version] of declared) {
|
|
9
|
+
console.warn(`- ${name} โ ${version}`);
|
|
10
|
+
}
|
|
7
11
|
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
rejectLegacyOverrides(monorepoRoot);
|
|
13
|
+
}
|
|
14
|
+
function listEntries(overrides) {
|
|
15
|
+
return Object.entries(overrides ?? {}).toSorted(([a], [b]) => a.localeCompare(b));
|
|
16
|
+
}
|
|
17
|
+
function rejectLegacyOverrides(monorepoRoot) {
|
|
18
|
+
const legacy = listEntries(getPnpmOverrides(readPackageJson(monorepoRoot)));
|
|
19
|
+
if (legacy.length === 0) {
|
|
20
|
+
return;
|
|
11
21
|
}
|
|
22
|
+
throw new UserError([
|
|
23
|
+
'pnpm 11 reads no `pnpm.overrides` from package.json, so these pin nothing while an upgrade run with `--write` goes on rewriting them:',
|
|
24
|
+
...legacy.map(([name, version]) => `- ${name} โ ${String(version)}`),
|
|
25
|
+
'Move them to the `overrides` block in pnpm-workspace.yaml, quoting each version, or run `pnpx codemod run pnpm-v10-to-v11`.',
|
|
26
|
+
].join('\n'));
|
|
12
27
|
}
|
|
@@ -2,6 +2,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
2
2
|
import process from 'node:process';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { reportError } from '@williamthorsen/nmr-core';
|
|
5
|
+
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
5
6
|
const TAZE_CLI_SPECIFIER = 'taze/cli';
|
|
6
7
|
export function runTaze(args, options = {}) {
|
|
7
8
|
const stderr = options.stderr ?? process.stderr;
|
|
@@ -12,7 +13,7 @@ export function runTaze(args, options = {}) {
|
|
|
12
13
|
cliPath = resolveCliPath();
|
|
13
14
|
}
|
|
14
15
|
catch (error) {
|
|
15
|
-
const detail =
|
|
16
|
+
const detail = describeError(error);
|
|
16
17
|
reportError(`Could not resolve '${TAZE_CLI_SPECIFIER}' from nmr's dependencies. Reinstall the workspace to restore it. (${detail})`, stderr);
|
|
17
18
|
return 1;
|
|
18
19
|
}
|
package/dist/esm/config.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { NmrConfig } from './types.js';
|
|
2
|
+
export declare const CONFIG_RELATIVE_PATH: string;
|
|
2
3
|
export declare function resolveConfigPath(baseDir: string): string;
|
|
3
4
|
export declare function loadConfig(baseDir: string): Promise<NmrConfig>;
|
|
4
5
|
export declare function loadRootConfig(monorepoRoot: string): Promise<NmrConfig>;
|