@williamthorsen/nmr 0.55.0 → 1.0.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 +124 -0
- package/.readyup/kits/default.js +667 -167
- package/.readyup/manifest.json +77 -46
- package/CHANGELOG.md +40 -0
- package/README.md +1 -14
- package/agents/guidance/rulebooks/nmr.md +1 -1
- package/bin/ensure-prepublish-hooks.js +1 -1
- package/bin/nmr-actionlint.js +1 -1
- package/bin/nmr-clean.js +1 -1
- package/bin/nmr-compile.js +1 -1
- package/bin/nmr-fmt.js +1 -1
- package/bin/nmr-report-catalog.js +1 -1
- package/bin/nmr-report-overrides.js +1 -1
- package/bin/nmr-shellcheck.js +1 -1
- package/bin/nmr-taze.js +1 -1
- package/bin/nmr-vitest.js +1 -1
- package/bin/nmr.js +1 -1
- package/dist/esm/bins.d.ts +11 -0
- package/dist/esm/bins.js +11 -0
- package/dist/esm/check-cache-entries.js +5 -5
- package/dist/esm/check-cache-gate.js +2 -1
- package/dist/esm/child-env.js +2 -1
- package/dist/esm/child-process.d.ts +2 -0
- package/dist/esm/child-process.js +3 -3
- package/dist/esm/cli-actionlint.js +2 -1
- package/dist/esm/cli-build.js +2 -1
- package/dist/esm/cli-clean.js +2 -1
- package/dist/esm/cli-ensure-prepublish-hooks.js +29 -16
- package/dist/esm/cli-fmt.js +2 -1
- package/dist/esm/cli-report-catalog.js +2 -1
- package/dist/esm/cli-report-overrides.js +2 -1
- package/dist/esm/cli-shellcheck.js +2 -1
- package/dist/esm/cli-taze.js +2 -1
- package/dist/esm/cli-vitest.js +2 -1
- package/dist/esm/cli.js +2 -1
- package/dist/esm/commands/actionlint.d.ts +3 -3
- package/dist/esm/commands/actionlint.js +5 -5
- package/dist/esm/commands/build.js +6 -6
- package/dist/esm/commands/clean.js +3 -1
- package/dist/esm/commands/compiler.js +3 -3
- package/dist/esm/commands/ensure-prepublish-hooks.d.ts +1 -1
- package/dist/esm/commands/ensure-prepublish-hooks.js +4 -5
- package/dist/esm/commands/fmt.d.ts +3 -3
- package/dist/esm/commands/fmt.js +33 -52
- package/dist/esm/commands/format-targets.js +1 -13
- package/dist/esm/commands/node-pin.js +3 -4
- package/dist/esm/commands/rewrite-specifiers.js +2 -2
- package/dist/esm/commands/run-vitest.js +5 -5
- package/dist/esm/commands/shellcheck.d.ts +3 -3
- package/dist/esm/commands/shellcheck.js +8 -11
- package/dist/esm/commands/taze.js +5 -4
- package/dist/esm/commands/tsconfig-chain.js +3 -3
- package/dist/esm/commands/workflow-tools.d.ts +12 -3
- package/dist/esm/commands/workflow-tools.js +29 -14
- package/dist/esm/config-validation.js +9 -9
- package/dist/esm/config.js +2 -3
- package/dist/esm/delegate-host.d.ts +1 -1
- package/dist/esm/delegate-host.js +5 -3
- package/dist/esm/entry-chain.js +2 -3
- package/dist/esm/fan-out.d.ts +3 -3
- package/dist/esm/fan-out.js +14 -6
- package/dist/esm/git-ignored-paths.js +3 -3
- package/dist/esm/help.d.ts +14 -15
- package/dist/esm/help.js +29 -54
- package/dist/esm/helpers/filter-selection.js +2 -2
- package/dist/esm/helpers/package-json.js +8 -9
- package/dist/esm/helpers/resolveXdgBaseDir.d.ts +5 -0
- package/dist/esm/helpers/resolveXdgBaseDir.js +12 -0
- package/dist/esm/helpers/type-guards.d.ts +0 -2
- package/dist/esm/helpers/type-guards.js +2 -4
- package/dist/esm/invocation-args.d.ts +0 -32
- package/dist/esm/invocation-args.js +23 -121
- package/dist/esm/invocation.d.ts +3 -1
- package/dist/esm/invocation.js +1 -0
- package/dist/esm/machine-lock.js +3 -4
- package/dist/esm/nmr-flags.d.ts +109 -0
- package/dist/esm/nmr-flags.js +120 -0
- package/dist/esm/output-style.d.ts +1 -1
- package/dist/esm/output-style.js +2 -1
- package/dist/esm/pass-manifest.js +2 -2
- package/dist/esm/presentation.d.ts +1 -1
- package/dist/esm/remote-host-session.d.ts +2 -0
- package/dist/esm/remote-host-session.js +1 -0
- package/dist/esm/remote-probe.js +2 -2
- package/dist/esm/remote-run.d.ts +3 -3
- package/dist/esm/remote-run.js +6 -4
- package/dist/esm/remote-session.js +4 -4
- package/dist/esm/remote-snapshot.js +8 -22
- package/dist/esm/resolver.js +3 -4
- package/dist/esm/route.d.ts +29 -0
- package/dist/esm/route.js +94 -0
- package/dist/esm/run-chain.d.ts +1 -1
- package/dist/esm/run-chain.js +4 -2
- package/dist/esm/runCli.d.ts +2 -0
- package/dist/esm/runCli.js +36 -78
- package/dist/esm/stated-outcome.js +3 -3
- package/dist/esm/steps.js +6 -30
- package/dist/esm/user-config.js +7 -14
- package/dist/esm/vitest-source-resolution.js +5 -6
- package/dist/esm/vitest-temp-isolation.d.ts +2 -0
- package/dist/esm/vitest-temp-isolation.js +44 -0
- package/dist/esm/vitest.d.ts +9 -0
- package/dist/esm/vitest.js +8 -5
- package/docs/reporting.md +3 -3
- package/docs/scripts.md +1 -1
- package/docs/utilities.md +5 -5
- package/docs/vitest.md +11 -6
- package/package.json +4 -4
|
@@ -1,24 +1,37 @@
|
|
|
1
|
-
import
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import { reportFailure } from '@williamthorsen/nmr-core';
|
|
3
|
+
import { defineCommand, runCli } from '@williamthorsen/nmr-core/cli';
|
|
4
|
+
import { ENSURE_PREPUBLISH_HOOKS_BIN } from "./bins.js";
|
|
2
5
|
import { DEFAULT_HOOK, ensurePrepublishHooks, reportPrepublishHooks } from "./commands/ensure-prepublish-hooks.js";
|
|
3
6
|
import { resolveBinStyles } from "./resolveBinStyles.js";
|
|
4
7
|
import { findMonorepoRoot } from "./workspace.js";
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
|
|
8
|
+
const command = defineCommand({
|
|
9
|
+
summary: 'Check that every publishable workspace package has a prepublishOnly script',
|
|
10
|
+
flags: {
|
|
11
|
+
fix: { type: 'boolean', description: 'Add the script to each publishable package that lacks it' },
|
|
12
|
+
dryRun: { type: 'boolean', description: 'With --fix, report the packages that it would change without writing' },
|
|
13
|
+
command: {
|
|
14
|
+
type: 'string',
|
|
15
|
+
description: `The command that the script runs (default: ${DEFAULT_HOOK})`,
|
|
16
|
+
valueHint: 'command',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
run: ({ flags }) => {
|
|
20
|
+
const styles = resolveBinStyles();
|
|
21
|
+
const options = { shouldFix: flags.fix, isDryRun: flags.dryRun, ...(flags.command && { command: flags.command }) };
|
|
22
|
+
const result = ensurePrepublishHooks(findMonorepoRoot(), options);
|
|
23
|
+
reportPrepublishHooks(result, flags.command ?? DEFAULT_HOOK, styles.stdout);
|
|
24
|
+
return result.hasFailures ? 1 : 0;
|
|
25
|
+
},
|
|
26
|
+
});
|
|
11
27
|
try {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (result.hasFailures) {
|
|
18
|
-
process.exitCode = 1;
|
|
19
|
-
}
|
|
28
|
+
process.exitCode = await runCli(process.argv.slice(2), command, {
|
|
29
|
+
name: ENSURE_PREPUBLISH_HOOKS_BIN,
|
|
30
|
+
stdout: process.stdout,
|
|
31
|
+
stderr: process.stderr,
|
|
32
|
+
});
|
|
20
33
|
}
|
|
21
34
|
catch (error) {
|
|
22
|
-
reportFailure(
|
|
35
|
+
reportFailure(ENSURE_PREPUBLISH_HOOKS_BIN, error);
|
|
23
36
|
process.exitCode = 1;
|
|
24
37
|
}
|
package/dist/esm/cli-fmt.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import process from 'node:process';
|
|
2
2
|
import { reportFailure } from '@williamthorsen/nmr-core';
|
|
3
|
+
import { NMR_FMT_BIN } from "./bins.js";
|
|
3
4
|
import { runFmt } from "./commands/fmt.js";
|
|
4
5
|
try {
|
|
5
6
|
process.exitCode = await runFmt(process.argv.slice(2), process.cwd());
|
|
6
7
|
}
|
|
7
8
|
catch (error) {
|
|
8
|
-
reportFailure(
|
|
9
|
+
reportFailure(NMR_FMT_BIN, error);
|
|
9
10
|
process.exitCode = 1;
|
|
10
11
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import process from 'node:process';
|
|
2
2
|
import { reportFailure } from '@williamthorsen/nmr-core';
|
|
3
|
+
import { NMR_REPORT_CATALOG_BIN } from "./bins.js";
|
|
3
4
|
import { reportCatalog } from "./commands/report-catalog.js";
|
|
4
5
|
import { resolveBinStyles } from "./resolveBinStyles.js";
|
|
5
6
|
try {
|
|
6
7
|
reportCatalog(process.cwd(), resolveBinStyles().stderr);
|
|
7
8
|
}
|
|
8
9
|
catch (error) {
|
|
9
|
-
reportFailure(
|
|
10
|
+
reportFailure(NMR_REPORT_CATALOG_BIN, error);
|
|
10
11
|
process.exitCode = 1;
|
|
11
12
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { reportFailure } from '@williamthorsen/nmr-core';
|
|
2
|
+
import { NMR_REPORT_OVERRIDES_BIN } from "./bins.js";
|
|
2
3
|
import { reportOverrides } from "./commands/report-overrides.js";
|
|
3
4
|
import { resolveBinStyles } from "./resolveBinStyles.js";
|
|
4
5
|
import { findMonorepoRoot } from "./workspace.js";
|
|
@@ -7,6 +8,6 @@ try {
|
|
|
7
8
|
reportOverrides(monorepoRoot, resolveBinStyles().stderr);
|
|
8
9
|
}
|
|
9
10
|
catch (error) {
|
|
10
|
-
reportFailure(
|
|
11
|
+
reportFailure(NMR_REPORT_OVERRIDES_BIN, error);
|
|
11
12
|
process.exitCode = 1;
|
|
12
13
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import process from 'node:process';
|
|
2
2
|
import { reportFailure } from '@williamthorsen/nmr-core';
|
|
3
|
+
import { NMR_SHELLCHECK_BIN } from "./bins.js";
|
|
3
4
|
import { runShellcheck } from "./commands/shellcheck.js";
|
|
4
5
|
import { findMonorepoRoot } from "./workspace.js";
|
|
5
6
|
try {
|
|
6
7
|
process.exitCode = await runShellcheck(findMonorepoRoot(), process.argv.slice(2));
|
|
7
8
|
}
|
|
8
9
|
catch (error) {
|
|
9
|
-
reportFailure(
|
|
10
|
+
reportFailure(NMR_SHELLCHECK_BIN, error);
|
|
10
11
|
process.exitCode = 1;
|
|
11
12
|
}
|
package/dist/esm/cli-taze.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import process from 'node:process';
|
|
2
2
|
import { reportFailure } from '@williamthorsen/nmr-core';
|
|
3
|
+
import { NMR_TAZE_BIN } from "./bins.js";
|
|
3
4
|
import { runTaze } from "./commands/taze.js";
|
|
4
5
|
import { resolveBinStyles } from "./resolveBinStyles.js";
|
|
5
6
|
try {
|
|
6
7
|
process.exitCode = await runTaze(process.argv.slice(2), { stderrStyle: resolveBinStyles().stderr });
|
|
7
8
|
}
|
|
8
9
|
catch (error) {
|
|
9
|
-
reportFailure(
|
|
10
|
+
reportFailure(NMR_TAZE_BIN, error);
|
|
10
11
|
process.exitCode = 1;
|
|
11
12
|
}
|
package/dist/esm/cli-vitest.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import process from 'node:process';
|
|
2
2
|
import { reportFailure } from '@williamthorsen/nmr-core';
|
|
3
|
+
import { NMR_VITEST_BIN } from "./bins.js";
|
|
3
4
|
import { runNmrVitest } from "./commands/run-vitest.js";
|
|
4
5
|
try {
|
|
5
6
|
process.exitCode = runNmrVitest(process.argv.slice(2), process.cwd());
|
|
6
7
|
}
|
|
7
8
|
catch (error) {
|
|
8
|
-
reportFailure(
|
|
9
|
+
reportFailure(NMR_VITEST_BIN, error);
|
|
9
10
|
process.exitCode = 1;
|
|
10
11
|
}
|
package/dist/esm/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import process from 'node:process';
|
|
2
2
|
import { reportFailure } from '@williamthorsen/nmr-core';
|
|
3
|
+
import { NMR_BIN } from "./bins.js";
|
|
3
4
|
import { runCli } from "./runCli.js";
|
|
4
5
|
try {
|
|
5
6
|
const { exitCode } = await runCli({
|
|
@@ -12,6 +13,6 @@ try {
|
|
|
12
13
|
process.exitCode = exitCode;
|
|
13
14
|
}
|
|
14
15
|
catch (error) {
|
|
15
|
-
reportFailure(
|
|
16
|
+
reportFailure(NMR_BIN, error);
|
|
16
17
|
process.exitCode = 1;
|
|
17
18
|
}
|
|
@@ -6,9 +6,9 @@ export interface ActionlintOutcome {
|
|
|
6
6
|
status: number | null;
|
|
7
7
|
}
|
|
8
8
|
export interface RunActionlintOptions {
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
ensure?: (tool: WorkflowTool,
|
|
9
|
+
/** Directories under which the pinned tools are cached, in lookup order; defaults to `resolveToolCacheRoots`. */
|
|
10
|
+
cacheRoots?: readonly [string, ...string[]];
|
|
11
|
+
ensure?: (tool: WorkflowTool, cacheRoots: readonly [string, ...string[]]) => Promise<string>;
|
|
12
12
|
spawn?: (file: string, args: string[], cwd: string) => ActionlintOutcome;
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
@@ -3,17 +3,17 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { UserError } from '@williamthorsen/nmr-core';
|
|
5
5
|
import { describeSignalExitCode } from "../child-process.js";
|
|
6
|
-
import { ensureTool } from "./workflow-tools.js";
|
|
6
|
+
import { ensureTool, resolveToolCacheRoots } from "./workflow-tools.js";
|
|
7
7
|
const WORKFLOWS_DIR = path.join('.github', 'workflows');
|
|
8
8
|
export async function runActionlint(monorepoRoot, args, options = {}) {
|
|
9
9
|
if (!fs.existsSync(path.join(monorepoRoot, WORKFLOWS_DIR))) {
|
|
10
10
|
console.info(`No workflows to lint: ${WORKFLOWS_DIR}/ does not exist.`);
|
|
11
11
|
return 0;
|
|
12
12
|
}
|
|
13
|
-
const
|
|
14
|
-
const ensure = options.ensure ?? ((tool,
|
|
15
|
-
const actionlintPath = await ensure('actionlint',
|
|
16
|
-
const shellcheckPath = await ensure('shellcheck',
|
|
13
|
+
const cacheRoots = options.cacheRoots ?? resolveToolCacheRoots(monorepoRoot);
|
|
14
|
+
const ensure = options.ensure ?? ((tool, roots) => ensureTool(tool, { cacheRoots: roots }));
|
|
15
|
+
const actionlintPath = await ensure('actionlint', cacheRoots);
|
|
16
|
+
const shellcheckPath = await ensure('shellcheck', cacheRoots);
|
|
17
17
|
const outcome = (options.spawn ?? spawnInherited)(actionlintPath, buildActionlintArgs(shellcheckPath, args), monorepoRoot);
|
|
18
18
|
if (outcome.error !== undefined) {
|
|
19
19
|
throw new UserError(`Cannot run actionlint: ${outcome.error.message}`, { cause: outcome.error });
|
|
@@ -92,7 +92,7 @@ async function emitPackage(packageDir, entryPoints, outdir) {
|
|
|
92
92
|
}
|
|
93
93
|
function assertEmitSucceeded(result) {
|
|
94
94
|
if (result.emitSkipped) {
|
|
95
|
-
throw new UserError(`
|
|
95
|
+
throw new UserError(`emit failed.\n${formatDiagnostics(result.diagnostics)}`);
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
function createCachingCompilerHost(compilerOptions) {
|
|
@@ -127,7 +127,7 @@ function resolveEmitDir(packageDir, outdir) {
|
|
|
127
127
|
if (relativePathFromPackage === '' ||
|
|
128
128
|
relativePathFromPackage.startsWith('..') ||
|
|
129
129
|
path.isAbsolute(relativePathFromPackage)) {
|
|
130
|
-
throw new UserError(`
|
|
130
|
+
throw new UserError(`refusing to build into '${outdir}', which does not resolve inside the package. ` +
|
|
131
131
|
'The build replaces its output directory on each emit, so the directory must be below the package root.');
|
|
132
132
|
}
|
|
133
133
|
return resolvedDir;
|
|
@@ -144,7 +144,7 @@ async function swapIntoPlace(emitDir, scratchDirs) {
|
|
|
144
144
|
if (hadPreviousOutput && !existsSync(emitDir)) {
|
|
145
145
|
await rename(scratchDirs.previousDir, emitDir);
|
|
146
146
|
}
|
|
147
|
-
throw new UserError(`
|
|
147
|
+
throw new UserError(`failed to move the built output into ${emitDir}: ${describeError(error)}`, {
|
|
148
148
|
cause: error,
|
|
149
149
|
});
|
|
150
150
|
}
|
|
@@ -155,11 +155,11 @@ function synthesizeCompilerOptions(packageDir, outdir) {
|
|
|
155
155
|
const configPath = path.join(packageDir, 'tsconfig.json');
|
|
156
156
|
const configFile = ts.readConfigFile(configPath, (fileName) => ts.sys.readFile(fileName));
|
|
157
157
|
if (configFile.error) {
|
|
158
|
-
throw new UserError(`
|
|
158
|
+
throw new UserError(`failed to read ${configPath}.\n${formatDiagnostics([configFile.error])}`);
|
|
159
159
|
}
|
|
160
160
|
const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, packageDir);
|
|
161
161
|
if (parsedConfig.errors.length > 0) {
|
|
162
|
-
throw new UserError(`
|
|
162
|
+
throw new UserError(`failed to parse ${configPath}.\n${formatDiagnostics(parsedConfig.errors)}`);
|
|
163
163
|
}
|
|
164
164
|
const resolvedOutDir = path.resolve(packageDir, outdir);
|
|
165
165
|
return {
|
|
@@ -180,7 +180,7 @@ function writeStagedOutput(stagedFiles, emitDir, stagingDir) {
|
|
|
180
180
|
const ts = getCompiler();
|
|
181
181
|
for (const [fileName, file] of stagedFiles) {
|
|
182
182
|
if (!isWithin(emitDir, fileName)) {
|
|
183
|
-
throw new Error(`
|
|
183
|
+
throw new Error(`the compiler emitted ${fileName}, which is outside the output directory ${emitDir}. ` +
|
|
184
184
|
"Verify the resolved tsconfig's 'outDir' and 'declarationDir'.");
|
|
185
185
|
}
|
|
186
186
|
ts.sys.writeFile(path.join(stagingDir, path.relative(emitDir, fileName)), file.text, file.writeByteOrderMark);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { rm } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { formatGlyphLine, UserError } from '@williamthorsen/nmr-core';
|
|
4
|
+
import { NMR_CLEAN_BIN } from "../bins.js";
|
|
4
5
|
import { removeCheckCache } from "../check-cache-entries.js";
|
|
5
6
|
import { loadRootConfig } from "../config.js";
|
|
6
7
|
import { findContainingPackageDir } from "../context.js";
|
|
@@ -68,10 +69,11 @@ async function sweepWorkspace(monorepoRoot, workspacePackageDirs, style) {
|
|
|
68
69
|
}
|
|
69
70
|
const command = applyDevBin(resolvedCommand, config.devBin, monorepoRoot);
|
|
70
71
|
const { exitCode } = await runCommand(command, packageDir, {
|
|
72
|
+
bin: NMR_CLEAN_BIN,
|
|
71
73
|
channels: { stderr: resolveChannel(process.stderr, false), stdout: resolveChannel(process.stdout, false) },
|
|
72
74
|
});
|
|
73
75
|
if (exitCode !== 0) {
|
|
74
|
-
throw new UserError(
|
|
76
|
+
throw new UserError(`\`${command}\` failed in ${path.basename(packageDir)} with exit code ${exitCode}.`);
|
|
75
77
|
}
|
|
76
78
|
cleanedCount++;
|
|
77
79
|
}
|
|
@@ -14,7 +14,7 @@ export function assertSupportedTypeScript() {
|
|
|
14
14
|
const minor = minorPart === undefined ? 0 : Number(minorPart);
|
|
15
15
|
const tooOld = major < MINIMUM_TYPESCRIPT_MAJOR || (major === MINIMUM_TYPESCRIPT_MAJOR && minor < MINIMUM_TYPESCRIPT_MINOR);
|
|
16
16
|
if (tooOld || major >= UNSUPPORTED_TYPESCRIPT_MAJOR) {
|
|
17
|
-
throw new UserError(`
|
|
17
|
+
throw new UserError(`requires TypeScript ${SUPPORTED_TYPESCRIPT_RANGE}, but found ` +
|
|
18
18
|
`${typeof version === 'string' ? version : 'a version without the compiler API'}. ${INSTALL_TYPESCRIPT_GUIDANCE}`);
|
|
19
19
|
}
|
|
20
20
|
}
|
|
@@ -28,7 +28,7 @@ export function formatDiagnostics(diagnostics) {
|
|
|
28
28
|
}
|
|
29
29
|
export function getCompiler() {
|
|
30
30
|
if (ts === undefined) {
|
|
31
|
-
throw new Error('
|
|
31
|
+
throw new Error('the compiler is not loaded; call loadCompiler first.');
|
|
32
32
|
}
|
|
33
33
|
return ts;
|
|
34
34
|
}
|
|
@@ -38,7 +38,7 @@ export async function loadCompiler(loadTypeScript = importTypeScript) {
|
|
|
38
38
|
}
|
|
39
39
|
catch (error) {
|
|
40
40
|
if (hasErrnoCode(error, 'ERR_MODULE_NOT_FOUND') && error.message.includes("'typescript'")) {
|
|
41
|
-
throw new UserError(`
|
|
41
|
+
throw new UserError(`requires TypeScript ${SUPPORTED_TYPESCRIPT_RANGE}, but 'typescript' could not be resolved. ` +
|
|
42
42
|
INSTALL_TYPESCRIPT_GUIDANCE, { cause: error });
|
|
43
43
|
}
|
|
44
44
|
throw error;
|
|
@@ -10,7 +10,7 @@ export interface EnsurePrepublishHooksResult {
|
|
|
10
10
|
packages: PackageHookStatus[];
|
|
11
11
|
hasFailures: boolean;
|
|
12
12
|
}
|
|
13
|
-
export declare const DEFAULT_HOOK = "
|
|
13
|
+
export declare const DEFAULT_HOOK = "nmr build";
|
|
14
14
|
/**
|
|
15
15
|
* Checks (and optionally fixes) whether all publishable workspace packages
|
|
16
16
|
* have a `prepublishOnly` script.
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { STATUS_GLYPHS, UserError } from '@williamthorsen/nmr-core';
|
|
3
|
+
import { isRecord, STATUS_GLYPHS, UserError } from '@williamthorsen/nmr-core';
|
|
4
4
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
5
5
|
import { readPackageJson } from "../helpers/package-json.js";
|
|
6
6
|
import { reportClosing } from "../helpers/reportClosing.js";
|
|
7
|
-
import { isObject } from "../helpers/type-guards.js";
|
|
8
7
|
import { getWorkspacePackageDirs } from "../workspace.js";
|
|
9
|
-
export const DEFAULT_HOOK = '
|
|
8
|
+
export const DEFAULT_HOOK = 'nmr build';
|
|
10
9
|
export function ensurePrepublishHooks(monorepoRoot, options) {
|
|
11
10
|
const hookCommand = options.command ?? DEFAULT_HOOK;
|
|
12
11
|
const packageDirs = getWorkspacePackageDirs(monorepoRoot);
|
|
@@ -83,10 +82,10 @@ function addPrepublishOnly(packageDir, command) {
|
|
|
83
82
|
catch (error) {
|
|
84
83
|
throw new UserError(`Invalid package.json in ${packageDir}: ${describeError(error)}`, { cause: error });
|
|
85
84
|
}
|
|
86
|
-
if (!
|
|
85
|
+
if (!isRecord(parsedManifest)) {
|
|
87
86
|
throw new UserError(`Invalid package.json in ${packageDir}: expected an object`);
|
|
88
87
|
}
|
|
89
|
-
const scripts =
|
|
88
|
+
const scripts = isRecord(parsedManifest['scripts']) ? parsedManifest['scripts'] : {};
|
|
90
89
|
scripts['prepublishOnly'] = command;
|
|
91
90
|
parsedManifest['scripts'] = scripts;
|
|
92
91
|
writeFileSync(filePath, JSON.stringify(parsedManifest, null, 2) + '\n', 'utf8');
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export type FormatMode = 'check' | 'write';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* without a Prettier config of its own is formatted with the house config. The run
|
|
5
|
-
* cache unless `--no-cache` or `NMR_NO_CACHE=1` bypasses it.
|
|
3
|
+
* Runs `nmr-fmt` against `argv` and returns the exit code to report: It formats or checks the files that git
|
|
4
|
+
* reports for `cwd`. A repository without a Prettier config of its own is formatted with the house config. The run
|
|
5
|
+
* uses Prettier's content-keyed cache unless `--no-cache` or `NMR_NO_CACHE=1` bypasses it.
|
|
6
6
|
*
|
|
7
7
|
* Trailing arguments are git pathspecs, not Prettier flags; an unrecognized option is rejected rather
|
|
8
8
|
* than passed along, since git would read it as a pathspec matching nothing and the run would report
|
package/dist/esm/commands/fmt.js
CHANGED
|
@@ -5,29 +5,41 @@ import { createRequire } from 'node:module';
|
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import process from 'node:process';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
-
import { reportError } from '@williamthorsen/nmr-core';
|
|
8
|
+
import { isRecord, reportError } from '@williamthorsen/nmr-core';
|
|
9
|
+
import { defineCommand, runCli, UsageError } from '@williamthorsen/nmr-core/cli';
|
|
9
10
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
10
|
-
import {
|
|
11
|
+
import { NMR_FMT_BIN } from "../bins.js";
|
|
11
12
|
import { prepareFmtCache } from "./fmt-cache.js";
|
|
12
13
|
import { resolveFormatTargets } from "./format-targets.js";
|
|
13
14
|
const PRETTIER_PACKAGE = 'prettier';
|
|
14
15
|
const DEFAULT_CONFIG_PATH = fileURLToPath(new URL(`../prettier-default.config${path.extname(fileURLToPath(import.meta.url))}`, import.meta.url));
|
|
15
16
|
const ARGUMENT_BUDGET_BYTES = 100_000;
|
|
16
|
-
const USAGE = 'Usage: nmr-fmt (--check | --write) [--no-cache] [--] [pathspec...]';
|
|
17
17
|
const MODE_ARGS = {
|
|
18
18
|
check: ['--check'],
|
|
19
19
|
write: ['--list-different', '--write'],
|
|
20
20
|
};
|
|
21
21
|
export async function runFmt(argv, cwd = process.cwd()) {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
const command = defineCommand({
|
|
23
|
+
summary: 'Format or check the files that git lists',
|
|
24
|
+
flags: {
|
|
25
|
+
check: { type: 'boolean', description: 'Report the files that are not formatted, without changing them' },
|
|
26
|
+
write: { type: 'boolean', description: 'Format the files in place, naming each one that changes' },
|
|
27
|
+
noCache: { type: 'boolean', description: "Ignore Prettier's cache of files that already passed" },
|
|
28
|
+
},
|
|
29
|
+
operands: [{ name: 'pathspec', description: 'Git pathspecs that limit the files', optional: true, variadic: true }],
|
|
30
|
+
run: ({ flags, operands }) => {
|
|
31
|
+
if (flags.check === flags.write) {
|
|
32
|
+
throw new UsageError(flags.check ? 'Pass --check or --write, not both.' : 'Pass --check or --write.');
|
|
33
|
+
}
|
|
34
|
+
return formatFiles({ mode: flags.check ? 'check' : 'write', pathspecs: operands.pathspec, shouldBypassCache: flags.noCache }, cwd);
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
return runCli(argv, command, { name: NMR_FMT_BIN, stdout: process.stdout, stderr: process.stderr, baseDir: cwd });
|
|
38
|
+
}
|
|
39
|
+
async function formatFiles(request, cwd) {
|
|
40
|
+
const resolvedTargets = resolveFormatTargets(cwd, request.pathspecs);
|
|
29
41
|
if (!resolvedTargets.ok) {
|
|
30
|
-
reportError(
|
|
42
|
+
reportError(NMR_FMT_BIN, resolvedTargets.error);
|
|
31
43
|
return 1;
|
|
32
44
|
}
|
|
33
45
|
const { excludedFiles, files, ignorePaths, repositoryRoot, unreadableFiles } = resolvedTargets.targets;
|
|
@@ -35,10 +47,10 @@ export async function runFmt(argv, cwd = process.cwd()) {
|
|
|
35
47
|
process.stderr.write(`nmr-fmt: skipped ${file}, which cannot be read (${code})\n`);
|
|
36
48
|
}
|
|
37
49
|
if (files.length === 0) {
|
|
38
|
-
if (
|
|
50
|
+
if (request.pathspecs.length === 0)
|
|
39
51
|
return 0;
|
|
40
52
|
if (excludedFiles.length === 0 && unreadableFiles.length === 0) {
|
|
41
|
-
reportError(`
|
|
53
|
+
reportError(NMR_FMT_BIN, `the pathspecs did not match any file that git lists: ${request.pathspecs.join(', ')}`);
|
|
42
54
|
return 1;
|
|
43
55
|
}
|
|
44
56
|
if (excludedFiles.length > 0) {
|
|
@@ -48,19 +60,19 @@ export async function runFmt(argv, cwd = process.cwd()) {
|
|
|
48
60
|
}
|
|
49
61
|
const cli = resolvePrettierCli(cwd);
|
|
50
62
|
if (!cli.ok) {
|
|
51
|
-
reportError(
|
|
63
|
+
reportError(NMR_FMT_BIN, cli.error);
|
|
52
64
|
return 1;
|
|
53
65
|
}
|
|
54
66
|
const repositoryConfig = await findRepositoryConfig(cli.manifestPath, cwd);
|
|
55
67
|
if (!repositoryConfig.ok) {
|
|
56
|
-
reportError(
|
|
68
|
+
reportError(NMR_FMT_BIN, repositoryConfig.error);
|
|
57
69
|
return 1;
|
|
58
70
|
}
|
|
59
71
|
const cache = prepareFmtCache({
|
|
60
72
|
cwd,
|
|
61
73
|
env: process.env,
|
|
62
74
|
repositoryRoot,
|
|
63
|
-
shouldBypassCache:
|
|
75
|
+
shouldBypassCache: request.shouldBypassCache,
|
|
64
76
|
});
|
|
65
77
|
if (cache.kind === 'failed') {
|
|
66
78
|
process.stderr.write(`nmr-fmt: running without the Prettier cache: ${cache.reason}\n`);
|
|
@@ -69,43 +81,12 @@ export async function runFmt(argv, cwd = process.cwd()) {
|
|
|
69
81
|
cacheLocation: cache.kind === 'cached' ? cache.location : undefined,
|
|
70
82
|
cliPath: cli.cliPath,
|
|
71
83
|
configPath: repositoryConfig.found ? undefined : DEFAULT_CONFIG_PATH,
|
|
72
|
-
mode:
|
|
84
|
+
mode: request.mode,
|
|
73
85
|
files,
|
|
74
86
|
ignorePaths,
|
|
75
87
|
cwd,
|
|
76
88
|
});
|
|
77
89
|
}
|
|
78
|
-
function parseFmtArgs(argv) {
|
|
79
|
-
let mode;
|
|
80
|
-
let shouldBypassCache = false;
|
|
81
|
-
const pathspecs = [];
|
|
82
|
-
for (const [index, arg] of argv.entries()) {
|
|
83
|
-
if (arg === '--') {
|
|
84
|
-
pathspecs.push(...argv.slice(index + 1));
|
|
85
|
-
break;
|
|
86
|
-
}
|
|
87
|
-
if (arg === '--no-cache') {
|
|
88
|
-
shouldBypassCache = true;
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
if (arg === '--check' || arg === '--write') {
|
|
92
|
-
const requestedMode = arg === '--check' ? 'check' : 'write';
|
|
93
|
-
if (mode !== undefined && mode !== requestedMode) {
|
|
94
|
-
return { ok: false, error: 'Pass --check or --write, not both.' };
|
|
95
|
-
}
|
|
96
|
-
mode = requestedMode;
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
if (arg.startsWith('-')) {
|
|
100
|
-
return { ok: false, error: `Unknown option: ${arg}` };
|
|
101
|
-
}
|
|
102
|
-
pathspecs.push(arg);
|
|
103
|
-
}
|
|
104
|
-
if (mode === undefined) {
|
|
105
|
-
return { ok: false, error: 'Pass --check or --write.' };
|
|
106
|
-
}
|
|
107
|
-
return { ok: true, mode, pathspecs, shouldBypassCache };
|
|
108
|
-
}
|
|
109
90
|
function resolvePrettierCli(cwd) {
|
|
110
91
|
const missingResult = {
|
|
111
92
|
ok: false,
|
|
@@ -116,9 +97,9 @@ function resolvePrettierCli(cwd) {
|
|
|
116
97
|
if (manifestPath === undefined)
|
|
117
98
|
return missingResult;
|
|
118
99
|
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
119
|
-
if (!
|
|
100
|
+
if (!isRecord(manifest))
|
|
120
101
|
return missingResult;
|
|
121
|
-
const bin =
|
|
102
|
+
const bin = isRecord(manifest['bin']) ? manifest['bin'][PRETTIER_PACKAGE] : manifest['bin'];
|
|
122
103
|
if (typeof bin !== 'string')
|
|
123
104
|
return missingResult;
|
|
124
105
|
return { ok: true, cliPath: path.resolve(path.dirname(manifestPath), bin), manifestPath };
|
|
@@ -126,7 +107,7 @@ function resolvePrettierCli(cwd) {
|
|
|
126
107
|
async function findRepositoryConfig(manifestPath, cwd) {
|
|
127
108
|
try {
|
|
128
109
|
const prettier = createRequire(manifestPath)(PRETTIER_PACKAGE);
|
|
129
|
-
const resolveConfigFile =
|
|
110
|
+
const resolveConfigFile = isRecord(prettier) ? prettier['resolveConfigFile'] : undefined;
|
|
130
111
|
if (typeof resolveConfigFile !== 'function') {
|
|
131
112
|
return {
|
|
132
113
|
ok: false,
|
|
@@ -157,7 +138,7 @@ export function runPrettier(options) {
|
|
|
157
138
|
for (const batch of batchWithinBudget(files, budgetBytes)) {
|
|
158
139
|
const result = spawnSync(process.execPath, [...prettierArgs, ...batch], { cwd, stdio: 'inherit' });
|
|
159
140
|
if (result.error) {
|
|
160
|
-
reportError(`
|
|
141
|
+
reportError(NMR_FMT_BIN, `could not run \`${PRETTIER_PACKAGE}\` (${result.error.message}).`);
|
|
161
142
|
return 1;
|
|
162
143
|
}
|
|
163
144
|
const status = result.status ?? 1;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process';
|
|
2
1
|
import { lstatSync } from 'node:fs';
|
|
3
2
|
import path from 'node:path';
|
|
4
|
-
import { hasErrnoCode } from '@williamthorsen/nmr-core';
|
|
3
|
+
import { hasErrnoCode, runGit } from '@williamthorsen/nmr-core';
|
|
5
4
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
6
5
|
const IGNORE_FILENAME = '.prettierignore';
|
|
7
6
|
const LIST_FILES_ARGS = ['ls-files', '-z', '--cached', '--others', '--exclude-standard'];
|
|
@@ -64,17 +63,6 @@ function classifyListedFile(cwd, file) {
|
|
|
64
63
|
return { kind: 'failed', error: `could not stat ${file}: ${describeError(error)}` };
|
|
65
64
|
}
|
|
66
65
|
}
|
|
67
|
-
function runGit(args, cwd) {
|
|
68
|
-
const result = spawnSync('git', args, { cwd, encoding: 'utf8' });
|
|
69
|
-
if (result.error) {
|
|
70
|
-
return { ok: false, error: result.error.message };
|
|
71
|
-
}
|
|
72
|
-
if (result.status !== 0) {
|
|
73
|
-
const stderr = result.stderr.trim();
|
|
74
|
-
return { ok: false, error: stderr || `\`git ${args.join(' ')}\` failed with exit code ${result.status}.` };
|
|
75
|
-
}
|
|
76
|
-
return { ok: true, stdout: result.stdout };
|
|
77
|
-
}
|
|
78
66
|
function splitNulSeparated(output) {
|
|
79
67
|
return output.split('\0').filter((entry) => entry !== '');
|
|
80
68
|
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import process from 'node:process';
|
|
4
|
-
import { formatGlyphLine } from '@williamthorsen/nmr-core';
|
|
4
|
+
import { formatGlyphLine, isRecord } from '@williamthorsen/nmr-core';
|
|
5
5
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
6
6
|
import { NMR_GLYPHS } from "../glyphs.js";
|
|
7
|
-
import { isObject } from "../helpers/type-guards.js";
|
|
8
7
|
export const NODE_RELEASE_INDEX_URL = 'https://nodejs.org/dist/index.json';
|
|
9
8
|
const TOOL_VERSIONS_FILE = '.tool-versions';
|
|
10
9
|
const SOAK_MS = 2 * 24 * 60 * 60 * 1000;
|
|
@@ -77,7 +76,7 @@ export function selectNodeUpgrade(pin, releases, now) {
|
|
|
77
76
|
let best;
|
|
78
77
|
let bestVersion;
|
|
79
78
|
for (const release of releases) {
|
|
80
|
-
if (!
|
|
79
|
+
if (!isRecord(release))
|
|
81
80
|
continue;
|
|
82
81
|
const { date, version } = release;
|
|
83
82
|
if (typeof version !== 'string' || typeof date !== 'string' || !RELEASE_DATE_PATTERN.test(date))
|
|
@@ -120,7 +119,7 @@ function findNodePin(content) {
|
|
|
120
119
|
return { start: match.index + match[0].length - version.length, version };
|
|
121
120
|
}
|
|
122
121
|
function isMissingFileError(error) {
|
|
123
|
-
return
|
|
122
|
+
return isRecord(error) && error['code'] === 'ENOENT';
|
|
124
123
|
}
|
|
125
124
|
function parseVersion(version) {
|
|
126
125
|
return version.split('.').map(Number);
|
|
@@ -89,7 +89,7 @@ function resolveSpecifierReplacement(specifier, sourceContainingFile, compilerOp
|
|
|
89
89
|
const ts = getCompiler();
|
|
90
90
|
const resolvedModule = ts.resolveModuleName(specifier, sourceContainingFile, compilerOptions, ts.sys).resolvedModule;
|
|
91
91
|
if (!resolvedModule) {
|
|
92
|
-
throw new UserError(`
|
|
92
|
+
throw new UserError(`could not resolve aliased import '${specifier}' from ${sourceContainingFile}. ` +
|
|
93
93
|
`Verify the tsconfig 'paths' mapping and that the target file exists.`);
|
|
94
94
|
}
|
|
95
95
|
if (!isWithin(sourceRoot, resolvedModule.resolvedFileName)) {
|
|
@@ -98,7 +98,7 @@ function resolveSpecifierReplacement(specifier, sourceContainingFile, compilerOp
|
|
|
98
98
|
if (bareResolvedModule) {
|
|
99
99
|
return undefined;
|
|
100
100
|
}
|
|
101
|
-
throw new UserError(`
|
|
101
|
+
throw new UserError(`aliased import '${specifier}' from ${sourceContainingFile} resolves to ` +
|
|
102
102
|
`${resolvedModule.resolvedFileName}, outside the package source root ${sourceRoot}, and does not resolve ` +
|
|
103
103
|
`the way Node will at runtime, which ignores tsconfig 'paths', 'baseUrl', and 'rootDirs'. The ` +
|
|
104
104
|
`emitted specifier would fail at runtime; re-anchor the alias inside the package.`);
|
|
@@ -3,10 +3,10 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import process from 'node:process';
|
|
6
|
-
import { reportError } from '@williamthorsen/nmr-core';
|
|
6
|
+
import { isRecord, reportError } from '@williamthorsen/nmr-core';
|
|
7
7
|
import { findMonorepoRoot as findMonorepoRootOrNothing } from '@williamthorsen/nmr-core/workspace';
|
|
8
|
+
import { NMR_VITEST_BIN } from "../bins.js";
|
|
8
9
|
import { describeSignalExitCode } from "../child-process.js";
|
|
9
|
-
import { isObject } from "../helpers/type-guards.js";
|
|
10
10
|
import { omitOutcomeFile } from "../stated-outcome.js";
|
|
11
11
|
const VITEST_PACKAGE = 'vitest';
|
|
12
12
|
const SUPPORTED_VITEST_RANGE = '>=4.0.0 <6';
|
|
@@ -14,7 +14,7 @@ const CONFIG_EXTENSIONS = ['ts', 'mts', 'cts', 'js', 'mjs', 'cjs'];
|
|
|
14
14
|
export function runNmrVitest(argv, cwd = process.cwd()) {
|
|
15
15
|
const cli = resolveVitestCli(cwd);
|
|
16
16
|
if (!cli.ok) {
|
|
17
|
-
reportError(
|
|
17
|
+
reportError(NMR_VITEST_BIN, cli.error);
|
|
18
18
|
return 1;
|
|
19
19
|
}
|
|
20
20
|
const result = spawnSync(process.execPath, [cli.cliPath, ...chooseVitestConfigArgs(argv, cwd)], {
|
|
@@ -23,7 +23,7 @@ export function runNmrVitest(argv, cwd = process.cwd()) {
|
|
|
23
23
|
stdio: 'inherit',
|
|
24
24
|
});
|
|
25
25
|
if (result.error) {
|
|
26
|
-
reportError(`
|
|
26
|
+
reportError(NMR_VITEST_BIN, `could not run \`${VITEST_PACKAGE}\` (${result.error.message}).`);
|
|
27
27
|
return 1;
|
|
28
28
|
}
|
|
29
29
|
if (result.signal !== null)
|
|
@@ -73,7 +73,7 @@ function resolveVitestCli(cwd) {
|
|
|
73
73
|
return missingResult;
|
|
74
74
|
}
|
|
75
75
|
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
76
|
-
const bin =
|
|
76
|
+
const bin = isRecord(manifest) && isRecord(manifest['bin']) ? manifest['bin'][VITEST_PACKAGE] : undefined;
|
|
77
77
|
if (typeof bin !== 'string') {
|
|
78
78
|
return { ok: false, error: `\`${VITEST_PACKAGE}\` at ${path.dirname(manifestPath)} does not declare a CLI.` };
|
|
79
79
|
}
|
|
@@ -6,9 +6,9 @@ export interface ShellcheckOutcome {
|
|
|
6
6
|
status: number | null;
|
|
7
7
|
}
|
|
8
8
|
export interface RunShellcheckOptions {
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
ensure?: (tool: WorkflowTool,
|
|
9
|
+
/** Directories under which the pinned tools are cached, in lookup order; defaults to `resolveToolCacheRoots`. */
|
|
10
|
+
cacheRoots?: readonly [string, ...string[]];
|
|
11
|
+
ensure?: (tool: WorkflowTool, cacheRoots: readonly [string, ...string[]]) => Promise<string>;
|
|
12
12
|
/** Lists the tracked `*.sh` files under the root, relative to it. */
|
|
13
13
|
listFiles?: (monorepoRoot: string) => string[];
|
|
14
14
|
spawn?: (file: string, args: string[], cwd: string) => ShellcheckOutcome;
|