@williamthorsen/nmr 0.55.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/.meta/changelog.json +124 -0
  2. package/.readyup/kits/default.js +667 -167
  3. package/.readyup/manifest.json +77 -46
  4. package/CHANGELOG.md +40 -0
  5. package/README.md +1 -14
  6. package/agents/guidance/rulebooks/nmr.md +1 -1
  7. package/bin/ensure-prepublish-hooks.js +1 -1
  8. package/bin/nmr-actionlint.js +1 -1
  9. package/bin/nmr-clean.js +1 -1
  10. package/bin/nmr-compile.js +1 -1
  11. package/bin/nmr-fmt.js +1 -1
  12. package/bin/nmr-report-catalog.js +1 -1
  13. package/bin/nmr-report-overrides.js +1 -1
  14. package/bin/nmr-shellcheck.js +1 -1
  15. package/bin/nmr-taze.js +1 -1
  16. package/bin/nmr-vitest.js +1 -1
  17. package/bin/nmr.js +1 -1
  18. package/dist/esm/bins.d.ts +11 -0
  19. package/dist/esm/bins.js +11 -0
  20. package/dist/esm/check-cache-entries.js +5 -5
  21. package/dist/esm/check-cache-gate.js +2 -1
  22. package/dist/esm/child-env.js +2 -1
  23. package/dist/esm/child-process.d.ts +2 -0
  24. package/dist/esm/child-process.js +3 -3
  25. package/dist/esm/cli-actionlint.js +2 -1
  26. package/dist/esm/cli-build.js +2 -1
  27. package/dist/esm/cli-clean.js +2 -1
  28. package/dist/esm/cli-ensure-prepublish-hooks.js +29 -16
  29. package/dist/esm/cli-fmt.js +2 -1
  30. package/dist/esm/cli-report-catalog.js +2 -1
  31. package/dist/esm/cli-report-overrides.js +2 -1
  32. package/dist/esm/cli-shellcheck.js +2 -1
  33. package/dist/esm/cli-taze.js +2 -1
  34. package/dist/esm/cli-vitest.js +2 -1
  35. package/dist/esm/cli.js +2 -1
  36. package/dist/esm/commands/actionlint.d.ts +3 -3
  37. package/dist/esm/commands/actionlint.js +5 -5
  38. package/dist/esm/commands/build.js +6 -6
  39. package/dist/esm/commands/clean.js +3 -1
  40. package/dist/esm/commands/compiler.js +3 -3
  41. package/dist/esm/commands/ensure-prepublish-hooks.d.ts +1 -1
  42. package/dist/esm/commands/ensure-prepublish-hooks.js +4 -5
  43. package/dist/esm/commands/fmt.d.ts +3 -3
  44. package/dist/esm/commands/fmt.js +33 -52
  45. package/dist/esm/commands/format-targets.js +1 -13
  46. package/dist/esm/commands/node-pin.js +3 -4
  47. package/dist/esm/commands/rewrite-specifiers.js +2 -2
  48. package/dist/esm/commands/run-vitest.js +5 -5
  49. package/dist/esm/commands/shellcheck.d.ts +3 -3
  50. package/dist/esm/commands/shellcheck.js +8 -11
  51. package/dist/esm/commands/taze.js +5 -4
  52. package/dist/esm/commands/tsconfig-chain.js +3 -3
  53. package/dist/esm/commands/workflow-tools.d.ts +12 -3
  54. package/dist/esm/commands/workflow-tools.js +29 -14
  55. package/dist/esm/config-validation.js +9 -9
  56. package/dist/esm/config.js +2 -3
  57. package/dist/esm/delegate-host.d.ts +1 -1
  58. package/dist/esm/delegate-host.js +5 -3
  59. package/dist/esm/entry-chain.js +2 -3
  60. package/dist/esm/fan-out.d.ts +3 -3
  61. package/dist/esm/fan-out.js +14 -6
  62. package/dist/esm/git-ignored-paths.js +3 -3
  63. package/dist/esm/help.d.ts +14 -15
  64. package/dist/esm/help.js +29 -54
  65. package/dist/esm/helpers/filter-selection.js +2 -2
  66. package/dist/esm/helpers/package-json.js +8 -9
  67. package/dist/esm/helpers/resolveXdgBaseDir.d.ts +5 -0
  68. package/dist/esm/helpers/resolveXdgBaseDir.js +12 -0
  69. package/dist/esm/helpers/type-guards.d.ts +0 -2
  70. package/dist/esm/helpers/type-guards.js +2 -4
  71. package/dist/esm/invocation-args.d.ts +0 -32
  72. package/dist/esm/invocation-args.js +23 -121
  73. package/dist/esm/invocation.d.ts +3 -1
  74. package/dist/esm/invocation.js +1 -0
  75. package/dist/esm/machine-lock.js +3 -4
  76. package/dist/esm/nmr-flags.d.ts +109 -0
  77. package/dist/esm/nmr-flags.js +120 -0
  78. package/dist/esm/output-style.d.ts +1 -1
  79. package/dist/esm/output-style.js +2 -1
  80. package/dist/esm/pass-manifest.js +2 -2
  81. package/dist/esm/presentation.d.ts +1 -1
  82. package/dist/esm/remote-host-session.d.ts +2 -0
  83. package/dist/esm/remote-host-session.js +1 -0
  84. package/dist/esm/remote-probe.js +2 -2
  85. package/dist/esm/remote-run.d.ts +3 -3
  86. package/dist/esm/remote-run.js +6 -4
  87. package/dist/esm/remote-session.js +4 -4
  88. package/dist/esm/remote-snapshot.js +8 -22
  89. package/dist/esm/resolver.js +3 -4
  90. package/dist/esm/route.d.ts +29 -0
  91. package/dist/esm/route.js +94 -0
  92. package/dist/esm/run-chain.d.ts +1 -1
  93. package/dist/esm/run-chain.js +4 -2
  94. package/dist/esm/runCli.d.ts +2 -0
  95. package/dist/esm/runCli.js +36 -78
  96. package/dist/esm/stated-outcome.js +3 -3
  97. package/dist/esm/steps.js +6 -30
  98. package/dist/esm/user-config.js +7 -14
  99. package/dist/esm/vitest-source-resolution.js +5 -6
  100. package/dist/esm/vitest-temp-isolation.d.ts +2 -0
  101. package/dist/esm/vitest-temp-isolation.js +44 -0
  102. package/dist/esm/vitest.d.ts +9 -0
  103. package/dist/esm/vitest.js +8 -5
  104. package/docs/reporting.md +3 -3
  105. package/docs/scripts.md +1 -1
  106. package/docs/utilities.md +5 -5
  107. package/docs/vitest.md +11 -6
  108. package/package.json +4 -4
@@ -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, path7) {
121
- const ctrl = callVisitor(key, node, visitor, path7);
120
+ function visit_(key, node, visitor, path9) {
121
+ const ctrl = callVisitor(key, node, visitor, path9);
122
122
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
123
- replaceNode(key, path7, ctrl);
124
- return visit_(key, ctrl, visitor, path7);
123
+ replaceNode(key, path9, ctrl);
124
+ return visit_(key, ctrl, visitor, path9);
125
125
  }
126
126
  if (typeof ctrl !== "symbol") {
127
127
  if (identity.isCollection(node)) {
128
- path7 = Object.freeze(path7.concat(node));
128
+ path9 = Object.freeze(path9.concat(node));
129
129
  for (let i = 0; i < node.items.length; ++i) {
130
- const ci = visit_(i, node.items[i], visitor, path7);
130
+ const ci = visit_(i, node.items[i], visitor, path9);
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
- path7 = Object.freeze(path7.concat(node));
142
- const ck = visit_("key", node.key, visitor, path7);
141
+ path9 = Object.freeze(path9.concat(node));
142
+ const ck = visit_("key", node.key, visitor, path9);
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, path7);
147
+ const cv = visit_("value", node.value, visitor, path9);
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, path7) {
169
- const ctrl = await callVisitor(key, node, visitor, path7);
168
+ async function visitAsync_(key, node, visitor, path9) {
169
+ const ctrl = await callVisitor(key, node, visitor, path9);
170
170
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
171
- replaceNode(key, path7, ctrl);
172
- return visitAsync_(key, ctrl, visitor, path7);
171
+ replaceNode(key, path9, ctrl);
172
+ return visitAsync_(key, ctrl, visitor, path9);
173
173
  }
174
174
  if (typeof ctrl !== "symbol") {
175
175
  if (identity.isCollection(node)) {
176
- path7 = Object.freeze(path7.concat(node));
176
+ path9 = Object.freeze(path9.concat(node));
177
177
  for (let i = 0; i < node.items.length; ++i) {
178
- const ci = await visitAsync_(i, node.items[i], visitor, path7);
178
+ const ci = await visitAsync_(i, node.items[i], visitor, path9);
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
- path7 = Object.freeze(path7.concat(node));
190
- const ck = await visitAsync_("key", node.key, visitor, path7);
189
+ path9 = Object.freeze(path9.concat(node));
190
+ const ck = await visitAsync_("key", node.key, visitor, path9);
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, path7);
195
+ const cv = await visitAsync_("value", node.value, visitor, path9);
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, path7) {
222
+ function callVisitor(key, node, visitor, path9) {
223
223
  if (typeof visitor === "function")
224
- return visitor(key, node, path7);
224
+ return visitor(key, node, path9);
225
225
  if (identity.isMap(node))
226
- return visitor.Map?.(key, node, path7);
226
+ return visitor.Map?.(key, node, path9);
227
227
  if (identity.isSeq(node))
228
- return visitor.Seq?.(key, node, path7);
228
+ return visitor.Seq?.(key, node, path9);
229
229
  if (identity.isPair(node))
230
- return visitor.Pair?.(key, node, path7);
230
+ return visitor.Pair?.(key, node, path9);
231
231
  if (identity.isScalar(node))
232
- return visitor.Scalar?.(key, node, path7);
232
+ return visitor.Scalar?.(key, node, path9);
233
233
  if (identity.isAlias(node))
234
- return visitor.Alias?.(key, node, path7);
234
+ return visitor.Alias?.(key, node, path9);
235
235
  return void 0;
236
236
  }
237
- function replaceNode(key, path7, node) {
238
- const parent = path7[path7.length - 1];
237
+ function replaceNode(key, path9, node) {
238
+ const parent = path9[path9.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, path7, value) {
850
+ function collectionFromPath(schema, path9, value) {
851
851
  let v = value;
852
- for (let i = path7.length - 1; i >= 0; --i) {
853
- const k = path7[i];
852
+ for (let i = path9.length - 1; i >= 0; --i) {
853
+ const k = path9[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 = (path7) => path7 == null || typeof path7 === "object" && !!path7[Symbol.iterator]().next().done;
872
+ var isEmptyPath = (path9) => path9 == null || typeof path9 === "object" && !!path9[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(path7, value) {
903
- if (isEmptyPath(path7))
902
+ addIn(path9, value) {
903
+ if (isEmptyPath(path9))
904
904
  this.add(value);
905
905
  else {
906
- const [key, ...rest] = path7;
906
+ const [key, ...rest] = path9;
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(path7) {
921
- const [key, ...rest] = path7;
920
+ deleteIn(path9) {
921
+ const [key, ...rest] = path9;
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(path7, keepScalar) {
936
- const [key, ...rest] = path7;
935
+ getIn(path9, keepScalar) {
936
+ const [key, ...rest] = path9;
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(path7) {
955
- const [key, ...rest] = path7;
954
+ hasIn(path9) {
955
+ const [key, ...rest] = path9;
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(path7, value) {
966
- const [key, ...rest] = path7;
965
+ setIn(path9, value) {
966
+ const [key, ...rest] = path9;
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(path7, value) {
3481
+ addIn(path9, value) {
3482
3482
  if (assertCollection(this.contents))
3483
- this.contents.addIn(path7, value);
3483
+ this.contents.addIn(path9, 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(path7) {
3559
- if (Collection.isEmptyPath(path7)) {
3558
+ deleteIn(path9) {
3559
+ if (Collection.isEmptyPath(path9)) {
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(path7) : false;
3565
+ return assertCollection(this.contents) ? this.contents.deleteIn(path9) : 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(path7, keepScalar) {
3581
- if (Collection.isEmptyPath(path7))
3580
+ getIn(path9, keepScalar) {
3581
+ if (Collection.isEmptyPath(path9))
3582
3582
  return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents;
3583
- return identity.isCollection(this.contents) ? this.contents.getIn(path7, keepScalar) : void 0;
3583
+ return identity.isCollection(this.contents) ? this.contents.getIn(path9, 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(path7) {
3595
- if (Collection.isEmptyPath(path7))
3594
+ hasIn(path9) {
3595
+ if (Collection.isEmptyPath(path9))
3596
3596
  return this.contents !== void 0;
3597
- return identity.isCollection(this.contents) ? this.contents.hasIn(path7) : false;
3597
+ return identity.isCollection(this.contents) ? this.contents.hasIn(path9) : 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(path7, value) {
3615
- if (Collection.isEmptyPath(path7)) {
3614
+ setIn(path9, value) {
3615
+ if (Collection.isEmptyPath(path9)) {
3616
3616
  this.contents = value;
3617
3617
  } else if (this.contents == null) {
3618
- this.contents = Collection.collectionFromPath(this.schema, Array.from(path7), value);
3618
+ this.contents = Collection.collectionFromPath(this.schema, Array.from(path9), value);
3619
3619
  } else if (assertCollection(this.contents)) {
3620
- this.contents.setIn(path7, value);
3620
+ this.contents.setIn(path9, 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, path7) => {
5581
+ visit.itemAtPath = (cst, path9) => {
5582
5582
  let item = cst;
5583
- for (const [field, index] of path7) {
5583
+ for (const [field, index] of path9) {
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, path7) => {
5593
- const parent = visit.itemAtPath(cst, path7.slice(0, -1));
5594
- const field = path7[path7.length - 1][0];
5592
+ visit.parentCollection = (cst, path9) => {
5593
+ const parent = visit.itemAtPath(cst, path9.slice(0, -1));
5594
+ const field = path9[path9.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(path7, item, visitor) {
5601
- let ctrl = visitor(item, path7);
5600
+ function _visit(path9, item, visitor) {
5601
+ let ctrl = visitor(item, path9);
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(path7.concat([[field, i]])), token.items[i], visitor);
5608
+ const ci = _visit(Object.freeze(path9.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, path7);
5619
+ ctrl = ctrl(item, path9);
5620
5620
  }
5621
5621
  }
5622
- return typeof ctrl === "function" ? ctrl(item, path7) : ctrl;
5622
+ return typeof ctrl === "function" ? ctrl(item, path9) : ctrl;
5623
5623
  }
5624
5624
  exports.visit = visit;
5625
5625
  }
@@ -7416,11 +7416,11 @@ function discoverWorkspacesOrFailure(filter) {
7416
7416
  }
7417
7417
  }
7418
7418
  function findFiles(patterns, cwd) {
7419
- return globSync(patterns, { cwd, exclude: (path7) => SCAN_EXCLUDE_DIRS.has(basename(path7)) }).map((path7) => path7.split(sep).join("/")).toSorted();
7419
+ return globSync(patterns, { cwd, exclude: (path9) => SCAN_EXCLUDE_DIRS.has(basename(path9)) }).map((path9) => path9.split(sep).join("/")).toSorted();
7420
7420
  }
7421
7421
  function formatPaths(paths) {
7422
7422
  return `${paths.length} found:
7423
- ${paths.map((path7) => ` ${path7}`).join("\n")}`;
7423
+ ${paths.map((path9) => ` ${path9}`).join("\n")}`;
7424
7424
  }
7425
7425
  function importsSharedExport(content, exportName, moduleSpecifier) {
7426
7426
  if (content === void 0) return false;
@@ -7518,7 +7518,7 @@ function readWrapperTarget(cwd, workspace, entry) {
7518
7518
  // .readyup/lib/root-scripts.ts
7519
7519
  import { existsSync as existsSync2, readdirSync } from "node:fs";
7520
7520
  import { join as join2 } from "node:path";
7521
- import { fileExists, isRecord as isRecord2, readFile as readFile2, readPackageJson } from "readyup/check-utils";
7521
+ import { fileExists, isRecord as isRecord3, readFile as readFile2, readPackageJson } from "readyup/check-utils";
7522
7522
 
7523
7523
  // src/shell-syntax.ts
7524
7524
  var ENV_ASSIGNMENT = /^\w+=/;
@@ -7725,12 +7725,17 @@ function buildRootScripts(workspaceGlobs) {
7725
7725
  };
7726
7726
  }
7727
7727
 
7728
- // ../nmr-core/dist/esm/workspace.js
7728
+ // ../nmr-core/dist/esm/workspace/workspace.js
7729
7729
  import { existsSync, readFileSync } from "node:fs";
7730
7730
  import path2 from "node:path";
7731
7731
  var import_yaml = __toESM(require_dist(), 1);
7732
7732
 
7733
- // ../nmr-core/dist/esm/helpers/workspace-patterns.js
7733
+ // ../nmr-core/dist/esm/portable/isRecord.js
7734
+ function isRecord2(value) {
7735
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7736
+ }
7737
+
7738
+ // ../nmr-core/dist/esm/workspace/workspace-patterns.js
7734
7739
  import { globSync as globSync2 } from "node:fs";
7735
7740
  import path from "node:path";
7736
7741
  var MANIFEST_FILENAME = "package.json";
@@ -7768,7 +7773,7 @@ function buildManifestPattern(pattern) {
7768
7773
  return `${pattern.replace(/\/?$/, "")}/${MANIFEST_FILENAME}`;
7769
7774
  }
7770
7775
 
7771
- // ../nmr-core/dist/esm/workspace.js
7776
+ // ../nmr-core/dist/esm/workspace/workspace.js
7772
7777
  var WORKSPACE_MANIFEST_FILENAME = "pnpm-workspace.yaml";
7773
7778
  function isMonorepoRoot(dir) {
7774
7779
  return existsSync(path2.join(dir, WORKSPACE_MANIFEST_FILENAME));
@@ -7804,7 +7809,7 @@ function diagnoseEmptyCause(monorepoRoot, patterns) {
7804
7809
  return "no-package";
7805
7810
  }
7806
7811
  function readDeclaredPackages(parsedManifest) {
7807
- if (!isObject(parsedManifest))
7812
+ if (!isRecord2(parsedManifest))
7808
7813
  return { kind: "absent" };
7809
7814
  const packages = parsedManifest["packages"];
7810
7815
  if (packages === void 0 || packages === null)
@@ -7815,9 +7820,6 @@ function readDeclaredPackages(parsedManifest) {
7815
7820
  return { kind: "unreadable" };
7816
7821
  return packages.length === 0 ? { kind: "absent" } : { kind: "patterns", patterns: packages };
7817
7822
  }
7818
- function isObject(value) {
7819
- return typeof value === "object" && value !== null && !Array.isArray(value);
7820
- }
7821
7823
  function readWorkspaceManifest(monorepoRoot) {
7822
7824
  const workspaceManifestPath = path2.join(monorepoRoot, WORKSPACE_MANIFEST_FILENAME);
7823
7825
  if (!existsSync(workspaceManifestPath)) {
@@ -7830,12 +7832,12 @@ function readWorkspaceManifest(monorepoRoot) {
7830
7832
  }
7831
7833
  }
7832
7834
 
7833
- // ../nmr-core/dist/esm/hasErrnoCode.js
7835
+ // ../nmr-core/dist/esm/errors/hasErrnoCode.js
7834
7836
  function hasErrnoCode(error, code) {
7835
7837
  return isError(error) && "code" in error && error.code === code;
7836
7838
  }
7837
7839
 
7838
- // ../nmr-core/dist/esm/UserError.js
7840
+ // ../nmr-core/dist/esm/errors/UserError.js
7839
7841
  var UserError = class extends Error {
7840
7842
  constructor(message, options) {
7841
7843
  super(message, options);
@@ -7843,6 +7845,32 @@ var UserError = class extends Error {
7843
7845
  }
7844
7846
  };
7845
7847
 
7848
+ // ../nmr-core/dist/esm/git/gitOutputLimit.js
7849
+ var GIT_OUTPUT_LIMIT_BYTES = 64 * 1024 * 1024;
7850
+
7851
+ // ../nmr-core/dist/esm/git/runGit.js
7852
+ import { spawnSync } from "node:child_process";
7853
+ function runGit(args, cwd, input, env) {
7854
+ const result = runGitForBytes(args, cwd, input === void 0 ? void 0 : Buffer.from(input, "utf8"), env);
7855
+ return result.ok ? { ok: true, stdout: result.stdout.toString("utf8") } : result;
7856
+ }
7857
+ function runGitForBytes(args, cwd, input, env) {
7858
+ const result = spawnSync("git", args, {
7859
+ cwd,
7860
+ env: { ...env ?? process.env, GIT_OPTIONAL_LOCKS: "0" },
7861
+ input,
7862
+ maxBuffer: GIT_OUTPUT_LIMIT_BYTES
7863
+ });
7864
+ if (result.error) {
7865
+ return { ok: false, error: result.error.message };
7866
+ }
7867
+ if (result.status !== 0) {
7868
+ const stderr = result.stderr.toString("utf8").trim();
7869
+ return { ok: false, error: stderr || `\`git ${args.join(" ")}\` failed with exit code ${result.status}.` };
7870
+ }
7871
+ return { ok: true, stdout: result.stdout };
7872
+ }
7873
+
7846
7874
  // src/workspace.ts
7847
7875
  var WORKSPACE_MANIFEST = "pnpm-workspace.yaml";
7848
7876
  function getWorkspacePackageDirs(monorepoRoot) {
@@ -7944,7 +7972,7 @@ function noRedundantRootScripts() {
7944
7972
  const pkg = readPackageJson();
7945
7973
  if (!pkg) return true;
7946
7974
  const scripts = pkg["scripts"];
7947
- if (!isRecord2(scripts)) return true;
7975
+ if (!isRecord3(scripts)) return true;
7948
7976
  const builtInNames = Object.keys(getDefaultRootScripts(process.cwd()));
7949
7977
  const redundantNames = Object.keys(scripts).filter((name) => builtInNames.includes(name));
7950
7978
  if (redundantNames.length === 0) return true;
@@ -7981,13 +8009,13 @@ function readRootScripts(cwd) {
7981
8009
  } catch {
7982
8010
  return {};
7983
8011
  }
7984
- if (!isRecord2(parsedManifest)) return {};
8012
+ if (!isRecord3(parsedManifest)) return {};
7985
8013
  const scripts = parsedManifest["scripts"];
7986
- return isRecord2(scripts) ? scripts : {};
8014
+ return isRecord3(scripts) ? scripts : {};
7987
8015
  }
7988
8016
 
7989
8017
  // .readyup/lib/self-hosted-runner.ts
7990
- import { isRecord as isRecord4 } from "readyup/check-utils";
8018
+ import { isRecord as isRecord5 } from "readyup/check-utils";
7991
8019
 
7992
8020
  // src/config.ts
7993
8021
  import { existsSync as existsSync3 } from "node:fs";
@@ -8044,9 +8072,10 @@ function isHookName(key) {
8044
8072
  }
8045
8073
 
8046
8074
  // src/resolver.ts
8047
- import { readFileSync as readFileSync2 } from "node:fs";
8075
+ import { readFileSync as readFileSync3 } from "node:fs";
8048
8076
 
8049
8077
  // src/helpers/package-json.ts
8078
+ import { readFileSync as readFileSync2 } from "node:fs";
8050
8079
  import path5 from "node:path";
8051
8080
 
8052
8081
  // src/helpers/config-path.ts
@@ -8058,15 +8087,52 @@ function resolveConfigPath(baseDir) {
8058
8087
  return path4.join(baseDir, CONFIG_DIR, CONFIG_FILENAME);
8059
8088
  }
8060
8089
 
8061
- // src/helpers/type-guards.ts
8062
- function isObject2(value) {
8063
- return typeof value === "object" && value !== null && !Array.isArray(value);
8064
- }
8065
- function isStringRecord(value) {
8066
- return isObject2(value) && Object.values(value).every((entry) => typeof entry === "string");
8090
+ // src/helpers/readStringValues.ts
8091
+ function readStringValues(record) {
8092
+ const result = {};
8093
+ for (const [key, value] of Object.entries(record)) {
8094
+ if (typeof value === "string") result[key] = value;
8095
+ }
8096
+ return result;
8067
8097
  }
8068
8098
 
8069
8099
  // src/helpers/package-json.ts
8100
+ var DEPENDENCY_FIELDS = [
8101
+ "dependencies",
8102
+ "devDependencies",
8103
+ "optionalDependencies",
8104
+ "peerDependencies"
8105
+ ];
8106
+ function readPackageJson2(dir) {
8107
+ const file = resolvePackageJsonPath(dir);
8108
+ const parsedManifest = parsePackageJson(readFileSync2(file, "utf8"), file);
8109
+ if (!isRecord2(parsedManifest)) {
8110
+ throw new UserError(`Invalid package.json in ${dir}: expected an object`);
8111
+ }
8112
+ const packageJson = {};
8113
+ if (typeof parsedManifest["name"] === "string") packageJson.name = parsedManifest["name"];
8114
+ if (parsedManifest["private"] === true) packageJson.private = true;
8115
+ if (typeof parsedManifest["version"] === "string") packageJson.version = parsedManifest["version"];
8116
+ if (typeof parsedManifest["packageManager"] === "string") {
8117
+ packageJson.packageManager = parsedManifest["packageManager"];
8118
+ }
8119
+ if (isRecord2(parsedManifest["scripts"])) {
8120
+ packageJson.scripts = readScriptRecord(dir, parsedManifest["scripts"]);
8121
+ }
8122
+ if (isRecord2(parsedManifest["pnpm"])) {
8123
+ const pnpm = parsedManifest["pnpm"];
8124
+ if (isRecord2(pnpm["overrides"])) {
8125
+ packageJson.pnpm = { overrides: pnpm["overrides"] };
8126
+ }
8127
+ }
8128
+ for (const field of DEPENDENCY_FIELDS) {
8129
+ const declaredDependencies = parsedManifest[field];
8130
+ if (isRecord2(declaredDependencies)) {
8131
+ packageJson[field] = readStringValues(declaredDependencies);
8132
+ }
8133
+ }
8134
+ return packageJson;
8135
+ }
8070
8136
  function parsePackageJson(rawText, file) {
8071
8137
  try {
8072
8138
  return JSON.parse(rawText);
@@ -8096,6 +8162,370 @@ function formatMalformedScript(dir, key, value) {
8096
8162
  return `${rejection} A step list belongs in \`${CONFIG_RELATIVE_PATH}\` under \`${field}\`.`;
8097
8163
  }
8098
8164
 
8165
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.0/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/parseArgs.js
8166
+ import path6 from "node:path";
8167
+ import process2 from "node:process";
8168
+
8169
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.0/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/UsageError.js
8170
+ var UsageError = class extends Error {
8171
+ constructor(message, options) {
8172
+ super(message, options);
8173
+ this.name = "UsageError";
8174
+ }
8175
+ };
8176
+
8177
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.0/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/ParseError.js
8178
+ var ParseError = class extends UsageError {
8179
+ kind;
8180
+ token;
8181
+ constructor(kind, token, message, options) {
8182
+ super(message, options);
8183
+ this.name = "ParseError";
8184
+ this.kind = kind;
8185
+ this.token = token;
8186
+ }
8187
+ };
8188
+
8189
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.0/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/resolveLongName.js
8190
+ function resolveLongName(key, definition) {
8191
+ return definition.long ?? key.replaceAll(/[A-Z]/g, (capital) => `-${capital.toLowerCase()}`);
8192
+ }
8193
+
8194
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.0/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/tokenizeArgs.js
8195
+ import { parseArgs as nodeParseArgs } from "node:util";
8196
+ function tokenizeArgs(argv, schema, extraBooleans = []) {
8197
+ const nodeOptions = {};
8198
+ const flagsByName = /* @__PURE__ */ new Map();
8199
+ for (const [key, definition] of Object.entries(schema)) {
8200
+ const name = resolveLongName(key, definition);
8201
+ const type = definition.type === "boolean" ? "boolean" : "string";
8202
+ nodeOptions[name] = definition.short === void 0 ? { type } : { type, short: definition.short };
8203
+ flagsByName.set(name, { key, definition });
8204
+ }
8205
+ for (const { long, short } of extraBooleans) {
8206
+ nodeOptions[long] = { type: "boolean", short };
8207
+ }
8208
+ const { tokens } = nodeParseArgs({
8209
+ args: [...argv],
8210
+ options: nodeOptions,
8211
+ strict: false,
8212
+ allowPositionals: true,
8213
+ tokens: true
8214
+ });
8215
+ return { tokens, flagsByName };
8216
+ }
8217
+ function findFlagEntry(flagsByName, token) {
8218
+ const entry = flagsByName.get(token.name);
8219
+ if (entry === void 0 || token.rawName.startsWith("--"))
8220
+ return entry;
8221
+ return entry.definition.short === token.rawName.slice(1) ? entry : void 0;
8222
+ }
8223
+
8224
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.0/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/validateSpec.js
8225
+ var CAMEL_CASE_KEY = /^[a-z][a-z0-9]*(?:[A-Z][a-z0-9]+)*$/;
8226
+ function validateSpec(spec) {
8227
+ validateFlags(spec.flags ?? {});
8228
+ validateOperands(spec.operands ?? []);
8229
+ }
8230
+ function validateFlags(flags) {
8231
+ const longNames = /* @__PURE__ */ new Set(["help"]);
8232
+ const shortNames = /* @__PURE__ */ new Set(["h"]);
8233
+ for (const [key, definition] of Object.entries(flags)) {
8234
+ if (definition.long === void 0 && !CAMEL_CASE_KEY.test(key)) {
8235
+ throw new Error(`Flag key '${key}' must be camelCase or declare 'long'.`);
8236
+ }
8237
+ if (definition.long !== void 0 && (definition.long === "" || definition.long.startsWith("-"))) {
8238
+ throw new Error(`Flag '${key}' declares a long name that is empty or starts with a dash: '${definition.long}'.`);
8239
+ }
8240
+ const longName = resolveLongName(key, definition);
8241
+ if (longNames.has(longName)) {
8242
+ throw new Error(`Flag '${key}' reuses the long name --${longName}, which is reserved or already declared.`);
8243
+ }
8244
+ longNames.add(longName);
8245
+ if (definition.short !== void 0) {
8246
+ if (definition.short.length !== 1 || definition.short === "-") {
8247
+ throw new Error(`Flag '${key}' must declare 'short' as one character other than a dash.`);
8248
+ }
8249
+ if (shortNames.has(definition.short)) {
8250
+ throw new Error(`Flag '${key}' reuses the short name -${definition.short}, which is reserved or already declared.`);
8251
+ }
8252
+ shortNames.add(definition.short);
8253
+ }
8254
+ validateFlagFields(key, definition);
8255
+ }
8256
+ }
8257
+ function validateFlagFields(key, definition) {
8258
+ if (definition.type === "boolean") {
8259
+ if (definition.valueHint !== void 0)
8260
+ throw new Error(`Boolean flag '${key}' cannot declare 'valueHint'.`);
8261
+ if (definition.parse !== void 0)
8262
+ throw new Error(`Boolean flag '${key}' cannot declare 'parse'.`);
8263
+ if ("default" in definition && typeof definition.default !== "boolean") {
8264
+ throw new Error(`Boolean flag '${key}' must declare a boolean 'default'.`);
8265
+ }
8266
+ if (definition.multiple === true)
8267
+ throw new Error(`Boolean flag '${key}' cannot declare 'multiple'.`);
8268
+ }
8269
+ if (definition.multiple === true && "default" in definition) {
8270
+ throw new Error(`Flag '${key}' declares 'multiple', which does not take a 'default'.`);
8271
+ }
8272
+ const { choices } = definition;
8273
+ if (choices === void 0)
8274
+ return;
8275
+ if (definition.type !== "string")
8276
+ throw new Error(`Flag '${key}' declares 'choices', which only a string flag takes.`);
8277
+ if (choices.length === 0)
8278
+ throw new Error(`Flag '${key}' declares empty 'choices'.`);
8279
+ if ("default" in definition && (typeof definition.default !== "string" || !choices.includes(definition.default))) {
8280
+ throw new Error(`Flag '${key}' declares a 'default' that is not one of its 'choices'.`);
8281
+ }
8282
+ }
8283
+ function validateOperands(operands) {
8284
+ const names = /* @__PURE__ */ new Set();
8285
+ let hasOptional = false;
8286
+ for (const [index, operand] of operands.entries()) {
8287
+ if (operand.name === "")
8288
+ throw new Error(`Operand ${index + 1} has an empty name.`);
8289
+ if (names.has(operand.name))
8290
+ throw new Error(`Operand name '${operand.name}' is declared twice.`);
8291
+ names.add(operand.name);
8292
+ if (operand.optional === true) {
8293
+ hasOptional = true;
8294
+ } else if (hasOptional) {
8295
+ throw new Error(`Required operand '${operand.name}' follows an optional one.`);
8296
+ }
8297
+ if (operand.variadic === true && index !== operands.length - 1) {
8298
+ throw new Error(`Variadic operand '${operand.name}' must be the last operand.`);
8299
+ }
8300
+ if (operand.choices?.length === 0)
8301
+ throw new Error(`Operand '${operand.name}' declares empty 'choices'.`);
8302
+ }
8303
+ }
8304
+
8305
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.0/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/parseArgs.js
8306
+ function parseArgs(argv, spec, options = {}) {
8307
+ validateSpec(spec);
8308
+ return parseValidatedArgs(argv, spec, options);
8309
+ }
8310
+ function parseValidatedArgs(argv, spec, options = {}) {
8311
+ const schema = spec.flags ?? {};
8312
+ const operandDefinitions = spec.operands ?? [];
8313
+ const baseDir = options.baseDir ?? process2.cwd();
8314
+ const stopAtPositional = options.stopAtPositional === true || options.stopAtUnclaimedFlag === true;
8315
+ const { tokens, flagsByName } = tokenizeArgs(argv, schema);
8316
+ const flags = {};
8317
+ for (const [key, definition] of Object.entries(schema)) {
8318
+ flags[key] = "default" in definition ? definition.default : initialValue(definition);
8319
+ }
8320
+ const positionals = [];
8321
+ let rest = [];
8322
+ let previousIndex = -1;
8323
+ let precedingInArgument = 0;
8324
+ for (const token of tokens) {
8325
+ if (token.kind === "option") {
8326
+ precedingInArgument = token.index === previousIndex ? precedingInArgument + 1 : 0;
8327
+ previousIndex = token.index;
8328
+ }
8329
+ if (stopAtPositional && token.kind !== "option") {
8330
+ rest = argv.slice(token.index);
8331
+ break;
8332
+ }
8333
+ if (token.kind === "option-terminator")
8334
+ continue;
8335
+ if (token.kind === "positional") {
8336
+ acceptPositional(token.value, positionals, operandDefinitions);
8337
+ continue;
8338
+ }
8339
+ const entry = findFlagEntry(flagsByName, token);
8340
+ if (entry === void 0) {
8341
+ if (options.stopAtUnclaimedFlag === true) {
8342
+ rest = splitAtOption(argv, token.index, precedingInArgument);
8343
+ break;
8344
+ }
8345
+ throw new ParseError("unknown-flag", token.rawName, `Unknown option: ${token.rawName}`);
8346
+ }
8347
+ const value = readFlagValue(token, entry.key, entry.definition, baseDir);
8348
+ const collected = flags[entry.key];
8349
+ if (entry.definition.multiple === true && Array.isArray(collected)) {
8350
+ collected.push(value);
8351
+ } else {
8352
+ flags[entry.key] = value;
8353
+ }
8354
+ }
8355
+ const operands = assignOperands(positionals, operandDefinitions);
8356
+ return { flags, operands, rest };
8357
+ }
8358
+ function acceptPositional(value, positionals, definitions) {
8359
+ const last = definitions.at(-1);
8360
+ const overflow = last?.variadic === true ? last : void 0;
8361
+ const definition = positionals.length < definitions.length ? definitions[positionals.length] : overflow;
8362
+ if (definition === void 0) {
8363
+ throw new ParseError("unexpected-positional", value, `Unexpected positional argument: ${value}`);
8364
+ }
8365
+ checkChoice(value, definition.choices, `<${definition.name}>`);
8366
+ positionals.push(value);
8367
+ }
8368
+ function assignOperands(positionals, definitions) {
8369
+ const operands = {};
8370
+ for (const [index, definition] of definitions.entries()) {
8371
+ const value = definition.variadic === true ? positionals.slice(index) : positionals[index];
8372
+ const isAbsent = value === void 0 || Array.isArray(value) && value.length === 0;
8373
+ if (isAbsent && definition.optional !== true) {
8374
+ const label = `<${definition.name}>`;
8375
+ throw new ParseError("missing-operand", label, `Missing argument: ${label}`);
8376
+ }
8377
+ operands[definition.name] = value;
8378
+ }
8379
+ return operands;
8380
+ }
8381
+ function initialValue(definition) {
8382
+ if (definition.multiple === true)
8383
+ return [];
8384
+ return definition.type === "boolean" ? false : void 0;
8385
+ }
8386
+ function checkChoice(value, choices, label) {
8387
+ if (choices === void 0 || choices.includes(value))
8388
+ return;
8389
+ throw new ParseError("invalid-choice", value, `Invalid value for ${label}: ${value}. Expected one of: ${choices.join(", ")}`);
8390
+ }
8391
+ function readFlagValue(token, key, definition, baseDir) {
8392
+ if (definition.type === "boolean") {
8393
+ if (token.value !== void 0) {
8394
+ throw new ParseError("unexpected-value", token.rawName, `Option does not accept a value: ${token.rawName}`);
8395
+ }
8396
+ return true;
8397
+ }
8398
+ const { value } = token;
8399
+ if (value === void 0 || value === "" || token.inlineValue !== true && value.startsWith("-") && value !== "-") {
8400
+ throw new ParseError("missing-value", token.rawName, `Missing value for option: ${token.rawName}`);
8401
+ }
8402
+ const label = `--${resolveLongName(key, definition)}`;
8403
+ checkChoice(value, definition.choices, label);
8404
+ const raw = definition.type === "path" ? path6.resolve(baseDir, value) : value;
8405
+ if (definition.parse === void 0)
8406
+ return raw;
8407
+ try {
8408
+ return definition.parse(raw);
8409
+ } catch (error) {
8410
+ const reason = describeError(error);
8411
+ throw new ParseError("invalid-value", value, `Invalid value for ${label}: ${value}. ${reason}`, { cause: error });
8412
+ }
8413
+ }
8414
+ function splitAtOption(argv, index, precedingInArgument) {
8415
+ const argument = argv[index];
8416
+ if (argument === void 0 || precedingInArgument === 0)
8417
+ return argv.slice(index);
8418
+ return [`-${argument.slice(1 + precedingInArgument)}`, ...argv.slice(index + 1)];
8419
+ }
8420
+
8421
+ // src/nmr-flags.ts
8422
+ var NMR_FLAGS = {
8423
+ delegate: {
8424
+ type: "boolean",
8425
+ description: "Run over SSH on the first free host that NMR_DELEGATE or the user config names"
8426
+ },
8427
+ delegateTo: {
8428
+ type: "string",
8429
+ description: "Run over SSH on the first free of <hosts>, comma-separated",
8430
+ valueHint: "hosts"
8431
+ },
8432
+ filter: {
8433
+ type: "string",
8434
+ description: "Run command in packages whose manifest name matches",
8435
+ short: "F",
8436
+ valueHint: "pattern"
8437
+ },
8438
+ json: { type: "boolean", description: "Report one JSON object per command, withholding command output" },
8439
+ log: { type: "boolean", description: "Print the recorded run instead of running (up to 256 KiB)" },
8440
+ noCache: { type: "boolean", description: "Run even if this tree already passed; record the result" },
8441
+ noDelegate: { type: "boolean", description: "Run here, whatever NMR_DELEGATE or the user config says" },
8442
+ noLock: { type: "boolean", description: "Run without waiting for another heavy command on this machine" },
8443
+ outputStyle: {
8444
+ type: "string",
8445
+ description: "Output style: auto (default), plain, or rich",
8446
+ valueHint: "auto|plain|rich"
8447
+ },
8448
+ quiet: { type: "boolean", description: "Suppress command output, keeping nmr's verdicts", short: "q" },
8449
+ recursive: { type: "boolean", description: "Run command in all packages", short: "R" },
8450
+ workspaceRoot: { type: "boolean", description: "Use root scripts, running at the monorepo root", short: "w" }
8451
+ };
8452
+ function readInvocationFlags(args) {
8453
+ const remaining = [...args];
8454
+ let shouldShowHelp = false;
8455
+ for (; ; ) {
8456
+ try {
8457
+ const parsed = parseArgs(remaining, { flags: READER_FLAGS }, { stopAtPositional: true });
8458
+ return { ok: true, parsedArgs: { ...toParsedArgs(parsed.flags, parsed.rest), shouldShowHelp } };
8459
+ } catch (error) {
8460
+ if (!(error instanceof ParseError)) {
8461
+ throw error;
8462
+ }
8463
+ if (error.kind !== "unknown-flag" || !HELP_SPELLINGS.has(error.token) || !removeHelpFlag(remaining, error.token)) {
8464
+ return { ok: false };
8465
+ }
8466
+ shouldShowHelp = true;
8467
+ }
8468
+ }
8469
+ }
8470
+ function toParsedArgs(flags, rest) {
8471
+ const [first, ...remaining] = rest;
8472
+ const hasCommand = first !== void 0 && first !== "--";
8473
+ return {
8474
+ ...flags.delegateTo !== void 0 && { delegateHosts: flags.delegateTo },
8475
+ ...flags.filter !== void 0 && { filter: flags.filter },
8476
+ isWorkspaceRoot: flags.workspaceRoot,
8477
+ ...flags.outputStyle !== void 0 && { outputStyle: flags.outputStyle },
8478
+ quiet: flags.quiet,
8479
+ recursive: flags.recursive,
8480
+ shouldBypassCache: flags.noCache,
8481
+ shouldBypassLock: flags.noLock,
8482
+ shouldDelegate: flags.delegate,
8483
+ shouldEmitJson: flags.json,
8484
+ shouldPrintLog: flags.log,
8485
+ shouldRunLocally: flags.noDelegate,
8486
+ shouldShowHelp: false,
8487
+ shouldShowVersion: flags.version ?? false,
8488
+ ...hasCommand && { command: first },
8489
+ passthrough: hasCommand ? remaining : []
8490
+ };
8491
+ }
8492
+ var HELP_SPELLINGS = /* @__PURE__ */ new Set(["--help", "-h"]);
8493
+ var READER_FLAGS = {
8494
+ ...NMR_FLAGS,
8495
+ version: { type: "boolean", description: "Print the version", short: "V" }
8496
+ };
8497
+ function isBooleanCluster(arg) {
8498
+ if (!/^-[^-]./.test(arg)) {
8499
+ return false;
8500
+ }
8501
+ const booleanShorts = /* @__PURE__ */ new Set(["h", "V"]);
8502
+ for (const definition of Object.values(NMR_FLAGS)) {
8503
+ if (definition.type === "boolean" && "short" in definition) {
8504
+ booleanShorts.add(definition.short);
8505
+ }
8506
+ }
8507
+ for (const letter of arg.slice(1)) {
8508
+ if (!booleanShorts.has(letter)) {
8509
+ return false;
8510
+ }
8511
+ }
8512
+ return true;
8513
+ }
8514
+ function removeHelpFlag(args, token) {
8515
+ const index = args.indexOf(token);
8516
+ if (index !== -1) {
8517
+ args.splice(index, 1);
8518
+ return true;
8519
+ }
8520
+ const clusterIndex = args.findIndex((arg) => isBooleanCluster(arg) && arg.includes("h"));
8521
+ const cluster = args[clusterIndex];
8522
+ if (cluster === void 0) {
8523
+ return false;
8524
+ }
8525
+ args[clusterIndex] = cluster.replace("h", "");
8526
+ return true;
8527
+ }
8528
+
8099
8529
  // src/steps.ts
8100
8530
  var LAUNCHERS = /* @__PURE__ */ new Map([
8101
8531
  ["bun", /* @__PURE__ */ new Set(["x"])],
@@ -8191,39 +8621,12 @@ function readNmrTail(segment) {
8191
8621
  return rest[nmrIndex] === "nmr" ? { launcher: head, launcherArgs: rest.slice(0, nmrIndex), tail: rest.slice(nmrIndex + 1) } : void 0;
8192
8622
  }
8193
8623
  function readNmrTarget(tokens) {
8194
- let isDelegate = false;
8195
- let isRecursive = false;
8196
- let isWorkspaceRoot = false;
8197
- let index = 0;
8198
- while (index < tokens.length) {
8199
- const token = tokens[index] ?? "";
8200
- switch (token) {
8201
- // The pattern is the flag's value, and reading it as a command name would name whichever package it
8202
- // selects rather than the command that every selected scope runs.
8203
- case "-F":
8204
- case "--filter":
8205
- isDelegate = true;
8206
- index += 2;
8207
- break;
8208
- case "-R":
8209
- case "--recursive":
8210
- isDelegate = true;
8211
- isRecursive = true;
8212
- index += 1;
8213
- break;
8214
- case "-w":
8215
- case "--workspace-root":
8216
- isWorkspaceRoot = true;
8217
- index += 1;
8218
- break;
8219
- default:
8220
- if (!token.startsWith("-")) {
8221
- return { command: token, isDelegate, isRecursive, isWorkspaceRoot };
8222
- }
8223
- index += 1;
8224
- }
8624
+ const reading = readInvocationFlags(tokens);
8625
+ if (!reading.ok || reading.parsedArgs.command === void 0) {
8626
+ return void 0;
8225
8627
  }
8226
- return void 0;
8628
+ const { command, filter, isWorkspaceRoot, recursive } = reading.parsedArgs;
8629
+ return { command, isDelegate: filter !== void 0 || recursive, isRecursive: recursive, isWorkspaceRoot };
8227
8630
  }
8228
8631
 
8229
8632
  // src/resolver.ts
@@ -8244,7 +8647,7 @@ function readPackageJsonScripts(packageDir) {
8244
8647
  const file = resolvePackageJsonPath(packageDir);
8245
8648
  let rawText;
8246
8649
  try {
8247
- rawText = readFileSync2(file, "utf8");
8650
+ rawText = readFileSync3(file, "utf8");
8248
8651
  } catch (error) {
8249
8652
  if (hasErrnoCode(error, "ENOENT")) {
8250
8653
  return void 0;
@@ -8252,9 +8655,9 @@ function readPackageJsonScripts(packageDir) {
8252
8655
  throw error;
8253
8656
  }
8254
8657
  const parsedManifest = parsePackageJson(rawText, file);
8255
- if (!isObject2(parsedManifest)) return void 0;
8658
+ if (!isRecord2(parsedManifest)) return void 0;
8256
8659
  const scripts = parsedManifest["scripts"];
8257
- if (!isObject2(scripts)) return void 0;
8660
+ if (!isRecord2(scripts)) return void 0;
8258
8661
  return readScriptRecord(packageDir, scripts);
8259
8662
  }
8260
8663
  function buildWorkspaceRegistry(config) {
@@ -8330,6 +8733,11 @@ function readPackageScripts(packageDir) {
8330
8733
  }
8331
8734
  }
8332
8735
 
8736
+ // src/helpers/type-guards.ts
8737
+ function isStringRecord(value) {
8738
+ return isRecord2(value) && Object.values(value).every((entry) => typeof entry === "string");
8739
+ }
8740
+
8333
8741
  // src/verbosity.ts
8334
8742
  var COMMAND_VERBOSITIES = ["full", "quiet"];
8335
8743
  function formatVerbosityRejection(source, value) {
@@ -8370,7 +8778,7 @@ function assertRecognizedKeys(value, recognizedKeys, configPath, prefix = "") {
8370
8778
  );
8371
8779
  }
8372
8780
  function validateConfig(value, configPath, baseDir) {
8373
- if (!isObject2(value)) {
8781
+ if (!isRecord2(value)) {
8374
8782
  throw new UserError(`Invalid nmr config at ${configPath}: expected an object, got ${typeof value}`);
8375
8783
  }
8376
8784
  assertRecognizedKeys(value, RECOGNIZED_KEYS, configPath);
@@ -8426,7 +8834,7 @@ function isOptionalBoolean(value) {
8426
8834
  return value === void 0 || typeof value === "boolean";
8427
8835
  }
8428
8836
  function isScriptRecord(value) {
8429
- if (!isObject2(value)) return false;
8837
+ if (!isRecord2(value)) return false;
8430
8838
  for (const v of Object.values(value)) {
8431
8839
  if (typeof v === "string") continue;
8432
8840
  if (!Array.isArray(v) || !v.every(isScriptElement)) return false;
@@ -8435,7 +8843,7 @@ function isScriptRecord(value) {
8435
8843
  }
8436
8844
  function isScriptElement(value) {
8437
8845
  if (typeof value === "string") return true;
8438
- if (!isObject2(value) || typeof value["run"] !== "string") return false;
8846
+ if (!isRecord2(value) || typeof value["run"] !== "string") return false;
8439
8847
  return isOptionalBoolean(value["shouldDeclineArguments"]) && isOptionalBoolean(value["shouldRunInBackground"]);
8440
8848
  }
8441
8849
  function isStringArray(value) {
@@ -8446,7 +8854,7 @@ function validateBuildField(value, configPath) {
8446
8854
  if (build === void 0) {
8447
8855
  return void 0;
8448
8856
  }
8449
- if (!isObject2(build)) {
8857
+ if (!isRecord2(build)) {
8450
8858
  throw new UserError(`Invalid nmr config at ${configPath}: \`build\` must be an object`);
8451
8859
  }
8452
8860
  assertRecognizedKeys(build, RECOGNIZED_BUILD_KEYS, configPath, "build.");
@@ -8461,7 +8869,7 @@ function validateCheckCacheField(value, configPath) {
8461
8869
  if (checkCache === void 0) {
8462
8870
  return void 0;
8463
8871
  }
8464
- if (!isObject2(checkCache)) {
8872
+ if (!isRecord2(checkCache)) {
8465
8873
  throw new UserError(`Invalid nmr config at ${configPath}: \`checkCache\` must be an object`);
8466
8874
  }
8467
8875
  assertRecognizedKeys(checkCache, RECOGNIZED_CHECK_CACHE_KEYS, configPath, "checkCache.");
@@ -8498,7 +8906,7 @@ function validateOutputField(value, configPath) {
8498
8906
  if (output === void 0) {
8499
8907
  return void 0;
8500
8908
  }
8501
- if (!isObject2(output)) {
8909
+ if (!isRecord2(output)) {
8502
8910
  throw new UserError(`Invalid nmr config at ${configPath}: \`output\` must be an object`);
8503
8911
  }
8504
8912
  assertRecognizedKeys(output, RECOGNIZED_OUTPUT_KEYS, configPath, "output.");
@@ -8527,7 +8935,7 @@ function validateSelfHostedRunnerField(value, configPath) {
8527
8935
  if (selfHostedRunner === void 0) {
8528
8936
  return void 0;
8529
8937
  }
8530
- if (!isObject2(selfHostedRunner)) {
8938
+ if (!isRecord2(selfHostedRunner)) {
8531
8939
  throw new UserError(`Invalid nmr config at ${configPath}: \`selfHostedRunner\` must be an object`);
8532
8940
  }
8533
8941
  assertRecognizedKeys(selfHostedRunner, RECOGNIZED_SELF_HOSTED_RUNNER_KEYS, configPath, "selfHostedRunner.");
@@ -8587,7 +8995,7 @@ async function loadConfig(baseDir) {
8587
8995
  return {};
8588
8996
  }
8589
8997
  const importedModule = await import(pathToFileURL(configPath).href);
8590
- const loadedConfig = isObject2(importedModule) ? importedModule["default"] : void 0;
8998
+ const loadedConfig = isRecord2(importedModule) ? importedModule["default"] : void 0;
8591
8999
  return validateConfig(loadedConfig, configPath, baseDir);
8592
9000
  }
8593
9001
  async function loadRootConfig(monorepoRoot) {
@@ -8638,17 +9046,15 @@ function formatSelfReferenceRemedy(options) {
8638
9046
 
8639
9047
  // src/tiers.ts
8640
9048
  import { readdirSync as readdirSync2 } from "node:fs";
8641
- import path6 from "node:path";
9049
+ import path7 from "node:path";
8642
9050
 
8643
9051
  // src/git-ignored-paths.ts
8644
- import { spawnSync } from "node:child_process";
8645
9052
  function listGitIgnoredPaths(dir) {
8646
- const result = spawnSync(
8647
- "git",
9053
+ const result = runGit(
8648
9054
  ["ls-files", "-z", "--others", "--ignored", "--exclude-standard", "--directory", "--no-empty-directory"],
8649
- { cwd: dir, encoding: "utf8" }
9055
+ dir
8650
9056
  );
8651
- if (result.error !== void 0 || result.status !== 0) return [];
9057
+ if (!result.ok) return [];
8652
9058
  return result.stdout.split("\0").filter((entry) => entry !== "").toSorted();
8653
9059
  }
8654
9060
 
@@ -8669,7 +9075,7 @@ function findUntieredTestFiles(rootDir, options = {}) {
8669
9075
  }
8670
9076
  function hasTierInfix(filePath) {
8671
9077
  const tiers = TIER_NAMES;
8672
- return tiers.includes(path6.basename(filePath).split(".").at(-3) ?? "");
9078
+ return tiers.includes(path7.basename(filePath).split(".").at(-3) ?? "");
8673
9079
  }
8674
9080
  var TEST_COLLECTION_EXCLUDE = [".git", "coverage", "dist", "node_modules"];
8675
9081
  var TIER_NAMES = ["unit", "tool", "localhost", "remote"];
@@ -8679,7 +9085,7 @@ function collectTestFiles(dir, relativeDir, isInTestDir, context) {
8679
9085
  const relativePath = relativeDir === "" ? entry.name : `${relativeDir}/${entry.name}`;
8680
9086
  if (entry.isDirectory()) {
8681
9087
  if (context.pruned.has(entry.name) || context.ignoredPaths.has(`${relativePath}/`)) continue;
8682
- collectTestFiles(path6.join(dir, entry.name), relativePath, isInTestDir || entry.name === TEST_DIR, context);
9088
+ collectTestFiles(path7.join(dir, entry.name), relativePath, isInTestDir || entry.name === TEST_DIR, context);
8683
9089
  } else if (isInTestDir !== context.misplaced && TEST_FILE_PATTERN.test(entry.name) && !context.ignoredPaths.has(relativePath)) {
8684
9090
  context.foundPaths.push(relativePath);
8685
9091
  }
@@ -8701,16 +9107,16 @@ var import_yaml2 = __toESM(require_dist(), 1);
8701
9107
  import { execFile } from "node:child_process";
8702
9108
  import { existsSync as existsSync4, readdirSync as readdirSync3 } from "node:fs";
8703
9109
  import { join as join3, posix as posix2 } from "node:path";
8704
- import { isRecord as isRecord3, readFile as readFile3 } from "readyup/check-utils";
9110
+ import { isRecord as isRecord4, readFile as readFile3 } from "readyup/check-utils";
8705
9111
  var CODE_QUALITY_WORKFLOW = "williamthorsen/.github/.github/workflows/code-quality-pnpm-workflow.yaml";
8706
9112
  var MIN_CODE_QUALITY_MAJOR = 8;
8707
9113
  var GH_TIMEOUT_MS = 15e3;
8708
9114
  var IMAGE_ABSENT_PROGRAM_PATTERN = /(?:^|[\s;&|(])(corepack|gh|node|npm|npx|pnpm)(?=\s|$)/gm;
8709
9115
  var NEVER_INSTALLED_PROGRAMS = /* @__PURE__ */ new Set(["corepack", "gh"]);
8710
9116
  function countOnlineSelfHostedRunners(response) {
8711
- if (!isRecord3(response) || !Array.isArray(response["runners"])) return 0;
9117
+ if (!isRecord4(response) || !Array.isArray(response["runners"])) return 0;
8712
9118
  return response["runners"].filter(
8713
- (runner) => isRecord3(runner) && runner["status"] === "online" && Array.isArray(runner["labels"]) && runner["labels"].some((label) => isRecord3(label) && label["name"] === "self-hosted")
9119
+ (runner) => isRecord4(runner) && runner["status"] === "online" && Array.isArray(runner["labels"]) && runner["labels"].some((label) => isRecord4(label) && label["name"] === "self-hosted")
8714
9120
  ).length;
8715
9121
  }
8716
9122
  function describeGhFailure(result, context) {
@@ -8737,7 +9143,7 @@ function findCodeQualityCallerDefects(workflows) {
8737
9143
  );
8738
9144
  }
8739
9145
  const inputs = job["with"];
8740
- if (isRecord3(inputs) && inputs["runs-on"] !== void 0) {
9146
+ if (isRecord4(inputs) && inputs["runs-on"] !== void 0) {
8741
9147
  defects.push(`${file} job ${jobName} passes runs-on, which overrides CI_RUNS_ON`);
8742
9148
  }
8743
9149
  }
@@ -8753,7 +9159,7 @@ function findImageIncompatibleSteps(workflows) {
8753
9159
  if (!Array.isArray(steps)) continue;
8754
9160
  const installed = /* @__PURE__ */ new Set();
8755
9161
  for (const [index, step] of steps.entries()) {
8756
- if (!isRecord3(step)) continue;
9162
+ if (!isRecord4(step)) continue;
8757
9163
  const stepLabel = typeof step["name"] === "string" ? step["name"] : `step ${index + 1}`;
8758
9164
  const run = step["run"];
8759
9165
  if (typeof run === "string") {
@@ -8784,14 +9190,14 @@ async function readOrganizationValue(nameWithOwner, cwd, runGh) {
8784
9190
  const result = await runGh(["api", `repos/${nameWithOwner}/actions/organization-variables`], cwd);
8785
9191
  if (!result.ok) return void 0;
8786
9192
  const response = parseJson(result.stdout);
8787
- if (!isRecord3(response) || !Array.isArray(response["variables"])) return void 0;
9193
+ if (!isRecord4(response) || !Array.isArray(response["variables"])) return void 0;
8788
9194
  const variable = response["variables"].find(
8789
- (entry) => isRecord3(entry) && entry["name"] === "CI_RUNS_ON"
9195
+ (entry) => isRecord4(entry) && entry["name"] === "CI_RUNS_ON"
8790
9196
  );
8791
9197
  return readVariableValue(variable);
8792
9198
  }
8793
9199
  function readVariableValue(variable) {
8794
- return isRecord3(variable) && typeof variable["value"] === "string" ? variable["value"] : void 0;
9200
+ return isRecord4(variable) && typeof variable["value"] === "string" ? variable["value"] : void 0;
8795
9201
  }
8796
9202
  function readWorkflows(cwd) {
8797
9203
  const dir = join3(cwd, ".github", "workflows");
@@ -8805,9 +9211,9 @@ function readWorkflows(cwd) {
8805
9211
  } catch {
8806
9212
  continue;
8807
9213
  }
8808
- if (!isRecord3(document) || !isRecord3(document["jobs"])) continue;
9214
+ if (!isRecord4(document) || !isRecord4(document["jobs"])) continue;
8809
9215
  const jobs = Object.entries(document["jobs"]).filter(
8810
- (entry) => isRecord3(entry[1])
9216
+ (entry) => isRecord4(entry[1])
8811
9217
  );
8812
9218
  workflows.push({ file: posix2.join(".github/workflows", file), jobs });
8813
9219
  }
@@ -8841,7 +9247,7 @@ function readInstalledPrograms(step) {
8841
9247
  if (action === "actions/setup-node") return ["node"];
8842
9248
  if (action === "pnpm/action-setup") return ["pnpm"];
8843
9249
  if (action !== "pnpm/setup") return [];
8844
- const inputs = isRecord3(step["with"]) ? step["with"] : {};
9250
+ const inputs = isRecord4(step["with"]) ? step["with"] : {};
8845
9251
  const runtime = inputs["runtime"];
8846
9252
  const installsNode = typeof runtime === "string" ? /^node\b/.test(runtime) : String(inputs["node-version-file"]) !== "false";
8847
9253
  return installsNode ? ["pnpm", "node"] : ["pnpm"];
@@ -8919,7 +9325,7 @@ async function readGitHubFacts(cwd, runGh = runGhCommand) {
8919
9325
  const repo = await runGh(["repo", "view", "--json", "nameWithOwner,visibility"], cwd);
8920
9326
  if (!repo.ok) return { skip: describeGhFailure(repo, "cannot resolve this repo on GitHub") };
8921
9327
  const repoView = parseJson(repo.stdout);
8922
- if (!isRecord4(repoView) || typeof repoView["nameWithOwner"] !== "string") {
9328
+ if (!isRecord5(repoView) || typeof repoView["nameWithOwner"] !== "string") {
8923
9329
  return { skip: "gh repo view returned an unexpected shape" };
8924
9330
  }
8925
9331
  const nameWithOwner = repoView["nameWithOwner"];
@@ -9018,7 +9424,7 @@ import {
9018
9424
  // .readyup/lib/catalog.ts
9019
9425
  import { posix as posix3 } from "node:path";
9020
9426
  var import_yaml3 = __toESM(require_dist(), 1);
9021
- import { isRecord as isRecord5 } from "readyup/check-utils";
9427
+ import { isRecord as isRecord6 } from "readyup/check-utils";
9022
9428
  var CATALOGUE_CHECKED_FIELDS = ["dependencies", "devDependencies", "optionalDependencies"];
9023
9429
  var PROTOCOL_PATTERN = /^[a-z][a-z0-9+.-]*:/i;
9024
9430
  function cataloguedDependenciesUseCatalog() {
@@ -9036,7 +9442,7 @@ function findLiteralCataloguedSpecifiers(workspace, names) {
9036
9442
  const manifestPath = posix3.join(workspace.dir, "package.json");
9037
9443
  return CATALOGUE_CHECKED_FIELDS.flatMap((field) => {
9038
9444
  const declared = workspace.packageJson[field];
9039
- if (!isRecord5(declared)) return [];
9445
+ if (!isRecord6(declared)) return [];
9040
9446
  return Object.entries(declared).flatMap(
9041
9447
  ([name, specifier]) => names.has(name) && typeof specifier === "string" && !PROTOCOL_PATTERN.test(specifier) ? { manifestPath, name, specifier } : []
9042
9448
  );
@@ -9051,14 +9457,14 @@ function readCataloguedNames(cwd) {
9051
9457
  } catch (error) {
9052
9458
  return { ok: false, detail: `cannot parse pnpm-workspace.yaml: ${describeError(error)}` };
9053
9459
  }
9054
- if (!isRecord5(workspaceManifest)) return { ok: true, names: /* @__PURE__ */ new Set() };
9460
+ if (!isRecord6(workspaceManifest)) return { ok: true, names: /* @__PURE__ */ new Set() };
9055
9461
  const namedCatalogs = workspaceManifest["catalogs"];
9056
- const catalogs = [workspaceManifest["catalog"], ...isRecord5(namedCatalogs) ? Object.values(namedCatalogs) : []];
9057
- return { ok: true, names: new Set(catalogs.filter(isRecord5).flatMap((catalog) => Object.keys(catalog))) };
9462
+ const catalogs = [workspaceManifest["catalog"], ...isRecord6(namedCatalogs) ? Object.values(namedCatalogs) : []];
9463
+ return { ok: true, names: new Set(catalogs.filter(isRecord6).flatMap((catalog) => Object.keys(catalog))) };
9058
9464
  }
9059
9465
 
9060
9466
  // .readyup/lib/manifest-fields.ts
9061
- import { isRecord as isRecord6 } from "readyup/check-utils";
9467
+ import { isRecord as isRecord7 } from "readyup/check-utils";
9062
9468
  function noPnpmFieldInPackageJson(cwd = process.cwd()) {
9063
9469
  const declaringFiles = findFiles(["**/package.json"], cwd).flatMap((relativePath) => {
9064
9470
  const keys = readPnpmFieldKeys(readFileIn(cwd, relativePath));
@@ -9079,7 +9485,7 @@ function declaresWorkspacesField(content) {
9079
9485
  if (content === void 0) return false;
9080
9486
  try {
9081
9487
  const parsedManifest = JSON.parse(content);
9082
- return isRecord6(parsedManifest) && "workspaces" in parsedManifest;
9488
+ return isRecord7(parsedManifest) && "workspaces" in parsedManifest;
9083
9489
  } catch {
9084
9490
  return false;
9085
9491
  }
@@ -9092,9 +9498,9 @@ function readPnpmFieldKeys(content) {
9092
9498
  } catch {
9093
9499
  return void 0;
9094
9500
  }
9095
- if (!isRecord6(parsedManifest)) return void 0;
9501
+ if (!isRecord7(parsedManifest)) return void 0;
9096
9502
  const pnpm = parsedManifest["pnpm"];
9097
- return isRecord6(pnpm) ? Object.keys(pnpm).toSorted() : void 0;
9503
+ return isRecord7(pnpm) ? Object.keys(pnpm).toSorted() : void 0;
9098
9504
  }
9099
9505
 
9100
9506
  // .readyup/lib/setup.ts
@@ -9205,7 +9611,7 @@ function hasSupportedStrictLintVersion() {
9205
9611
  });
9206
9612
  }
9207
9613
  function getMinVersion() {
9208
- const pickedFields = { "version": "0.55.0" };
9614
+ const pickedFields = { "version": "1.0.0" };
9209
9615
  if (typeof pickedFields["version"] !== "string") {
9210
9616
  throw new TypeError("nmr/package.json: 'version' must be a string");
9211
9617
  }
@@ -9221,7 +9627,7 @@ function toolVersionsHasNoPnpm() {
9221
9627
  }
9222
9628
 
9223
9629
  // .readyup/lib/shared-configs.ts
9224
- import { isRecord as isRecord7 } from "readyup/check-utils";
9630
+ import { isRecord as isRecord8 } from "readyup/check-utils";
9225
9631
  var SHARED_PRETTIER_MODULE = "@williamthorsen/nmr/prettier";
9226
9632
  var INERT_PRETTIER_CONFIGS = [".prettierrc", ".prettierrc.{json,json5,yaml,yml,toml}"];
9227
9633
  var SHARED_TAZE_MODULE = "@williamthorsen/nmr/taze";
@@ -9315,7 +9721,7 @@ function hasPrettierConfigKey(cwd) {
9315
9721
  if (manifest === void 0) return false;
9316
9722
  try {
9317
9723
  const parsedManifest = JSON.parse(manifest);
9318
- return isRecord7(parsedManifest) && parsedManifest["prettier"] !== void 0;
9724
+ return isRecord8(parsedManifest) && parsedManifest["prettier"] !== void 0;
9319
9725
  } catch {
9320
9726
  return false;
9321
9727
  }
@@ -9404,8 +9810,8 @@ function everyViteConfigHasVitestConfig() {
9404
9810
  return { ok: false, detail: formatPaths(unpairedConfigs) };
9405
9811
  }
9406
9812
  function noReExportOnlyVitestConfigs(cwd = process.cwd()) {
9407
- const nonRootConfigs = findFiles([`**/${VITEST_CONFIG_PATTERN}`], cwd).filter((path7) => path7.includes("/"));
9408
- const reExports = nonRootConfigs.filter((path7) => isOwnedByReExportCheck(cwd, path7));
9813
+ const nonRootConfigs = findFiles([`**/${VITEST_CONFIG_PATTERN}`], cwd).filter((path9) => path9.includes("/"));
9814
+ const reExports = nonRootConfigs.filter((path9) => isOwnedByReExportCheck(cwd, path9));
9409
9815
  if (reExports.length === 0) return true;
9410
9816
  return { ok: false, detail: formatPaths(reExports) };
9411
9817
  }
@@ -9480,14 +9886,108 @@ function isReExportOnly(content) {
9480
9886
  import { existsSync as existsSync5, readdirSync as readdirSync4 } from "node:fs";
9481
9887
  import { join as join5 } from "node:path";
9482
9888
  import { fileExists as fileExists3, readFile as readFile5 } from "readyup/check-utils";
9889
+
9890
+ // src/commands/ensure-prepublish-hooks.ts
9891
+ import { readFileSync as readFileSync4, writeFileSync } from "node:fs";
9892
+ import path8 from "node:path";
9893
+ var DEFAULT_HOOK = "nmr build";
9894
+ function ensurePrepublishHooks(monorepoRoot, options) {
9895
+ const hookCommand = options.command ?? DEFAULT_HOOK;
9896
+ const packageDirs = getWorkspacePackageDirs(monorepoRoot);
9897
+ const packages = [];
9898
+ for (const packageDir of packageDirs) {
9899
+ const packageJson = readPackageJson2(packageDir);
9900
+ const packageName = packageJson.name ?? path8.basename(packageDir);
9901
+ const isPrivate = packageJson.private === true;
9902
+ if (isPrivate) {
9903
+ packages.push({
9904
+ packageName,
9905
+ packageDir,
9906
+ isPrivate: true,
9907
+ prepublishOnly: packageJson.scripts?.["prepublishOnly"],
9908
+ action: "ok"
9909
+ });
9910
+ continue;
9911
+ }
9912
+ const existingHook = packageJson.scripts?.["prepublishOnly"];
9913
+ if (existingHook) {
9914
+ packages.push({
9915
+ packageName,
9916
+ packageDir,
9917
+ isPrivate: false,
9918
+ prepublishOnly: existingHook,
9919
+ action: "ok"
9920
+ });
9921
+ continue;
9922
+ }
9923
+ if (options.shouldFix) {
9924
+ const action = options.isDryRun ? "would-fix" : "fixed";
9925
+ if (!options.isDryRun) {
9926
+ addPrepublishOnly(packageDir, hookCommand);
9927
+ }
9928
+ packages.push({
9929
+ packageName,
9930
+ packageDir,
9931
+ isPrivate: false,
9932
+ prepublishOnly: void 0,
9933
+ action
9934
+ });
9935
+ } else {
9936
+ packages.push({
9937
+ packageName,
9938
+ packageDir,
9939
+ isPrivate: false,
9940
+ prepublishOnly: void 0,
9941
+ action: "missing"
9942
+ });
9943
+ }
9944
+ }
9945
+ const hasFailures = packages.some((p) => p.action === "missing");
9946
+ return { packages, hasFailures };
9947
+ }
9948
+ function addPrepublishOnly(packageDir, command) {
9949
+ const filePath = path8.join(packageDir, "package.json");
9950
+ const rawText = readFileSync4(filePath, "utf8");
9951
+ let parsedManifest;
9952
+ try {
9953
+ parsedManifest = JSON.parse(rawText);
9954
+ } catch (error) {
9955
+ throw new UserError(`Invalid package.json in ${packageDir}: ${describeError(error)}`, { cause: error });
9956
+ }
9957
+ if (!isRecord2(parsedManifest)) {
9958
+ throw new UserError(`Invalid package.json in ${packageDir}: expected an object`);
9959
+ }
9960
+ const scripts = isRecord2(parsedManifest["scripts"]) ? parsedManifest["scripts"] : {};
9961
+ scripts["prepublishOnly"] = command;
9962
+ parsedManifest["scripts"] = scripts;
9963
+ writeFileSync(filePath, JSON.stringify(parsedManifest, null, 2) + "\n", "utf8");
9964
+ }
9965
+
9966
+ // .readyup/lib/workspace-build.ts
9483
9967
  var buildReadinessChecks = [
9484
9968
  {
9485
9969
  name: "all workspace packages can build",
9486
9970
  severity: "warn",
9487
9971
  check: allWorkspacePackagesCanBuild,
9488
9972
  fix: `Add "build": ":" to packages that don't need a build, or ensure packages that use the default nmr build have a tsconfig.json and a src/ directory`
9973
+ },
9974
+ {
9975
+ name: "every publishable package declares prepublishOnly",
9976
+ severity: "warn",
9977
+ check: everyPublishablePackageDeclaresPrepublishOnly,
9978
+ fix: 'Add "prepublishOnly": "nmr build" to the scripts of each listed package, or run ensure-prepublish-hooks --fix. A package without it publishes without its build output'
9489
9979
  }
9490
9980
  ];
9981
+ function everyPublishablePackageDeclaresPrepublishOnly() {
9982
+ try {
9983
+ const { packages } = ensurePrepublishHooks(process.cwd(), { shouldFix: false, isDryRun: false });
9984
+ const offenders = packages.filter((packageStatus) => packageStatus.action === "missing").map((packageStatus) => packageStatus.packageName);
9985
+ if (offenders.length === 0) return true;
9986
+ return { ok: false, detail: formatPaths(offenders) };
9987
+ } catch (error) {
9988
+ return { ok: false, detail: describeError(error) };
9989
+ }
9990
+ }
9491
9991
  function allWorkspacePackagesCanBuild() {
9492
9992
  const packagesDir = join5(process.cwd(), "packages");
9493
9993
  if (!existsSync5(packagesDir)) return true;