@williamthorsen/nmr 0.40.0 → 0.42.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 +107 -0
- package/.readyup/kits/default.js +7447 -10
- package/.readyup/manifest.json +7 -6
- package/CHANGELOG.md +32 -0
- package/README.md +26 -11
- package/agents/guidance/rulebooks/nmr.md +1 -1
- package/dist/esm/cli-fmt.js +1 -1
- package/dist/esm/commands/build.d.ts +15 -0
- package/dist/esm/commands/build.js +27 -6
- package/dist/esm/commands/fmt.d.ts +14 -2
- package/dist/esm/commands/fmt.js +89 -22
- package/dist/esm/help.d.ts +7 -3
- package/dist/esm/help.js +52 -9
- package/dist/esm/prettier-default-config.d.ts +6 -0
- package/dist/esm/prettier-default-config.js +2 -0
- package/dist/esm/runCli.js +83 -6
- package/dist/esm/steps.d.ts +2 -0
- package/dist/esm/steps.js +6 -6
- package/dist/esm/vitest-source-resolution.js +5 -11
- package/docs/prettier.md +1 -1
- package/docs/scripts.md +1 -1
- package/docs/utilities.md +4 -4
- package/package.json +13 -13
package/dist/esm/help.js
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
import { isHookName } from "./helpers/hook-name.js";
|
|
2
2
|
import { OUTPUT_STYLE_ENV_VAR, OUTPUT_STYLE_FLAG } from "./output-style.js";
|
|
3
3
|
import { buildRootRegistry, buildWorkspaceRegistry, describeScript, isSelfReferential, readPackageJsonScripts, } from "./resolver.js";
|
|
4
|
+
const LIFECYCLE_SCRIPT_NAMES = new Set([
|
|
5
|
+
'dependencies',
|
|
6
|
+
'install',
|
|
7
|
+
'pnpm:devPreinstall',
|
|
8
|
+
'postinstall',
|
|
9
|
+
'postpack',
|
|
10
|
+
'postprepare',
|
|
11
|
+
'postpublish',
|
|
12
|
+
'postuninstall',
|
|
13
|
+
'postversion',
|
|
14
|
+
'preinstall',
|
|
15
|
+
'prepack',
|
|
16
|
+
'prepare',
|
|
17
|
+
'preprepare',
|
|
18
|
+
'prepublish',
|
|
19
|
+
'prepublishOnly',
|
|
20
|
+
'preuninstall',
|
|
21
|
+
'preversion',
|
|
22
|
+
'publish',
|
|
23
|
+
'uninstall',
|
|
24
|
+
'version',
|
|
25
|
+
]);
|
|
4
26
|
export function generateHelp(config, packageDir, shouldUseRoot) {
|
|
5
27
|
const lines = [
|
|
6
28
|
'Usage: nmr [flags] <command> [args...]',
|
|
@@ -23,7 +45,8 @@ export function generateHelp(config, packageDir, shouldUseRoot) {
|
|
|
23
45
|
'',
|
|
24
46
|
'Workspace commands:',
|
|
25
47
|
];
|
|
26
|
-
const
|
|
48
|
+
const packageScripts = packageDir === undefined ? {} : readRunnableScripts(packageDir);
|
|
49
|
+
const overrides = collectOverrides(packageScripts);
|
|
27
50
|
let hadOverride = false;
|
|
28
51
|
const workspaceRegistry = filterHooks(buildWorkspaceRegistry(config));
|
|
29
52
|
const workspaceMarkedNames = !shouldUseRoot ? applyOverrides(workspaceRegistry, overrides) : new Set();
|
|
@@ -36,24 +59,44 @@ export function generateHelp(config, packageDir, shouldUseRoot) {
|
|
|
36
59
|
if (rootMarkedNames.size > 0)
|
|
37
60
|
hadOverride = true;
|
|
38
61
|
formatRegistry(rootRegistry, rootMarkedNames, lines);
|
|
62
|
+
const activeRegistry = shouldUseRoot ? buildRootRegistry(config) : buildWorkspaceRegistry(config);
|
|
63
|
+
const unregisteredScripts = collectUnregisteredScripts(packageScripts, activeRegistry);
|
|
64
|
+
if (Object.keys(unregisteredScripts).length > 0) {
|
|
65
|
+
lines.push('', 'Package scripts:');
|
|
66
|
+
formatRegistry(unregisteredScripts, new Set(), lines);
|
|
67
|
+
}
|
|
39
68
|
if (hadOverride) {
|
|
40
69
|
lines.push('', '* Overridden by package.json');
|
|
41
70
|
}
|
|
42
71
|
return lines.join('\n');
|
|
43
72
|
}
|
|
44
|
-
function collectOverrides(
|
|
73
|
+
function collectOverrides(packageScripts) {
|
|
74
|
+
const candidateOverrides = {};
|
|
75
|
+
for (const [name, value] of Object.entries(packageScripts)) {
|
|
76
|
+
if (!isHookName(name))
|
|
77
|
+
candidateOverrides[name] = value;
|
|
78
|
+
}
|
|
79
|
+
return candidateOverrides;
|
|
80
|
+
}
|
|
81
|
+
function collectUnregisteredScripts(packageScripts, registry) {
|
|
82
|
+
const unregisteredScripts = {};
|
|
83
|
+
for (const [name, value] of Object.entries(packageScripts)) {
|
|
84
|
+
if (Object.hasOwn(registry, name) || LIFECYCLE_SCRIPT_NAMES.has(name))
|
|
85
|
+
continue;
|
|
86
|
+
unregisteredScripts[name] = value;
|
|
87
|
+
}
|
|
88
|
+
return unregisteredScripts;
|
|
89
|
+
}
|
|
90
|
+
function readRunnableScripts(packageDir) {
|
|
45
91
|
const scripts = readPackageJsonScripts(packageDir);
|
|
46
92
|
if (!scripts)
|
|
47
93
|
return {};
|
|
48
|
-
const
|
|
94
|
+
const runnableScripts = {};
|
|
49
95
|
for (const [name, value] of Object.entries(scripts)) {
|
|
50
|
-
if (
|
|
51
|
-
|
|
52
|
-
if (isSelfReferential(value, name, packageDir))
|
|
53
|
-
continue;
|
|
54
|
-
candidateOverrides[name] = value;
|
|
96
|
+
if (!isSelfReferential(value, name, packageDir))
|
|
97
|
+
runnableScripts[name] = value;
|
|
55
98
|
}
|
|
56
|
-
return
|
|
99
|
+
return runnableScripts;
|
|
57
100
|
}
|
|
58
101
|
function applyOverrides(registry, overrides) {
|
|
59
102
|
const markedNames = new Set();
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The house config that `nmr-fmt` passes to Prettier by path when the repository does not have a config of its own.
|
|
3
|
+
* It is a default export because that is the export that Prettier reads from a config file.
|
|
4
|
+
*/
|
|
5
|
+
declare const _default: import("prettier").Config;
|
|
6
|
+
export default _default;
|
package/dist/esm/runCli.js
CHANGED
|
@@ -16,9 +16,9 @@ import { isTerminalStream, OUTPUT_STYLE_ENV_VAR, OUTPUT_STYLE_FLAG, resolveOutpu
|
|
|
16
16
|
import { renderRecording, renderRefusal, resolveRecording } from "./recording.js";
|
|
17
17
|
import { assembleReplay } from "./replay-assembly.js";
|
|
18
18
|
import { readReportFormatEnv, REPORT_FORMAT_ENV_VAR, resolveReportFormat } from "./report-format.js";
|
|
19
|
-
import { applyDevBinToSteps, buildRootRegistry, buildWorkspaceRegistry, expandScript, findChainedSelfReference, resolveScript, } from "./resolver.js";
|
|
19
|
+
import { applyDevBinToSteps, buildRootRegistry, buildWorkspaceRegistry, expandScript, findChainedSelfReference, readPackageJsonScripts, resolveScript, } from "./resolver.js";
|
|
20
20
|
import { resolveChannel, runSteps } from "./runner.js";
|
|
21
|
-
import { composeNmrStep, dropRecursiveSteps, findNmrCrossing, renderChain } from "./steps.js";
|
|
21
|
+
import { composeNmrStep, dropRecursiveSteps, findNmrCrossing, quoteToken, renderChain } from "./steps.js";
|
|
22
22
|
import { UserError } from "./UserError.js";
|
|
23
23
|
import { COMMAND_VERBOSITY_ENV_VAR, readVerbosityEnv, resolveVerbosity, } from "./verbosity.js";
|
|
24
24
|
import { writeVerdict } from "./verdict.js";
|
|
@@ -34,7 +34,7 @@ const NAMED_ESCAPES = new Map([
|
|
|
34
34
|
]);
|
|
35
35
|
export const RUN_IF_PRESENT_ENV_VAR = 'NMR_RUN_IF_PRESENT';
|
|
36
36
|
export async function runCli(options) {
|
|
37
|
-
const { args,
|
|
37
|
+
const { args, env, stdout, stderr } = options;
|
|
38
38
|
const parseResult = parseArgs(args);
|
|
39
39
|
if (!parseResult.ok) {
|
|
40
40
|
reportError(parseResult.error, stderr);
|
|
@@ -52,6 +52,18 @@ export async function runCli(options) {
|
|
|
52
52
|
return { exitCode: 1 };
|
|
53
53
|
}
|
|
54
54
|
const { styles } = presentationRead;
|
|
55
|
+
const { exitCode, isUnknownCommand = false } = await runInvocation({ ...options, parsedArgs, presentationRead });
|
|
56
|
+
if (exitCode !== 0 && !isUnknownCommand && isTopLevelInvocation(env)) {
|
|
57
|
+
const hint = formatMisplacedFlagHint(args, parsedArgs.passthrough, styles.stderr);
|
|
58
|
+
if (hint !== undefined) {
|
|
59
|
+
stderr.write(`${hint}\n`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return { exitCode };
|
|
63
|
+
}
|
|
64
|
+
async function runInvocation(options) {
|
|
65
|
+
const { cwd, env, parsedArgs, presentationRead, stderr, stdout } = options;
|
|
66
|
+
const { styles } = presentationRead;
|
|
55
67
|
if (parsedArgs.shouldShowVersion) {
|
|
56
68
|
stdout.write(`${VERSION}\n`);
|
|
57
69
|
return { exitCode: 0 };
|
|
@@ -103,7 +115,8 @@ export async function runCli(options) {
|
|
|
103
115
|
};
|
|
104
116
|
const delegation = composeDelegation({ childEnv, command, parsedArgs });
|
|
105
117
|
if (delegation !== undefined) {
|
|
106
|
-
|
|
118
|
+
const result = await runDelegation({ context, delegation, parsedArgs, runOptions, stderr });
|
|
119
|
+
return { ...result, isUnknownCommand: result.exitCode !== 0 && !isDefinedInAnyScope(command, context) };
|
|
107
120
|
}
|
|
108
121
|
const registry = shouldUseRoot ? buildRootRegistry(context.config) : buildWorkspaceRegistry(context.config);
|
|
109
122
|
assertNoSelfReference({
|
|
@@ -120,7 +133,7 @@ export async function runCli(options) {
|
|
|
120
133
|
return { exitCode: 0 };
|
|
121
134
|
}
|
|
122
135
|
reportError(`Unknown command: ${command}`, stderr);
|
|
123
|
-
return { exitCode: 1 };
|
|
136
|
+
return { exitCode: 1, isUnknownCommand: true };
|
|
124
137
|
}
|
|
125
138
|
const { isEmptiedByWorkspace, steps: runnableSteps } = readRunnableSteps(resolvedScript, context.workspacePackageDirs);
|
|
126
139
|
const resolvedCommand = renderChain(runnableSteps);
|
|
@@ -200,6 +213,7 @@ export async function runCli(options) {
|
|
|
200
213
|
reportVerdict({ command, scope, ...outcome }, stdout, format, styles.stdout);
|
|
201
214
|
return { exitCode };
|
|
202
215
|
}
|
|
216
|
+
const FILTER_FLAGS = new Set(['-F', '--filter']);
|
|
203
217
|
const BOOLEAN_FLAGS = new Map([
|
|
204
218
|
['-?', 'shouldShowHelp'],
|
|
205
219
|
['--help', 'shouldShowHelp'],
|
|
@@ -587,7 +601,7 @@ function parseArgs(args) {
|
|
|
587
601
|
const arg = args[index];
|
|
588
602
|
if (arg === undefined)
|
|
589
603
|
break;
|
|
590
|
-
if (arg
|
|
604
|
+
if (FILTER_FLAGS.has(arg)) {
|
|
591
605
|
index++;
|
|
592
606
|
const filterValue = args[index];
|
|
593
607
|
if (!filterValue) {
|
|
@@ -862,6 +876,69 @@ async function runGated(options) {
|
|
|
862
876
|
outcome: exitCode === 0 ? { outcome: 'passed', durationMs } : { outcome: 'failed', durationMs, exitCode },
|
|
863
877
|
};
|
|
864
878
|
}
|
|
879
|
+
function formatMisplacedFlagHint(args, passthrough, style) {
|
|
880
|
+
const { flagNames, movedTokens, remainingTokens } = splitMisplacedFlags(passthrough);
|
|
881
|
+
if (flagNames.length === 0) {
|
|
882
|
+
return undefined;
|
|
883
|
+
}
|
|
884
|
+
const commandIndex = args.length - passthrough.length - 1;
|
|
885
|
+
const correctedTokens = [
|
|
886
|
+
'nmr',
|
|
887
|
+
...args.slice(0, commandIndex),
|
|
888
|
+
...movedTokens,
|
|
889
|
+
...args.slice(commandIndex, commandIndex + 1),
|
|
890
|
+
...remainingTokens,
|
|
891
|
+
];
|
|
892
|
+
const subject = flagNames.length === 1 ? 'is an nmr flag' : 'are nmr flags';
|
|
893
|
+
return (`${STATUS_GLYPHS[style].warning.text} ${renderQuotedList(flagNames)} ${subject}; ` +
|
|
894
|
+
`nmr reads its flags only before the command: \`${correctedTokens.map(quoteToken).join(' ')}\``);
|
|
895
|
+
}
|
|
896
|
+
function isDefinedInAnyScope(command, context) {
|
|
897
|
+
const registries = [buildRootRegistry(context.config), buildWorkspaceRegistry(context.config)];
|
|
898
|
+
if (registries.some((registry) => Object.hasOwn(registry, command))) {
|
|
899
|
+
return true;
|
|
900
|
+
}
|
|
901
|
+
return [context.monorepoRoot, ...context.workspacePackageDirs].some((dir) => Object.hasOwn(readPackageJsonScripts(dir) ?? {}, command));
|
|
902
|
+
}
|
|
903
|
+
function isTopLevelInvocation(env) {
|
|
904
|
+
const runId = env[RUN_ID_ENV_VAR];
|
|
905
|
+
return runId === undefined || runId === '';
|
|
906
|
+
}
|
|
907
|
+
function readMisplacedFlagLength(args, index) {
|
|
908
|
+
const arg = args[index] ?? '';
|
|
909
|
+
if (FILTER_FLAGS.has(arg)) {
|
|
910
|
+
return (args[index + 1] ?? '') === '' ? undefined : 2;
|
|
911
|
+
}
|
|
912
|
+
const booleanFlag = BOOLEAN_FLAGS.get(arg);
|
|
913
|
+
if (booleanFlag !== undefined) {
|
|
914
|
+
return booleanFlag === 'shouldShowHelp' || booleanFlag === 'shouldShowVersion' ? undefined : 1;
|
|
915
|
+
}
|
|
916
|
+
const styleArgument = readOutputStyleArgument(args, index);
|
|
917
|
+
return styleArgument?.ok === true ? styleArgument.consumedCount : undefined;
|
|
918
|
+
}
|
|
919
|
+
function splitMisplacedFlags(passthrough) {
|
|
920
|
+
const flagNames = new Set();
|
|
921
|
+
const movedTokens = [];
|
|
922
|
+
const remainingTokens = [];
|
|
923
|
+
let index = 0;
|
|
924
|
+
while (index < passthrough.length) {
|
|
925
|
+
const arg = passthrough[index] ?? '';
|
|
926
|
+
if (arg === '--') {
|
|
927
|
+
remainingTokens.push(...passthrough.slice(index));
|
|
928
|
+
break;
|
|
929
|
+
}
|
|
930
|
+
const flagLength = readMisplacedFlagLength(passthrough, index);
|
|
931
|
+
if (flagLength === undefined) {
|
|
932
|
+
remainingTokens.push(arg);
|
|
933
|
+
index++;
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
936
|
+
flagNames.add(arg.startsWith(`${OUTPUT_STYLE_FLAG}=`) ? OUTPUT_STYLE_FLAG : arg);
|
|
937
|
+
movedTokens.push(...passthrough.slice(index, index + flagLength));
|
|
938
|
+
index += flagLength;
|
|
939
|
+
}
|
|
940
|
+
return { flagNames: [...flagNames], movedTokens, remainingTokens };
|
|
941
|
+
}
|
|
865
942
|
function quoteForShell(arg) {
|
|
866
943
|
return "'" + arg.replace(/'/g, String.raw `'\''`) + "'";
|
|
867
944
|
}
|
package/dist/esm/steps.d.ts
CHANGED
|
@@ -75,6 +75,8 @@ export declare function findNmrCrossing(steps: readonly Step[]): string | undefi
|
|
|
75
75
|
* literally would be quoted whole by the rendering, turning the element into a command that nobody wrote.
|
|
76
76
|
*/
|
|
77
77
|
export declare function findUnexpressibleToken(element: string): string | undefined;
|
|
78
|
+
/** Quotes a token that the shell would not read literally, and leaves every other token bare. */
|
|
79
|
+
export declare function quoteToken(token: string): string;
|
|
78
80
|
/**
|
|
79
81
|
* Returns the command that a structural step's nmr process runs, whether the step hands that command to other
|
|
80
82
|
* scopes rather than running it in its own scope, and whether it anchors the command at the monorepo root.
|
package/dist/esm/steps.js
CHANGED
|
@@ -33,6 +33,12 @@ export function findNmrCrossing(steps) {
|
|
|
33
33
|
export function findUnexpressibleToken(element) {
|
|
34
34
|
return tokenize(element).find((token) => !SHELL_SAFE_TOKEN.test(token));
|
|
35
35
|
}
|
|
36
|
+
export function quoteToken(token) {
|
|
37
|
+
if (SHELL_SAFE_TOKEN.test(token)) {
|
|
38
|
+
return token;
|
|
39
|
+
}
|
|
40
|
+
return "'" + token.replaceAll("'", String.raw `'\''`) + "'";
|
|
41
|
+
}
|
|
36
42
|
export function readNmrStep(step) {
|
|
37
43
|
if (step.kind !== 'structural') {
|
|
38
44
|
return undefined;
|
|
@@ -66,12 +72,6 @@ function isRedirectionOperator(char, precedingText, nextChar) {
|
|
|
66
72
|
}
|
|
67
73
|
return (char === '&' && nextChar === '>') || /[<>]\s*$/.test(precedingText);
|
|
68
74
|
}
|
|
69
|
-
function quoteToken(token) {
|
|
70
|
-
if (SHELL_SAFE_TOKEN.test(token)) {
|
|
71
|
-
return token;
|
|
72
|
-
}
|
|
73
|
-
return "'" + token.replaceAll("'", String.raw `'\''`) + "'";
|
|
74
|
-
}
|
|
75
75
|
function readNmrTail(segment) {
|
|
76
76
|
const [head, ...rest] = dropLeadingAssignments(tokenizeSegment(segment));
|
|
77
77
|
if (head === undefined) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
|
|
2
2
|
import { isBuiltin } from 'node:module';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { findDirectoryChainMatch } from '@williamthorsen/toolbelt.filesystem';
|
|
4
5
|
import { parsePackageJson } from "./helpers/package-json.js";
|
|
5
6
|
import { isObject } from "./helpers/type-guards.js";
|
|
6
7
|
import { UserError } from "./UserError.js";
|
|
@@ -57,17 +58,10 @@ function findPackageDir(name, fromDir, cache) {
|
|
|
57
58
|
if (cache?.has(key))
|
|
58
59
|
return cache.get(key);
|
|
59
60
|
let foundDir = findSelfReferenceDir(name, fromDir);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
foundDir = realpathSync(candidate);
|
|
65
|
-
break;
|
|
66
|
-
}
|
|
67
|
-
const parent = path.dirname(dir);
|
|
68
|
-
if (parent === dir)
|
|
69
|
-
break;
|
|
70
|
-
dir = parent;
|
|
61
|
+
if (foundDir === undefined) {
|
|
62
|
+
const match = findDirectoryChainMatch(fromDir, [path.join(NODE_MODULES, name, 'package.json')]);
|
|
63
|
+
if (match !== undefined)
|
|
64
|
+
foundDir = realpathSync(path.join(match.dir, NODE_MODULES, name));
|
|
71
65
|
}
|
|
72
66
|
cache?.set(key, foundDir);
|
|
73
67
|
return foundDir;
|
package/docs/prettier.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The Prettier config that `@williamthorsen/nmr/prettier` publishes: what it formats, how it scopes the shell plugin, and how to customize and adopt it.
|
|
4
4
|
|
|
5
|
-
`prettier` is a
|
|
5
|
+
`prettier` is a dependency of nmr. When the repo installs its own copy, `nmr-fmt` runs that copy instead, so that the editor and the formatter agree. When Prettier does not find a config searching from the working directory, `nmr-fmt` passes this config with `--config`, which also applies it to files below that directory; `.editorconfig` still applies alongside it. The shell and Dockerfile plugin is a dependency of nmr rather than a peer, so its version is pinned centrally and two repos cannot format the same script differently.
|
|
6
6
|
|
|
7
7
|
## What it formats
|
|
8
8
|
|
package/docs/scripts.md
CHANGED
|
@@ -141,7 +141,7 @@ For example, if a workspace script resolves to `my-cli --verbose`, nmr rewrites
|
|
|
141
141
|
|
|
142
142
|
## Default commands
|
|
143
143
|
|
|
144
|
-
`nmr --help` lists every default command and the shell command to which it resolves. Repo-wide config (tier 2) and per-package overrides (tier 3) can add to or replace any of them. What the listing does not show is below.
|
|
144
|
+
`nmr --help` lists every default command and the shell command to which it resolves. Repo-wide config (tier 2) and per-package overrides (tier 3) can add to or replace any of them. Under `Package scripts:`, the listing also shows each script in the current scope's `package.json` whose name the registry does not contain, a hook such as `build:pre` included; it omits npm and pnpm lifecycle scripts, such as `prepare` and `postinstall`, which the package manager runs. What the listing does not show is below.
|
|
145
145
|
|
|
146
146
|
### Build and CI
|
|
147
147
|
|
package/docs/utilities.md
CHANGED
|
@@ -22,7 +22,7 @@ Compile a single package's `src` tree to `dist/esm` with the TypeScript compiler
|
|
|
22
22
|
|
|
23
23
|
**What busts the cache.** The key folds each input's content and path, the emit options, the resolved TypeScript version, and a fingerprint of the nmr running the build -- its own build digest in a workspace checkout, its package version otherwise. Upgrading nmr in a consuming repo therefore rebuilds every package that nmr compiles, and an edit to nmr's own build sources rebuilds each sibling once nmr itself has been rebuilt. A build of nmr's own package folds the version rather than the digest, which is the cache entry that this build is about to write.
|
|
24
24
|
|
|
25
|
-
`typescript` is
|
|
25
|
+
`typescript` is an optional peer dependency (`>=5.7.0 <7`), needed only by `nmr-compile`; the consuming repo provides it, and `nmr-compile` run without it fails with an error naming the peer and the version that it requires. The floor is what `rewriteRelativeImportExtensions` requires; the ceiling exists because TypeScript 7 does not include a compiler API: Its root export is a version constant, so `nmr-compile` cannot run on it. Relative imports in source must use explicit `.ts` extensions for them to be rewritten.
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
28
|
nmr-compile
|
|
@@ -32,9 +32,9 @@ nmr-compile
|
|
|
32
32
|
|
|
33
33
|
Format, or check the formatting of, the files that git reports for the working directory. Exactly one of `--check` and `--write` is required; a bare invocation prints usage and exits non-zero rather than defaulting to a mutation. `--write` also lists the files that it rewrote. This is what `fmt` and `fmt:check` resolve to, in both registries.
|
|
34
34
|
|
|
35
|
-
`prettier` is
|
|
35
|
+
`prettier` is a dependency of nmr, but a repo's own copy takes precedence: `nmr-fmt` resolves Prettier from the working directory first and from nmr's installation second. A repository's formatter has to be the one that its editor and pre-commit hook also run, and resolution goes through the module graph rather than `PATH`, so the copy that runs is the one that the repo declares and not whichever `prettier` happens to come first. A repo that does not install Prettier, or nmr, formats with nmr's copy; when it does not have a Prettier config either, the [house config](prettier.md) applies.
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
A repo's own copy must be Prettier 3.0.0 or later. The design requires `--ignore-path` to honour every flag rather than only the last, so that a repository-root ignore file passed alongside a package-level one is not silently dropped, and 2.x honoured only the final flag.
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
40
|
nmr-fmt --check
|
|
@@ -47,7 +47,7 @@ nmr-fmt --write packages/nmr
|
|
|
47
47
|
|
|
48
48
|
A file that git ignores is never formatted, even when named directly. Because trailing arguments are git pathspecs rather than Prettier flags, `nmr fmt:check packages/nmr` narrows the run, while an unrecognized option and a pathspec that does not match any formattable file both fail it. Running outside a git repository fails rather than reporting a clean run.
|
|
49
49
|
|
|
50
|
-
`nmr-fmt` drops paths that the index names but the filesystem does not have (a file deleted but not yet staged),
|
|
50
|
+
`nmr-fmt` drops paths that the index names but the filesystem does not have (a file deleted but not yet staged), submodule gitlinks, and symlinks whatever they point at, before calling Prettier, which rejects a symlink named explicitly. It also skips a path that it is denied access to, such as a `.envrc` that a sandbox shields, and names each one on stderr, so that a check that passes does not hide a file that it never read. Any other failure to stat a listed path fails the run, with an error that names the path.
|
|
51
51
|
|
|
52
52
|
## `ensure-prepublish-hooks`
|
|
53
53
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@williamthorsen/nmr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.42.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Context-aware script runner for pnpm monorepos",
|
|
6
6
|
"keywords": [
|
|
@@ -77,32 +77,32 @@
|
|
|
77
77
|
"!docs/*.v*.md"
|
|
78
78
|
],
|
|
79
79
|
"dependencies": {
|
|
80
|
-
"@williamthorsen/nmr-core": "0.13.
|
|
81
|
-
"@williamthorsen/toolbelt.errors": "0.8.
|
|
80
|
+
"@williamthorsen/nmr-core": "0.13.2",
|
|
81
|
+
"@williamthorsen/toolbelt.errors": "0.8.2",
|
|
82
|
+
"@williamthorsen/toolbelt.filesystem": "0.9.2",
|
|
82
83
|
"glob": "13.0.6",
|
|
83
|
-
"prettier
|
|
84
|
-
"
|
|
84
|
+
"prettier": "3.9.9",
|
|
85
|
+
"prettier-plugin-sh": "0.20.2",
|
|
86
|
+
"taze": "21.3.0"
|
|
85
87
|
},
|
|
86
88
|
"devDependencies": {
|
|
87
|
-
"@williamthorsen/toolbelt.
|
|
88
|
-
"@williamthorsen/toolbelt.
|
|
89
|
-
"@williamthorsen/toolbelt.vitest": "0.10.1",
|
|
89
|
+
"@williamthorsen/toolbelt.testing": "0.6.2",
|
|
90
|
+
"@williamthorsen/toolbelt.vitest": "0.10.2",
|
|
90
91
|
"esbuild": "0.28.2",
|
|
91
|
-
"
|
|
92
|
-
"readyup": "0.38.0",
|
|
92
|
+
"readyup": "0.40.0",
|
|
93
93
|
"tinyglobby": "0.2.17",
|
|
94
94
|
"typescript": "6.0.3",
|
|
95
95
|
"vite": "8.3.1",
|
|
96
|
-
"vitest": "4.1.11"
|
|
96
|
+
"vitest": "4.1.11",
|
|
97
|
+
"yaml": "2.9.1"
|
|
97
98
|
},
|
|
98
99
|
"peerDependencies": {
|
|
99
|
-
"prettier": ">=3.9.5 <4",
|
|
100
100
|
"typescript": ">=5.7.0 <7",
|
|
101
101
|
"vite": ">=8.0.0 <9",
|
|
102
102
|
"vitest": ">=4.0.0 <5"
|
|
103
103
|
},
|
|
104
104
|
"peerDependenciesMeta": {
|
|
105
|
-
"
|
|
105
|
+
"typescript": {
|
|
106
106
|
"optional": true
|
|
107
107
|
},
|
|
108
108
|
"vite": {
|