@williamthorsen/nmr 0.43.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.
Files changed (47) hide show
  1. package/.meta/changelog.json +168 -0
  2. package/.readyup/kits/default.js +1331 -136
  3. package/.readyup/manifest.json +148 -9
  4. package/CHANGELOG.md +51 -0
  5. package/README.md +17 -3
  6. package/agents/guidance/rulebooks/nmr.md +1 -1
  7. package/dist/esm/commands/fmt-cache.d.ts +26 -0
  8. package/dist/esm/commands/fmt-cache.js +71 -0
  9. package/dist/esm/commands/fmt.d.ts +10 -3
  10. package/dist/esm/commands/fmt.js +34 -6
  11. package/dist/esm/config.js +74 -9
  12. package/dist/esm/default-scripts.d.ts +10 -1
  13. package/dist/esm/default-scripts.js +45 -39
  14. package/dist/esm/defineConfig.d.ts +1 -1
  15. package/dist/esm/entry-chain.d.ts +24 -0
  16. package/dist/esm/entry-chain.js +57 -0
  17. package/dist/esm/glyphs.d.ts +1 -1
  18. package/dist/esm/help.d.ts +1 -1
  19. package/dist/esm/help.js +3 -3
  20. package/dist/esm/helpers/createOutputRecorder.d.ts +17 -0
  21. package/dist/esm/helpers/createOutputRecorder.js +22 -0
  22. package/dist/esm/helpers/escapeControlCharacters.d.ts +10 -0
  23. package/dist/esm/helpers/escapeControlCharacters.js +12 -0
  24. package/dist/esm/resolve-scripts.d.ts +5 -3
  25. package/dist/esm/resolve-scripts.js +7 -3
  26. package/dist/esm/resolver.d.ts +5 -4
  27. package/dist/esm/resolver.js +16 -6
  28. package/dist/esm/runCli.js +146 -101
  29. package/dist/esm/runner.d.ts +7 -0
  30. package/dist/esm/runner.js +105 -15
  31. package/dist/esm/steps.d.ts +50 -18
  32. package/dist/esm/steps.js +82 -13
  33. package/dist/esm/taze.d.ts +5 -2
  34. package/dist/esm/taze.js +4 -1
  35. package/dist/esm/tiers.d.ts +5 -4
  36. package/dist/esm/types.d.ts +10 -0
  37. package/dist/esm/vitest-git-isolation.js +3 -1
  38. package/dist/esm/vitest.js +9 -5
  39. package/dist/esm/workspace-globs.d.ts +5 -0
  40. package/dist/esm/workspace-globs.js +8 -0
  41. package/docs/check-cache.md +2 -0
  42. package/docs/reporting.md +2 -0
  43. package/docs/scripts.md +36 -10
  44. package/docs/upgrades.md +1 -1
  45. package/docs/utilities.md +14 -2
  46. package/docs/vitest.md +20 -8
  47. package/package.json +7 -7
@@ -117,17 +117,17 @@ var require_visit = __commonJS({
117
117
  visit.BREAK = BREAK;
118
118
  visit.SKIP = SKIP;
119
119
  visit.REMOVE = REMOVE;
120
- function visit_(key, node, visitor, path2) {
121
- const ctrl = callVisitor(key, node, visitor, path2);
120
+ function visit_(key, node, visitor, path7) {
121
+ const ctrl = callVisitor(key, node, visitor, path7);
122
122
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
123
- replaceNode(key, path2, ctrl);
124
- return visit_(key, ctrl, visitor, path2);
123
+ replaceNode(key, path7, ctrl);
124
+ return visit_(key, ctrl, visitor, path7);
125
125
  }
126
126
  if (typeof ctrl !== "symbol") {
127
127
  if (identity.isCollection(node)) {
128
- path2 = Object.freeze(path2.concat(node));
128
+ path7 = Object.freeze(path7.concat(node));
129
129
  for (let i = 0; i < node.items.length; ++i) {
130
- const ci = visit_(i, node.items[i], visitor, path2);
130
+ const ci = visit_(i, node.items[i], visitor, path7);
131
131
  if (typeof ci === "number")
132
132
  i = ci - 1;
133
133
  else if (ci === BREAK)
@@ -138,13 +138,13 @@ var require_visit = __commonJS({
138
138
  }
139
139
  }
140
140
  } else if (identity.isPair(node)) {
141
- path2 = Object.freeze(path2.concat(node));
142
- const ck = visit_("key", node.key, visitor, path2);
141
+ path7 = Object.freeze(path7.concat(node));
142
+ const ck = visit_("key", node.key, visitor, path7);
143
143
  if (ck === BREAK)
144
144
  return BREAK;
145
145
  else if (ck === REMOVE)
146
146
  node.key = null;
147
- const cv = visit_("value", node.value, visitor, path2);
147
+ const cv = visit_("value", node.value, visitor, path7);
148
148
  if (cv === BREAK)
149
149
  return BREAK;
150
150
  else if (cv === REMOVE)
@@ -165,17 +165,17 @@ var require_visit = __commonJS({
165
165
  visitAsync.BREAK = BREAK;
166
166
  visitAsync.SKIP = SKIP;
167
167
  visitAsync.REMOVE = REMOVE;
168
- async function visitAsync_(key, node, visitor, path2) {
169
- const ctrl = await callVisitor(key, node, visitor, path2);
168
+ async function visitAsync_(key, node, visitor, path7) {
169
+ const ctrl = await callVisitor(key, node, visitor, path7);
170
170
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
171
- replaceNode(key, path2, ctrl);
172
- return visitAsync_(key, ctrl, visitor, path2);
171
+ replaceNode(key, path7, ctrl);
172
+ return visitAsync_(key, ctrl, visitor, path7);
173
173
  }
174
174
  if (typeof ctrl !== "symbol") {
175
175
  if (identity.isCollection(node)) {
176
- path2 = Object.freeze(path2.concat(node));
176
+ path7 = Object.freeze(path7.concat(node));
177
177
  for (let i = 0; i < node.items.length; ++i) {
178
- const ci = await visitAsync_(i, node.items[i], visitor, path2);
178
+ const ci = await visitAsync_(i, node.items[i], visitor, path7);
179
179
  if (typeof ci === "number")
180
180
  i = ci - 1;
181
181
  else if (ci === BREAK)
@@ -186,13 +186,13 @@ var require_visit = __commonJS({
186
186
  }
187
187
  }
188
188
  } else if (identity.isPair(node)) {
189
- path2 = Object.freeze(path2.concat(node));
190
- const ck = await visitAsync_("key", node.key, visitor, path2);
189
+ path7 = Object.freeze(path7.concat(node));
190
+ const ck = await visitAsync_("key", node.key, visitor, path7);
191
191
  if (ck === BREAK)
192
192
  return BREAK;
193
193
  else if (ck === REMOVE)
194
194
  node.key = null;
195
- const cv = await visitAsync_("value", node.value, visitor, path2);
195
+ const cv = await visitAsync_("value", node.value, visitor, path7);
196
196
  if (cv === BREAK)
197
197
  return BREAK;
198
198
  else if (cv === REMOVE)
@@ -219,23 +219,23 @@ var require_visit = __commonJS({
219
219
  }
220
220
  return visitor;
221
221
  }
222
- function callVisitor(key, node, visitor, path2) {
222
+ function callVisitor(key, node, visitor, path7) {
223
223
  if (typeof visitor === "function")
224
- return visitor(key, node, path2);
224
+ return visitor(key, node, path7);
225
225
  if (identity.isMap(node))
226
- return visitor.Map?.(key, node, path2);
226
+ return visitor.Map?.(key, node, path7);
227
227
  if (identity.isSeq(node))
228
- return visitor.Seq?.(key, node, path2);
228
+ return visitor.Seq?.(key, node, path7);
229
229
  if (identity.isPair(node))
230
- return visitor.Pair?.(key, node, path2);
230
+ return visitor.Pair?.(key, node, path7);
231
231
  if (identity.isScalar(node))
232
- return visitor.Scalar?.(key, node, path2);
232
+ return visitor.Scalar?.(key, node, path7);
233
233
  if (identity.isAlias(node))
234
- return visitor.Alias?.(key, node, path2);
234
+ return visitor.Alias?.(key, node, path7);
235
235
  return void 0;
236
236
  }
237
- function replaceNode(key, path2, node) {
238
- const parent = path2[path2.length - 1];
237
+ function replaceNode(key, path7, node) {
238
+ const parent = path7[path7.length - 1];
239
239
  if (identity.isCollection(parent)) {
240
240
  parent.items[key] = node;
241
241
  } else if (identity.isPair(parent)) {
@@ -847,10 +847,10 @@ var require_Collection = __commonJS({
847
847
  var createNode = require_createNode();
848
848
  var identity = require_identity();
849
849
  var Node = require_Node();
850
- function collectionFromPath(schema, path2, value) {
850
+ function collectionFromPath(schema, path7, value) {
851
851
  let v = value;
852
- for (let i = path2.length - 1; i >= 0; --i) {
853
- const k = path2[i];
852
+ for (let i = path7.length - 1; i >= 0; --i) {
853
+ const k = path7[i];
854
854
  if (typeof k === "number" && Number.isInteger(k) && k >= 0) {
855
855
  const a = [];
856
856
  a[k] = v;
@@ -869,7 +869,7 @@ var require_Collection = __commonJS({
869
869
  sourceObjects: /* @__PURE__ */ new Map()
870
870
  });
871
871
  }
872
- var isEmptyPath = (path2) => path2 == null || typeof path2 === "object" && !!path2[Symbol.iterator]().next().done;
872
+ var isEmptyPath = (path7) => path7 == null || typeof path7 === "object" && !!path7[Symbol.iterator]().next().done;
873
873
  var Collection = class extends Node.NodeBase {
874
874
  constructor(type, schema) {
875
875
  super(type);
@@ -899,11 +899,11 @@ var require_Collection = __commonJS({
899
899
  * be a Pair instance or a `{ key, value }` object, which may not have a key
900
900
  * that already exists in the map.
901
901
  */
902
- addIn(path2, value) {
903
- if (isEmptyPath(path2))
902
+ addIn(path7, value) {
903
+ if (isEmptyPath(path7))
904
904
  this.add(value);
905
905
  else {
906
- const [key, ...rest] = path2;
906
+ const [key, ...rest] = path7;
907
907
  const node = this.get(key, true);
908
908
  if (identity.isCollection(node))
909
909
  node.addIn(rest, value);
@@ -917,8 +917,8 @@ var require_Collection = __commonJS({
917
917
  * Removes a value from the collection.
918
918
  * @returns `true` if the item was found and removed.
919
919
  */
920
- deleteIn(path2) {
921
- const [key, ...rest] = path2;
920
+ deleteIn(path7) {
921
+ const [key, ...rest] = path7;
922
922
  if (rest.length === 0)
923
923
  return this.delete(key);
924
924
  const node = this.get(key, true);
@@ -932,8 +932,8 @@ var require_Collection = __commonJS({
932
932
  * scalar values from their surrounding node; to disable set `keepScalar` to
933
933
  * `true` (collections are always returned intact).
934
934
  */
935
- getIn(path2, keepScalar) {
936
- const [key, ...rest] = path2;
935
+ getIn(path7, keepScalar) {
936
+ const [key, ...rest] = path7;
937
937
  const node = this.get(key, true);
938
938
  if (rest.length === 0)
939
939
  return !keepScalar && identity.isScalar(node) ? node.value : node;
@@ -951,8 +951,8 @@ var require_Collection = __commonJS({
951
951
  /**
952
952
  * Checks if the collection includes a value with the key `key`.
953
953
  */
954
- hasIn(path2) {
955
- const [key, ...rest] = path2;
954
+ hasIn(path7) {
955
+ const [key, ...rest] = path7;
956
956
  if (rest.length === 0)
957
957
  return this.has(key);
958
958
  const node = this.get(key, true);
@@ -962,8 +962,8 @@ var require_Collection = __commonJS({
962
962
  * Sets a value in this collection. For `!!set`, `value` needs to be a
963
963
  * boolean to add/remove the item from the set.
964
964
  */
965
- setIn(path2, value) {
966
- const [key, ...rest] = path2;
965
+ setIn(path7, value) {
966
+ const [key, ...rest] = path7;
967
967
  if (rest.length === 0) {
968
968
  this.set(key, value);
969
969
  } else {
@@ -3478,9 +3478,9 @@ var require_Document = __commonJS({
3478
3478
  this.contents.add(value);
3479
3479
  }
3480
3480
  /** Adds a value to the document. */
3481
- addIn(path2, value) {
3481
+ addIn(path7, value) {
3482
3482
  if (assertCollection(this.contents))
3483
- this.contents.addIn(path2, value);
3483
+ this.contents.addIn(path7, value);
3484
3484
  }
3485
3485
  /**
3486
3486
  * Create a new `Alias` node, ensuring that the target `node` has the required anchor.
@@ -3555,14 +3555,14 @@ var require_Document = __commonJS({
3555
3555
  * Removes a value from the document.
3556
3556
  * @returns `true` if the item was found and removed.
3557
3557
  */
3558
- deleteIn(path2) {
3559
- if (Collection.isEmptyPath(path2)) {
3558
+ deleteIn(path7) {
3559
+ if (Collection.isEmptyPath(path7)) {
3560
3560
  if (this.contents == null)
3561
3561
  return false;
3562
3562
  this.contents = null;
3563
3563
  return true;
3564
3564
  }
3565
- return assertCollection(this.contents) ? this.contents.deleteIn(path2) : false;
3565
+ return assertCollection(this.contents) ? this.contents.deleteIn(path7) : false;
3566
3566
  }
3567
3567
  /**
3568
3568
  * Returns item at `key`, or `undefined` if not found. By default unwraps
@@ -3577,10 +3577,10 @@ var require_Document = __commonJS({
3577
3577
  * scalar values from their surrounding node; to disable set `keepScalar` to
3578
3578
  * `true` (collections are always returned intact).
3579
3579
  */
3580
- getIn(path2, keepScalar) {
3581
- if (Collection.isEmptyPath(path2))
3580
+ getIn(path7, keepScalar) {
3581
+ if (Collection.isEmptyPath(path7))
3582
3582
  return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents;
3583
- return identity.isCollection(this.contents) ? this.contents.getIn(path2, keepScalar) : void 0;
3583
+ return identity.isCollection(this.contents) ? this.contents.getIn(path7, keepScalar) : void 0;
3584
3584
  }
3585
3585
  /**
3586
3586
  * Checks if the document includes a value with the key `key`.
@@ -3591,10 +3591,10 @@ var require_Document = __commonJS({
3591
3591
  /**
3592
3592
  * Checks if the document includes a value at `path`.
3593
3593
  */
3594
- hasIn(path2) {
3595
- if (Collection.isEmptyPath(path2))
3594
+ hasIn(path7) {
3595
+ if (Collection.isEmptyPath(path7))
3596
3596
  return this.contents !== void 0;
3597
- return identity.isCollection(this.contents) ? this.contents.hasIn(path2) : false;
3597
+ return identity.isCollection(this.contents) ? this.contents.hasIn(path7) : false;
3598
3598
  }
3599
3599
  /**
3600
3600
  * Sets a value in this document. For `!!set`, `value` needs to be a
@@ -3611,13 +3611,13 @@ var require_Document = __commonJS({
3611
3611
  * Sets a value in this document. For `!!set`, `value` needs to be a
3612
3612
  * boolean to add/remove the item from the set.
3613
3613
  */
3614
- setIn(path2, value) {
3615
- if (Collection.isEmptyPath(path2)) {
3614
+ setIn(path7, value) {
3615
+ if (Collection.isEmptyPath(path7)) {
3616
3616
  this.contents = value;
3617
3617
  } else if (this.contents == null) {
3618
- this.contents = Collection.collectionFromPath(this.schema, Array.from(path2), value);
3618
+ this.contents = Collection.collectionFromPath(this.schema, Array.from(path7), value);
3619
3619
  } else if (assertCollection(this.contents)) {
3620
- this.contents.setIn(path2, value);
3620
+ this.contents.setIn(path7, value);
3621
3621
  }
3622
3622
  }
3623
3623
  /**
@@ -5578,9 +5578,9 @@ var require_cst_visit = __commonJS({
5578
5578
  visit.BREAK = BREAK;
5579
5579
  visit.SKIP = SKIP;
5580
5580
  visit.REMOVE = REMOVE;
5581
- visit.itemAtPath = (cst, path2) => {
5581
+ visit.itemAtPath = (cst, path7) => {
5582
5582
  let item = cst;
5583
- for (const [field, index] of path2) {
5583
+ for (const [field, index] of path7) {
5584
5584
  const tok = item?.[field];
5585
5585
  if (tok && "items" in tok) {
5586
5586
  item = tok.items[index];
@@ -5589,23 +5589,23 @@ var require_cst_visit = __commonJS({
5589
5589
  }
5590
5590
  return item;
5591
5591
  };
5592
- visit.parentCollection = (cst, path2) => {
5593
- const parent = visit.itemAtPath(cst, path2.slice(0, -1));
5594
- const field = path2[path2.length - 1][0];
5592
+ visit.parentCollection = (cst, path7) => {
5593
+ const parent = visit.itemAtPath(cst, path7.slice(0, -1));
5594
+ const field = path7[path7.length - 1][0];
5595
5595
  const coll = parent?.[field];
5596
5596
  if (coll && "items" in coll)
5597
5597
  return coll;
5598
5598
  throw new Error("Parent collection not found");
5599
5599
  };
5600
- function _visit(path2, item, visitor) {
5601
- let ctrl = visitor(item, path2);
5600
+ function _visit(path7, item, visitor) {
5601
+ let ctrl = visitor(item, path7);
5602
5602
  if (typeof ctrl === "symbol")
5603
5603
  return ctrl;
5604
5604
  for (const field of ["key", "value"]) {
5605
5605
  const token = item[field];
5606
5606
  if (token && "items" in token) {
5607
5607
  for (let i = 0; i < token.items.length; ++i) {
5608
- const ci = _visit(Object.freeze(path2.concat([[field, i]])), token.items[i], visitor);
5608
+ const ci = _visit(Object.freeze(path7.concat([[field, i]])), token.items[i], visitor);
5609
5609
  if (typeof ci === "number")
5610
5610
  i = ci - 1;
5611
5611
  else if (ci === BREAK)
@@ -5616,10 +5616,10 @@ var require_cst_visit = __commonJS({
5616
5616
  }
5617
5617
  }
5618
5618
  if (typeof ctrl === "function" && field === "key")
5619
- ctrl = ctrl(item, path2);
5619
+ ctrl = ctrl(item, path7);
5620
5620
  }
5621
5621
  }
5622
- return typeof ctrl === "function" ? ctrl(item, path2) : ctrl;
5622
+ return typeof ctrl === "function" ? ctrl(item, path7) : ctrl;
5623
5623
  }
5624
5624
  exports.visit = visit;
5625
5625
  }
@@ -7270,7 +7270,7 @@ var require_public_api = __commonJS({
7270
7270
  }
7271
7271
  return doc;
7272
7272
  }
7273
- function parse2(src, reviver, options) {
7273
+ function parse3(src, reviver, options) {
7274
7274
  let _reviver = void 0;
7275
7275
  if (typeof reviver === "function") {
7276
7276
  _reviver = reviver;
@@ -7311,7 +7311,7 @@ var require_public_api = __commonJS({
7311
7311
  return value.toString(options);
7312
7312
  return new Document.Document(value, _replacer, options).toString(options);
7313
7313
  }
7314
- exports.parse = parse2;
7314
+ exports.parse = parse3;
7315
7315
  exports.parseAllDocuments = parseAllDocuments;
7316
7316
  exports.parseDocument = parseDocument;
7317
7317
  exports.stringify = stringify;
@@ -7371,15 +7371,16 @@ var require_dist = __commonJS({
7371
7371
  });
7372
7372
 
7373
7373
  // .readyup/kits/default.ts
7374
- import { existsSync, globSync, readdirSync as readdirSync2 } from "node:fs";
7374
+ import { execFile } from "node:child_process";
7375
+ import { existsSync as existsSync3, globSync as globSync2, readdirSync as readdirSync2 } from "node:fs";
7375
7376
  import { basename, dirname, join, posix, sep } from "node:path";
7376
7377
 
7377
- // ../../node_modules/.pnpm/@williamthorsen+toolbelt.errors@0.8.2/node_modules/@williamthorsen/toolbelt.errors/dist/esm/4-release/isError.js
7378
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.errors@0.8.3/node_modules/@williamthorsen/toolbelt.errors/dist/esm/4-release/isError.js
7378
7379
  function isError(error) {
7379
7380
  return error instanceof Error || Object.prototype.toString.call(error) === "[object Error]";
7380
7381
  }
7381
7382
 
7382
- // ../../node_modules/.pnpm/@williamthorsen+toolbelt.errors@0.8.2/node_modules/@williamthorsen/toolbelt.errors/dist/esm/4-release/describeError.js
7383
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.errors@0.8.3/node_modules/@williamthorsen/toolbelt.errors/dist/esm/4-release/describeError.js
7383
7384
  function describeError(error) {
7384
7385
  try {
7385
7386
  if (isError(error) && typeof error.message === "string" && error.message !== "") {
@@ -7392,7 +7393,7 @@ function describeError(error) {
7392
7393
  }
7393
7394
 
7394
7395
  // .readyup/kits/default.ts
7395
- var import_yaml = __toESM(require_dist(), 1);
7396
+ var import_yaml2 = __toESM(require_dist(), 1);
7396
7397
  import { defineRdyKit } from "readyup";
7397
7398
  import {
7398
7399
  discoverWorkspaces,
@@ -7407,14 +7408,345 @@ import {
7407
7408
  readPackageJson
7408
7409
  } from "readyup/check-utils";
7409
7410
 
7411
+ // src/config.ts
7412
+ import { existsSync as existsSync2 } from "node:fs";
7413
+ import { pathToFileURL } from "node:url";
7414
+
7415
+ // src/helpers/config-path.ts
7416
+ import path from "node:path";
7417
+ var CONFIG_DIR = ".config";
7418
+ var CONFIG_FILENAME = "nmr.config.ts";
7419
+ var CONFIG_RELATIVE_PATH = path.join(CONFIG_DIR, CONFIG_FILENAME);
7420
+ function resolveConfigPath(baseDir) {
7421
+ return path.join(baseDir, CONFIG_DIR, CONFIG_FILENAME);
7422
+ }
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
+
7438
+ // src/helpers/findClosestName.ts
7439
+ function findClosestName(name, candidates) {
7440
+ const ceiling = Math.max(1, Math.floor(name.length / 4));
7441
+ let closestName;
7442
+ let shortestDistance = Infinity;
7443
+ for (const candidate of candidates) {
7444
+ const distance = measureDistance(name, candidate);
7445
+ if (distance < shortestDistance) {
7446
+ shortestDistance = distance;
7447
+ closestName = candidate;
7448
+ }
7449
+ }
7450
+ return shortestDistance <= ceiling ? closestName : void 0;
7451
+ }
7452
+ function measureDistance(first, second) {
7453
+ if (first.length === 0 || second.length === 0) {
7454
+ return Math.max(first.length, second.length);
7455
+ }
7456
+ let previousRow = Array.from({ length: second.length + 1 }, (_unused, column) => column);
7457
+ let currentRow = Array.from({ length: second.length + 1 }, () => 0);
7458
+ for (let row = 1; row <= first.length; row += 1) {
7459
+ currentRow[0] = row;
7460
+ for (let column = 1; column <= second.length; column += 1) {
7461
+ const substitution = readCell(previousRow, column - 1) + (first[row - 1] === second[column - 1] ? 0 : 1);
7462
+ currentRow[column] = Math.min(
7463
+ readCell(currentRow, column - 1) + 1,
7464
+ readCell(previousRow, column) + 1,
7465
+ substitution
7466
+ );
7467
+ }
7468
+ const completedRow = currentRow;
7469
+ currentRow = previousRow;
7470
+ previousRow = completedRow;
7471
+ }
7472
+ return readCell(previousRow, second.length);
7473
+ }
7474
+ function readCell(row, index) {
7475
+ const value = row[index];
7476
+ if (value === void 0) {
7477
+ throw new RangeError(`No cell at index ${index} in a row of ${row.length}`);
7478
+ }
7479
+ return value;
7480
+ }
7481
+
7482
+ // src/helpers/hook-name.ts
7483
+ function isHookName(key) {
7484
+ return key.length > 4 && key.endsWith(":pre") || key.length > 5 && key.endsWith(":post");
7485
+ }
7486
+
7487
+ // src/helpers/type-guards.ts
7488
+ function isObject(value) {
7489
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7490
+ }
7491
+ function isStringRecord(value) {
7492
+ return isObject(value) && Object.values(value).every((entry) => typeof entry === "string");
7493
+ }
7494
+
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));
7511
+ }
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);
7736
+ }
7737
+ return tokens;
7738
+ }
7739
+
7410
7740
  // src/default-scripts.ts
7411
7741
  var GATE_PROJECTS = "--project unit --project tool";
7412
7742
  var TYPECHECK_STEP = { run: "typecheck", shouldDeclineArguments: true };
7413
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 };
7414
7746
  var workspaceScripts = {
7415
7747
  build: ["compile"],
7416
- check: [TYPECHECK_STEP, "fmt:check", "lint:check", "test"],
7417
- "check:strict": [TYPECHECK_STEP, "fmt:check", "lint:strict", "test"],
7748
+ check: [LINT_CHECK_STEP, TYPECHECK_STEP, "fmt:check", "test"],
7749
+ "check:strict": [LINT_STRICT_STEP, TYPECHECK_STEP, "fmt:check", "test"],
7418
7750
  clean: "nmr-clean",
7419
7751
  compile: "nmr-compile",
7420
7752
  fix: ["lint", "fmt"],
@@ -7436,62 +7768,621 @@ var workspaceScripts = {
7436
7768
  upgrade: "nmr-report-catalog && nmr-taze",
7437
7769
  "view-coverage": "open coverage/index.html"
7438
7770
  };
7439
- var rootScripts = {
7440
- audit: ["audit:prod", "audit:dev"],
7441
- "audit:dev": "pnpm exec v11y --dev",
7442
- "audit:prod": "pnpm exec v11y --prod",
7443
- build: ["-R build"],
7444
- check: [TYPECHECK_STEP, "fmt:check", "lint:check", "test"],
7445
- "check:strict": [TYPECHECK_STEP, "fmt:check", "lint:strict", "test"],
7446
- // Excludes the audit, which in CI has a workflow of its own. The narrowed check runs against the build, so
7447
- // the build declines the arguments rather than being narrowed by them.
7448
- ci: [{ run: "build", shouldDeclineArguments: true }, "check:strict"],
7449
- clean: "nmr-clean",
7450
- fix: ["lint", "fmt"],
7451
- "fix:check": ["fmt:check", "lint:check"],
7452
- fmt: "nmr-fmt --write",
7453
- "fmt:check": "nmr-fmt --check",
7454
- lint: "eslint --fix .",
7455
- "lint:check": "eslint .",
7456
- "lint:strict": "strict-lint",
7457
- // The audit takes seconds and `ci` takes minutes, so the cheap gate fails first. The audit reads the
7458
- // dependency tree, and an argument narrowing the code under test does not say anything about that tree.
7459
- prepush: [{ run: "audit", shouldDeclineArguments: true }, "ci"],
7460
- "report-overrides": "nmr-report-overrides",
7461
- "root:check": [ROOT_TYPECHECK_STEP, "fmt:check", "root:lint:check", "root:test"],
7462
- "root:lint": "eslint --fix --ignore-pattern 'packages/**' .",
7463
- "root:lint:check": "eslint --ignore-pattern 'packages/**' .",
7464
- "root:lint:strict": "strict-lint --ignore-pattern 'packages/**' .",
7465
- "root:test": `vitest --config ./vitest.root.config.ts ${GATE_PROJECTS}`,
7466
- "root:test:all": "vitest --config ./vitest.root.config.ts",
7467
- "root:test:tool": "vitest --config ./vitest.root.config.ts --project tool",
7468
- "root:test:unit": "vitest --config ./vitest.root.config.ts --project unit",
7469
- "root:typecheck": "tsgo --noEmit",
7470
- // Includes the override report for the same reason `upgrade` does: Both end in the tool that rewrites a
7471
- // `pnpm.overrides` block, so both need the reporter's rejection ahead of them.
7472
- "root:upgrade": "nmr-report-overrides && nmr-taze",
7473
- test: ["root:test", "-R test"],
7474
- "test:all": ["root:test:all", "-R test:all"],
7475
- "test:coverage": ["root:test", "-R test:coverage"],
7476
- "test:tool": ["root:test:tool", "-R test:tool"],
7477
- "test:unit": ["root:test:unit", "-R test:unit"],
7478
- "test:watch": `vitest ${GATE_PROJECTS} --watch`,
7479
- // Neither step is narrowable, so `nmr typecheck <file>` is rejected rather than checking that file under
7480
- // default options at the root and searching for it in every package.
7481
- typecheck: [ROOT_TYPECHECK_STEP, { run: "-R typecheck", shouldDeclineArguments: true }],
7482
- // The command is a string because neither half names an nmr command: Both are binaries, and a composite
7483
- // element can name only a command.
7484
- upgrade: "nmr-report-overrides && nmr-taze --recursive"
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
+ }
7821
+
7822
+ // ../nmr-core/dist/esm/workspace.js
7823
+ import { existsSync, readFileSync } from "node:fs";
7824
+ import path3 from "node:path";
7825
+ var import_yaml = __toESM(require_dist(), 1);
7826
+
7827
+ // ../nmr-core/dist/esm/helpers/workspace-patterns.js
7828
+ import { globSync } from "node:fs";
7829
+ import path2 from "node:path";
7830
+ var MANIFEST = "package.json";
7831
+ var ALWAYS_EXCLUDED = ["**/node_modules/**"];
7832
+ function matchPackageDirs(monorepoRoot, includedPatterns, excludedPatterns) {
7833
+ const options = {
7834
+ cwd: monorepoRoot,
7835
+ exclude: [...excludedPatterns, ...ALWAYS_EXCLUDED],
7836
+ followSymlinks: true
7837
+ };
7838
+ const matches = globSync([...includedPatterns], options);
7839
+ const dirs = matches.map((match) => path2.dirname(path2.resolve(monorepoRoot, match)));
7840
+ return [...new Set(dirs)].toSorted();
7841
+ }
7842
+ function resolvePackageDirs(monorepoRoot, patterns) {
7843
+ const { excludedPatterns, includedPatterns } = splitWorkspacePatterns(patterns);
7844
+ if (includedPatterns.length === 0) {
7845
+ return [];
7846
+ }
7847
+ return matchPackageDirs(monorepoRoot, includedPatterns, excludedPatterns);
7848
+ }
7849
+ function splitWorkspacePatterns(patterns) {
7850
+ const includedPatterns = [];
7851
+ const excludedPatterns = [];
7852
+ for (const pattern of patterns) {
7853
+ if (pattern.trim() === "")
7854
+ continue;
7855
+ const isNegated = pattern.startsWith("!");
7856
+ const target = isNegated ? excludedPatterns : includedPatterns;
7857
+ target.push(buildManifestPattern(isNegated ? pattern.slice(1) : pattern));
7858
+ }
7859
+ return { excludedPatterns, includedPatterns };
7860
+ }
7861
+ function buildManifestPattern(pattern) {
7862
+ return `${pattern.replace(/\/?$/, "")}/${MANIFEST}`;
7863
+ }
7864
+
7865
+ // ../nmr-core/dist/esm/workspace.js
7866
+ var WORKSPACE_MANIFEST = "pnpm-workspace.yaml";
7867
+ function isMonorepoRoot(dir) {
7868
+ return existsSync(path3.join(dir, WORKSPACE_MANIFEST));
7869
+ }
7870
+ function resolveWorkspace(monorepoRoot) {
7871
+ const manifestRead = readWorkspaceManifest(monorepoRoot);
7872
+ if (manifestRead.kind === "absent") {
7873
+ return { kind: "not-a-workspace" };
7874
+ }
7875
+ if (manifestRead.kind === "unreadable") {
7876
+ return { cause: "unreadable-manifest", kind: "empty", patterns: [] };
7877
+ }
7878
+ const packagesRead = readDeclaredPackages(manifestRead.value);
7879
+ if (packagesRead.kind !== "patterns") {
7880
+ const cause = packagesRead.kind === "absent" ? "no-packages-list" : "unreadable-packages";
7881
+ return { cause, kind: "empty", patterns: [] };
7882
+ }
7883
+ const { patterns } = packagesRead;
7884
+ const packageDirs = resolvePackageDirs(monorepoRoot, patterns);
7885
+ if (packageDirs.length > 0) {
7886
+ return { kind: "packages", packageDirs, patterns };
7887
+ }
7888
+ return { cause: diagnoseEmptyCause(monorepoRoot, patterns), kind: "empty", patterns };
7889
+ }
7890
+ function diagnoseEmptyCause(monorepoRoot, patterns) {
7891
+ const { excludedPatterns, includedPatterns } = splitWorkspacePatterns(patterns);
7892
+ if (includedPatterns.length === 0) {
7893
+ return "no-pattern";
7894
+ }
7895
+ if (excludedPatterns.length > 0 && matchPackageDirs(monorepoRoot, includedPatterns, []).length > 0) {
7896
+ return "all-excluded";
7897
+ }
7898
+ return "no-package";
7899
+ }
7900
+ function readDeclaredPackages(parsedManifest) {
7901
+ if (!isObject2(parsedManifest))
7902
+ return { kind: "absent" };
7903
+ const packages = parsedManifest["packages"];
7904
+ if (packages === void 0 || packages === null)
7905
+ return { kind: "absent" };
7906
+ if (!Array.isArray(packages))
7907
+ return { kind: "unreadable" };
7908
+ if (!packages.every((entry) => typeof entry === "string"))
7909
+ return { kind: "unreadable" };
7910
+ return packages.length === 0 ? { kind: "absent" } : { kind: "patterns", patterns: packages };
7911
+ }
7912
+ function isObject2(value) {
7913
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7914
+ }
7915
+ function readWorkspaceManifest(monorepoRoot) {
7916
+ const workspaceFile = path3.join(monorepoRoot, WORKSPACE_MANIFEST);
7917
+ if (!existsSync(workspaceFile)) {
7918
+ return { kind: "absent" };
7919
+ }
7920
+ try {
7921
+ return { kind: "parsed", value: (0, import_yaml.parse)(readFileSync(workspaceFile, "utf8")) };
7922
+ } catch {
7923
+ return { kind: "unreadable" };
7924
+ }
7925
+ }
7926
+
7927
+ // src/UserError.ts
7928
+ var UserError = class extends Error {
7929
+ constructor(message) {
7930
+ super(message);
7931
+ this.name = "UserError";
7932
+ }
7485
7933
  };
7486
7934
 
7935
+ // src/workspace.ts
7936
+ var WORKSPACE_MANIFEST2 = "pnpm-workspace.yaml";
7937
+ function getWorkspacePackageDirs(monorepoRoot) {
7938
+ const resolution = resolveWorkspace(monorepoRoot);
7939
+ if (resolution.kind === "not-a-workspace") {
7940
+ throw new UserError(`Not a monorepo root: no ${WORKSPACE_MANIFEST2} in ${monorepoRoot}`);
7941
+ }
7942
+ return resolution.kind === "packages" ? resolution.packageDirs : [];
7943
+ }
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
+
7487
7951
  // src/resolve-scripts.ts
7488
- function getDefaultRootScripts() {
7489
- return { ...rootScripts };
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
+
7969
+ // src/helpers/package-json.ts
7970
+ import path5 from "node:path";
7971
+ function parsePackageJson(rawText, file) {
7972
+ try {
7973
+ return JSON.parse(rawText);
7974
+ } catch (error) {
7975
+ throw new UserError(`Invalid package.json at ${file}: ${describeError(error)}`);
7976
+ }
7977
+ }
7978
+ function readScriptRecord(dir, scripts) {
7979
+ const result = {};
7980
+ for (const [key, value] of Object.entries(scripts)) {
7981
+ if (typeof value !== "string") {
7982
+ throw new UserError(formatMalformedScript(dir, key, value));
7983
+ }
7984
+ result[key] = value;
7985
+ }
7986
+ return result;
7987
+ }
7988
+ function resolvePackageJsonPath(dir) {
7989
+ return path5.join(dir, "package.json");
7990
+ }
7991
+ function formatMalformedScript(dir, key, value) {
7992
+ const rejection = `Invalid package.json at ${resolvePackageJsonPath(dir)}: \`scripts.${key}\` must be a string.`;
7993
+ if (!Array.isArray(value)) {
7994
+ return rejection;
7995
+ }
7996
+ const field = isMonorepoRoot(dir) ? "rootScripts" : "workspaceScripts";
7997
+ return `${rejection} A step list belongs in \`${CONFIG_RELATIVE_PATH}\` under \`${field}\`.`;
7998
+ }
7999
+
8000
+ // src/resolver.ts
8001
+ function describeScript(script) {
8002
+ return typeof script === "string" ? script : `[${script.map(describeElement).join(", ")}]`;
8003
+ }
8004
+ function describeElement(element) {
8005
+ if (typeof element === "string") {
8006
+ return element;
8007
+ }
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(", ")})`;
8013
+ }
8014
+ function readPackageJsonScripts(packageDir) {
8015
+ const file = resolvePackageJsonPath(packageDir);
8016
+ let rawText;
8017
+ try {
8018
+ rawText = readFileSync2(file, "utf8");
8019
+ } catch (error) {
8020
+ if (hasErrnoCode(error, "ENOENT")) {
8021
+ return void 0;
8022
+ }
8023
+ throw error;
8024
+ }
8025
+ const parsedManifest = parsePackageJson(rawText, file);
8026
+ if (!isObject(parsedManifest)) return void 0;
8027
+ const scripts = parsedManifest["scripts"];
8028
+ if (!isObject(scripts)) return void 0;
8029
+ return readScriptRecord(packageDir, scripts);
8030
+ }
8031
+ function buildWorkspaceRegistry(config) {
8032
+ return {
8033
+ ...getDefaultWorkspaceScripts(),
8034
+ ...config.workspaceScripts
8035
+ };
8036
+ }
8037
+ function buildRootRegistry(config, monorepoRoot) {
8038
+ return {
8039
+ ...getDefaultRootScripts(monorepoRoot),
8040
+ ...config.rootScripts
8041
+ };
8042
+ }
8043
+
8044
+ // src/verbosity.ts
8045
+ var COMMAND_VERBOSITIES = ["full", "quiet"];
8046
+ function formatVerbosityRejection(source, value) {
8047
+ return `${source} is \`${value}\`, which is not one of: ${COMMAND_VERBOSITIES.join(", ")}`;
8048
+ }
8049
+ function isCommandVerbosity(value) {
8050
+ const names = COMMAND_VERBOSITIES;
8051
+ return names.includes(value);
8052
+ }
8053
+
8054
+ // src/config.ts
8055
+ var CONFIG_TIERS = {
8056
+ root: {
8057
+ label: "a monorepo-root config",
8058
+ honoredKeys: ["checkCache", "devBin", "output", "rootScripts", "selfHostedRunner", "workspaceScripts"],
8059
+ elsewhere: "the package's own config"
8060
+ },
8061
+ workspace: {
8062
+ label: "a package config",
8063
+ honoredKeys: ["build"],
8064
+ elsewhere: "the monorepo-root config"
8065
+ }
8066
+ };
8067
+ var RECOGNIZED_KEYS = [...CONFIG_TIERS.root.honoredKeys, ...CONFIG_TIERS.workspace.honoredKeys];
8068
+ var RECOGNIZED_BUILD_KEYS = ["extraIgnorePatterns"];
8069
+ var RECOGNIZED_CHECK_CACHE_KEYS = ["enabled", "excludeCommands", "extraCommands"];
8070
+ var RECOGNIZED_OUTPUT_KEYS = ["commandVerbosity", "extraAgentEnvVars"];
8071
+ var RECOGNIZED_SELF_HOSTED_RUNNER_KEYS = ["exempt"];
8072
+ var RECOGNIZED_STEP_KEYS = ["run", "shouldDeclineArguments", "shouldRunInBackground"];
8073
+ var SCRIPT_FIELDS = [
8074
+ { field: "rootScripts", scope: "root" },
8075
+ { field: "workspaceScripts", scope: "workspaceRegistry" }
8076
+ ];
8077
+ var RETIRED_STEP_KEYS = /* @__PURE__ */ new Map([["declinesArgs", "shouldDeclineArguments"]]);
8078
+ function isScriptRecord(value) {
8079
+ if (!isObject(value)) return false;
8080
+ for (const v of Object.values(value)) {
8081
+ if (typeof v === "string") continue;
8082
+ if (!Array.isArray(v) || !v.every(isScriptElement)) return false;
8083
+ }
8084
+ return true;
8085
+ }
8086
+ function isScriptElement(value) {
8087
+ if (typeof value === "string") return true;
8088
+ if (!isObject(value) || typeof value["run"] !== "string") return false;
8089
+ return isOptionalBoolean(value["shouldDeclineArguments"]) && isOptionalBoolean(value["shouldRunInBackground"]);
8090
+ }
8091
+ function validateScriptField(value, fieldName, configPath) {
8092
+ if (!Object.hasOwn(value, fieldName) || value[fieldName] === void 0) {
8093
+ return void 0;
8094
+ }
8095
+ const scripts = value[fieldName];
8096
+ if (!isScriptRecord(scripts)) {
8097
+ throw new UserError(
8098
+ `Invalid nmr config at ${configPath}: \`${fieldName}\` must be a Record<string, string | element[]>, where an element is a command string or \`{ run: string, shouldDeclineArguments?: boolean, shouldRunInBackground?: boolean }\``
8099
+ );
8100
+ }
8101
+ assertValidElements(scripts, fieldName, configPath);
8102
+ return scripts;
8103
+ }
8104
+ function assertValidElements(scripts, fieldName, configPath) {
8105
+ for (const [command, script] of Object.entries(scripts)) {
8106
+ if (typeof script === "string") continue;
8107
+ for (const spec of script) {
8108
+ if (typeof spec !== "string") {
8109
+ assertNoRetiredKeys(spec, RETIRED_STEP_KEYS, configPath, `${fieldName}.${command}.`);
8110
+ assertRecognizedKeys(spec, RECOGNIZED_STEP_KEYS, configPath, `${fieldName}.${command}.`);
8111
+ }
8112
+ const element = typeof spec === "string" ? spec : spec.run;
8113
+ const token = findUnexpressibleToken(element);
8114
+ if (token === void 0) continue;
8115
+ throw new UserError(
8116
+ `Invalid nmr config at ${configPath}: \`${fieldName}.${command}\` element \`${element}\` contains \`${token}\`, which is neither a command name nor an nmr flag. Name it as a script of its own and reference that name here.`
8117
+ );
8118
+ }
8119
+ }
8120
+ }
8121
+ function isOptionalBoolean(value) {
8122
+ return value === void 0 || typeof value === "boolean";
8123
+ }
8124
+ function isStringArray(value) {
8125
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
8126
+ }
8127
+ function validateBuildField(value, configPath) {
8128
+ const build = value["build"];
8129
+ if (build === void 0) {
8130
+ return void 0;
8131
+ }
8132
+ if (!isObject(build)) {
8133
+ throw new UserError(`Invalid nmr config at ${configPath}: \`build\` must be an object`);
8134
+ }
8135
+ assertRecognizedKeys(build, RECOGNIZED_BUILD_KEYS, configPath, "build.");
8136
+ const extraIgnorePatterns = build["extraIgnorePatterns"];
8137
+ if (extraIgnorePatterns !== void 0 && !isStringArray(extraIgnorePatterns)) {
8138
+ throw new UserError(`Invalid nmr config at ${configPath}: \`build.extraIgnorePatterns\` must be a string[]`);
8139
+ }
8140
+ return extraIgnorePatterns === void 0 ? {} : { extraIgnorePatterns };
8141
+ }
8142
+ function validateCheckCacheField(value, configPath) {
8143
+ const checkCache = value["checkCache"];
8144
+ if (checkCache === void 0) {
8145
+ return void 0;
8146
+ }
8147
+ if (!isObject(checkCache)) {
8148
+ throw new UserError(`Invalid nmr config at ${configPath}: \`checkCache\` must be an object`);
8149
+ }
8150
+ assertRecognizedKeys(checkCache, RECOGNIZED_CHECK_CACHE_KEYS, configPath, "checkCache.");
8151
+ const config = {};
8152
+ const enabledValue = checkCache["enabled"];
8153
+ if (enabledValue !== void 0) {
8154
+ if (typeof enabledValue !== "boolean") {
8155
+ throw new UserError(`Invalid nmr config at ${configPath}: \`checkCache.enabled\` must be a boolean`);
8156
+ }
8157
+ config.enabled = enabledValue;
8158
+ }
8159
+ for (const field of ["excludeCommands", "extraCommands"]) {
8160
+ const commands = checkCache[field];
8161
+ if (commands === void 0) {
8162
+ continue;
8163
+ }
8164
+ if (!isStringArray(commands)) {
8165
+ throw new UserError(`Invalid nmr config at ${configPath}: \`checkCache.${field}\` must be a string[]`);
8166
+ }
8167
+ config[field] = commands;
8168
+ }
8169
+ return config;
8170
+ }
8171
+ function validateSelfHostedRunnerField(value, configPath) {
8172
+ const selfHostedRunner = value["selfHostedRunner"];
8173
+ if (selfHostedRunner === void 0) {
8174
+ return void 0;
8175
+ }
8176
+ if (!isObject(selfHostedRunner)) {
8177
+ throw new UserError(`Invalid nmr config at ${configPath}: \`selfHostedRunner\` must be an object`);
8178
+ }
8179
+ assertRecognizedKeys(selfHostedRunner, RECOGNIZED_SELF_HOSTED_RUNNER_KEYS, configPath, "selfHostedRunner.");
8180
+ const exempt = selfHostedRunner["exempt"];
8181
+ if (typeof exempt !== "string" || exempt.trim() === "") {
8182
+ throw new UserError(
8183
+ `Invalid nmr config at ${configPath}: \`selfHostedRunner.exempt\` must be a non-empty string giving the reason`
8184
+ );
8185
+ }
8186
+ return { exempt };
8187
+ }
8188
+ function assertResolvableCheckCacheCommands(config, configPath, baseDir) {
8189
+ const { checkCache } = config;
8190
+ if (checkCache === void 0 || !isMonorepoRoot(baseDir)) {
8191
+ return;
8192
+ }
8193
+ const fields = ["excludeCommands", "extraCommands"];
8194
+ const entries = fields.flatMap((field) => (checkCache[field] ?? []).map((command) => ({ command, field })));
8195
+ const hook = entries.find(({ command }) => isHookName(command));
8196
+ if (hook !== void 0) {
8197
+ throw new UserError(
8198
+ `Invalid nmr config at ${configPath}: \`checkCache.${hook.field}\` names the hook \`${hook.command}\`, which is never gated on its own: It runs as part of the chain of the command that it wraps.`
8199
+ );
8200
+ }
8201
+ const registeredNames = /* @__PURE__ */ new Set([
8202
+ ...Object.keys(buildRootRegistry(config, baseDir)),
8203
+ ...Object.keys(buildWorkspaceRegistry(config))
8204
+ ]);
8205
+ const missingEntries = entries.filter(({ command }) => !registeredNames.has(command));
8206
+ if (missingEntries.length === 0) {
8207
+ return;
8208
+ }
8209
+ const declaredNames = readDeclaredScriptNames(baseDir);
8210
+ const unresolvableEntry = missingEntries.find(({ command }) => !declaredNames.has(command));
8211
+ if (unresolvableEntry === void 0) {
8212
+ return;
8213
+ }
8214
+ const closestName = findClosestName(unresolvableEntry.command, [...registeredNames, ...declaredNames]);
8215
+ throw new UserError(
8216
+ `Invalid nmr config at ${configPath}: \`checkCache.${unresolvableEntry.field}\` does not name any command: \`${unresolvableEntry.command}\`.${closestName === void 0 ? "" : ` Did you mean \`${closestName}\`?`}`
8217
+ );
8218
+ }
8219
+ function readDeclaredScriptNames(monorepoRoot) {
8220
+ let packageDirs;
8221
+ try {
8222
+ packageDirs = getWorkspacePackageDirs(monorepoRoot);
8223
+ } catch {
8224
+ packageDirs = [];
8225
+ }
8226
+ const names = /* @__PURE__ */ new Set();
8227
+ const dirs = [monorepoRoot, ...packageDirs];
8228
+ for (const dir of dirs) {
8229
+ const scripts = readPackageScripts(dir);
8230
+ for (const name of Object.keys(scripts)) {
8231
+ names.add(name);
8232
+ }
8233
+ }
8234
+ return names;
8235
+ }
8236
+ function readPackageScripts(packageDir) {
8237
+ try {
8238
+ return readPackageJsonScripts(packageDir) ?? {};
8239
+ } catch (error) {
8240
+ if (error instanceof UserError) {
8241
+ return {};
8242
+ }
8243
+ throw error;
8244
+ }
8245
+ }
8246
+ function validateOutputField(value, configPath) {
8247
+ const output = value["output"];
8248
+ if (output === void 0) {
8249
+ return void 0;
8250
+ }
8251
+ if (!isObject(output)) {
8252
+ throw new UserError(`Invalid nmr config at ${configPath}: \`output\` must be an object`);
8253
+ }
8254
+ assertRecognizedKeys(output, RECOGNIZED_OUTPUT_KEYS, configPath, "output.");
8255
+ const config = {};
8256
+ const commandVerbosity = output["commandVerbosity"];
8257
+ if (commandVerbosity !== void 0) {
8258
+ if (typeof commandVerbosity !== "string" || !isCommandVerbosity(commandVerbosity)) {
8259
+ const renderedValue = typeof commandVerbosity === "string" ? commandVerbosity : JSON.stringify(commandVerbosity);
8260
+ throw new UserError(
8261
+ `Invalid nmr config at ${configPath}: ${formatVerbosityRejection("`output.commandVerbosity`", renderedValue)}`
8262
+ );
8263
+ }
8264
+ config.commandVerbosity = commandVerbosity;
8265
+ }
8266
+ const extraAgentEnvVars = output["extraAgentEnvVars"];
8267
+ if (extraAgentEnvVars !== void 0) {
8268
+ if (!isStringArray(extraAgentEnvVars)) {
8269
+ throw new UserError(`Invalid nmr config at ${configPath}: \`output.extraAgentEnvVars\` must be a string[]`);
8270
+ }
8271
+ config.extraAgentEnvVars = extraAgentEnvVars;
8272
+ }
8273
+ return config;
8274
+ }
8275
+ function validateStringRecordField(value, fieldName, configPath) {
8276
+ if (!Object.hasOwn(value, fieldName) || value[fieldName] === void 0) {
8277
+ return void 0;
8278
+ }
8279
+ if (!isStringRecord(value[fieldName])) {
8280
+ throw new UserError(`Invalid nmr config at ${configPath}: \`${fieldName}\` must be a Record<string, string>`);
8281
+ }
8282
+ return value[fieldName];
8283
+ }
8284
+ function validateConfig(value, configPath, baseDir) {
8285
+ if (!isObject(value)) {
8286
+ throw new UserError(`Invalid nmr config at ${configPath}: expected an object, got ${typeof value}`);
8287
+ }
8288
+ assertRecognizedKeys(value, RECOGNIZED_KEYS, configPath);
8289
+ const config = {};
8290
+ const build = validateBuildField(value, configPath);
8291
+ if (build) config.build = build;
8292
+ const checkCache = validateCheckCacheField(value, configPath);
8293
+ if (checkCache) config.checkCache = checkCache;
8294
+ const devBin = validateStringRecordField(value, "devBin", configPath);
8295
+ if (devBin) config.devBin = devBin;
8296
+ const output = validateOutputField(value, configPath);
8297
+ if (output) config.output = output;
8298
+ const workspaceScripts2 = validateScriptField(value, "workspaceScripts", configPath);
8299
+ if (workspaceScripts2) config.workspaceScripts = workspaceScripts2;
8300
+ const rootScripts = validateScriptField(value, "rootScripts", configPath);
8301
+ if (rootScripts) config.rootScripts = rootScripts;
8302
+ const selfHostedRunner = validateSelfHostedRunnerField(value, configPath);
8303
+ if (selfHostedRunner) config.selfHostedRunner = selfHostedRunner;
8304
+ assertResolvableCheckCacheCommands(config, configPath, baseDir);
8305
+ return config;
8306
+ }
8307
+ async function loadConfig(baseDir) {
8308
+ const configPath = resolveConfigPath(baseDir);
8309
+ if (!existsSync2(configPath)) {
8310
+ return {};
8311
+ }
8312
+ const importedModule = await import(pathToFileURL(configPath).href);
8313
+ const loadedConfig = isObject(importedModule) ? importedModule["default"] : void 0;
8314
+ return validateConfig(loadedConfig, configPath, baseDir);
8315
+ }
8316
+ async function loadRootConfig(monorepoRoot) {
8317
+ const config = await loadConfig(monorepoRoot);
8318
+ const configPath = resolveConfigPath(monorepoRoot);
8319
+ assertTierKeys(config, CONFIG_TIERS.root, configPath);
8320
+ assertNoSelfReferences(config, configPath, monorepoRoot);
8321
+ return config;
8322
+ }
8323
+ function assertNoRetiredKeys(value, retiredKeys, configPath, prefix) {
8324
+ for (const key of Object.keys(value)) {
8325
+ const replacement = retiredKeys.get(key);
8326
+ if (replacement === void 0) continue;
8327
+ throw new UserError(
8328
+ `Invalid nmr config at ${configPath}: \`${prefix}${key}\` was renamed to \`${prefix}${replacement}\`.`
8329
+ );
8330
+ }
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
+ }
8354
+ function assertRecognizedKeys(value, recognizedKeys, configPath, prefix = "") {
8355
+ const unrecognizedKeys = Object.keys(value).filter((key) => !recognizedKeys.includes(key));
8356
+ if (unrecognizedKeys.length === 0) {
8357
+ return;
8358
+ }
8359
+ throw new UserError(
8360
+ `Invalid nmr config at ${configPath}: unrecognized ${unrecognizedKeys.length === 1 ? "key" : "keys"} ${formatKeyList(unrecognizedKeys, prefix)}. Recognized: ${formatKeyList(recognizedKeys, prefix)}.`
8361
+ );
8362
+ }
8363
+ function assertTierKeys(config, tier, configPath) {
8364
+ const unsupportedKeys = Object.keys(config).filter((key) => !tier.honoredKeys.includes(key));
8365
+ if (unsupportedKeys.length === 0) {
8366
+ return;
8367
+ }
8368
+ throw new UserError(
8369
+ `Invalid nmr config at ${configPath}: ${tier.label} honors ${tier.honoredKeys.toSorted().join(", ")} alone, not ${unsupportedKeys.toSorted().join(", ")}. Move those keys to ${tier.elsewhere}.`
8370
+ );
8371
+ }
8372
+ function formatKeyList(keys, prefix) {
8373
+ return keys.toSorted().map((key) => `\`${prefix}${key}\``).join(", ");
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.`;
7490
8381
  }
7491
8382
 
7492
8383
  // src/tiers.ts
7493
8384
  import { readdirSync } from "node:fs";
7494
- import path from "node:path";
8385
+ import path6 from "node:path";
7495
8386
 
7496
8387
  // src/git-ignored-paths.ts
7497
8388
  import { spawnSync } from "node:child_process";
@@ -7522,7 +8413,7 @@ function findUntieredTestFiles(rootDir, options = {}) {
7522
8413
  }
7523
8414
  function hasTierInfix(filePath) {
7524
8415
  const tiers = TIER_NAMES;
7525
- return tiers.includes(path.basename(filePath).split(".").at(-3) ?? "");
8416
+ return tiers.includes(path6.basename(filePath).split(".").at(-3) ?? "");
7526
8417
  }
7527
8418
  var TEST_COLLECTION_EXCLUDE = [".git", "coverage", "dist", "node_modules"];
7528
8419
  var TIER_NAMES = ["unit", "tool", "localhost", "remote"];
@@ -7532,7 +8423,7 @@ function collectTestFiles(dir, relativeDir, isInTestDir, context) {
7532
8423
  const relativePath = relativeDir === "" ? entry.name : `${relativeDir}/${entry.name}`;
7533
8424
  if (entry.isDirectory()) {
7534
8425
  if (context.pruned.has(entry.name) || context.ignoredPaths.has(`${relativePath}/`)) continue;
7535
- collectTestFiles(path.join(dir, entry.name), relativePath, isInTestDir || entry.name === TEST_DIR, context);
8426
+ collectTestFiles(path6.join(dir, entry.name), relativePath, isInTestDir || entry.name === TEST_DIR, context);
7536
8427
  } else if (isInTestDir !== context.misplaced && TEST_FILE_PATTERN.test(entry.name) && !context.ignoredPaths.has(relativePath)) {
7537
8428
  context.foundPaths.push(relativePath);
7538
8429
  }
@@ -7780,6 +8671,32 @@ var default_default = defineRdyKit({
7780
8671
  fix: 'Remove "ws" script entries and replace any "pnpm run ws" invocations with nmr in each packages/*/package.json'
7781
8672
  }
7782
8673
  ]
8674
+ },
8675
+ {
8676
+ name: "self-hosted runner",
8677
+ checks: [
8678
+ {
8679
+ name: "GitHub reports the repo's runner settings",
8680
+ severity: "recommend",
8681
+ quiet: true,
8682
+ skip: () => describeSelfHostedRunnerSkip(),
8683
+ check: () => true,
8684
+ checks: [
8685
+ {
8686
+ name: "CI_RUNS_ON selects the self-hosted runner",
8687
+ severity: "recommend",
8688
+ skip: () => describeRecommendationSkip(),
8689
+ check: () => recommendSelfHostedRunner()
8690
+ },
8691
+ {
8692
+ name: "the self-hosted runner can run CI",
8693
+ severity: "warn",
8694
+ skip: () => describeReadinessSkip(),
8695
+ check: () => selfHostedRunnerCanRunCi()
8696
+ }
8697
+ ]
8698
+ }
8699
+ ]
7783
8700
  }
7784
8701
  ]
7785
8702
  });
@@ -7821,7 +8738,7 @@ var CATALOGUE_CHECKED_FIELDS = ["dependencies", "devDependencies", "optionalDepe
7821
8738
  var PROTOCOL_PATTERN = /^[a-z][a-z0-9+.-]*:/i;
7822
8739
  function allWorkspacePackagesCanBuild() {
7823
8740
  const packagesDir = join(process.cwd(), "packages");
7824
- if (!existsSync(packagesDir)) return true;
8741
+ if (!existsSync3(packagesDir)) return true;
7825
8742
  const entries = readdirSync2(packagesDir, { withFileTypes: true });
7826
8743
  const failingPackages = [];
7827
8744
  for (const entry of entries) {
@@ -7830,7 +8747,7 @@ function allWorkspacePackagesCanBuild() {
7830
8747
  const content = readFile(pkgPath);
7831
8748
  if (!content) continue;
7832
8749
  const hasBuildOverride = /"build"\s*:/.test(content);
7833
- const hasDefaultBuildInputs = fileExists(`packages/${entry.name}/tsconfig.json`) && existsSync(join(packagesDir, entry.name, "src"));
8750
+ const hasDefaultBuildInputs = fileExists(`packages/${entry.name}/tsconfig.json`) && existsSync3(join(packagesDir, entry.name, "src"));
7834
8751
  if (!hasBuildOverride && !hasDefaultBuildInputs) {
7835
8752
  failingPackages.push(entry.name);
7836
8753
  }
@@ -8002,7 +8919,7 @@ function everyViteConfigHasVitestConfig() {
8002
8919
  return { ok: false, detail: formatPaths(unpairedConfigs) };
8003
8920
  }
8004
8921
  function findFiles(patterns, cwd) {
8005
- return globSync(patterns, { cwd, exclude: (path2) => SCAN_EXCLUDE_DIRS.has(basename(path2)) }).map((path2) => path2.split(sep).join("/")).toSorted();
8922
+ return globSync2(patterns, { cwd, exclude: (path7) => SCAN_EXCLUDE_DIRS.has(basename(path7)) }).map((path7) => path7.split(sep).join("/")).toSorted();
8006
8923
  }
8007
8924
  function findLiteralCataloguedSpecifiers(workspace, names) {
8008
8925
  const manifestPath = posix.join(workspace.dir, "package.json");
@@ -8019,10 +8936,10 @@ function findWorkspaceConfigs(workspace, pattern) {
8019
8936
  }
8020
8937
  function formatPaths(paths) {
8021
8938
  return `${paths.length} found:
8022
- ${paths.map((path2) => ` ${path2}`).join("\n")}`;
8939
+ ${paths.map((path7) => ` ${path7}`).join("\n")}`;
8023
8940
  }
8024
8941
  function getMinVersion() {
8025
- const pickedFields = { "version": "0.43.0" };
8942
+ const pickedFields = { "version": "0.45.0" };
8026
8943
  if (typeof pickedFields["version"] !== "string") {
8027
8944
  throw new TypeError("nmr/package.json: 'version' must be a string");
8028
8945
  }
@@ -8064,8 +8981,8 @@ function noPnpmFieldInPackageJson(cwd = process.cwd()) {
8064
8981
  return { ok: false, detail: formatPaths(declaringFiles) };
8065
8982
  }
8066
8983
  function noReExportOnlyVitestConfigs(cwd = process.cwd()) {
8067
- const nonRootConfigs = findFiles([`**/${VITEST_CONFIG_PATTERN}`], cwd).filter((path2) => path2.includes("/"));
8068
- const reExports = nonRootConfigs.filter((path2) => isOwnedByReExportCheck(cwd, path2));
8984
+ const nonRootConfigs = findFiles([`**/${VITEST_CONFIG_PATTERN}`], cwd).filter((path7) => path7.includes("/"));
8985
+ const reExports = nonRootConfigs.filter((path7) => isOwnedByReExportCheck(cwd, path7));
8069
8986
  if (reExports.length === 0) return true;
8070
8987
  return { ok: false, detail: formatPaths(reExports) };
8071
8988
  }
@@ -8074,7 +8991,7 @@ function noRedundantRootScripts() {
8074
8991
  if (!pkg) return true;
8075
8992
  const scripts = pkg["scripts"];
8076
8993
  if (!isRecord(scripts)) return true;
8077
- const builtInNames = Object.keys(getDefaultRootScripts());
8994
+ const builtInNames = Object.keys(getDefaultRootScripts(process.cwd()));
8078
8995
  const redundantNames = Object.keys(scripts).filter((name) => builtInNames.includes(name));
8079
8996
  if (redundantNames.length === 0) return true;
8080
8997
  return {
@@ -8101,7 +9018,7 @@ function noUnguardedLefthookInstall(cwd = process.cwd()) {
8101
9018
  }
8102
9019
  function noWorkspaceRunScriptReferences() {
8103
9020
  const packagesDir = join(process.cwd(), "packages");
8104
- if (!existsSync(packagesDir)) return true;
9021
+ if (!existsSync3(packagesDir)) return true;
8105
9022
  const legacyPattern = /run-workspace-script|"pnpm\s+run\s+ws\b/;
8106
9023
  const entries = readdirSync2(packagesDir, { withFileTypes: true });
8107
9024
  const matches = [];
@@ -8139,7 +9056,7 @@ function readCataloguedNames(cwd) {
8139
9056
  if (content === void 0) return { ok: true, names: /* @__PURE__ */ new Set() };
8140
9057
  let workspaceManifest;
8141
9058
  try {
8142
- workspaceManifest = (0, import_yaml.parse)(content);
9059
+ workspaceManifest = (0, import_yaml2.parse)(content);
8143
9060
  } catch (error) {
8144
9061
  return { ok: false, detail: `cannot parse pnpm-workspace.yaml: ${describeError(error)}` };
8145
9062
  }
@@ -8244,14 +9161,288 @@ function vitestConfigBuildsOnSharedConfig() {
8244
9161
  function vitestRootConfigBuildsOnSharedConfig(cwd = process.cwd()) {
8245
9162
  return checkRootVitestConfig("vitest.root.config", "defineRootVitestConfig", cwd);
8246
9163
  }
9164
+ var SET_CI_RUNS_ON_COMMAND = `gh variable set CI_RUNS_ON --body '"self-hosted"'`;
9165
+ var CODE_QUALITY_WORKFLOW = "williamthorsen/.github/.github/workflows/code-quality-pnpm-workflow.yaml";
9166
+ var MIN_CODE_QUALITY_MAJOR = 8;
9167
+ var GH_TIMEOUT_MS = 15e3;
9168
+ var IMAGE_ABSENT_PROGRAM_PATTERN = /(?:^|[\s;&|(])(corepack|gh|node|npm|npx|pnpm)(?=\s|$)/gm;
9169
+ var NEVER_INSTALLED_PROGRAMS = /* @__PURE__ */ new Set(["corepack", "gh"]);
9170
+ var statusByCwd = /* @__PURE__ */ new Map();
9171
+ async function describeSelfHostedRunnerSkip(cwd = process.cwd(), readFacts) {
9172
+ const status = await getSelfHostedRunnerStatus(cwd, readFacts);
9173
+ return "skip" in status ? status.skip : false;
9174
+ }
9175
+ function findSelfHostedRunnerFailures(facts, cwd) {
9176
+ const failures = [];
9177
+ if (!facts.isPrivate) {
9178
+ failures.push(
9179
+ `${facts.nameWithOwner} is public: A self-hosted runner must never run code from a fork's pull request`
9180
+ );
9181
+ }
9182
+ if (facts.onlineSelfHostedRunners === 0) {
9183
+ failures.push(
9184
+ `${facts.nameWithOwner} does not have an online runner labelled self-hosted. When williamthorsen/hq allocates the repo slots in apps/runners/config/repos.yaml, the runners have not registered yet`
9185
+ );
9186
+ }
9187
+ const workflows = readWorkflows(cwd);
9188
+ failures.push(...findCodeQualityCallerDefects(workflows), ...findImageIncompatibleSteps(workflows));
9189
+ return failures;
9190
+ }
9191
+ async function readGitHubFacts(cwd, runGh = runGhCommand) {
9192
+ const repo = await runGh(["repo", "view", "--json", "nameWithOwner,visibility"], cwd);
9193
+ if (!repo.ok) return { skip: describeGhFailure(repo, "cannot resolve this repo on GitHub") };
9194
+ const repoView = parseJson(repo.stdout);
9195
+ if (!isRecord(repoView) || typeof repoView["nameWithOwner"] !== "string") {
9196
+ return { skip: "gh repo view returned an unexpected shape" };
9197
+ }
9198
+ const nameWithOwner = repoView["nameWithOwner"];
9199
+ const runners = await runGh(["api", `repos/${nameWithOwner}/actions/runners?per_page=100`], cwd);
9200
+ if (!runners.ok) {
9201
+ return {
9202
+ skip: describeGhFailure(runners, `cannot list the runners of ${nameWithOwner}, which needs admin access`)
9203
+ };
9204
+ }
9205
+ const repoVariable = await runGh(["api", `repos/${nameWithOwner}/actions/variables/CI_RUNS_ON`], cwd);
9206
+ if (!repoVariable.ok && !isNotFound(repoVariable)) {
9207
+ return { skip: describeGhFailure(repoVariable, `cannot read the CI_RUNS_ON variable of ${nameWithOwner}`) };
9208
+ }
9209
+ const repoValue = repoVariable.ok ? readVariableValue(parseJson(repoVariable.stdout)) : void 0;
9210
+ return {
9211
+ nameWithOwner,
9212
+ isPrivate: repoView["visibility"] !== "PUBLIC",
9213
+ onlineSelfHostedRunners: countOnlineSelfHostedRunners(parseJson(runners.stdout)),
9214
+ ciRunsOn: repoValue ?? await readOrganizationValue(nameWithOwner, cwd, runGh)
9215
+ };
9216
+ }
9217
+ function readRunnerSelection(value) {
9218
+ if (value === void 0 || value.trim() === "") return "unset";
9219
+ const parsed = parseJson(value);
9220
+ if (parsed === void 0) return "invalid";
9221
+ const labels = Array.isArray(parsed) ? parsed : [parsed];
9222
+ return labels.includes("self-hosted") ? "self-hosted" : "other";
9223
+ }
9224
+ async function recommendSelfHostedRunner(cwd = process.cwd(), readFacts) {
9225
+ const status = await getSelfHostedRunnerStatus(cwd, readFacts);
9226
+ if ("skip" in status) return true;
9227
+ const toolTests = status.toolTestFiles.length === 0 ? "The repo does not have any tool-tier tests." : `Before switching, confirm that the image provides every program that these tool-tier tests spawn: ${formatPaths(status.toolTestFiles)}`;
9228
+ return {
9229
+ ok: false,
9230
+ detail: `${status.facts.nameWithOwner} can run its CI on the self-hosted runner. ${toolTests}`,
9231
+ fix: `${SET_CI_RUNS_ON_COMMAND} --repo ${status.facts.nameWithOwner}`
9232
+ };
9233
+ }
9234
+ async function describeRecommendationSkip(cwd = process.cwd(), readFacts) {
9235
+ const status = await getSelfHostedRunnerStatus(cwd, readFacts);
9236
+ if ("skip" in status) return status.skip;
9237
+ if (status.selection !== "unset") return `CI_RUNS_ON is already set to ${status.facts.ciRunsOn ?? ""}`;
9238
+ if (status.exempt !== void 0) return `the repo is exempt: ${status.exempt}`;
9239
+ if (status.failures.length > 0) return `not ready: ${status.failures.join("; ")}`;
9240
+ return false;
9241
+ }
9242
+ async function describeReadinessSkip(cwd = process.cwd(), readFacts) {
9243
+ const status = await getSelfHostedRunnerStatus(cwd, readFacts);
9244
+ if ("skip" in status) return status.skip;
9245
+ if (status.selection === "unset") return "CI_RUNS_ON is unset";
9246
+ if (status.selection === "other") return `CI_RUNS_ON selects ${status.facts.ciRunsOn ?? ""}`;
9247
+ return false;
9248
+ }
9249
+ async function selfHostedRunnerCanRunCi(cwd = process.cwd(), readFacts) {
9250
+ const status = await getSelfHostedRunnerStatus(cwd, readFacts);
9251
+ if ("skip" in status) return true;
9252
+ const { facts } = status;
9253
+ const conditions = status.exempt === void 0 ? status.failures : [`the repo is exempt: ${status.exempt}`, ...status.failures];
9254
+ if (status.selection !== "invalid" && conditions.length === 0) return true;
9255
+ const invalidValue = `CI_RUNS_ON is ${facts.ciRunsOn ?? ""}, which is not JSON, so every workflow that reads it with fromJSON fails`;
9256
+ if (conditions.length === 0) {
9257
+ return { ok: false, detail: invalidValue, fix: `${SET_CI_RUNS_ON_COMMAND} --repo ${facts.nameWithOwner}` };
9258
+ }
9259
+ const problems = status.selection === "invalid" ? [invalidValue, ...conditions] : conditions;
9260
+ return {
9261
+ ok: false,
9262
+ detail: problems.map((problem) => ` ${problem}`).join("\n"),
9263
+ fix: `Fix each condition, or return the repo's CI to ubuntu-latest: gh variable delete CI_RUNS_ON --repo ${facts.nameWithOwner}`
9264
+ };
9265
+ }
9266
+ function countOnlineSelfHostedRunners(response) {
9267
+ if (!isRecord(response) || !Array.isArray(response["runners"])) return 0;
9268
+ return response["runners"].filter(
9269
+ (runner) => isRecord(runner) && runner["status"] === "online" && Array.isArray(runner["labels"]) && runner["labels"].some((label) => isRecord(label) && label["name"] === "self-hosted")
9270
+ ).length;
9271
+ }
9272
+ function describeGhFailure(result, context) {
9273
+ if (result.isMissing) return "gh is not installed";
9274
+ if (/gh auth login|not logged in/i.test(result.stderr)) return "gh is not authenticated";
9275
+ const firstLine = result.stderr.trim().split("\n", 1)[0] ?? "";
9276
+ return firstLine === "" ? `gh ${context}` : `gh ${context}: ${firstLine}`;
9277
+ }
9278
+ function findCodeQualityCallerDefects(workflows) {
9279
+ const defects = [];
9280
+ for (const { file, jobs } of workflows) {
9281
+ for (const [jobName, job] of jobs) {
9282
+ const uses = job["uses"];
9283
+ if (typeof uses !== "string" || !uses.startsWith(`${CODE_QUALITY_WORKFLOW}@`)) continue;
9284
+ const ref = uses.slice(CODE_QUALITY_WORKFLOW.length + 1);
9285
+ const major = /^v(\d+)(?:\.\d+){0,2}$/.exec(ref)?.[1];
9286
+ if (major === void 0) {
9287
+ defects.push(
9288
+ `${file} job ${jobName} pins the code-quality workflow at ${ref}, whose release cannot be determined`
9289
+ );
9290
+ } else if (Number(major) < MIN_CODE_QUALITY_MAJOR) {
9291
+ defects.push(
9292
+ `${file} job ${jobName} pins the code-quality workflow at ${ref}, which predates CI_RUNS_ON; pin v${MIN_CODE_QUALITY_MAJOR} or later`
9293
+ );
9294
+ }
9295
+ const inputs = job["with"];
9296
+ if (isRecord(inputs) && inputs["runs-on"] !== void 0) {
9297
+ defects.push(`${file} job ${jobName} passes runs-on, which overrides CI_RUNS_ON`);
9298
+ }
9299
+ }
9300
+ }
9301
+ return defects;
9302
+ }
9303
+ function findImageIncompatibleSteps(workflows) {
9304
+ const defects = [];
9305
+ for (const { file, jobs } of workflows) {
9306
+ for (const [jobName, job] of jobs) {
9307
+ if (!/\bvars\.CI_RUNS_ON\b/.test(JSON.stringify(job["runs-on"] ?? ""))) continue;
9308
+ const steps = job["steps"];
9309
+ if (!Array.isArray(steps)) continue;
9310
+ const installed = /* @__PURE__ */ new Set();
9311
+ for (const [index, step] of steps.entries()) {
9312
+ if (!isRecord(step)) continue;
9313
+ const stepLabel = typeof step["name"] === "string" ? step["name"] : `step ${index + 1}`;
9314
+ const run = step["run"];
9315
+ if (typeof run === "string") {
9316
+ const missing = findUninstalledPrograms(run, installed);
9317
+ if (missing.length > 0) {
9318
+ defects.push(
9319
+ `${file} job ${jobName}, ${stepLabel}: runs ${missing.join(", ")}, which the runner image lacks at that step`
9320
+ );
9321
+ }
9322
+ }
9323
+ for (const program of readInstalledPrograms(step)) installed.add(program);
9324
+ }
9325
+ }
9326
+ }
9327
+ return defects;
9328
+ }
9329
+ function findUninstalledPrograms(run, installed) {
9330
+ const programs = /* @__PURE__ */ new Set();
9331
+ for (const match of run.matchAll(IMAGE_ABSENT_PROGRAM_PATTERN)) {
9332
+ const program = match[1];
9333
+ if (program === void 0) continue;
9334
+ const provider = program === "npm" || program === "npx" ? "node" : program;
9335
+ if (NEVER_INSTALLED_PROGRAMS.has(program) || !installed.has(provider)) programs.add(program);
9336
+ }
9337
+ return [...programs].toSorted();
9338
+ }
9339
+ function getSelfHostedRunnerStatus(cwd, readFacts) {
9340
+ if (readFacts !== void 0) return gatherSelfHostedRunnerStatus(cwd, readFacts);
9341
+ let status = statusByCwd.get(cwd);
9342
+ if (status === void 0) {
9343
+ status = gatherSelfHostedRunnerStatus(cwd, (dir) => readGitHubFacts(dir));
9344
+ statusByCwd.set(cwd, status);
9345
+ }
9346
+ return status;
9347
+ }
9348
+ async function gatherSelfHostedRunnerStatus(cwd, readFacts) {
9349
+ const facts = await readFacts(cwd);
9350
+ if ("skip" in facts) return facts;
9351
+ const failures = findSelfHostedRunnerFailures(facts, cwd);
9352
+ let exempt;
9353
+ try {
9354
+ exempt = (await loadRootConfig(cwd)).selfHostedRunner?.exempt;
9355
+ } catch (error) {
9356
+ failures.push(`the nmr config cannot be loaded: ${describeError(error)}`);
9357
+ }
9358
+ return {
9359
+ facts,
9360
+ selection: readRunnerSelection(facts.ciRunsOn),
9361
+ failures,
9362
+ exempt,
9363
+ toolTestFiles: findTestFiles(cwd).filter(
9364
+ (file) => file.endsWith(".tool.test.ts") || file.endsWith(".tool.test.tsx")
9365
+ )
9366
+ };
9367
+ }
9368
+ function isNotFound(result) {
9369
+ return /HTTP 404/.test(result.stderr);
9370
+ }
9371
+ function parseJson(text) {
9372
+ try {
9373
+ return JSON.parse(text);
9374
+ } catch {
9375
+ return void 0;
9376
+ }
9377
+ }
9378
+ function readInstalledPrograms(step) {
9379
+ const uses = step["uses"];
9380
+ if (typeof uses !== "string") return [];
9381
+ const action = uses.split("@", 1)[0];
9382
+ if (action === "actions/setup-node") return ["node"];
9383
+ if (action === "pnpm/action-setup") return ["pnpm"];
9384
+ if (action !== "pnpm/setup") return [];
9385
+ const inputs = isRecord(step["with"]) ? step["with"] : {};
9386
+ const runtime = inputs["runtime"];
9387
+ const installsNode = typeof runtime === "string" ? /^node\b/.test(runtime) : String(inputs["node-version-file"]) !== "false";
9388
+ return installsNode ? ["pnpm", "node"] : ["pnpm"];
9389
+ }
9390
+ async function readOrganizationValue(nameWithOwner, cwd, runGh) {
9391
+ const result = await runGh(["api", `repos/${nameWithOwner}/actions/organization-variables`], cwd);
9392
+ if (!result.ok) return void 0;
9393
+ const response = parseJson(result.stdout);
9394
+ if (!isRecord(response) || !Array.isArray(response["variables"])) return void 0;
9395
+ const variable = response["variables"].find(
9396
+ (entry) => isRecord(entry) && entry["name"] === "CI_RUNS_ON"
9397
+ );
9398
+ return readVariableValue(variable);
9399
+ }
9400
+ function readVariableValue(variable) {
9401
+ return isRecord(variable) && typeof variable["value"] === "string" ? variable["value"] : void 0;
9402
+ }
9403
+ function readWorkflows(cwd) {
9404
+ const dir = join(cwd, ".github", "workflows");
9405
+ if (!existsSync3(dir)) return [];
9406
+ const workflows = [];
9407
+ const files = readdirSync2(dir).filter((name) => /\.ya?ml$/.test(name)).toSorted();
9408
+ for (const file of files) {
9409
+ let document;
9410
+ try {
9411
+ document = (0, import_yaml2.parse)(readFile(join(dir, file)) ?? "");
9412
+ } catch {
9413
+ continue;
9414
+ }
9415
+ if (!isRecord(document) || !isRecord(document["jobs"])) continue;
9416
+ const jobs = Object.entries(document["jobs"]).filter(
9417
+ (entry) => isRecord(entry[1])
9418
+ );
9419
+ workflows.push({ file: posix.join(".github/workflows", file), jobs });
9420
+ }
9421
+ return workflows;
9422
+ }
9423
+ function runGhCommand(args, cwd) {
9424
+ return new Promise((resolve) => {
9425
+ void execFile("gh", args, { cwd, timeout: GH_TIMEOUT_MS }, (error, stdout, stderr) => {
9426
+ if (error === null) {
9427
+ resolve({ ok: true, stdout });
9428
+ return;
9429
+ }
9430
+ resolve({ ok: false, isMissing: "code" in error && error.code === "ENOENT", stderr: stderr || error.message });
9431
+ });
9432
+ });
9433
+ }
8247
9434
  export {
8248
9435
  cataloguedDependenciesUseCatalog,
8249
9436
  default_default as default,
9437
+ describeReadinessSkip,
9438
+ describeRecommendationSkip,
9439
+ describeSelfHostedRunnerSkip,
8250
9440
  everyBinTargetIsACommittedWrapper,
8251
9441
  everyBinWrapperTargetIsCoveredByFiles,
8252
9442
  everyTestFileNamesItsTier,
8253
9443
  everyTestFileSitsUnderTestsDir,
8254
9444
  everyViteConfigHasVitestConfig,
9445
+ findSelfHostedRunnerFailures,
8255
9446
  hasSupportedEslintVersion,
8256
9447
  hasSupportedStrictLintVersion,
8257
9448
  noPnpmFieldInPackageJson,
@@ -8260,6 +9451,10 @@ export {
8260
9451
  noUnguardedLefthookInstall,
8261
9452
  noWorkspacesFieldInPackageJson,
8262
9453
  prettierConfigBuildsOnSharedConfig,
9454
+ readGitHubFacts,
9455
+ readRunnerSelection,
9456
+ recommendSelfHostedRunner,
9457
+ selfHostedRunnerCanRunCi,
8263
9458
  tazeConfigAvoidsClobberedOptions,
8264
9459
  tazeConfigBuildsOnSharedConfig,
8265
9460
  testSuiteGatesTestFileConventions,