@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.
- package/.meta/changelog.json +168 -0
- package/.readyup/kits/default.js +1331 -136
- package/.readyup/manifest.json +148 -9
- package/CHANGELOG.md +51 -0
- package/README.md +17 -3
- package/agents/guidance/rulebooks/nmr.md +1 -1
- package/dist/esm/commands/fmt-cache.d.ts +26 -0
- package/dist/esm/commands/fmt-cache.js +71 -0
- package/dist/esm/commands/fmt.d.ts +10 -3
- package/dist/esm/commands/fmt.js +34 -6
- package/dist/esm/config.js +74 -9
- package/dist/esm/default-scripts.d.ts +10 -1
- package/dist/esm/default-scripts.js +45 -39
- package/dist/esm/defineConfig.d.ts +1 -1
- package/dist/esm/entry-chain.d.ts +24 -0
- package/dist/esm/entry-chain.js +57 -0
- package/dist/esm/glyphs.d.ts +1 -1
- package/dist/esm/help.d.ts +1 -1
- package/dist/esm/help.js +3 -3
- package/dist/esm/helpers/createOutputRecorder.d.ts +17 -0
- package/dist/esm/helpers/createOutputRecorder.js +22 -0
- package/dist/esm/helpers/escapeControlCharacters.d.ts +10 -0
- package/dist/esm/helpers/escapeControlCharacters.js +12 -0
- package/dist/esm/resolve-scripts.d.ts +5 -3
- package/dist/esm/resolve-scripts.js +7 -3
- package/dist/esm/resolver.d.ts +5 -4
- package/dist/esm/resolver.js +16 -6
- package/dist/esm/runCli.js +146 -101
- package/dist/esm/runner.d.ts +7 -0
- package/dist/esm/runner.js +105 -15
- package/dist/esm/steps.d.ts +50 -18
- package/dist/esm/steps.js +82 -13
- package/dist/esm/taze.d.ts +5 -2
- package/dist/esm/taze.js +4 -1
- package/dist/esm/tiers.d.ts +5 -4
- package/dist/esm/types.d.ts +10 -0
- package/dist/esm/vitest-git-isolation.js +3 -1
- package/dist/esm/vitest.js +9 -5
- package/dist/esm/workspace-globs.d.ts +5 -0
- package/dist/esm/workspace-globs.js +8 -0
- package/docs/check-cache.md +2 -0
- package/docs/reporting.md +2 -0
- package/docs/scripts.md +36 -10
- package/docs/upgrades.md +1 -1
- package/docs/utilities.md +14 -2
- package/docs/vitest.md +20 -8
- package/package.json +7 -7
package/.readyup/kits/default.js
CHANGED
|
@@ -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,
|
|
121
|
-
const ctrl = callVisitor(key, node, visitor,
|
|
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,
|
|
124
|
-
return visit_(key, ctrl, visitor,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
142
|
-
const ck = visit_("key", node.key, visitor,
|
|
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,
|
|
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,
|
|
169
|
-
const ctrl = await callVisitor(key, node, visitor,
|
|
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,
|
|
172
|
-
return visitAsync_(key, ctrl, visitor,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
190
|
-
const ck = await visitAsync_("key", node.key, visitor,
|
|
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,
|
|
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,
|
|
222
|
+
function callVisitor(key, node, visitor, path7) {
|
|
223
223
|
if (typeof visitor === "function")
|
|
224
|
-
return visitor(key, node,
|
|
224
|
+
return visitor(key, node, path7);
|
|
225
225
|
if (identity.isMap(node))
|
|
226
|
-
return visitor.Map?.(key, node,
|
|
226
|
+
return visitor.Map?.(key, node, path7);
|
|
227
227
|
if (identity.isSeq(node))
|
|
228
|
-
return visitor.Seq?.(key, node,
|
|
228
|
+
return visitor.Seq?.(key, node, path7);
|
|
229
229
|
if (identity.isPair(node))
|
|
230
|
-
return visitor.Pair?.(key, node,
|
|
230
|
+
return visitor.Pair?.(key, node, path7);
|
|
231
231
|
if (identity.isScalar(node))
|
|
232
|
-
return visitor.Scalar?.(key, node,
|
|
232
|
+
return visitor.Scalar?.(key, node, path7);
|
|
233
233
|
if (identity.isAlias(node))
|
|
234
|
-
return visitor.Alias?.(key, node,
|
|
234
|
+
return visitor.Alias?.(key, node, path7);
|
|
235
235
|
return void 0;
|
|
236
236
|
}
|
|
237
|
-
function replaceNode(key,
|
|
238
|
-
const parent =
|
|
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,
|
|
850
|
+
function collectionFromPath(schema, path7, value) {
|
|
851
851
|
let v = value;
|
|
852
|
-
for (let i =
|
|
853
|
-
const k =
|
|
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 = (
|
|
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(
|
|
903
|
-
if (isEmptyPath(
|
|
902
|
+
addIn(path7, value) {
|
|
903
|
+
if (isEmptyPath(path7))
|
|
904
904
|
this.add(value);
|
|
905
905
|
else {
|
|
906
|
-
const [key, ...rest] =
|
|
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(
|
|
921
|
-
const [key, ...rest] =
|
|
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(
|
|
936
|
-
const [key, ...rest] =
|
|
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(
|
|
955
|
-
const [key, ...rest] =
|
|
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(
|
|
966
|
-
const [key, ...rest] =
|
|
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(
|
|
3481
|
+
addIn(path7, value) {
|
|
3482
3482
|
if (assertCollection(this.contents))
|
|
3483
|
-
this.contents.addIn(
|
|
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(
|
|
3559
|
-
if (Collection.isEmptyPath(
|
|
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(
|
|
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(
|
|
3581
|
-
if (Collection.isEmptyPath(
|
|
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(
|
|
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(
|
|
3595
|
-
if (Collection.isEmptyPath(
|
|
3594
|
+
hasIn(path7) {
|
|
3595
|
+
if (Collection.isEmptyPath(path7))
|
|
3596
3596
|
return this.contents !== void 0;
|
|
3597
|
-
return identity.isCollection(this.contents) ? this.contents.hasIn(
|
|
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(
|
|
3615
|
-
if (Collection.isEmptyPath(
|
|
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(
|
|
3618
|
+
this.contents = Collection.collectionFromPath(this.schema, Array.from(path7), value);
|
|
3619
3619
|
} else if (assertCollection(this.contents)) {
|
|
3620
|
-
this.contents.setIn(
|
|
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,
|
|
5581
|
+
visit.itemAtPath = (cst, path7) => {
|
|
5582
5582
|
let item = cst;
|
|
5583
|
-
for (const [field, index] of
|
|
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,
|
|
5593
|
-
const parent = visit.itemAtPath(cst,
|
|
5594
|
-
const field =
|
|
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(
|
|
5601
|
-
let ctrl = visitor(item,
|
|
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(
|
|
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,
|
|
5619
|
+
ctrl = ctrl(item, path7);
|
|
5620
5620
|
}
|
|
5621
5621
|
}
|
|
5622
|
-
return typeof ctrl === "function" ? ctrl(item,
|
|
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
|
|
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 =
|
|
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 {
|
|
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.
|
|
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.
|
|
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
|
|
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", "
|
|
7417
|
-
"check:strict": [TYPECHECK_STEP, "fmt:check", "
|
|
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
|
-
|
|
7440
|
-
|
|
7441
|
-
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
|
|
7446
|
-
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
|
|
7453
|
-
|
|
7454
|
-
|
|
7455
|
-
|
|
7456
|
-
|
|
7457
|
-
|
|
7458
|
-
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
|
|
7465
|
-
|
|
7466
|
-
|
|
7467
|
-
|
|
7468
|
-
|
|
7469
|
-
|
|
7470
|
-
|
|
7471
|
-
|
|
7472
|
-
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
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
|
|
7489
|
-
return { ...
|
|
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
|
|
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(
|
|
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(
|
|
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 (!
|
|
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`) &&
|
|
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
|
|
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((
|
|
8939
|
+
${paths.map((path7) => ` ${path7}`).join("\n")}`;
|
|
8023
8940
|
}
|
|
8024
8941
|
function getMinVersion() {
|
|
8025
|
-
const pickedFields = { "version": "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((
|
|
8068
|
-
const reExports = nonRootConfigs.filter((
|
|
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 (!
|
|
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,
|
|
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,
|