@williamthorsen/nmr 0.44.0 → 0.45.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 +80 -0
- package/.readyup/kits/default.js +419 -129
- package/.readyup/manifest.json +13 -17
- package/CHANGELOG.md +24 -0
- package/README.md +10 -17
- package/agents/guidance/rulebooks/nmr.md +1 -1
- package/dist/esm/commands/fmt.d.ts +4 -2
- package/dist/esm/commands/fmt.js +11 -3
- package/dist/esm/config.js +46 -3
- package/dist/esm/entry-chain.d.ts +24 -0
- package/dist/esm/entry-chain.js +57 -0
- package/dist/esm/glyphs.d.ts +1 -1
- package/dist/esm/helpers/escapeControlCharacters.d.ts +10 -0
- package/dist/esm/helpers/escapeControlCharacters.js +12 -0
- package/dist/esm/resolver.js +5 -2
- package/dist/esm/runCli.js +141 -98
- package/dist/esm/steps.d.ts +38 -13
- package/dist/esm/steps.js +77 -10
- package/dist/esm/tiers.d.ts +5 -4
- package/dist/esm/vitest-git-isolation.js +3 -1
- package/docs/reporting.md +2 -0
- package/docs/scripts.md +10 -2
- package/docs/utilities.md +2 -2
- package/docs/vitest.md +12 -8
- package/package.json +2 -2
package/.readyup/kits/default.js
CHANGED
|
@@ -7421,6 +7421,20 @@ function resolveConfigPath(baseDir) {
|
|
|
7421
7421
|
return path.join(baseDir, CONFIG_DIR, CONFIG_FILENAME);
|
|
7422
7422
|
}
|
|
7423
7423
|
|
|
7424
|
+
// src/helpers/escapeControlCharacters.ts
|
|
7425
|
+
var NAMED_ESCAPES = /* @__PURE__ */ new Map([
|
|
7426
|
+
["\n", String.raw`\n`],
|
|
7427
|
+
["\r", String.raw`\r`],
|
|
7428
|
+
[" ", String.raw`\t`]
|
|
7429
|
+
]);
|
|
7430
|
+
function escapeControlCharacters(text) {
|
|
7431
|
+
let escapedText = text;
|
|
7432
|
+
for (const [char, escape] of NAMED_ESCAPES) {
|
|
7433
|
+
escapedText = escapedText.split(char).join(escape);
|
|
7434
|
+
}
|
|
7435
|
+
return escapedText;
|
|
7436
|
+
}
|
|
7437
|
+
|
|
7424
7438
|
// src/helpers/findClosestName.ts
|
|
7425
7439
|
function findClosestName(name, candidates) {
|
|
7426
7440
|
const ceiling = Math.max(1, Math.floor(name.length / 4));
|
|
@@ -7478,24 +7492,332 @@ function isStringRecord(value) {
|
|
|
7478
7492
|
return isObject(value) && Object.values(value).every((entry) => typeof entry === "string");
|
|
7479
7493
|
}
|
|
7480
7494
|
|
|
7481
|
-
// src/
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
7495
|
+
// src/steps.ts
|
|
7496
|
+
var ENV_ASSIGNMENT = /^\w+=/;
|
|
7497
|
+
var LAUNCHERS = /* @__PURE__ */ new Map([
|
|
7498
|
+
["bun", /* @__PURE__ */ new Set(["x"])],
|
|
7499
|
+
["bunx", /* @__PURE__ */ new Set()],
|
|
7500
|
+
["npm", /* @__PURE__ */ new Set(["exec"])],
|
|
7501
|
+
["npx", /* @__PURE__ */ new Set()],
|
|
7502
|
+
["pnpm", /* @__PURE__ */ new Set(["dlx", "exec"])],
|
|
7503
|
+
["yarn", /* @__PURE__ */ new Set(["dlx", "exec"])]
|
|
7504
|
+
]);
|
|
7505
|
+
var QUOTES = /* @__PURE__ */ new Set(['"', "'"]);
|
|
7506
|
+
var SEGMENT_SEPARATORS = /* @__PURE__ */ new Set([";", "|", "&", "\n", "\r"]);
|
|
7507
|
+
var SHELL_SAFE_TOKEN = /^[\w@%+=:,./-]+$/;
|
|
7508
|
+
var TOKEN_SEPARATORS = /* @__PURE__ */ new Set([" ", " ", "\n", "\r"]);
|
|
7509
|
+
function findUnexpressibleToken(element) {
|
|
7510
|
+
return tokenize(element).find((token) => !SHELL_SAFE_TOKEN.test(token));
|
|
7487
7511
|
}
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
|
|
7496
|
-
|
|
7512
|
+
function quoteToken(token) {
|
|
7513
|
+
if (SHELL_SAFE_TOKEN.test(token)) {
|
|
7514
|
+
return token;
|
|
7515
|
+
}
|
|
7516
|
+
return "'" + token.replaceAll("'", String.raw`'\''`) + "'";
|
|
7517
|
+
}
|
|
7518
|
+
function readSelfReference(options) {
|
|
7519
|
+
const { commandName, scope, script } = options;
|
|
7520
|
+
const segments = splitSegments(script).filter((segment) => segment.trim() !== "");
|
|
7521
|
+
const doesReenter = segments.some((segment) => {
|
|
7522
|
+
const invocation = readNmrTail(segment);
|
|
7523
|
+
const target = invocation === void 0 ? void 0 : readNmrTarget(invocation.tail);
|
|
7524
|
+
if (invocation === void 0 || target === void 0) {
|
|
7525
|
+
return false;
|
|
7526
|
+
}
|
|
7527
|
+
const launcherScope = readLauncherScope(invocation.launcher, invocation.launcherArgs);
|
|
7528
|
+
const effectiveTarget = {
|
|
7529
|
+
command: target.command,
|
|
7530
|
+
isDelegate: target.isDelegate || launcherScope.isDelegate,
|
|
7531
|
+
isRecursive: target.isRecursive || launcherScope.isRecursive,
|
|
7532
|
+
isWorkspaceRoot: target.isWorkspaceRoot || launcherScope.isWorkspaceRoot
|
|
7533
|
+
};
|
|
7534
|
+
return isReentry(effectiveTarget, commandName, scope);
|
|
7535
|
+
});
|
|
7536
|
+
if (!doesReenter) {
|
|
7537
|
+
return void 0;
|
|
7538
|
+
}
|
|
7539
|
+
return segments.length === 1 ? "sole" : "chained";
|
|
7540
|
+
}
|
|
7541
|
+
function readStepListSelfReference(options) {
|
|
7542
|
+
const { commandName, elements, scope } = options;
|
|
7543
|
+
const doesReenter = elements.some((element) => {
|
|
7544
|
+
const target = readNmrTarget(tokenize(element));
|
|
7545
|
+
return target !== void 0 && isReentry(target, commandName, scope);
|
|
7546
|
+
});
|
|
7547
|
+
if (!doesReenter) {
|
|
7548
|
+
return void 0;
|
|
7549
|
+
}
|
|
7550
|
+
return elements.length === 1 ? "sole" : "chained";
|
|
7551
|
+
}
|
|
7552
|
+
function dropLeadingAssignments(tokens) {
|
|
7553
|
+
const start = tokens.findIndex((token) => !ENV_ASSIGNMENT.test(token));
|
|
7554
|
+
return start === -1 ? [] : tokens.slice(start);
|
|
7555
|
+
}
|
|
7556
|
+
function isRedirectionOperator(char, precedingText, nextChar) {
|
|
7557
|
+
if (char !== "&" && char !== "|") {
|
|
7558
|
+
return false;
|
|
7559
|
+
}
|
|
7560
|
+
return char === "&" && nextChar === ">" || /[<>]\s*$/.test(precedingText);
|
|
7561
|
+
}
|
|
7562
|
+
function isReentry(target, commandName, scope) {
|
|
7563
|
+
if (target.command !== commandName) {
|
|
7564
|
+
return false;
|
|
7565
|
+
}
|
|
7566
|
+
switch (scope) {
|
|
7567
|
+
case "root":
|
|
7568
|
+
return !target.isDelegate;
|
|
7569
|
+
case "package":
|
|
7570
|
+
return target.isRecursive || !target.isDelegate && !target.isWorkspaceRoot;
|
|
7571
|
+
case "workspaceRegistry":
|
|
7572
|
+
return target.isDelegate || !target.isWorkspaceRoot;
|
|
7573
|
+
}
|
|
7574
|
+
}
|
|
7575
|
+
function readLauncherScope(launcher, launcherArgs) {
|
|
7576
|
+
const scope = { isDelegate: false, isRecursive: false, isWorkspaceRoot: false };
|
|
7577
|
+
if (launcher !== "pnpm") {
|
|
7578
|
+
return scope;
|
|
7579
|
+
}
|
|
7580
|
+
for (const token of launcherArgs) {
|
|
7581
|
+
if (token === "-r" || token === "--recursive") {
|
|
7582
|
+
scope.isDelegate = true;
|
|
7583
|
+
scope.isRecursive = true;
|
|
7584
|
+
} else if (token === "-F" || token === "--filter" || token.startsWith("--filter=")) {
|
|
7585
|
+
scope.isDelegate = true;
|
|
7586
|
+
} else if (token === "-w" || token === "--workspace-root") {
|
|
7587
|
+
scope.isWorkspaceRoot = true;
|
|
7588
|
+
}
|
|
7589
|
+
}
|
|
7590
|
+
return scope;
|
|
7591
|
+
}
|
|
7592
|
+
function readNmrTail(segment) {
|
|
7593
|
+
const [head, ...rest] = dropLeadingAssignments(tokenizeSegment(segment));
|
|
7594
|
+
if (head === void 0) {
|
|
7595
|
+
return void 0;
|
|
7596
|
+
}
|
|
7597
|
+
if (head === "nmr") {
|
|
7598
|
+
return { launcher: void 0, launcherArgs: [], tail: rest };
|
|
7599
|
+
}
|
|
7600
|
+
const subcommands = LAUNCHERS.get(head);
|
|
7601
|
+
if (subcommands === void 0) {
|
|
7602
|
+
return void 0;
|
|
7603
|
+
}
|
|
7604
|
+
const nmrIndex = subcommands.size === 0 ? rest.findIndex((token) => !token.startsWith("-")) : rest.findIndex((token) => subcommands.has(token)) + 1;
|
|
7605
|
+
if (nmrIndex <= 0 && subcommands.size > 0) {
|
|
7606
|
+
return void 0;
|
|
7607
|
+
}
|
|
7608
|
+
return rest[nmrIndex] === "nmr" ? { launcher: head, launcherArgs: rest.slice(0, nmrIndex), tail: rest.slice(nmrIndex + 1) } : void 0;
|
|
7609
|
+
}
|
|
7610
|
+
function readNmrTarget(tokens) {
|
|
7611
|
+
let isDelegate = false;
|
|
7612
|
+
let isRecursive = false;
|
|
7613
|
+
let isWorkspaceRoot = false;
|
|
7614
|
+
let index = 0;
|
|
7615
|
+
while (index < tokens.length) {
|
|
7616
|
+
const token = tokens[index] ?? "";
|
|
7617
|
+
switch (token) {
|
|
7618
|
+
// The pattern is the flag's value, and reading it as a command name would name whichever package it
|
|
7619
|
+
// selects rather than the command that every selected scope runs.
|
|
7620
|
+
case "-F":
|
|
7621
|
+
case "--filter":
|
|
7622
|
+
isDelegate = true;
|
|
7623
|
+
index += 2;
|
|
7624
|
+
break;
|
|
7625
|
+
case "-R":
|
|
7626
|
+
case "--recursive":
|
|
7627
|
+
isDelegate = true;
|
|
7628
|
+
isRecursive = true;
|
|
7629
|
+
index += 1;
|
|
7630
|
+
break;
|
|
7631
|
+
case "-w":
|
|
7632
|
+
case "--workspace-root":
|
|
7633
|
+
isWorkspaceRoot = true;
|
|
7634
|
+
index += 1;
|
|
7635
|
+
break;
|
|
7636
|
+
default:
|
|
7637
|
+
if (!token.startsWith("-")) {
|
|
7638
|
+
return { command: token, isDelegate, isRecursive, isWorkspaceRoot };
|
|
7639
|
+
}
|
|
7640
|
+
index += 1;
|
|
7641
|
+
}
|
|
7642
|
+
}
|
|
7643
|
+
return void 0;
|
|
7644
|
+
}
|
|
7645
|
+
function splitSegments(command) {
|
|
7646
|
+
const segments = [];
|
|
7647
|
+
let currentSegment = "";
|
|
7648
|
+
let quote;
|
|
7649
|
+
let index = 0;
|
|
7650
|
+
while (index < command.length) {
|
|
7651
|
+
const char = command[index] ?? "";
|
|
7652
|
+
if (quote !== void 0) {
|
|
7653
|
+
if (char === "\\" && quote === '"') {
|
|
7654
|
+
currentSegment += char + (command[index + 1] ?? "");
|
|
7655
|
+
index += 2;
|
|
7656
|
+
continue;
|
|
7657
|
+
}
|
|
7658
|
+
currentSegment += char;
|
|
7659
|
+
if (char === quote) quote = void 0;
|
|
7660
|
+
index += 1;
|
|
7661
|
+
continue;
|
|
7662
|
+
}
|
|
7663
|
+
if (char === "\\") {
|
|
7664
|
+
currentSegment += char + (command[index + 1] ?? "");
|
|
7665
|
+
index += 2;
|
|
7666
|
+
continue;
|
|
7667
|
+
}
|
|
7668
|
+
if (QUOTES.has(char)) {
|
|
7669
|
+
quote = char;
|
|
7670
|
+
currentSegment += char;
|
|
7671
|
+
index += 1;
|
|
7672
|
+
continue;
|
|
7673
|
+
}
|
|
7674
|
+
if (SEGMENT_SEPARATORS.has(char)) {
|
|
7675
|
+
if (isRedirectionOperator(char, currentSegment, command[index + 1])) {
|
|
7676
|
+
currentSegment += char;
|
|
7677
|
+
index += 1;
|
|
7678
|
+
continue;
|
|
7679
|
+
}
|
|
7680
|
+
index += command[index + 1] === char ? 2 : 1;
|
|
7681
|
+
segments.push(currentSegment);
|
|
7682
|
+
currentSegment = "";
|
|
7683
|
+
continue;
|
|
7684
|
+
}
|
|
7685
|
+
currentSegment += char;
|
|
7686
|
+
index += 1;
|
|
7687
|
+
}
|
|
7688
|
+
segments.push(currentSegment);
|
|
7689
|
+
return segments;
|
|
7690
|
+
}
|
|
7691
|
+
function tokenize(element) {
|
|
7692
|
+
return element.split(/\s+/).filter((token) => token.length > 0);
|
|
7693
|
+
}
|
|
7694
|
+
function tokenizeSegment(segment) {
|
|
7695
|
+
const tokens = [];
|
|
7696
|
+
let currentToken = "";
|
|
7697
|
+
let quote;
|
|
7698
|
+
let index = 0;
|
|
7699
|
+
while (index < segment.length) {
|
|
7700
|
+
const char = segment[index] ?? "";
|
|
7701
|
+
if (quote !== void 0) {
|
|
7702
|
+
if (char === "\\" && quote === '"') {
|
|
7703
|
+
currentToken += char + (segment[index + 1] ?? "");
|
|
7704
|
+
index += 2;
|
|
7705
|
+
continue;
|
|
7706
|
+
}
|
|
7707
|
+
currentToken += char;
|
|
7708
|
+
if (char === quote) quote = void 0;
|
|
7709
|
+
index += 1;
|
|
7710
|
+
continue;
|
|
7711
|
+
}
|
|
7712
|
+
if (char === "\\") {
|
|
7713
|
+
currentToken += char + (segment[index + 1] ?? "");
|
|
7714
|
+
index += 2;
|
|
7715
|
+
continue;
|
|
7716
|
+
}
|
|
7717
|
+
if (QUOTES.has(char)) {
|
|
7718
|
+
quote = char;
|
|
7719
|
+
currentToken += char;
|
|
7720
|
+
index += 1;
|
|
7721
|
+
continue;
|
|
7722
|
+
}
|
|
7723
|
+
if (TOKEN_SEPARATORS.has(char)) {
|
|
7724
|
+
if (currentToken !== "") {
|
|
7725
|
+
tokens.push(currentToken);
|
|
7726
|
+
}
|
|
7727
|
+
currentToken = "";
|
|
7728
|
+
index += 1;
|
|
7729
|
+
continue;
|
|
7730
|
+
}
|
|
7731
|
+
currentToken += char;
|
|
7732
|
+
index += 1;
|
|
7733
|
+
}
|
|
7734
|
+
if (currentToken !== "") {
|
|
7735
|
+
tokens.push(currentToken);
|
|
7497
7736
|
}
|
|
7737
|
+
return tokens;
|
|
7738
|
+
}
|
|
7739
|
+
|
|
7740
|
+
// src/default-scripts.ts
|
|
7741
|
+
var GATE_PROJECTS = "--project unit --project tool";
|
|
7742
|
+
var TYPECHECK_STEP = { run: "typecheck", shouldDeclineArguments: true };
|
|
7743
|
+
var ROOT_TYPECHECK_STEP = { run: "root:typecheck", shouldDeclineArguments: true };
|
|
7744
|
+
var LINT_CHECK_STEP = { run: "lint:check", shouldRunInBackground: true };
|
|
7745
|
+
var LINT_STRICT_STEP = { run: "lint:strict", shouldRunInBackground: true };
|
|
7746
|
+
var workspaceScripts = {
|
|
7747
|
+
build: ["compile"],
|
|
7748
|
+
check: [LINT_CHECK_STEP, TYPECHECK_STEP, "fmt:check", "test"],
|
|
7749
|
+
"check:strict": [LINT_STRICT_STEP, TYPECHECK_STEP, "fmt:check", "test"],
|
|
7750
|
+
clean: "nmr-clean",
|
|
7751
|
+
compile: "nmr-compile",
|
|
7752
|
+
fix: ["lint", "fmt"],
|
|
7753
|
+
"fix:check": ["fmt:check", "lint:check"],
|
|
7754
|
+
fmt: "nmr-fmt --write",
|
|
7755
|
+
"fmt:check": "nmr-fmt --check",
|
|
7756
|
+
lint: "eslint --fix .",
|
|
7757
|
+
"lint:check": "eslint .",
|
|
7758
|
+
"lint:strict": "strict-lint",
|
|
7759
|
+
"report-catalog": "nmr-report-catalog",
|
|
7760
|
+
test: `pnpm exec vitest ${GATE_PROJECTS}`,
|
|
7761
|
+
"test:all": "pnpm exec vitest",
|
|
7762
|
+
"test:coverage": `pnpm exec vitest ${GATE_PROJECTS} --coverage`,
|
|
7763
|
+
"test:tool": "pnpm exec vitest --project tool",
|
|
7764
|
+
"test:unit": "pnpm exec vitest --project unit",
|
|
7765
|
+
"test:watch": `pnpm exec vitest ${GATE_PROJECTS} --watch`,
|
|
7766
|
+
typecheck: "tsgo --noEmit",
|
|
7767
|
+
// The command is a string because neither half names an nmr command: Both are binaries.
|
|
7768
|
+
upgrade: "nmr-report-catalog && nmr-taze",
|
|
7769
|
+
"view-coverage": "open coverage/index.html"
|
|
7498
7770
|
};
|
|
7771
|
+
function buildRootScripts(workspaceGlobs) {
|
|
7772
|
+
const ignorePatterns = workspaceGlobs.map((glob) => `--ignore-pattern ${quoteToken(glob)} `).join("");
|
|
7773
|
+
return {
|
|
7774
|
+
audit: ["audit:prod", "audit:dev"],
|
|
7775
|
+
"audit:dev": "pnpm exec v11y --dev",
|
|
7776
|
+
"audit:prod": "pnpm exec v11y --prod",
|
|
7777
|
+
build: ["-R build"],
|
|
7778
|
+
check: [LINT_CHECK_STEP, TYPECHECK_STEP, "fmt:check", "test"],
|
|
7779
|
+
"check:strict": [LINT_STRICT_STEP, TYPECHECK_STEP, "fmt:check", "test"],
|
|
7780
|
+
// Excludes the audit, which in CI has a workflow of its own. The narrowed check runs against the build, so
|
|
7781
|
+
// the build declines the arguments rather than being narrowed by them.
|
|
7782
|
+
ci: [{ run: "build", shouldDeclineArguments: true }, "check:strict"],
|
|
7783
|
+
clean: "nmr-clean",
|
|
7784
|
+
fix: ["lint", "fmt"],
|
|
7785
|
+
"fix:check": ["fmt:check", "lint:check"],
|
|
7786
|
+
fmt: "nmr-fmt --write",
|
|
7787
|
+
"fmt:check": "nmr-fmt --check",
|
|
7788
|
+
lint: "eslint --fix .",
|
|
7789
|
+
"lint:check": "eslint .",
|
|
7790
|
+
"lint:strict": "strict-lint",
|
|
7791
|
+
// The audit takes seconds and `ci` takes minutes, so the cheap gate fails first. The audit reads the
|
|
7792
|
+
// dependency tree, and an argument narrowing the code under test does not say anything about that tree.
|
|
7793
|
+
prepush: [{ run: "audit", shouldDeclineArguments: true }, "ci"],
|
|
7794
|
+
"report-overrides": "nmr-report-overrides",
|
|
7795
|
+
"root:check": [ROOT_TYPECHECK_STEP, "fmt:check", "root:lint:check", "root:test"],
|
|
7796
|
+
"root:lint": `eslint --fix ${ignorePatterns}.`,
|
|
7797
|
+
"root:lint:check": `eslint ${ignorePatterns}.`,
|
|
7798
|
+
"root:lint:strict": `strict-lint ${ignorePatterns}.`,
|
|
7799
|
+
"root:test": `vitest --config ./vitest.root.config.ts ${GATE_PROJECTS}`,
|
|
7800
|
+
"root:test:all": "vitest --config ./vitest.root.config.ts",
|
|
7801
|
+
"root:test:tool": "vitest --config ./vitest.root.config.ts --project tool",
|
|
7802
|
+
"root:test:unit": "vitest --config ./vitest.root.config.ts --project unit",
|
|
7803
|
+
"root:typecheck": "tsgo --noEmit",
|
|
7804
|
+
// Includes the override report for the same reason `upgrade` does: Both end in the tool that rewrites a
|
|
7805
|
+
// `pnpm.overrides` block, so both need the reporter's rejection ahead of them.
|
|
7806
|
+
"root:upgrade": "nmr-report-overrides && nmr-taze",
|
|
7807
|
+
test: ["root:test", "-R test"],
|
|
7808
|
+
"test:all": ["root:test:all", "-R test:all"],
|
|
7809
|
+
"test:coverage": ["root:test", "-R test:coverage"],
|
|
7810
|
+
"test:tool": ["root:test:tool", "-R test:tool"],
|
|
7811
|
+
"test:unit": ["root:test:unit", "-R test:unit"],
|
|
7812
|
+
"test:watch": `vitest ${GATE_PROJECTS} --watch`,
|
|
7813
|
+
// Neither step is narrowable, so `nmr typecheck <file>` is rejected rather than checking that file under
|
|
7814
|
+
// default options at the root and searching for it in every package.
|
|
7815
|
+
typecheck: [ROOT_TYPECHECK_STEP, { run: "-R typecheck", shouldDeclineArguments: true }],
|
|
7816
|
+
// The command is a string because neither half names an nmr command: Both are binaries, and a composite
|
|
7817
|
+
// element can name only a command.
|
|
7818
|
+
upgrade: "nmr-report-overrides && nmr-taze --recursive"
|
|
7819
|
+
};
|
|
7820
|
+
}
|
|
7499
7821
|
|
|
7500
7822
|
// ../nmr-core/dist/esm/workspace.js
|
|
7501
7823
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -7602,6 +7924,14 @@ function readWorkspaceManifest(monorepoRoot) {
|
|
|
7602
7924
|
}
|
|
7603
7925
|
}
|
|
7604
7926
|
|
|
7927
|
+
// src/UserError.ts
|
|
7928
|
+
var UserError = class extends Error {
|
|
7929
|
+
constructor(message) {
|
|
7930
|
+
super(message);
|
|
7931
|
+
this.name = "UserError";
|
|
7932
|
+
}
|
|
7933
|
+
};
|
|
7934
|
+
|
|
7605
7935
|
// src/workspace.ts
|
|
7606
7936
|
var WORKSPACE_MANIFEST2 = "pnpm-workspace.yaml";
|
|
7607
7937
|
function getWorkspacePackageDirs(monorepoRoot) {
|
|
@@ -7612,7 +7942,32 @@ function getWorkspacePackageDirs(monorepoRoot) {
|
|
|
7612
7942
|
return resolution.kind === "packages" ? resolution.packageDirs : [];
|
|
7613
7943
|
}
|
|
7614
7944
|
|
|
7945
|
+
// src/workspace-globs.ts
|
|
7946
|
+
import path4 from "node:path";
|
|
7947
|
+
function toWorkspacePackageGlobs(monorepoRoot, packageDirs) {
|
|
7948
|
+
return packageDirs.map((dir) => path4.relative(monorepoRoot, dir).split(path4.sep).join("/")).filter((relativeDir) => relativeDir !== "").map((relativeDir) => `${relativeDir}/**`).toSorted();
|
|
7949
|
+
}
|
|
7950
|
+
|
|
7951
|
+
// src/resolve-scripts.ts
|
|
7952
|
+
function getDefaultWorkspaceScripts() {
|
|
7953
|
+
return { ...workspaceScripts };
|
|
7954
|
+
}
|
|
7955
|
+
function getDefaultRootScripts(monorepoRoot) {
|
|
7956
|
+
const resolution = resolveWorkspace(monorepoRoot);
|
|
7957
|
+
const packageDirs = resolution.kind === "packages" ? resolution.packageDirs : [];
|
|
7958
|
+
return buildRootScripts(toWorkspacePackageGlobs(monorepoRoot, packageDirs));
|
|
7959
|
+
}
|
|
7960
|
+
|
|
7961
|
+
// src/resolver.ts
|
|
7962
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
7963
|
+
|
|
7964
|
+
// ../nmr-core/dist/esm/hasErrnoCode.js
|
|
7965
|
+
function hasErrnoCode(error, code) {
|
|
7966
|
+
return isError(error) && "code" in error && error.code === code;
|
|
7967
|
+
}
|
|
7968
|
+
|
|
7615
7969
|
// src/helpers/package-json.ts
|
|
7970
|
+
import path5 from "node:path";
|
|
7616
7971
|
function parsePackageJson(rawText, file) {
|
|
7617
7972
|
try {
|
|
7618
7973
|
return JSON.parse(rawText);
|
|
@@ -7631,7 +7986,7 @@ function readScriptRecord(dir, scripts) {
|
|
|
7631
7986
|
return result;
|
|
7632
7987
|
}
|
|
7633
7988
|
function resolvePackageJsonPath(dir) {
|
|
7634
|
-
return
|
|
7989
|
+
return path5.join(dir, "package.json");
|
|
7635
7990
|
}
|
|
7636
7991
|
function formatMalformedScript(dir, key, value) {
|
|
7637
7992
|
const rejection = `Invalid package.json at ${resolvePackageJsonPath(dir)}: \`scripts.${key}\` must be a string.`;
|
|
@@ -7642,120 +7997,20 @@ function formatMalformedScript(dir, key, value) {
|
|
|
7642
7997
|
return `${rejection} A step list belongs in \`${CONFIG_RELATIVE_PATH}\` under \`${field}\`.`;
|
|
7643
7998
|
}
|
|
7644
7999
|
|
|
7645
|
-
// src/
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
return tokenize(element).find((token) => !SHELL_SAFE_TOKEN.test(token));
|
|
8000
|
+
// src/resolver.ts
|
|
8001
|
+
function describeScript(script) {
|
|
8002
|
+
return typeof script === "string" ? script : `[${script.map(describeElement).join(", ")}]`;
|
|
7649
8003
|
}
|
|
7650
|
-
function
|
|
7651
|
-
if (
|
|
7652
|
-
return
|
|
8004
|
+
function describeElement(element) {
|
|
8005
|
+
if (typeof element === "string") {
|
|
8006
|
+
return element;
|
|
7653
8007
|
}
|
|
7654
|
-
|
|
7655
|
-
|
|
7656
|
-
|
|
7657
|
-
|
|
7658
|
-
}
|
|
7659
|
-
|
|
7660
|
-
// src/default-scripts.ts
|
|
7661
|
-
var GATE_PROJECTS = "--project unit --project tool";
|
|
7662
|
-
var TYPECHECK_STEP = { run: "typecheck", shouldDeclineArguments: true };
|
|
7663
|
-
var ROOT_TYPECHECK_STEP = { run: "root:typecheck", shouldDeclineArguments: true };
|
|
7664
|
-
var LINT_CHECK_STEP = { run: "lint:check", shouldRunInBackground: true };
|
|
7665
|
-
var LINT_STRICT_STEP = { run: "lint:strict", shouldRunInBackground: true };
|
|
7666
|
-
var workspaceScripts = {
|
|
7667
|
-
build: ["compile"],
|
|
7668
|
-
check: [LINT_CHECK_STEP, TYPECHECK_STEP, "fmt:check", "test"],
|
|
7669
|
-
"check:strict": [LINT_STRICT_STEP, TYPECHECK_STEP, "fmt:check", "test"],
|
|
7670
|
-
clean: "nmr-clean",
|
|
7671
|
-
compile: "nmr-compile",
|
|
7672
|
-
fix: ["lint", "fmt"],
|
|
7673
|
-
"fix:check": ["fmt:check", "lint:check"],
|
|
7674
|
-
fmt: "nmr-fmt --write",
|
|
7675
|
-
"fmt:check": "nmr-fmt --check",
|
|
7676
|
-
lint: "eslint --fix .",
|
|
7677
|
-
"lint:check": "eslint .",
|
|
7678
|
-
"lint:strict": "strict-lint",
|
|
7679
|
-
"report-catalog": "nmr-report-catalog",
|
|
7680
|
-
test: `pnpm exec vitest ${GATE_PROJECTS}`,
|
|
7681
|
-
"test:all": "pnpm exec vitest",
|
|
7682
|
-
"test:coverage": `pnpm exec vitest ${GATE_PROJECTS} --coverage`,
|
|
7683
|
-
"test:tool": "pnpm exec vitest --project tool",
|
|
7684
|
-
"test:unit": "pnpm exec vitest --project unit",
|
|
7685
|
-
"test:watch": `pnpm exec vitest ${GATE_PROJECTS} --watch`,
|
|
7686
|
-
typecheck: "tsgo --noEmit",
|
|
7687
|
-
// The command is a string because neither half names an nmr command: Both are binaries.
|
|
7688
|
-
upgrade: "nmr-report-catalog && nmr-taze",
|
|
7689
|
-
"view-coverage": "open coverage/index.html"
|
|
7690
|
-
};
|
|
7691
|
-
function buildRootScripts(workspaceGlobs) {
|
|
7692
|
-
const ignorePatterns = workspaceGlobs.map((glob) => `--ignore-pattern ${quoteToken(glob)} `).join("");
|
|
7693
|
-
return {
|
|
7694
|
-
audit: ["audit:prod", "audit:dev"],
|
|
7695
|
-
"audit:dev": "pnpm exec v11y --dev",
|
|
7696
|
-
"audit:prod": "pnpm exec v11y --prod",
|
|
7697
|
-
build: ["-R build"],
|
|
7698
|
-
check: [LINT_CHECK_STEP, TYPECHECK_STEP, "fmt:check", "test"],
|
|
7699
|
-
"check:strict": [LINT_STRICT_STEP, TYPECHECK_STEP, "fmt:check", "test"],
|
|
7700
|
-
// Excludes the audit, which in CI has a workflow of its own. The narrowed check runs against the build, so
|
|
7701
|
-
// the build declines the arguments rather than being narrowed by them.
|
|
7702
|
-
ci: [{ run: "build", shouldDeclineArguments: true }, "check:strict"],
|
|
7703
|
-
clean: "nmr-clean",
|
|
7704
|
-
fix: ["lint", "fmt"],
|
|
7705
|
-
"fix:check": ["fmt:check", "lint:check"],
|
|
7706
|
-
fmt: "nmr-fmt --write",
|
|
7707
|
-
"fmt:check": "nmr-fmt --check",
|
|
7708
|
-
lint: "eslint --fix .",
|
|
7709
|
-
"lint:check": "eslint .",
|
|
7710
|
-
"lint:strict": "strict-lint",
|
|
7711
|
-
// The audit takes seconds and `ci` takes minutes, so the cheap gate fails first. The audit reads the
|
|
7712
|
-
// dependency tree, and an argument narrowing the code under test does not say anything about that tree.
|
|
7713
|
-
prepush: [{ run: "audit", shouldDeclineArguments: true }, "ci"],
|
|
7714
|
-
"report-overrides": "nmr-report-overrides",
|
|
7715
|
-
"root:check": [ROOT_TYPECHECK_STEP, "fmt:check", "root:lint:check", "root:test"],
|
|
7716
|
-
"root:lint": `eslint --fix ${ignorePatterns}.`,
|
|
7717
|
-
"root:lint:check": `eslint ${ignorePatterns}.`,
|
|
7718
|
-
"root:lint:strict": `strict-lint ${ignorePatterns}.`,
|
|
7719
|
-
"root:test": `vitest --config ./vitest.root.config.ts ${GATE_PROJECTS}`,
|
|
7720
|
-
"root:test:all": "vitest --config ./vitest.root.config.ts",
|
|
7721
|
-
"root:test:tool": "vitest --config ./vitest.root.config.ts --project tool",
|
|
7722
|
-
"root:test:unit": "vitest --config ./vitest.root.config.ts --project unit",
|
|
7723
|
-
"root:typecheck": "tsgo --noEmit",
|
|
7724
|
-
// Includes the override report for the same reason `upgrade` does: Both end in the tool that rewrites a
|
|
7725
|
-
// `pnpm.overrides` block, so both need the reporter's rejection ahead of them.
|
|
7726
|
-
"root:upgrade": "nmr-report-overrides && nmr-taze",
|
|
7727
|
-
test: ["root:test", "-R test"],
|
|
7728
|
-
"test:all": ["root:test:all", "-R test:all"],
|
|
7729
|
-
"test:coverage": ["root:test", "-R test:coverage"],
|
|
7730
|
-
"test:tool": ["root:test:tool", "-R test:tool"],
|
|
7731
|
-
"test:unit": ["root:test:unit", "-R test:unit"],
|
|
7732
|
-
"test:watch": `vitest ${GATE_PROJECTS} --watch`,
|
|
7733
|
-
// Neither step is narrowable, so `nmr typecheck <file>` is rejected rather than checking that file under
|
|
7734
|
-
// default options at the root and searching for it in every package.
|
|
7735
|
-
typecheck: [ROOT_TYPECHECK_STEP, { run: "-R typecheck", shouldDeclineArguments: true }],
|
|
7736
|
-
// The command is a string because neither half names an nmr command: Both are binaries, and a composite
|
|
7737
|
-
// element can name only a command.
|
|
7738
|
-
upgrade: "nmr-report-overrides && nmr-taze --recursive"
|
|
7739
|
-
};
|
|
7740
|
-
}
|
|
7741
|
-
|
|
7742
|
-
// src/workspace-globs.ts
|
|
7743
|
-
import path5 from "node:path";
|
|
7744
|
-
function toWorkspacePackageGlobs(monorepoRoot, packageDirs) {
|
|
7745
|
-
return packageDirs.map((dir) => path5.relative(monorepoRoot, dir).split(path5.sep).join("/")).filter((relativeDir) => relativeDir !== "").map((relativeDir) => `${relativeDir}/**`).toSorted();
|
|
8008
|
+
const notes = [
|
|
8009
|
+
...element.shouldRunInBackground === true ? ["background"] : [],
|
|
8010
|
+
...element.shouldDeclineArguments === true ? ["no args"] : []
|
|
8011
|
+
];
|
|
8012
|
+
return notes.length === 0 ? element.run : `${element.run} (${notes.join(", ")})`;
|
|
7746
8013
|
}
|
|
7747
|
-
|
|
7748
|
-
// src/resolve-scripts.ts
|
|
7749
|
-
function getDefaultWorkspaceScripts() {
|
|
7750
|
-
return { ...workspaceScripts };
|
|
7751
|
-
}
|
|
7752
|
-
function getDefaultRootScripts(monorepoRoot) {
|
|
7753
|
-
const resolution = resolveWorkspace(monorepoRoot);
|
|
7754
|
-
const packageDirs = resolution.kind === "packages" ? resolution.packageDirs : [];
|
|
7755
|
-
return buildRootScripts(toWorkspacePackageGlobs(monorepoRoot, packageDirs));
|
|
7756
|
-
}
|
|
7757
|
-
|
|
7758
|
-
// src/resolver.ts
|
|
7759
8014
|
function readPackageJsonScripts(packageDir) {
|
|
7760
8015
|
const file = resolvePackageJsonPath(packageDir);
|
|
7761
8016
|
let rawText;
|
|
@@ -7815,6 +8070,10 @@ var RECOGNIZED_CHECK_CACHE_KEYS = ["enabled", "excludeCommands", "extraCommands"
|
|
|
7815
8070
|
var RECOGNIZED_OUTPUT_KEYS = ["commandVerbosity", "extraAgentEnvVars"];
|
|
7816
8071
|
var RECOGNIZED_SELF_HOSTED_RUNNER_KEYS = ["exempt"];
|
|
7817
8072
|
var RECOGNIZED_STEP_KEYS = ["run", "shouldDeclineArguments", "shouldRunInBackground"];
|
|
8073
|
+
var SCRIPT_FIELDS = [
|
|
8074
|
+
{ field: "rootScripts", scope: "root" },
|
|
8075
|
+
{ field: "workspaceScripts", scope: "workspaceRegistry" }
|
|
8076
|
+
];
|
|
7818
8077
|
var RETIRED_STEP_KEYS = /* @__PURE__ */ new Map([["declinesArgs", "shouldDeclineArguments"]]);
|
|
7819
8078
|
function isScriptRecord(value) {
|
|
7820
8079
|
if (!isObject(value)) return false;
|
|
@@ -8056,7 +8315,9 @@ async function loadConfig(baseDir) {
|
|
|
8056
8315
|
}
|
|
8057
8316
|
async function loadRootConfig(monorepoRoot) {
|
|
8058
8317
|
const config = await loadConfig(monorepoRoot);
|
|
8059
|
-
|
|
8318
|
+
const configPath = resolveConfigPath(monorepoRoot);
|
|
8319
|
+
assertTierKeys(config, CONFIG_TIERS.root, configPath);
|
|
8320
|
+
assertNoSelfReferences(config, configPath, monorepoRoot);
|
|
8060
8321
|
return config;
|
|
8061
8322
|
}
|
|
8062
8323
|
function assertNoRetiredKeys(value, retiredKeys, configPath, prefix) {
|
|
@@ -8068,6 +8329,28 @@ function assertNoRetiredKeys(value, retiredKeys, configPath, prefix) {
|
|
|
8068
8329
|
);
|
|
8069
8330
|
}
|
|
8070
8331
|
}
|
|
8332
|
+
function assertNoSelfReferences(config, configPath, monorepoRoot) {
|
|
8333
|
+
for (const { field, scope } of SCRIPT_FIELDS) {
|
|
8334
|
+
const scripts = config[field] ?? {};
|
|
8335
|
+
for (const [key, script] of Object.entries(scripts)) {
|
|
8336
|
+
const selfReference = typeof script === "string" ? readSelfReference({ commandName: key, scope, script }) : readStepListSelfReference({
|
|
8337
|
+
commandName: key,
|
|
8338
|
+
elements: script.map((element) => typeof element === "string" ? element : element.run),
|
|
8339
|
+
scope
|
|
8340
|
+
});
|
|
8341
|
+
if (selfReference === void 0) continue;
|
|
8342
|
+
const defaults = field === "rootScripts" ? getDefaultRootScripts(monorepoRoot) : getDefaultWorkspaceScripts();
|
|
8343
|
+
const remedy = formatSelfReferenceRemedy({
|
|
8344
|
+
isDefault: !isHookName(key) && Object.hasOwn(defaults, key),
|
|
8345
|
+
key,
|
|
8346
|
+
selfReference
|
|
8347
|
+
});
|
|
8348
|
+
throw new UserError(
|
|
8349
|
+
`Invalid nmr config at ${configPath}: \`${field}.${key}\` re-invokes \`${key}\` (\`${escapeControlCharacters(describeScript(script))}\`), which would re-enter nmr without bound. ${remedy}`
|
|
8350
|
+
);
|
|
8351
|
+
}
|
|
8352
|
+
}
|
|
8353
|
+
}
|
|
8071
8354
|
function assertRecognizedKeys(value, recognizedKeys, configPath, prefix = "") {
|
|
8072
8355
|
const unrecognizedKeys = Object.keys(value).filter((key) => !recognizedKeys.includes(key));
|
|
8073
8356
|
if (unrecognizedKeys.length === 0) {
|
|
@@ -8089,6 +8372,13 @@ function assertTierKeys(config, tier, configPath) {
|
|
|
8089
8372
|
function formatKeyList(keys, prefix) {
|
|
8090
8373
|
return keys.toSorted().map((key) => `\`${prefix}${key}\``).join(", ");
|
|
8091
8374
|
}
|
|
8375
|
+
function formatSelfReferenceRemedy(options) {
|
|
8376
|
+
const { isDefault, key, selfReference } = options;
|
|
8377
|
+
if (isDefault) {
|
|
8378
|
+
return selfReference === "sole" ? `Delete the entry: nmr's own \`${key}\` then runs.` : `Delete the entry and move the steps that it adds to a \`${key}:pre\` or \`${key}:post\` script.`;
|
|
8379
|
+
}
|
|
8380
|
+
return selfReference === "sole" ? "Delete the entry." : `Delete the re-invocation: \`${key}\` runs the steps beside it.`;
|
|
8381
|
+
}
|
|
8092
8382
|
|
|
8093
8383
|
// src/tiers.ts
|
|
8094
8384
|
import { readdirSync } from "node:fs";
|
|
@@ -8649,7 +8939,7 @@ function formatPaths(paths) {
|
|
|
8649
8939
|
${paths.map((path7) => ` ${path7}`).join("\n")}`;
|
|
8650
8940
|
}
|
|
8651
8941
|
function getMinVersion() {
|
|
8652
|
-
const pickedFields = { "version": "0.
|
|
8942
|
+
const pickedFields = { "version": "0.45.0" };
|
|
8653
8943
|
if (typeof pickedFields["version"] !== "string") {
|
|
8654
8944
|
throw new TypeError("nmr/package.json: 'version' must be a string");
|
|
8655
8945
|
}
|