@venizia/dev-configs 0.4.7-2 → 0.4.7-4

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.
@@ -1 +1 @@
1
- {"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../../../src/checks/purity/probe.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,OAAO,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,+EAA+E;IAC/E,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,kIAAkI;AAClI,eAAO,MAAM,kBAAkB,sBAAsB,CAAC;AAStD,wFAAwF;AACxF,qBAAa,eAAe;IAC1B,MAAM,CAAC,QAAQ,CAAC,KAAK,WAAW;IAChC,MAAM,CAAC,QAAQ,CAAC,OAAO,aAAa;IAEpC,MAAM,CAAC,QAAQ,CAAC,UAAU,cAA+C;IAEzE,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,cAAc;CAGvD;AAED,MAAM,MAAM,cAAc,GAAG,OAAO,CAClC,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,OAAO,eAAe,CAAC,EACtD,MAAM,CACP,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,cAAc,CAAC;CAC1B,CAgCA,CAAC;AAiJF,qBAAa,WAAW;IACtB;;;;OAIG;IACH,MAAM,CAAC,wBAAwB,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM;IAI/D;;;OAGG;WACU,UAAU,CAAC,IAAI,EAAE;QAC5B,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;QACZ,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,0DAA0D;QAC1D,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC,aAAa,CAAC;CA8G3B"}
1
+ {"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../../../src/checks/purity/probe.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,OAAO,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,+EAA+E;IAC/E,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,kIAAkI;AAClI,eAAO,MAAM,kBAAkB,sBAAsB,CAAC;AAStD,wFAAwF;AACxF,qBAAa,eAAe;IAC1B,MAAM,CAAC,QAAQ,CAAC,KAAK,WAAW;IAChC,MAAM,CAAC,QAAQ,CAAC,OAAO,aAAa;IAEpC,MAAM,CAAC,QAAQ,CAAC,UAAU,cAA+C;IAEzE,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,cAAc;CAGvD;AAED,MAAM,MAAM,cAAc,GAAG,OAAO,CAClC,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,OAAO,eAAe,CAAC,EACtD,MAAM,CACP,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,cAAc,CAAC;CAC1B,CAgCA,CAAC;AAiJF,qBAAa,WAAW;IACtB;;;;OAIG;IACH,MAAM,CAAC,wBAAwB,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM;IAI/D;;;OAGG;WACU,UAAU,CAAC,IAAI,EAAE;QAC5B,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;QACZ,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,0DAA0D;QAC1D,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC,aAAa,CAAC;CA8G3B"}
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PurityProbe = exports.GLOBAL_PATTERNS = exports.ProbeSeverities = exports.PURITY_SCRATCH_DIR = void 0;
4
- const node_crypto_1 = require("node:crypto");
5
4
  const promises_1 = require("node:fs/promises");
6
5
  const node_module_1 = require("node:module");
7
6
  const node_path_1 = require("node:path");
@@ -200,7 +199,7 @@ class PurityProbe {
200
199
  static async probeEntry(opts) {
201
200
  const { entry, cwd, external = [], scratchDir = (0, node_path_1.join)(cwd, exports.PURITY_SCRATCH_DIR) } = opts;
202
201
  await (0, promises_1.mkdir)(scratchDir, { recursive: true });
203
- const metafilePath = (0, node_path_1.join)(scratchDir, `purity-${(0, node_crypto_1.randomUUID)()}.json`);
202
+ const metafilePath = (0, node_path_1.join)(scratchDir, `purity-${crypto.randomUUID()}.json`);
204
203
  // `--env=disable`: Bun inlines `process.env.NODE_ENV` as a compile-time string constant by
205
204
  // default, regardless of whether the read is reachable. That erases the very read this probe
206
205
  // exists to catch, and the erasure depends on the ambient `NODE_ENV` the probe happens to run
@@ -1 +1 @@
1
- {"version":3,"file":"probe.js","sourceRoot":"","sources":["../../../src/checks/purity/probe.ts"],"names":[],"mappings":";;;AAAA,6CAAyC;AACzC,+CAA6C;AAC7C,6CAA6C;AAC7C,yCAAiC;AAmBjC,kIAAkI;AACrH,QAAA,kBAAkB,GAAG,mBAAmB,CAAC;AAEtD;;;GAGG;AACH,MAAM,gBAAgB,GACpB,2FAA2F,CAAC;AAE9F,wFAAwF;AACxF,MAAa,eAAe;aACV,UAAK,GAAG,OAAO,CAAC;aAChB,YAAO,GAAG,SAAS,CAAC;aAEpB,eAAU,GAAG,IAAI,GAAG,CAAS,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IAEzE,MAAM,CAAC,OAAO,CAAC,KAAa;QAC1B,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACpC,CAAC;;AARH,0CASC;AAOD;;;;;;;;;;;;;;;;;;GAkBG;AACU,QAAA,eAAe,GAIvB;IACH,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,iCAAiC,EAAE,QAAQ,EAAE,OAAO,EAAE;IAClF,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,mCAAmC,EAAE,QAAQ,EAAE,SAAS,EAAE;IACxF;QACE,IAAI,EAAE,oBAAoB;QAC1B,OAAO,EAAE,mDAAmD;QAC5D,QAAQ,EAAE,OAAO;KAClB;IACD;QACE,IAAI,EAAE,sBAAsB;QAC5B,OAAO,EAAE,qDAAqD;QAC9D,QAAQ,EAAE,SAAS;KACpB;IACD;QACE,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,+CAA+C;QACxD,QAAQ,EAAE,OAAO;KAClB;IACD;QACE,IAAI,EAAE,YAAY;QAClB,OAAO,EAAE,gDAAgD;QACzD,QAAQ,EAAE,OAAO;KAClB;IACD;QACE,IAAI,EAAE,eAAe;QACrB,OAAO,EAAE,uCAAuC;QAChD,QAAQ,EAAE,OAAO;KAClB;IACD,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,iCAAiC,EAAE,QAAQ,EAAE,OAAO,EAAE;IACnF,6FAA6F;IAC7F,8CAA8C;IAC9C,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,6BAA6B,EAAE,QAAQ,EAAE,OAAO,EAAE;CAC3E,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,oBAAoB,GAAG,gCAAgC,CAAC;AAgB9D;;;;GAIG;AACH,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC,4BAAc,CAAC,CAAC;AAErD,MAAM,kBAAkB,GAAG,CAAC,SAAiB,EAAW,EAAE;IACxD,MAAM,QAAQ,GAAG,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC7F,OAAO,oBAAoB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;AAC5C,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,qBAAqB,GAAG,CAAC,QAAmB,EAAY,EAAE;IAC9D,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IAEnC,MAAM,qBAAqB,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC9D,KAAK,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,qBAAqB,EAAE,CAAC;QACvD,IAAI,kBAAkB,CAAC,SAAS,CAAC,EAAE,CAAC;YAClC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC1B,CAAC;QAED,KAAK,MAAM,UAAU,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YACvC,MAAM,SAAS,GAAG,UAAU,CAAC,QAAQ,IAAI,UAAU,CAAC,IAAI,CAAC;YACzD,IAAI,kBAAkB,CAAC,SAAS,CAAC,EAAE,CAAC;gBAClC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;AAC9B,CAAC,CAAC;AAEF;;;;;;;;;GASG;AACH;;;;;;;GAOG;AACH,MAAM,mBAAmB,GAAG,CAAC,SAAiB,EAAW,EAAE;IACzD,OAAO,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;AACnE,CAAC,CAAC;AAEF,MAAM,gCAAgC,GAAG,CAAC,IAIzC,EAAY,EAAE;IACb,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,iBAAiB,EAAE,GAAG,IAAI,CAAC;IACvD,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IAErC,MAAM,mBAAmB,GAAG,CAAC,SAAiB,EAAW,EAAE;QACzD,KAAK,MAAM,SAAS,IAAI,iBAAiB,EAAE,CAAC;YAC1C,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,CAAC,UAAU,CAAC,GAAG,SAAS,GAAG,CAAC,EAAE,CAAC;gBACrE,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;IAEF,MAAM,oBAAoB,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC5D,KAAK,MAAM,KAAK,IAAI,oBAAoB,EAAE,CAAC;QACzC,KAAK,MAAM,UAAU,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YACvC,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC;gBACzB,SAAS;YACX,CAAC;YAED,MAAM,SAAS,GAAG,UAAU,CAAC,QAAQ,IAAI,UAAU,CAAC,IAAI,CAAC;YACzD,IACE,mBAAmB,CAAC,SAAS,CAAC;gBAC9B,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC;gBACvB,mBAAmB,CAAC,SAAS,CAAC,EAC9B,CAAC;gBACD,SAAS;YACX,CAAC;YAED,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,UAAU,CAAC,CAAC,IAAI,EAAE,CAAC;AAChC,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,YAAY,GAAG,KAAK,EAAE,IAAY,EAAkC,EAAE;IAC1E,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;QAC3C,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAc,CAAC;IACzC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtE,OAAO,CAAC,KAAK,CAAC,iDAAiD,IAAI,KAAK,MAAM,EAAE,CAAC,CAAC;QAClF,OAAO,SAAS,CAAC;IACnB,CAAC;YAAS,CAAC;QACT,MAAM,IAAA,aAAE,EAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAClC,CAAC;AACH,CAAC,CAAC;AAEF,MAAa,WAAW;IACtB;;;;OAIG;IACH,MAAM,CAAC,wBAAwB,CAAC,IAAsB;QACpD,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC;IACrD,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,IAMvB;QACC,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,GAAG,EAAE,EAAE,UAAU,GAAG,IAAA,gBAAI,EAAC,GAAG,EAAE,0BAAkB,CAAC,EAAE,GAAG,IAAI,CAAC;QACvF,MAAM,IAAA,gBAAK,EAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7C,MAAM,YAAY,GAAG,IAAA,gBAAI,EAAC,UAAU,EAAE,UAAU,IAAA,wBAAU,GAAE,OAAO,CAAC,CAAC;QAErE,2FAA2F;QAC3F,6FAA6F;QAC7F,8FAA8F;QAC9F,wFAAwF;QACxF,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC,CAAC;QAChF,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CACpB;YACE,KAAK;YACL,OAAO;YACP,KAAK;YACL,kBAAkB;YAClB,cAAc;YACd,eAAe;YACf,cAAc,YAAY,EAAE;YAC5B,GAAG,YAAY;SAChB,EACD,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CACxC,CAAC;QAEF,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACnD,IAAI,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE;YAChC,IAAI,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE;YAChC,IAAI,CAAC,MAAM;SACZ,CAAC,CAAC;QAEH,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;YACnB,MAAM,IAAA,aAAE,EAAC,YAAY,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YACxC,OAAO;gBACL,KAAK;gBACL,EAAE,EAAE,KAAK;gBACT,SAAS,EAAE,CAAC;gBACZ,QAAQ,EAAE,EAAE;gBACZ,OAAO,EAAE,EAAE;gBACX,cAAc,EAAE,EAAE;gBAClB,QAAQ;gBACR,UAAU,EAAE,MAAM,CAAC,IAAI,EAAE;aAC1B,CAAC;QACJ,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,YAAY,CAAC,CAAC;QAClD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO;gBACL,KAAK;gBACL,EAAE,EAAE,KAAK;gBACT,SAAS,EAAE,MAAM,CAAC,MAAM;gBACxB,QAAQ,EAAE,EAAE;gBACZ,OAAO,EAAE,EAAE;gBACX,cAAc,EAAE,EAAE;gBAClB,QAAQ;gBACR,UAAU,EAAE,6EAA6E;aAC1F,CAAC;QACJ,CAAC;QAED,MAAM,QAAQ,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QACjD,MAAM,iBAAiB,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CACxE,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC,CACjC,CAAC;QACF,MAAM,mBAAmB,GAAG,gCAAgC,CAAC;YAC3D,QAAQ;YACR,QAAQ,EAAE,IAAI,GAAG,CAAC,QAAQ,CAAC;YAC3B,iBAAiB,EAAE,IAAI,GAAG,CAAC,QAAQ,CAAC;SACrC,CAAC,CAAC;QACH,MAAM,aAAa,GAAG,WAAW,CAAC,wBAAwB,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QAC7E,MAAM,OAAO,GAAG,uBAAe,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC;QAC7E,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,eAAe,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC/F,MAAM,cAAc,GAAG,OAAO;aAC3B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,eAAe,CAAC,OAAO,CAAC;aACrD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAEtB,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,mBAAmB,CAAC,MAAM,GAAG,CAAC,IAAI,iBAAiB,EAAE,CAAC;YAC/E,MAAM,OAAO,GAAa,EAAE,CAAC;YAC7B,IAAI,iBAAiB,EAAE,CAAC;gBACtB,OAAO,CAAC,IAAI,CACV,wFAAwF,CACzF,CAAC;YACJ,CAAC;YACD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACxB,OAAO,CAAC,IAAI,CAAC,2BAA2B,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACjE,CAAC;YACD,IAAI,mBAAmB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACnC,OAAO,CAAC,IAAI,CAAC,kCAAkC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACnF,CAAC;YAED,OAAO;gBACL,KAAK;gBACL,EAAE,EAAE,KAAK;gBACT,SAAS,EAAE,MAAM,CAAC,MAAM;gBACxB,QAAQ;gBACR,OAAO;gBACP,cAAc;gBACd,QAAQ;gBACR,UAAU,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;aAChC,CAAC;QACJ,CAAC;QAED,OAAO;YACL,KAAK;YACL,EAAE,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC;YACxB,SAAS,EAAE,MAAM,CAAC,MAAM;YACxB,QAAQ;YACR,OAAO;YACP,cAAc;YACd,QAAQ;SACT,CAAC;IACJ,CAAC;CACF;AAlID,kCAkIC","sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { mkdir, rm } from 'node:fs/promises';\nimport { builtinModules } from 'node:module';\nimport { join } from 'node:path';\n\nexport interface IPurityResult {\n entry: string;\n ok: boolean;\n sizeBytes: number;\n builtins: string[];\n globals: string[];\n /** Node-global reads that cannot throw in a browser. Reported, never fatal. */\n guardedGlobals: string[];\n /**\n * Specifiers this probe run excluded from its own module graph. Always echoed back, on both the\n * passing and failing path - an entry that externalises something is measuring less than an entry\n * that does not, and that must stay visible next to the result, not be a silent input.\n */\n external: string[];\n buildError?: string;\n}\n\n/** Where the probe keeps its metafile, relative to the `cwd` it is given: the repository's own scratch, never the system temp. */\nexport const PURITY_SCRATCH_DIR = '.agents/local/tmp';\n\n/**\n * A browser entry must never reach `@venizia/dev-configs`: it is the Bun-only tooling package (its\n * `./testing` entry imports `bun:test`). Matches the workspace folder (not its own test fixtures) and the installed copy.\n */\nconst DEV_CONFIGS_PATH =\n /(^|\\/)(packages\\/dev-configs\\/(?!src\\/__tests__\\/)|node_modules\\/@venizia\\/dev-configs\\/)/;\n\n/** What a matched global means: ERROR is fatal, GUARDED is reported and never fatal. */\nexport class ProbeSeverities {\n static readonly ERROR = 'error';\n static readonly GUARDED = 'guarded';\n\n static readonly SCHEME_SET = new Set<string>([this.ERROR, this.GUARDED]);\n\n static isValid(value: string): value is TProbeSeverity {\n return this.SCHEME_SET.has(value);\n }\n}\n\nexport type TProbeSeverity = Extract<\n (typeof ProbeSeverities)[keyof typeof ProbeSeverities],\n string\n>;\n\n/**\n * Node globals survive verbatim into a `target: 'browser'` bundle, so a gate reading only module\n * specifiers reports clean on `process.env.NODE_ENV`. Each pattern must be anchored: a bundle\n * legitimately contains identifiers like `processQueue`. A lookbehind that rejects a preceding dot\n * also rejects `globalThis.process`, so every access path needs its own pattern.\n *\n * `severity` separates a read that throws from one that cannot. Optional chaining is not itself the\n * difference: `process?.env` throws a ReferenceError when `process` is a free identifier, while\n * `globalThis.process?.env` is an ordinary property read on an object that always exists.\n *\n * `guarded` means \"reported, never fatal\". Bare `process?.` sits there because this probe matches\n * bundled text and has no scope information: hono's `getColorEnabled` writes\n * `const { process, Deno } = globalThis;` and then `process?.env`, which is a local binding and\n * perfectly safe, and no regex can tell that apart from a free global. Failing on it would make the\n * gate cry wolf on a dependency; the unguarded `process.` and `globalThis.process.` forms stay\n * fatal, and those are the ones that are unambiguously browser-breaking.\n *\n * Exported for `probe.test.ts` to assert against directly, alongside {@link PurityProbe.stripBundlerPathComments}.\n */\nexport const GLOBAL_PATTERNS: Array<{\n name: string;\n pattern: RegExp;\n severity: TProbeSeverity;\n}> = [\n { name: 'process', pattern: /(?<![A-Za-z0-9_$.])process\\s*\\./, severity: 'error' },\n { name: 'process?.', pattern: /(?<![A-Za-z0-9_$.])process\\s*\\?\\./, severity: 'guarded' },\n {\n name: 'globalThis.process',\n pattern: /(?<![A-Za-z0-9_$.])globalThis\\s*\\.\\s*process\\s*\\./,\n severity: 'error',\n },\n {\n name: 'globalThis.process?.',\n pattern: /(?<![A-Za-z0-9_$.])globalThis\\s*\\.\\s*process\\s*\\?\\./,\n severity: 'guarded',\n },\n {\n name: '__dirname',\n pattern: /(?<![A-Za-z0-9_$.])__dirname(?![A-Za-z0-9_$])/,\n severity: 'error',\n },\n {\n name: '__filename',\n pattern: /(?<![A-Za-z0-9_$.])__filename(?![A-Za-z0-9_$])/,\n severity: 'error',\n },\n {\n name: 'createRequire',\n pattern: /(?<![A-Za-z0-9_$.])createRequire\\s*\\(/,\n severity: 'error',\n },\n { name: 'require(', pattern: /(?<![A-Za-z0-9_$.])require\\s*\\(/, severity: 'error' },\n // Member access only. `typeof Bun !== 'undefined'` is the legitimate runtime probe and never\n // throws; `Bun.serve(...)` in a browser does.\n { name: 'Bun', pattern: /(?<![A-Za-z0-9_$.])Bun\\s*\\./, severity: 'error' },\n];\n\n/**\n * Bun's bundler prefixes every CommonJS-wrapped module with a full-line `// <path>.js` header (this\n * whole workspace emits CJS `dist/*.js` - no package declares `\"type\": \"module\"`). A source path is\n * free-form and can legitimately contain `process` as a substring with no identifier boundary on\n * its left, e.g. this gate's own `transport/in-process.js`: the header reads\n * `// packages/core-worker/dist/transport/in-process.js`, and `process.` there is a filename\n * fragment, not a read of the global. `GLOBAL_PATTERNS` matches bundled TEXT with no scope\n * information, so it cannot tell a path comment from code - stripped before matching.\n */\nconst BUNDLER_PATH_COMMENT = /^\\/\\/ [\\w@./-]+\\.(?:c|m)?js$/gm;\n\ninterface IMetafileImport {\n path: string;\n original?: string;\n external?: boolean;\n}\n\ninterface IMetafileInput {\n imports: IMetafileImport[];\n}\n\ninterface IMetafile {\n inputs: Record<string, IMetafileInput>;\n}\n\n/**\n * `node:module`'s own list, not a hand-maintained one: a bare `import ... from 'fs'` bundles\n * identically to `'node:fs'` (Bun stubs both to an empty object), and the metafile records\n * whichever spelling the source used. Matching by prefix alone misses the bare form entirely.\n */\nconst BUILTIN_MODULE_NAMES = new Set(builtinModules);\n\nconst isBuiltinSpecifier = (specifier: string): boolean => {\n const bareName = specifier.startsWith('node:') ? specifier.slice('node:'.length) : specifier;\n return BUILTIN_MODULE_NAMES.has(bareName);\n};\n\n/**\n * Measured against Bun 1.3.14: `--target=browser` never fails on a `node:` import and never leaves\n * the specifier in the bundled text. An unpolyfillable builtin (`fs`, `child_process`, ...) is\n * stubbed to an empty object; a polyfillable one (`crypto`, `path`, ...) is inlined in full under a\n * `/bun-vfs$$/` virtual path. Either way `exitCode` stays 0 and the specifier never survives into\n * stdout. The `--metafile` module graph is the only place it is preserved.\n */\nconst findBuiltinSpecifiers = (metafile: IMetafile): string[] => {\n const builtins = new Set<string>();\n\n const metafileInputsEntries = Object.entries(metafile.inputs);\n for (const [inputPath, input] of metafileInputsEntries) {\n if (isBuiltinSpecifier(inputPath)) {\n builtins.add(inputPath);\n }\n\n for (const dependency of input.imports) {\n const specifier = dependency.original ?? dependency.path;\n if (isBuiltinSpecifier(specifier)) {\n builtins.add(specifier);\n }\n }\n }\n\n return [...builtins].sort();\n};\n\n/**\n * A real npm dependency bundles in full under this target (measured: `zod` produces zero\n * `external` entries) and a genuinely missing package fails the build outright (nonzero exit,\n * caught before this runs). But a package's own `browser` field can remap a dependency to `false` -\n * the standard convention for disabling a Node-only dependency in browser builds - and Bun honours\n * that remap by leaving the import external with exit 0, whether or not the remapped name is a\n * builtin. An unresolved import in a browser bundle is not safe to wave through either way -\n * UNLESS the manifest entry asked for exactly this specifier via `external`, in which case its\n * presence here is the requested exclusion working, not a leak.\n */\n/**\n * A relative specifier is the package's own file, and a truly unreachable one fails the build with a\n * nonzero exit, which is caught before this runs. Bun's METAFILE nonetheless marks a re-export of the\n * form `export { name } from './x.js'` as `external` while inlining it - measured on\n * `helpers/dist/esm/core.js`: six such specifiers reported external, `Bundled 192 modules`, and the\n * output contains `class BaseHelper` with no bare relative import left in it. Reading the metafile\n * literally there reports a leak the bundle does not have.\n */\nconst isRelativeSpecifier = (specifier: string): boolean => {\n return specifier.startsWith('./') || specifier.startsWith('../');\n};\n\nconst findUnresolvedExternalSpecifiers = (opts: {\n metafile: IMetafile;\n builtins: Set<string>;\n requestedExternal: Set<string>;\n}): string[] => {\n const { metafile, builtins, requestedExternal } = opts;\n const unresolved = new Set<string>();\n\n const isRequestedExternal = (specifier: string): boolean => {\n for (const requested of requestedExternal) {\n if (specifier === requested || specifier.startsWith(`${requested}/`)) {\n return true;\n }\n }\n return false;\n };\n\n const metafileInputsValues = Object.values(metafile.inputs);\n for (const input of metafileInputsValues) {\n for (const dependency of input.imports) {\n if (!dependency.external) {\n continue;\n }\n\n const specifier = dependency.original ?? dependency.path;\n if (\n isRelativeSpecifier(specifier) ||\n builtins.has(specifier) ||\n isRequestedExternal(specifier)\n ) {\n continue;\n }\n\n unresolved.add(specifier);\n }\n }\n\n return [...unresolved].sort();\n};\n\n/**\n * Reads and removes the metafile unconditionally. Returns `undefined` rather than throwing -\n * `PurityProbe.probeEntry` reports on every entry it is given rather than aborting the run of the other five.\n */\nconst readMetafile = async (path: string): Promise<IMetafile | undefined> => {\n try {\n const result = await Bun.file(path).text();\n return JSON.parse(result) as IMetafile;\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n console.error(`[purity] Failed to read the build metafile at ${path}: ${reason}`);\n return undefined;\n } finally {\n await rm(path, { force: true });\n }\n};\n\nexport class PurityProbe {\n /**\n * Public so `probe.test.ts` can assert against the literal collision string directly, without\n * spawning `bun build` - a fixture whose OWN entry-path comment happens to reproduce the collision\n * is not something a filename choice can be relied on to keep reproducing.\n */\n static stripBundlerPathComments(opts: { text: string }): string {\n return opts.text.replace(BUNDLER_PATH_COMMENT, '');\n }\n\n /**\n * The CLI, not `Bun.build()`: the in-process bundler resolves this workspace's symlinked\n * dependencies to directories and dies on them.\n */\n static async probeEntry(opts: {\n entry: string;\n cwd: string;\n external?: string[];\n /** Defaults to {@link PURITY_SCRATCH_DIR} under `cwd`. */\n scratchDir?: string;\n }): Promise<IPurityResult> {\n const { entry, cwd, external = [], scratchDir = join(cwd, PURITY_SCRATCH_DIR) } = opts;\n await mkdir(scratchDir, { recursive: true });\n const metafilePath = join(scratchDir, `purity-${randomUUID()}.json`);\n\n // `--env=disable`: Bun inlines `process.env.NODE_ENV` as a compile-time string constant by\n // default, regardless of whether the read is reachable. That erases the very read this probe\n // exists to catch, and the erasure depends on the ambient `NODE_ENV` the probe happens to run\n // under - the same failure shape as the undetected leak this gate was built to prevent.\n const externalArgs = external.flatMap((specifier) => ['--external', specifier]);\n const proc = Bun.spawn(\n [\n 'bun',\n 'build',\n entry,\n '--target=browser',\n '--format=esm',\n '--env=disable',\n `--metafile=${metafilePath}`,\n ...externalArgs,\n ],\n { cwd, stdout: 'pipe', stderr: 'pipe' },\n );\n\n const [stdout, stderr, exitCode] = await Promise.all([\n new Response(proc.stdout).text(),\n new Response(proc.stderr).text(),\n proc.exited,\n ]);\n\n if (exitCode !== 0) {\n await rm(metafilePath, { force: true });\n return {\n entry,\n ok: false,\n sizeBytes: 0,\n builtins: [],\n globals: [],\n guardedGlobals: [],\n external,\n buildError: stderr.trim(),\n };\n }\n\n const metafile = await readMetafile(metafilePath);\n if (!metafile) {\n return {\n entry,\n ok: false,\n sizeBytes: stdout.length,\n builtins: [],\n globals: [],\n guardedGlobals: [],\n external,\n buildError: 'Could not read the bun build metafile - the probe result is not trustworthy',\n };\n }\n\n const builtins = findBuiltinSpecifiers(metafile);\n const reachesDevConfigs = Object.keys(metafile.inputs).some((inputPath) =>\n DEV_CONFIGS_PATH.test(inputPath),\n );\n const unresolvedExternals = findUnresolvedExternalSpecifiers({\n metafile,\n builtins: new Set(builtins),\n requestedExternal: new Set(external),\n });\n const scannableText = PurityProbe.stripBundlerPathComments({ text: stdout });\n const matched = GLOBAL_PATTERNS.filter((g) => g.pattern.test(scannableText));\n const globals = matched.filter((g) => g.severity === ProbeSeverities.ERROR).map((g) => g.name);\n const guardedGlobals = matched\n .filter((g) => g.severity === ProbeSeverities.GUARDED)\n .map((g) => g.name);\n\n if (builtins.length > 0 || unresolvedExternals.length > 0 || reachesDevConfigs) {\n const reasons: string[] = [];\n if (reachesDevConfigs) {\n reasons.push(\n 'reaches @venizia/dev-configs, which is Bun-only tooling and never a browser dependency',\n );\n }\n if (builtins.length > 0) {\n reasons.push(`node builtin import(s): ${builtins.join(', ')}`);\n }\n if (unresolvedExternals.length > 0) {\n reasons.push(`unresolved external import(s): ${unresolvedExternals.join(', ')}`);\n }\n\n return {\n entry,\n ok: false,\n sizeBytes: stdout.length,\n builtins,\n globals,\n guardedGlobals,\n external,\n buildError: reasons.join(' | '),\n };\n }\n\n return {\n entry,\n ok: globals.length === 0,\n sizeBytes: stdout.length,\n builtins,\n globals,\n guardedGlobals,\n external,\n };\n }\n}\n"]}
1
+ {"version":3,"file":"probe.js","sourceRoot":"","sources":["../../../src/checks/purity/probe.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdir, rm } from 'node:fs/promises';\nimport { builtinModules } from 'node:module';\nimport { join } from 'node:path';\n\nexport interface IPurityResult {\n entry: string;\n ok: boolean;\n sizeBytes: number;\n builtins: string[];\n globals: string[];\n /** Node-global reads that cannot throw in a browser. Reported, never fatal. */\n guardedGlobals: string[];\n /**\n * Specifiers this probe run excluded from its own module graph. Always echoed back, on both the\n * passing and failing path - an entry that externalises something is measuring less than an entry\n * that does not, and that must stay visible next to the result, not be a silent input.\n */\n external: string[];\n buildError?: string;\n}\n\n/** Where the probe keeps its metafile, relative to the `cwd` it is given: the repository's own scratch, never the system temp. */\nexport const PURITY_SCRATCH_DIR = '.agents/local/tmp';\n\n/**\n * A browser entry must never reach `@venizia/dev-configs`: it is the Bun-only tooling package (its\n * `./testing` entry imports `bun:test`). Matches the workspace folder (not its own test fixtures) and the installed copy.\n */\nconst DEV_CONFIGS_PATH =\n /(^|\\/)(packages\\/dev-configs\\/(?!src\\/__tests__\\/)|node_modules\\/@venizia\\/dev-configs\\/)/;\n\n/** What a matched global means: ERROR is fatal, GUARDED is reported and never fatal. */\nexport class ProbeSeverities {\n static readonly ERROR = 'error';\n static readonly GUARDED = 'guarded';\n\n static readonly SCHEME_SET = new Set<string>([this.ERROR, this.GUARDED]);\n\n static isValid(value: string): value is TProbeSeverity {\n return this.SCHEME_SET.has(value);\n }\n}\n\nexport type TProbeSeverity = Extract<\n (typeof ProbeSeverities)[keyof typeof ProbeSeverities],\n string\n>;\n\n/**\n * Node globals survive verbatim into a `target: 'browser'` bundle, so a gate reading only module\n * specifiers reports clean on `process.env.NODE_ENV`. Each pattern must be anchored: a bundle\n * legitimately contains identifiers like `processQueue`. A lookbehind that rejects a preceding dot\n * also rejects `globalThis.process`, so every access path needs its own pattern.\n *\n * `severity` separates a read that throws from one that cannot. Optional chaining is not itself the\n * difference: `process?.env` throws a ReferenceError when `process` is a free identifier, while\n * `globalThis.process?.env` is an ordinary property read on an object that always exists.\n *\n * `guarded` means \"reported, never fatal\". Bare `process?.` sits there because this probe matches\n * bundled text and has no scope information: hono's `getColorEnabled` writes\n * `const { process, Deno } = globalThis;` and then `process?.env`, which is a local binding and\n * perfectly safe, and no regex can tell that apart from a free global. Failing on it would make the\n * gate cry wolf on a dependency; the unguarded `process.` and `globalThis.process.` forms stay\n * fatal, and those are the ones that are unambiguously browser-breaking.\n *\n * Exported for `probe.test.ts` to assert against directly, alongside {@link PurityProbe.stripBundlerPathComments}.\n */\nexport const GLOBAL_PATTERNS: Array<{\n name: string;\n pattern: RegExp;\n severity: TProbeSeverity;\n}> = [\n { name: 'process', pattern: /(?<![A-Za-z0-9_$.])process\\s*\\./, severity: 'error' },\n { name: 'process?.', pattern: /(?<![A-Za-z0-9_$.])process\\s*\\?\\./, severity: 'guarded' },\n {\n name: 'globalThis.process',\n pattern: /(?<![A-Za-z0-9_$.])globalThis\\s*\\.\\s*process\\s*\\./,\n severity: 'error',\n },\n {\n name: 'globalThis.process?.',\n pattern: /(?<![A-Za-z0-9_$.])globalThis\\s*\\.\\s*process\\s*\\?\\./,\n severity: 'guarded',\n },\n {\n name: '__dirname',\n pattern: /(?<![A-Za-z0-9_$.])__dirname(?![A-Za-z0-9_$])/,\n severity: 'error',\n },\n {\n name: '__filename',\n pattern: /(?<![A-Za-z0-9_$.])__filename(?![A-Za-z0-9_$])/,\n severity: 'error',\n },\n {\n name: 'createRequire',\n pattern: /(?<![A-Za-z0-9_$.])createRequire\\s*\\(/,\n severity: 'error',\n },\n { name: 'require(', pattern: /(?<![A-Za-z0-9_$.])require\\s*\\(/, severity: 'error' },\n // Member access only. `typeof Bun !== 'undefined'` is the legitimate runtime probe and never\n // throws; `Bun.serve(...)` in a browser does.\n { name: 'Bun', pattern: /(?<![A-Za-z0-9_$.])Bun\\s*\\./, severity: 'error' },\n];\n\n/**\n * Bun's bundler prefixes every CommonJS-wrapped module with a full-line `// <path>.js` header (this\n * whole workspace emits CJS `dist/*.js` - no package declares `\"type\": \"module\"`). A source path is\n * free-form and can legitimately contain `process` as a substring with no identifier boundary on\n * its left, e.g. this gate's own `transport/in-process.js`: the header reads\n * `// packages/core-worker/dist/transport/in-process.js`, and `process.` there is a filename\n * fragment, not a read of the global. `GLOBAL_PATTERNS` matches bundled TEXT with no scope\n * information, so it cannot tell a path comment from code - stripped before matching.\n */\nconst BUNDLER_PATH_COMMENT = /^\\/\\/ [\\w@./-]+\\.(?:c|m)?js$/gm;\n\ninterface IMetafileImport {\n path: string;\n original?: string;\n external?: boolean;\n}\n\ninterface IMetafileInput {\n imports: IMetafileImport[];\n}\n\ninterface IMetafile {\n inputs: Record<string, IMetafileInput>;\n}\n\n/**\n * `node:module`'s own list, not a hand-maintained one: a bare `import ... from 'fs'` bundles\n * identically to `'node:fs'` (Bun stubs both to an empty object), and the metafile records\n * whichever spelling the source used. Matching by prefix alone misses the bare form entirely.\n */\nconst BUILTIN_MODULE_NAMES = new Set(builtinModules);\n\nconst isBuiltinSpecifier = (specifier: string): boolean => {\n const bareName = specifier.startsWith('node:') ? specifier.slice('node:'.length) : specifier;\n return BUILTIN_MODULE_NAMES.has(bareName);\n};\n\n/**\n * Measured against Bun 1.3.14: `--target=browser` never fails on a `node:` import and never leaves\n * the specifier in the bundled text. An unpolyfillable builtin (`fs`, `child_process`, ...) is\n * stubbed to an empty object; a polyfillable one (`crypto`, `path`, ...) is inlined in full under a\n * `/bun-vfs$$/` virtual path. Either way `exitCode` stays 0 and the specifier never survives into\n * stdout. The `--metafile` module graph is the only place it is preserved.\n */\nconst findBuiltinSpecifiers = (metafile: IMetafile): string[] => {\n const builtins = new Set<string>();\n\n const metafileInputsEntries = Object.entries(metafile.inputs);\n for (const [inputPath, input] of metafileInputsEntries) {\n if (isBuiltinSpecifier(inputPath)) {\n builtins.add(inputPath);\n }\n\n for (const dependency of input.imports) {\n const specifier = dependency.original ?? dependency.path;\n if (isBuiltinSpecifier(specifier)) {\n builtins.add(specifier);\n }\n }\n }\n\n return [...builtins].sort();\n};\n\n/**\n * A real npm dependency bundles in full under this target (measured: `zod` produces zero\n * `external` entries) and a genuinely missing package fails the build outright (nonzero exit,\n * caught before this runs). But a package's own `browser` field can remap a dependency to `false` -\n * the standard convention for disabling a Node-only dependency in browser builds - and Bun honours\n * that remap by leaving the import external with exit 0, whether or not the remapped name is a\n * builtin. An unresolved import in a browser bundle is not safe to wave through either way -\n * UNLESS the manifest entry asked for exactly this specifier via `external`, in which case its\n * presence here is the requested exclusion working, not a leak.\n */\n/**\n * A relative specifier is the package's own file, and a truly unreachable one fails the build with a\n * nonzero exit, which is caught before this runs. Bun's METAFILE nonetheless marks a re-export of the\n * form `export { name } from './x.js'` as `external` while inlining it - measured on\n * `helpers/dist/esm/core.js`: six such specifiers reported external, `Bundled 192 modules`, and the\n * output contains `class BaseHelper` with no bare relative import left in it. Reading the metafile\n * literally there reports a leak the bundle does not have.\n */\nconst isRelativeSpecifier = (specifier: string): boolean => {\n return specifier.startsWith('./') || specifier.startsWith('../');\n};\n\nconst findUnresolvedExternalSpecifiers = (opts: {\n metafile: IMetafile;\n builtins: Set<string>;\n requestedExternal: Set<string>;\n}): string[] => {\n const { metafile, builtins, requestedExternal } = opts;\n const unresolved = new Set<string>();\n\n const isRequestedExternal = (specifier: string): boolean => {\n for (const requested of requestedExternal) {\n if (specifier === requested || specifier.startsWith(`${requested}/`)) {\n return true;\n }\n }\n return false;\n };\n\n const metafileInputsValues = Object.values(metafile.inputs);\n for (const input of metafileInputsValues) {\n for (const dependency of input.imports) {\n if (!dependency.external) {\n continue;\n }\n\n const specifier = dependency.original ?? dependency.path;\n if (\n isRelativeSpecifier(specifier) ||\n builtins.has(specifier) ||\n isRequestedExternal(specifier)\n ) {\n continue;\n }\n\n unresolved.add(specifier);\n }\n }\n\n return [...unresolved].sort();\n};\n\n/**\n * Reads and removes the metafile unconditionally. Returns `undefined` rather than throwing -\n * `PurityProbe.probeEntry` reports on every entry it is given rather than aborting the run of the other five.\n */\nconst readMetafile = async (path: string): Promise<IMetafile | undefined> => {\n try {\n const result = await Bun.file(path).text();\n return JSON.parse(result) as IMetafile;\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n console.error(`[purity] Failed to read the build metafile at ${path}: ${reason}`);\n return undefined;\n } finally {\n await rm(path, { force: true });\n }\n};\n\nexport class PurityProbe {\n /**\n * Public so `probe.test.ts` can assert against the literal collision string directly, without\n * spawning `bun build` - a fixture whose OWN entry-path comment happens to reproduce the collision\n * is not something a filename choice can be relied on to keep reproducing.\n */\n static stripBundlerPathComments(opts: { text: string }): string {\n return opts.text.replace(BUNDLER_PATH_COMMENT, '');\n }\n\n /**\n * The CLI, not `Bun.build()`: the in-process bundler resolves this workspace's symlinked\n * dependencies to directories and dies on them.\n */\n static async probeEntry(opts: {\n entry: string;\n cwd: string;\n external?: string[];\n /** Defaults to {@link PURITY_SCRATCH_DIR} under `cwd`. */\n scratchDir?: string;\n }): Promise<IPurityResult> {\n const { entry, cwd, external = [], scratchDir = join(cwd, PURITY_SCRATCH_DIR) } = opts;\n await mkdir(scratchDir, { recursive: true });\n const metafilePath = join(scratchDir, `purity-${crypto.randomUUID()}.json`);\n\n // `--env=disable`: Bun inlines `process.env.NODE_ENV` as a compile-time string constant by\n // default, regardless of whether the read is reachable. That erases the very read this probe\n // exists to catch, and the erasure depends on the ambient `NODE_ENV` the probe happens to run\n // under - the same failure shape as the undetected leak this gate was built to prevent.\n const externalArgs = external.flatMap((specifier) => ['--external', specifier]);\n const proc = Bun.spawn(\n [\n 'bun',\n 'build',\n entry,\n '--target=browser',\n '--format=esm',\n '--env=disable',\n `--metafile=${metafilePath}`,\n ...externalArgs,\n ],\n { cwd, stdout: 'pipe', stderr: 'pipe' },\n );\n\n const [stdout, stderr, exitCode] = await Promise.all([\n new Response(proc.stdout).text(),\n new Response(proc.stderr).text(),\n proc.exited,\n ]);\n\n if (exitCode !== 0) {\n await rm(metafilePath, { force: true });\n return {\n entry,\n ok: false,\n sizeBytes: 0,\n builtins: [],\n globals: [],\n guardedGlobals: [],\n external,\n buildError: stderr.trim(),\n };\n }\n\n const metafile = await readMetafile(metafilePath);\n if (!metafile) {\n return {\n entry,\n ok: false,\n sizeBytes: stdout.length,\n builtins: [],\n globals: [],\n guardedGlobals: [],\n external,\n buildError: 'Could not read the bun build metafile - the probe result is not trustworthy',\n };\n }\n\n const builtins = findBuiltinSpecifiers(metafile);\n const reachesDevConfigs = Object.keys(metafile.inputs).some((inputPath) =>\n DEV_CONFIGS_PATH.test(inputPath),\n );\n const unresolvedExternals = findUnresolvedExternalSpecifiers({\n metafile,\n builtins: new Set(builtins),\n requestedExternal: new Set(external),\n });\n const scannableText = PurityProbe.stripBundlerPathComments({ text: stdout });\n const matched = GLOBAL_PATTERNS.filter((g) => g.pattern.test(scannableText));\n const globals = matched.filter((g) => g.severity === ProbeSeverities.ERROR).map((g) => g.name);\n const guardedGlobals = matched\n .filter((g) => g.severity === ProbeSeverities.GUARDED)\n .map((g) => g.name);\n\n if (builtins.length > 0 || unresolvedExternals.length > 0 || reachesDevConfigs) {\n const reasons: string[] = [];\n if (reachesDevConfigs) {\n reasons.push(\n 'reaches @venizia/dev-configs, which is Bun-only tooling and never a browser dependency',\n );\n }\n if (builtins.length > 0) {\n reasons.push(`node builtin import(s): ${builtins.join(', ')}`);\n }\n if (unresolvedExternals.length > 0) {\n reasons.push(`unresolved external import(s): ${unresolvedExternals.join(', ')}`);\n }\n\n return {\n entry,\n ok: false,\n sizeBytes: stdout.length,\n builtins,\n globals,\n guardedGlobals,\n external,\n buildError: reasons.join(' | '),\n };\n }\n\n return {\n entry,\n ok: globals.length === 0,\n sizeBytes: stdout.length,\n builtins,\n globals,\n guardedGlobals,\n external,\n };\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"test-mutate.d.ts","sourceRoot":"","sources":["../../src/checks/test-mutate.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;GAUG;AACH,MAAM,WAAW,OAAO;IACtB,yFAAyF;IACzF,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,MAAM,EAAE,OAAO,GAAG,OAAO,EAAE,CAAC;IAC5B,4FAA4F;IAC5F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iHAAiH;IACjH,eAAe,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC;IACpC,YAAY,CAAC,EAAE,OAAO,GAAG,OAAO,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,EAAE,OAAO,CAAC;IACtB,kGAAkG;IAClG,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAgJD,qBAAa,eAAe;IAC1B,6GAA6G;IAC7G,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,aAAa,EAAE;IAyD/F,6EAA6E;IAC7E,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,aAAa,EAAE,CAAA;KAAE,GAAG,MAAM,EAAE;CAazE"}
1
+ {"version":3,"file":"test-mutate.d.ts","sourceRoot":"","sources":["../../src/checks/test-mutate.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AACH,MAAM,WAAW,OAAO;IACtB,yFAAyF;IACzF,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,MAAM,EAAE,OAAO,GAAG,OAAO,EAAE,CAAC;IAC5B,4FAA4F;IAC5F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iHAAiH;IACjH,eAAe,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC;IACpC,YAAY,CAAC,EAAE,OAAO,GAAG,OAAO,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,EAAE,OAAO,CAAC;IACtB,kGAAkG;IAClG,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA6ID,qBAAa,eAAe;IAC1B,6GAA6G;IAC7G,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,aAAa,EAAE;IAyD/F,6EAA6E;IAC7E,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,aAAa,EAAE,CAAA;KAAE,GAAG,MAAM,EAAE;CAazE"}
@@ -2,14 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TestMutateCheck = void 0;
4
4
  const node_child_process_1 = require("node:child_process");
5
- const node_crypto_1 = require("node:crypto");
6
5
  const node_fs_1 = require("node:fs");
7
6
  const node_path_1 = require("node:path");
8
7
  const TEST_TIMEOUT_MILLISECONDS = 300_000;
9
8
  /** The one place this tool builds an `Error`: `getError` would make dev-configs depend on inversion, which depends on dev-configs. */
10
9
  // eslint-disable-next-line venizia/no-new-error -- getError would be a dev-configs -> inversion cycle
11
10
  const replayError = (message) => new Error(message);
12
- const checksum = (bytes) => (0, node_crypto_1.createHash)('sha256').update(bytes).digest('hex');
13
11
  /** The nearest folder at or above the test file that holds a `package.json`: where `bun test` runs. */
14
12
  const packageDirectoryOf = (opts) => {
15
13
  let current = (0, node_path_1.dirname)((0, node_path_1.join)(opts.root, opts.file));
@@ -49,7 +47,7 @@ const runIsRed = (opts) => {
49
47
  }
50
48
  return result.status !== 0 || failed > 0;
51
49
  };
52
- /** Applies `mutant`, runs `action`, and puts the file back byte for byte, verified by checksum. */
50
+ /** Applies `mutant`, runs `action`, and puts the file back byte for byte, verified byte for byte. */
53
51
  const withMutant = (opts) => {
54
52
  const path = (0, node_path_1.join)(opts.root, opts.mutant.file);
55
53
  const original = (0, node_fs_1.readFileSync)(path);
@@ -64,7 +62,6 @@ const withMutant = (opts) => {
64
62
  throw replayError(`[test-mutate][${opts.id}] a backup from an interrupted run exists: ${backup} | restore ${opts.mutant.file} from it, then delete it`);
65
63
  }
66
64
  (0, node_fs_1.writeFileSync)(backup, original);
67
- const expected = checksum(original);
68
65
  let outcome;
69
66
  try {
70
67
  (0, node_fs_1.writeFileSync)(path, text.replace(opts.mutant.find, () => opts.mutant.replace));
@@ -75,7 +72,7 @@ const withMutant = (opts) => {
75
72
  }
76
73
  // Restored whatever the action did, and checked, before its result or error is passed on.
77
74
  (0, node_fs_1.writeFileSync)(path, original);
78
- if (checksum((0, node_fs_1.readFileSync)(path)) !== expected) {
75
+ if (!(0, node_fs_1.readFileSync)(path).equals(original)) {
79
76
  throw replayError(`[test-mutate][${opts.id}] ${opts.mutant.file} was not restored | the backup is ${backup}`);
80
77
  }
81
78
  (0, node_fs_1.rmSync)(backup, { force: true });
@@ -84,7 +81,7 @@ const withMutant = (opts) => {
84
81
  }
85
82
  return outcome.value;
86
83
  };
87
- /** Applies every edit of `mutant`, runs `action`, and puts each file back byte for byte, verified by checksum. */
84
+ /** Applies every edit of `mutant`, runs `action`, and puts each file back byte for byte, verified byte for byte. */
88
85
  const withMutants = (opts) => {
89
86
  const edits = Array.isArray(opts.mutant) ? opts.mutant : [opts.mutant];
90
87
  const apply = (index) => index === edits.length
@@ -1 +1 @@
1
- {"version":3,"file":"test-mutate.js","sourceRoot":"","sources":["../../src/checks/test-mutate.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from 'node:child_process';\nimport { createHash } from 'node:crypto';\nimport { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\n\n/**\n * Replays the mutation proof of the trim-tests method. A ledger line says: \"this test (the\n * candidate) is redundant because that test (the survivor) already catches the same break\". The\n * replay applies the break (the mutant) to a file, runs the survivor and the candidate, and puts the\n * file back. The survivor must go red. The candidate must go red too, unless the line says it is\n * vacuous and a second mutant leaves it green as well.\n *\n * The mutant edits the file the test really loads: the package's own `src` for a test that imports\n * through the `@/` alias, or the `dist` of the dependency for a test that reaches another package\n * through its `exports`. A rebuild is never needed.\n */\nexport interface IMutant {\n /** Repository-relative file to edit (a `src` file, or a `dist` file of a dependency). */\n file: string;\n /** Text that must occur exactly once in the file. */\n find: string;\n replace: string;\n}\n\nexport interface ILedgerLine {\n id: string;\n /** Repository-relative test file that is deleted. */\n candidate: string;\n /** Run only the cases whose name matches (`bun test -t`). Default: the whole file. */\n candidateName?: string;\n /** Repository-relative test file that stays. */\n survivor: string;\n survivorName?: string;\n /**\n * The break. An array applies every edit together: a survivor in the package that owns the code\n * loads its `src`, a candidate in another package loads that package's `dist`, so one break is two edits.\n */\n mutant: IMutant | IMutant[];\n /** The source line the mutant stands for, `path:line`; recorded, not used by the replay. */\n srcLine?: string;\n /** `red` (default): the candidate must fail. `vacuous`: it must stay green under `mutant` AND `secondMutant`. */\n expectCandidate?: 'red' | 'vacuous';\n secondMutant?: IMutant | IMutant[];\n}\n\nexport interface ILedger {\n mutants: ILedgerLine[];\n}\n\nexport interface ILedgerResult {\n id: string;\n survivorRed: boolean;\n candidateRed: boolean;\n /** Only for a `vacuous` line: whether the candidate also stayed green under the second mutant. */\n candidateGreenUnderSecond?: boolean;\n ok: boolean;\n problem?: string;\n}\n\nconst TEST_TIMEOUT_MILLISECONDS = 300_000;\n\n/** The one place this tool builds an `Error`: `getError` would make dev-configs depend on inversion, which depends on dev-configs. */\n// eslint-disable-next-line venizia/no-new-error -- getError would be a dev-configs -> inversion cycle\nconst replayError = (message: string): Error => new Error(message);\n\nconst checksum = (bytes: Buffer): string => createHash('sha256').update(bytes).digest('hex');\n\n/** The nearest folder at or above the test file that holds a `package.json`: where `bun test` runs. */\nconst packageDirectoryOf = (opts: { root: string; file: string }): string => {\n let current = dirname(join(opts.root, opts.file));\n\n for (;;) {\n if (existsSync(join(current, 'package.json'))) {\n return current;\n }\n\n const parent = dirname(current);\n if (parent === current || parent.length < opts.root.length) {\n return opts.root;\n }\n\n current = parent;\n }\n};\n\n/** Runs one test file (or the cases matching `name`) and reports whether it failed. */\nconst runIsRed = (opts: { root: string; file: string; name?: string }): boolean => {\n const directory = packageDirectoryOf({ root: opts.root, file: opts.file });\n const target = join(opts.root, opts.file).slice(directory.length + 1);\n const args = ['test', target, ...(opts.name ? ['-t', opts.name] : [])];\n const result = spawnSync('bun', args, {\n cwd: directory,\n encoding: 'utf8',\n timeout: TEST_TIMEOUT_MILLISECONDS,\n killSignal: 'SIGKILL',\n env: { ...process.env, NODE_ENV: 'test' },\n });\n\n if (result.error) {\n throw replayError(`[test-mutate] could not run bun test ${target}: ${result.error.message}`);\n }\n\n // A name filter that matches nothing runs no case and exits 0: that would read as \"green\" and could\n // hide a typo as a vacuous test, so a run that executed nothing is an unusable ledger line.\n const output = `${result.stdout ?? ''}${result.stderr ?? ''}`;\n const passed = Number(/^\\s*(\\d+) pass/m.exec(output)?.[1] ?? 0);\n const failed = Number(/^\\s*(\\d+) fail/m.exec(output)?.[1] ?? 0);\n\n if (passed + failed === 0) {\n throw replayError(\n `[test-mutate] bun test ${target}${opts.name ? ` -t ${opts.name}` : ''} ran no case | check the file and the name`,\n );\n }\n\n return result.status !== 0 || failed > 0;\n};\n\n/** Applies `mutant`, runs `action`, and puts the file back byte for byte, verified by checksum. */\nconst withMutant = <T>(opts: {\n root: string;\n mutant: IMutant;\n scratch: string;\n id: string;\n action: () => T;\n}): T => {\n const path = join(opts.root, opts.mutant.file);\n const original = readFileSync(path);\n const text = original.toString('utf8');\n const occurrences = text.split(opts.mutant.find).length - 1;\n\n if (occurrences !== 1) {\n throw replayError(\n `[test-mutate][${opts.id}] \\`find\\` occurs ${occurrences} times in ${opts.mutant.file}, it must occur exactly once`,\n );\n }\n\n mkdirSync(opts.scratch, { recursive: true });\n const backup = join(opts.scratch, `test-mutate-${opts.id.replace(/[^\\w.-]/g, '_')}.orig`);\n if (existsSync(backup)) {\n throw replayError(\n `[test-mutate][${opts.id}] a backup from an interrupted run exists: ${backup} | restore ${opts.mutant.file} from it, then delete it`,\n );\n }\n\n writeFileSync(backup, original);\n const expected = checksum(original);\n\n let outcome: { value: T } | { error: unknown };\n\n try {\n writeFileSync(\n path,\n text.replace(opts.mutant.find, () => opts.mutant.replace),\n );\n outcome = { value: opts.action() };\n } catch (error) {\n outcome = { error };\n }\n\n // Restored whatever the action did, and checked, before its result or error is passed on.\n writeFileSync(path, original);\n\n if (checksum(readFileSync(path)) !== expected) {\n throw replayError(\n `[test-mutate][${opts.id}] ${opts.mutant.file} was not restored | the backup is ${backup}`,\n );\n }\n\n rmSync(backup, { force: true });\n\n if ('error' in outcome) {\n throw outcome.error;\n }\n\n return outcome.value;\n};\n\n/** Applies every edit of `mutant`, runs `action`, and puts each file back byte for byte, verified by checksum. */\nconst withMutants = <T>(opts: {\n root: string;\n mutant: IMutant | IMutant[];\n scratch: string;\n id: string;\n action: () => T;\n}): T => {\n const edits = Array.isArray(opts.mutant) ? opts.mutant : [opts.mutant];\n\n const apply = (index: number): T =>\n index === edits.length\n ? opts.action()\n : withMutant({\n root: opts.root,\n mutant: edits[index],\n scratch: opts.scratch,\n id: `${opts.id}-${index}`,\n action: () => apply(index + 1),\n });\n\n return apply(0);\n};\n\nexport class TestMutateCheck {\n /** Replays every line; never throws for a green survivor (that is a result), only for an unusable ledger. */\n static replayLedger(opts: { root: string; ledger: ILedger; scratch?: string }): ILedgerResult[] {\n const root = resolve(opts.root);\n const scratch = opts.scratch ?? join(root, '.agents/local/tmp');\n const results: ILedgerResult[] = [];\n\n for (const line of opts.ledger.mutants) {\n const { survivorRed, candidateRed } = withMutants({\n root,\n mutant: line.mutant,\n scratch,\n id: line.id,\n action: () => ({\n survivorRed: runIsRed({ root, file: line.survivor, name: line.survivorName }),\n candidateRed: runIsRed({ root, file: line.candidate, name: line.candidateName }),\n }),\n });\n const result: ILedgerResult = { id: line.id, survivorRed, candidateRed, ok: true };\n const expectation = line.expectCandidate ?? 'red';\n\n if (!survivorRed) {\n result.ok = false;\n result.problem =\n 'the survivor stayed green under the mutant: it does not cover the behaviour';\n } else if (expectation === 'red' && !candidateRed) {\n result.ok = false;\n result.problem =\n 'the candidate stayed green under the mutant: record it as vacuous with a second mutant, or pick a mutant it checks';\n } else if (expectation === 'vacuous') {\n if (!line.secondMutant) {\n result.ok = false;\n result.problem = 'a vacuous line needs a secondMutant';\n } else if (candidateRed) {\n result.ok = false;\n result.problem = 'the candidate went red under the mutant: it is not vacuous';\n } else {\n const greenUnderSecond = !withMutants({\n root,\n mutant: line.secondMutant,\n scratch,\n id: `${line.id}-second`,\n action: () => runIsRed({ root, file: line.candidate, name: line.candidateName }),\n });\n result.candidateGreenUnderSecond = greenUnderSecond;\n\n if (!greenUnderSecond) {\n result.ok = false;\n result.problem = 'the candidate went red under the second mutant: it is not vacuous';\n }\n }\n }\n\n results.push(result);\n }\n\n return results;\n }\n\n /** The report lines of a replay, one per ledger line and a closing tally. */\n static formatLedgerResults(opts: { results: ILedgerResult[] }): string[] {\n const { results } = opts;\n const lines = results.map(\n (result) =>\n `${result.ok ? 'ok ' : 'FAIL'} ${result.id}: survivor ${result.survivorRed ? 'red' : 'GREEN'}, candidate ${result.candidateRed ? 'red' : 'green'}${result.problem ? ` | ${result.problem}` : ''}`,\n );\n const failed = results.filter((result) => !result.ok).length;\n lines.push(\n `\\n[test-mutate] ${results.length - failed}/${results.length} ledger lines hold${failed ? `, ${failed} do not` : ''}.`,\n );\n\n return lines;\n }\n}\n"]}
1
+ {"version":3,"file":"test-mutate.js","sourceRoot":"","sources":["../../src/checks/test-mutate.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from 'node:child_process';\nimport { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\n\n/**\n * Replays the mutation proof of the trim-tests method. A ledger line says: \"this test (the\n * candidate) is redundant because that test (the survivor) already catches the same break\". The\n * replay applies the break (the mutant) to a file, runs the survivor and the candidate, and puts the\n * file back. The survivor must go red. The candidate must go red too, unless the line says it is\n * vacuous and a second mutant leaves it green as well.\n *\n * The mutant edits the file the test really loads: the package's own `src` for a test that imports\n * through the `@/` alias, or the `dist` of the dependency for a test that reaches another package\n * through its `exports`. A rebuild is never needed.\n */\nexport interface IMutant {\n /** Repository-relative file to edit (a `src` file, or a `dist` file of a dependency). */\n file: string;\n /** Text that must occur exactly once in the file. */\n find: string;\n replace: string;\n}\n\nexport interface ILedgerLine {\n id: string;\n /** Repository-relative test file that is deleted. */\n candidate: string;\n /** Run only the cases whose name matches (`bun test -t`). Default: the whole file. */\n candidateName?: string;\n /** Repository-relative test file that stays. */\n survivor: string;\n survivorName?: string;\n /**\n * The break. An array applies every edit together: a survivor in the package that owns the code\n * loads its `src`, a candidate in another package loads that package's `dist`, so one break is two edits.\n */\n mutant: IMutant | IMutant[];\n /** The source line the mutant stands for, `path:line`; recorded, not used by the replay. */\n srcLine?: string;\n /** `red` (default): the candidate must fail. `vacuous`: it must stay green under `mutant` AND `secondMutant`. */\n expectCandidate?: 'red' | 'vacuous';\n secondMutant?: IMutant | IMutant[];\n}\n\nexport interface ILedger {\n mutants: ILedgerLine[];\n}\n\nexport interface ILedgerResult {\n id: string;\n survivorRed: boolean;\n candidateRed: boolean;\n /** Only for a `vacuous` line: whether the candidate also stayed green under the second mutant. */\n candidateGreenUnderSecond?: boolean;\n ok: boolean;\n problem?: string;\n}\n\nconst TEST_TIMEOUT_MILLISECONDS = 300_000;\n\n/** The one place this tool builds an `Error`: `getError` would make dev-configs depend on inversion, which depends on dev-configs. */\n// eslint-disable-next-line venizia/no-new-error -- getError would be a dev-configs -> inversion cycle\nconst replayError = (message: string): Error => new Error(message);\n\n/** The nearest folder at or above the test file that holds a `package.json`: where `bun test` runs. */\nconst packageDirectoryOf = (opts: { root: string; file: string }): string => {\n let current = dirname(join(opts.root, opts.file));\n\n for (;;) {\n if (existsSync(join(current, 'package.json'))) {\n return current;\n }\n\n const parent = dirname(current);\n if (parent === current || parent.length < opts.root.length) {\n return opts.root;\n }\n\n current = parent;\n }\n};\n\n/** Runs one test file (or the cases matching `name`) and reports whether it failed. */\nconst runIsRed = (opts: { root: string; file: string; name?: string }): boolean => {\n const directory = packageDirectoryOf({ root: opts.root, file: opts.file });\n const target = join(opts.root, opts.file).slice(directory.length + 1);\n const args = ['test', target, ...(opts.name ? ['-t', opts.name] : [])];\n const result = spawnSync('bun', args, {\n cwd: directory,\n encoding: 'utf8',\n timeout: TEST_TIMEOUT_MILLISECONDS,\n killSignal: 'SIGKILL',\n env: { ...process.env, NODE_ENV: 'test' },\n });\n\n if (result.error) {\n throw replayError(`[test-mutate] could not run bun test ${target}: ${result.error.message}`);\n }\n\n // A name filter that matches nothing runs no case and exits 0: that would read as \"green\" and could\n // hide a typo as a vacuous test, so a run that executed nothing is an unusable ledger line.\n const output = `${result.stdout ?? ''}${result.stderr ?? ''}`;\n const passed = Number(/^\\s*(\\d+) pass/m.exec(output)?.[1] ?? 0);\n const failed = Number(/^\\s*(\\d+) fail/m.exec(output)?.[1] ?? 0);\n\n if (passed + failed === 0) {\n throw replayError(\n `[test-mutate] bun test ${target}${opts.name ? ` -t ${opts.name}` : ''} ran no case | check the file and the name`,\n );\n }\n\n return result.status !== 0 || failed > 0;\n};\n\n/** Applies `mutant`, runs `action`, and puts the file back byte for byte, verified byte for byte. */\nconst withMutant = <T>(opts: {\n root: string;\n mutant: IMutant;\n scratch: string;\n id: string;\n action: () => T;\n}): T => {\n const path = join(opts.root, opts.mutant.file);\n const original = readFileSync(path);\n const text = original.toString('utf8');\n const occurrences = text.split(opts.mutant.find).length - 1;\n\n if (occurrences !== 1) {\n throw replayError(\n `[test-mutate][${opts.id}] \\`find\\` occurs ${occurrences} times in ${opts.mutant.file}, it must occur exactly once`,\n );\n }\n\n mkdirSync(opts.scratch, { recursive: true });\n const backup = join(opts.scratch, `test-mutate-${opts.id.replace(/[^\\w.-]/g, '_')}.orig`);\n if (existsSync(backup)) {\n throw replayError(\n `[test-mutate][${opts.id}] a backup from an interrupted run exists: ${backup} | restore ${opts.mutant.file} from it, then delete it`,\n );\n }\n\n writeFileSync(backup, original);\n\n let outcome: { value: T } | { error: unknown };\n\n try {\n writeFileSync(\n path,\n text.replace(opts.mutant.find, () => opts.mutant.replace),\n );\n outcome = { value: opts.action() };\n } catch (error) {\n outcome = { error };\n }\n\n // Restored whatever the action did, and checked, before its result or error is passed on.\n writeFileSync(path, original);\n\n if (!readFileSync(path).equals(original)) {\n throw replayError(\n `[test-mutate][${opts.id}] ${opts.mutant.file} was not restored | the backup is ${backup}`,\n );\n }\n\n rmSync(backup, { force: true });\n\n if ('error' in outcome) {\n throw outcome.error;\n }\n\n return outcome.value;\n};\n\n/** Applies every edit of `mutant`, runs `action`, and puts each file back byte for byte, verified byte for byte. */\nconst withMutants = <T>(opts: {\n root: string;\n mutant: IMutant | IMutant[];\n scratch: string;\n id: string;\n action: () => T;\n}): T => {\n const edits = Array.isArray(opts.mutant) ? opts.mutant : [opts.mutant];\n\n const apply = (index: number): T =>\n index === edits.length\n ? opts.action()\n : withMutant({\n root: opts.root,\n mutant: edits[index],\n scratch: opts.scratch,\n id: `${opts.id}-${index}`,\n action: () => apply(index + 1),\n });\n\n return apply(0);\n};\n\nexport class TestMutateCheck {\n /** Replays every line; never throws for a green survivor (that is a result), only for an unusable ledger. */\n static replayLedger(opts: { root: string; ledger: ILedger; scratch?: string }): ILedgerResult[] {\n const root = resolve(opts.root);\n const scratch = opts.scratch ?? join(root, '.agents/local/tmp');\n const results: ILedgerResult[] = [];\n\n for (const line of opts.ledger.mutants) {\n const { survivorRed, candidateRed } = withMutants({\n root,\n mutant: line.mutant,\n scratch,\n id: line.id,\n action: () => ({\n survivorRed: runIsRed({ root, file: line.survivor, name: line.survivorName }),\n candidateRed: runIsRed({ root, file: line.candidate, name: line.candidateName }),\n }),\n });\n const result: ILedgerResult = { id: line.id, survivorRed, candidateRed, ok: true };\n const expectation = line.expectCandidate ?? 'red';\n\n if (!survivorRed) {\n result.ok = false;\n result.problem =\n 'the survivor stayed green under the mutant: it does not cover the behaviour';\n } else if (expectation === 'red' && !candidateRed) {\n result.ok = false;\n result.problem =\n 'the candidate stayed green under the mutant: record it as vacuous with a second mutant, or pick a mutant it checks';\n } else if (expectation === 'vacuous') {\n if (!line.secondMutant) {\n result.ok = false;\n result.problem = 'a vacuous line needs a secondMutant';\n } else if (candidateRed) {\n result.ok = false;\n result.problem = 'the candidate went red under the mutant: it is not vacuous';\n } else {\n const greenUnderSecond = !withMutants({\n root,\n mutant: line.secondMutant,\n scratch,\n id: `${line.id}-second`,\n action: () => runIsRed({ root, file: line.candidate, name: line.candidateName }),\n });\n result.candidateGreenUnderSecond = greenUnderSecond;\n\n if (!greenUnderSecond) {\n result.ok = false;\n result.problem = 'the candidate went red under the second mutant: it is not vacuous';\n }\n }\n }\n\n results.push(result);\n }\n\n return results;\n }\n\n /** The report lines of a replay, one per ledger line and a closing tally. */\n static formatLedgerResults(opts: { results: ILedgerResult[] }): string[] {\n const { results } = opts;\n const lines = results.map(\n (result) =>\n `${result.ok ? 'ok ' : 'FAIL'} ${result.id}: survivor ${result.survivorRed ? 'red' : 'GREEN'}, candidate ${result.candidateRed ? 'red' : 'green'}${result.problem ? ` | ${result.problem}` : ''}`,\n );\n const failed = results.filter((result) => !result.ok).length;\n lines.push(\n `\\n[test-mutate] ${results.length - failed}/${results.length} ledger lines hold${failed ? `, ${failed} do not` : ''}.`,\n );\n\n return lines;\n }\n}\n"]}
@@ -53,7 +53,7 @@ export declare class AsyncStyle {
53
53
  * change or is left unfixed.
54
54
  */
55
55
  export declare class AsyncStyleCommand {
56
- static readonly USAGE = "Usage: devz codemod async-style [paths...] [--write | --check]\n\nApplies the fixers of three house lint rules, so a repository can sweep them without a lint pass:\n\n venizia/no-inline-await `expect(await f())` becomes `const result = await f(); expect(result)`\n venizia/no-return-await `return await f()` becomes `return f()` (`const result = await f(); return result;` in a try)\n venizia/no-await-promise-chain `const x = await f().catch(() => null)` becomes\n `let x; try { x = await f(); } catch (error) { x = null; }`\n\nIt changes only what the fixers can change safely, and lists the rest with its file, line and rule:\nan `await` inside a ternary or logical operand, a loop test, an unbraced `if` branch, or after a call\nthat may have effects; an awaited `.then` or `.finally`, a `.catch` whose handler has a block body or\ncalls something, and an awaited chain that is not the whole initializer of a `const` or `let`; and\nevery venizia/no-silent-catch finding (a handler or catch block that swallows the error), which it never\nfixes: what to log is your decision.\n\n --write write the changed files (without it, nothing is written)\n --check dry run that exits 1 when anything would change or is left unfixed (for CI)\n --help print this text and exit\n\nWith no flag it lists what would change and writes nothing. Inside a git repository it reads the\nfiles git knows (tracked, or untracked and not ignored), and --write refuses a file with local changes\n(changed, staged or untracked): commit or stash it first, so git can undo the codemod.";
56
+ static readonly USAGE = "Usage: devz codemod async-style [paths...] [--write | --check]\n\nApplies the fixers of three house lint rules, so a repository can sweep them without a lint pass:\n\n venizia/no-inline-await `expect(await f())` becomes `const result = await f(); expect(result)`\n venizia/no-return-await `return await f()` becomes `return f()` (`const result = await f(); return result;` in a try)\n venizia/no-await-promise-chain `const x = await f().catch(() => null)` becomes\n `let x; try { x = await f(); } catch (error) { x = null; }`\n\nIt changes only what the fixers can change safely, and lists the rest with its file, line and rule:\nan `await` inside a ternary or logical operand, a loop test, an unbraced `if` branch, or after a call\nthat may have effects; an awaited `.then` or `.finally`, a `.catch` whose handler has a block body or\ncalls something, and an awaited chain that is not the whole initializer of a `const` or `let`; and\nevery venizia/no-silent-catch finding (a handler or catch block that swallows the error), which it never\nfixes: what to log is your decision.\n\n --write write the changed files (without it, nothing is written)\n --check dry run that exits 1 when anything would change or is left unfixed (for CI)\n --all with --write and no path: rewrite every source file under the working directory (--write needs a path or --all)\n --help print this text and exit\n\nWith no flag it lists what would change and writes nothing. Inside a git repository it reads the\nfiles git knows (tracked, or untracked and not ignored), and --write refuses a file with local changes\n(changed, staged or untracked): commit or stash it first, so git can undo the codemod.";
57
57
  static run(opts: {
58
58
  root: string;
59
59
  args: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"async-style.d.ts","sourceRoot":"","sources":["../../../src/cli/codemod/async-style.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAmDrC,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,0FAA0F;IAC1F,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,OAAO,EAAE,iBAAiB,EAAE,CAAC;CAC9B;AAED,iGAAiG;AACjG,qBAAa,UAAU;IACrB,iGAAiG;WACpF,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;IAgBrD;;;;OAIG;WACU,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;IAsBrD,sGAAsG;IACtG,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE;QACrB,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;QACxB,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,MAAM,EAAE;IAmBZ;;;;;OAKG;IACH,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE;QACf,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;QACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;QACxB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,iBAAiB;CA+CtB;AAED;;;;;GAKG;AACH,qBAAa,iBAAiB;IAC5B,MAAM,CAAC,QAAQ,CAAC,KAAK,+lDAAS;WAEjB,GAAG,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;CA8G1E"}
1
+ {"version":3,"file":"async-style.d.ts","sourceRoot":"","sources":["../../../src/cli/codemod/async-style.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAsDrC,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,0FAA0F;IAC1F,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,OAAO,EAAE,iBAAiB,EAAE,CAAC;CAC9B;AAED,iGAAiG;AACjG,qBAAa,UAAU;IACrB,iGAAiG;WACpF,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;IAgBrD;;;;OAIG;WACU,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;IAsBrD,sGAAsG;IACtG,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE;QACrB,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;QACxB,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,MAAM,EAAE;IAmBZ;;;;;OAKG;IACH,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE;QACf,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;QACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;QACxB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,iBAAiB;CA+CtB;AAED;;;;;GAKG;AACH,qBAAa,iBAAiB;IAC5B,MAAM,CAAC,QAAQ,CAAC,KAAK,6tDAAS;WAEjB,GAAG,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;CAyH1E"}
@@ -22,6 +22,7 @@ fixes: what to log is your decision.
22
22
 
23
23
  --write write the changed files (without it, nothing is written)
24
24
  --check dry run that exits 1 when anything would change or is left unfixed (for CI)
25
+ --all with --write and no path: rewrite every source file under the working directory (--write needs a path or --all)
25
26
  --help print this text and exit
26
27
 
27
28
  With no flag it lists what would change and writes nothing. Inside a git repository it reads the
@@ -34,11 +35,13 @@ const ASYNC_TEXT = /\b(await|catch)\b/;
34
35
  class AsyncStyleFlags {
35
36
  static { this.WRITE = '--write'; }
36
37
  static { this.CHECK = '--check'; }
38
+ static { this.ALL = '--all'; }
37
39
  static { this.HELP = '--help'; }
38
40
  static { this.HELP_SHORT = '-h'; }
39
41
  static { this.SCHEME_SET = new Set([
40
42
  this.WRITE,
41
43
  this.CHECK,
44
+ this.ALL,
42
45
  this.HELP,
43
46
  this.HELP_SHORT,
44
47
  ]); }
@@ -184,6 +187,16 @@ class AsyncStyleCommand {
184
187
  console.error(`devz codemod: ${AsyncStyleFlags.WRITE} and ${AsyncStyleFlags.CHECK} contradict each other\n${USAGE}`);
185
188
  return USAGE_ERROR_CODE;
186
189
  }
190
+ const refusal = files_1.CodemodFiles.refusal({
191
+ command: 'async-style',
192
+ isWrite,
193
+ isAll: flags.includes(AsyncStyleFlags.ALL),
194
+ paths,
195
+ });
196
+ if (refusal) {
197
+ console.error(`${refusal}\n${USAGE}`);
198
+ return USAGE_ERROR_CODE;
199
+ }
187
200
  let linter;
188
201
  try {
189
202
  const { Linter: EslintLinter } = await import('eslint');
@@ -1 +1 @@
1
- {"version":3,"file":"async-style.js","sourceRoot":"","sources":["../../../src/cli/codemod/async-style.ts"],"names":[],"mappings":";;;AAAA,qCAAsD;AACtD,yCAA8C;AAE9C,mCAAuC;AAEvC,MAAM,KAAK,GAAG;;;;;;;;;;;;;;;;;;;;;;uFAsByE,CAAC;AAExF,kDAAkD;AAClD,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAE3B,oDAAoD;AACpD,MAAM,UAAU,GAAG,mBAAmB,CAAC;AAEvC,MAAM,eAAe;aACH,UAAK,GAAG,SAAS,CAAC;aAClB,UAAK,GAAG,SAAS,CAAC;aAClB,SAAI,GAAG,QAAQ,CAAC;aAChB,eAAU,GAAG,IAAI,CAAC;aAElB,eAAU,GAAG,IAAI,GAAG,CAAS;QAC3C,IAAI,CAAC,KAAK;QACV,IAAI,CAAC,KAAK;QACV,IAAI,CAAC,IAAI;QACT,IAAI,CAAC,UAAU;KAChB,CAAC,CAAC;IAEH,MAAM,CAAC,OAAO,CAAC,KAAa;QAC1B,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACpC,CAAC;;AAoBH,iGAAiG;AACjG,MAAa,UAAU;IACrB,iGAAiG;IACjG,MAAM,CAAC,KAAK,CAAC,YAAY;QACvB,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;QAChE,MAAM,EAAE,iBAAiB,EAAE,GAAG,MAAM,MAAM,CAAC,8BAA8B,CAAC,CAAC;QAE3E,OAAO;YACL;gBACE,KAAK,EAAE,CAAC,uBAAuB,CAAC;gBAChC,eAAe,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE;gBAC5C,8GAA8G;gBAC9G,uEAAuE;gBACvE,aAAa,EAAE,EAAE,6BAA6B,EAAE,KAAK,EAAE;aACxD;YACD,GAAG,iBAAiB;SACrB,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,KAAK,CAAC,YAAY;QACvB,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;QAEhE,OAAO;YACL;gBACE,KAAK,EAAE,CAAC,uBAAuB,CAAC;gBAChC,eAAe,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE;gBAC5C,aAAa,EAAE,EAAE,6BAA6B,EAAE,KAAK,EAAE;gBACvD,OAAO,EAAE,EAAE,oBAAoB,EAAE,QAAQ,CAAC,MAAe,EAAE;gBAC3D,KAAK,EAAE;oBACL,iCAAiC,EAAE,OAAO;oBAC1C,8BAA8B,EAAE,OAAO;oBACvC,yCAAyC,EAAE;wBACzC,OAAO;wBACP,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE;qBACrC;oBACD,mCAAmC,EAAE,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC;iBACvF;aACF;SACF,CAAC;IACJ,CAAC;IAED,sGAAsG;IACtG,MAAM,CAAC,SAAS,CAAC,IAMhB;QACC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;QAC1D,MAAM,KAAK,GAAG,CAAC,IAAY,EAAuB,EAAE;YAClD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;YACxC,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;gBAChE,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,IAAI,aAAa,CAAC;gBAC5C,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC5C,CAAC;YAED,OAAO,KAAK,CAAC;QACf,CAAC,CAAC;QACF,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;QAC1B,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;QAE1B,OAAO,CAAC,GAAG,GAAG,CAAC;aACZ,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;aACvD,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,KAAK,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,KAAK,QAAQ,CAAC,CAAC;IACrF,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,GAAG,CAAC,IAMV;QACC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;QAC1D,2GAA2G;QAC3G,wGAAwG;QACxG,MAAM,OAAO,GAAG,MAAM;aACnB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,CAAC;aACpC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,MAAM,CAAC;QACzD,MAAM,MAAM,GAAG,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;QAEjE,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC7B,MAAM,MAAM,GAAG,UAAU,CAAC,SAAS,CAAC;gBAClC,MAAM;gBACN,MAAM;gBACN,MAAM;gBACN,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,QAAQ;aACT,CAAC,CAAC;YAEH,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACtB,OAAO;oBACL,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,CAAC;oBACR,OAAO,EAAE;wBACP;4BACE,IAAI,EAAE,CAAC;4BACP,MAAM,EAAE,CAAC;4BACT,MAAM,EAAE,YAAY;4BACpB,OAAO,EAAE,+CAA+C,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,0CAA0C;yBACpH;qBACF;iBACF,CAAC;YACJ,CAAC;QACH,CAAC;QAED,OAAO;YACL,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,OAAO,EAAE,MAAM,CAAC,MAAM,KAAK,MAAM;YACjC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YACjC,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;gBACzC,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,OAAO,EAAE,OAAO,CAAC,OAAO;aACzB,CAAC,CAAC;SACJ,CAAC;IACJ,CAAC;CACF;AAlID,gCAkIC;AAED;;;;;GAKG;AACH,MAAa,iBAAiB;aACZ,UAAK,GAAG,KAAK,CAAC;IAE9B,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAsC;QACrD,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC;QAE5B,IAAI,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,UAAU,CAAC,EAAE,CAAC;YACrF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACnB,OAAO,CAAC,CAAC;QACX,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QAClE,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QAEnE,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACvE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,OAAO,CAAC,KAAK,CACX,gCAAgC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,KAAK,EAAE,CAC1F,CAAC;YACF,OAAO,gBAAgB,CAAC;QAC1B,CAAC;QAED,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;QACtD,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;QAEtD,IAAI,OAAO,IAAI,OAAO,EAAE,CAAC;YACvB,OAAO,CAAC,KAAK,CACX,iBAAiB,eAAe,CAAC,KAAK,QAAQ,eAAe,CAAC,KAAK,2BAA2B,KAAK,EAAE,CACtG,CAAC;YACF,OAAO,gBAAgB,CAAC;QAC1B,CAAC;QAED,IAAI,MAAc,CAAC;QACnB,IAAI,CAAC;YACH,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;YACxD,MAAM,GAAG,IAAI,YAAY,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,KAAK,CAAC,uEAAuE,CAAC,CAAC;YACvF,OAAO,CAAC,CAAC;QACX,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,YAAY,EAAE,CAAC;QAC/C,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,YAAY,EAAE,CAAC;QAC/C,MAAM,OAAO,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAA,mBAAO,EAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;QACtF,IAAI,YAAY,GAAG,CAAC,CAAC;QACrB,IAAI,QAAQ,GAAG,CAAC,CAAC;QACjB,IAAI,WAAW,GAAG,CAAC,CAAC;QACpB,IAAI,YAAY,GAAG,CAAC,CAAC;QAErB,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,MAAM,KAAK,GAAG,oBAAY,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;YAC/C,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,oBAAY,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YAEnE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC3C,OAAO,CAAC,IAAI,CACV,wCAAwC,IAAA,oBAAQ,EAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,8DAA8D,CACvI,CAAC;YACJ,CAAC;YAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,MAAM,MAAM,GAAG,IAAA,sBAAY,EAAC,IAAI,EAAE,MAAM,CAAC,CAAC;gBAE1C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;oBAC7B,SAAS;gBACX,CAAC;gBAED,MAAM,KAAK,GAAG,IAAA,oBAAQ,EAAC,IAAI,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACzD,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;gBAEnF,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;oBACpC,WAAW,IAAI,CAAC,CAAC;oBACjB,OAAO,CAAC,IAAI,CACV,GAAG,KAAK,IAAI,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,IAAI,aAAa,KAAK,MAAM,CAAC,OAAO,EAAE,CACjG,CAAC;gBACJ,CAAC;gBAED,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;oBACpB,SAAS;gBACX,CAAC;gBAED,IAAI,KAAK,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;oBACrB,YAAY,IAAI,CAAC,CAAC;oBAClB,OAAO,CAAC,IAAI,CACV,YAAY,KAAK,4DAA4D,CAC9E,CAAC;oBACF,SAAS;gBACX,CAAC;gBAED,YAAY,IAAI,CAAC,CAAC;gBAClB,QAAQ,IAAI,MAAM,CAAC,KAAK,CAAC;gBACzB,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,cAAc,KAAK,KAAK,MAAM,MAAM,CAAC,KAAK,WAAW,CAAC,CAAC;gBAE5F,IAAI,OAAO,EAAE,CAAC;oBACZ,IAAA,uBAAa,EAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;gBACrC,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,CAAC,GAAG,CACT,6BAA6B,YAAY,YAAY,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,cAAc,KAAK,QAAQ,YAAY,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,kBAAkB,KAAK,WAAW,eAAe;YACvL,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,YAAY,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CACxD,CAAC;QAEF,IAAI,CAAC,OAAO,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;YACjC,OAAO,CAAC,GAAG,CAAC,uCAAuC,eAAe,CAAC,KAAK,YAAY,CAAC,CAAC;QACxF,CAAC;QAED,IAAI,OAAO,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;YAChC,OAAO,CAAC,CAAC;QACX,CAAC;QAED,OAAO,OAAO,IAAI,CAAC,YAAY,GAAG,CAAC,IAAI,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAClE,CAAC;;AAhHH,8CAiHC","sourcesContent":["import { readFileSync, writeFileSync } from 'node:fs';\nimport { relative, resolve } from 'node:path';\nimport type { Linter } from 'eslint';\nimport { CodemodFiles } from './files';\n\nconst USAGE = `Usage: devz codemod async-style [paths...] [--write | --check]\n\nApplies the fixers of three house lint rules, so a repository can sweep them without a lint pass:\n\n venizia/no-inline-await \\`expect(await f())\\` becomes \\`const result = await f(); expect(result)\\`\n venizia/no-return-await \\`return await f()\\` becomes \\`return f()\\` (\\`const result = await f(); return result;\\` in a try)\n venizia/no-await-promise-chain \\`const x = await f().catch(() => null)\\` becomes\n \\`let x; try { x = await f(); } catch (error) { x = null; }\\`\n\nIt changes only what the fixers can change safely, and lists the rest with its file, line and rule:\nan \\`await\\` inside a ternary or logical operand, a loop test, an unbraced \\`if\\` branch, or after a call\nthat may have effects; an awaited \\`.then\\` or \\`.finally\\`, a \\`.catch\\` whose handler has a block body or\ncalls something, and an awaited chain that is not the whole initializer of a \\`const\\` or \\`let\\`; and\nevery venizia/no-silent-catch finding (a handler or catch block that swallows the error), which it never\nfixes: what to log is your decision.\n\n --write write the changed files (without it, nothing is written)\n --check dry run that exits 1 when anything would change or is left unfixed (for CI)\n --help print this text and exit\n\nWith no flag it lists what would change and writes nothing. Inside a git repository it reads the\nfiles git knows (tracked, or untracked and not ignored), and --write refuses a file with local changes\n(changed, staged or untracked): commit or stash it first, so git can undo the codemod.`;\n\n/** A usage error: nothing was read or written. */\nconst USAGE_ERROR_CODE = 2;\n\n/** A file that can hold an `await` or a `catch`. */\nconst ASYNC_TEXT = /\\b(await|catch)\\b/;\n\nclass AsyncStyleFlags {\n static readonly WRITE = '--write';\n static readonly CHECK = '--check';\n static readonly HELP = '--help';\n static readonly HELP_SHORT = '-h';\n\n static readonly SCHEME_SET = new Set<string>([\n this.WRITE,\n this.CHECK,\n this.HELP,\n this.HELP_SHORT,\n ]);\n\n static isValid(value: string): boolean {\n return this.SCHEME_SET.has(value);\n }\n}\n\nexport interface IAsyncStyleReport {\n line: number;\n column: number;\n /** The rule id, or `undefined` for a file ESLint could not parse. */\n ruleId: string | null;\n message: string;\n}\n\nexport interface IAsyncStyleResult {\n output: string;\n changed: boolean;\n /** How many findings of the original source carried a fix, once the fixers changed it. */\n fixes: number;\n /** What is still reported after every pass. */\n reports: IAsyncStyleReport[];\n}\n\n/** Runs the async-style rules, and only those, over source text with ESLint's own fixer loop. */\nexport class AsyncStyle {\n /** The `Linter` config: the TypeScript parser and the rules of `asyncStyleConfigs`, at error. */\n static async createConfig(): Promise<Linter.Config[]> {\n const { default: tseslint } = await import('typescript-eslint');\n const { asyncStyleConfigs } = await import('../../linters/house/index.js');\n\n return [\n {\n files: ['**/*.{ts,tsx,mts,cts}'],\n languageOptions: { parser: tseslint.parser },\n // Only the async-style rules run here, so a `// eslint-disable-next-line` for any other rule looks unused and\n // ESLint's fixer would delete it. A codemod never touches a directive.\n linterOptions: { reportUnusedDisableDirectives: 'off' },\n },\n ...asyncStyleConfigs,\n ];\n }\n\n /**\n * The self-check's config: four scope rules, run on the text before and after the fixers. A hoisted name must be\n * declared once, shadow nothing, be used after it is declared and be used at all; when the fixers break one of these\n * the count of that rule goes up, and the file is left as it was.\n */\n static async createChecks(): Promise<Linter.Config[]> {\n const { default: tseslint } = await import('typescript-eslint');\n\n return [\n {\n files: ['**/*.{ts,tsx,mts,cts}'],\n languageOptions: { parser: tseslint.parser },\n linterOptions: { reportUnusedDisableDirectives: 'off' },\n plugins: { '@typescript-eslint': tseslint.plugin as never },\n rules: {\n '@typescript-eslint/no-redeclare': 'error',\n '@typescript-eslint/no-shadow': 'error',\n '@typescript-eslint/no-use-before-define': [\n 'error',\n { functions: false, classes: false },\n ],\n '@typescript-eslint/no-unused-vars': ['error', { args: 'none', caughtErrors: 'none' }],\n },\n },\n ];\n }\n\n /** What got worse between `source` and `output` by the self-check's rules: empty when nothing did. */\n static selfCheck(opts: {\n linter: Linter;\n checks: Linter.Config[];\n source: string;\n output: string;\n filename: string;\n }): string[] {\n const { linter, checks, source, output, filename } = opts;\n const count = (text: string): Map<string, number> => {\n const found = new Map<string, number>();\n for (const message of linter.verify(text, checks, { filename })) {\n const key = message.ruleId ?? 'parse error';\n found.set(key, (found.get(key) ?? 0) + 1);\n }\n\n return found;\n };\n const was = count(source);\n const now = count(output);\n\n return [...now]\n .filter(([rule, total]) => total > (was.get(rule) ?? 0))\n .map(([rule, total]) => `${rule}: ${was.get(rule) ?? 0} before, ${total} after`);\n }\n\n /**\n * Fixes `source` (ESLint applies up to ten passes) and returns the text and what is left. `filename` is relative to\n * the linter's `cwd`. The fixed text is checked against the original (`checks`): if it has more redeclarations,\n * shadowed names, uses before declaration or unused variables than the original, no fix is returned and the file is\n * reported as `self-check`, so a hoisted name that does not match the expression it replaced never reaches a file.\n */\n static fix(opts: {\n linter: Linter;\n config: Linter.Config[];\n checks: Linter.Config[];\n source: string;\n filename: string;\n }): IAsyncStyleResult {\n const { linter, config, checks, source, filename } = opts;\n // A fix can leave a new finding behind (the try/catch of no-await-promise-chain is a silent catch when the\n // fallback is a constant), so the count is of the fixable findings, not of the findings that went away.\n const fixable = linter\n .verify(source, config, { filename })\n .filter((message) => message.fix !== undefined).length;\n const result = linter.verifyAndFix(source, config, { filename });\n\n if (result.output !== source) {\n const broken = AsyncStyle.selfCheck({\n linter,\n checks,\n source,\n output: result.output,\n filename,\n });\n\n if (broken.length > 0) {\n return {\n output: source,\n changed: false,\n fixes: 0,\n reports: [\n {\n line: 1,\n column: 1,\n ruleId: 'self-check',\n message: `the fixers would have made this file worse (${broken.join('; ')}); it is left as it was - fix it by hand`,\n },\n ],\n };\n }\n }\n\n return {\n output: result.output,\n changed: result.output !== source,\n fixes: result.fixed ? fixable : 0,\n reports: result.messages.map((message) => ({\n line: message.line,\n column: message.column,\n ruleId: message.ruleId,\n message: message.message,\n })),\n };\n }\n}\n\n/**\n * `devz codemod async-style [paths...] [--write | --check]`: applies the fixers of `no-inline-await`,\n * `no-return-await` and `no-await-promise-chain`, and lists what is left of those and of `no-silent-catch`.\n * It is a dry run unless `--write` is given. `--check` is the same dry run, and exits 1 when anything would\n * change or is left unfixed.\n */\nexport class AsyncStyleCommand {\n static readonly USAGE = USAGE;\n\n static async run(opts: { root: string; args: string[] }): Promise<number> {\n const { root, args } = opts;\n\n if (args.includes(AsyncStyleFlags.HELP) || args.includes(AsyncStyleFlags.HELP_SHORT)) {\n console.log(USAGE);\n return 0;\n }\n\n const flags = args.filter((argument) => argument.startsWith('-'));\n const paths = args.filter((argument) => !argument.startsWith('-'));\n\n const unknown = flags.filter((flag) => !AsyncStyleFlags.isValid(flag));\n if (unknown.length > 0) {\n console.error(\n `devz codemod: unknown option ${unknown.map((flag) => `\"${flag}\"`).join(', ')}\\n${USAGE}`,\n );\n return USAGE_ERROR_CODE;\n }\n\n const isWrite = flags.includes(AsyncStyleFlags.WRITE);\n const isCheck = flags.includes(AsyncStyleFlags.CHECK);\n\n if (isWrite && isCheck) {\n console.error(\n `devz codemod: ${AsyncStyleFlags.WRITE} and ${AsyncStyleFlags.CHECK} contradict each other\\n${USAGE}`,\n );\n return USAGE_ERROR_CODE;\n }\n\n let linter: Linter;\n try {\n const { Linter: EslintLinter } = await import('eslint');\n linter = new EslintLinter({ cwd: root });\n } catch {\n console.error('devz codemod: the \"eslint\" package is required to run the lint fixers');\n return 1;\n }\n\n const config = await AsyncStyle.createConfig();\n const checks = await AsyncStyle.createChecks();\n const targets = (paths.length > 0 ? paths : ['.']).map((path) => resolve(root, path));\n let changedCount = 0;\n let fixCount = 0;\n let reportCount = 0;\n let refusedCount = 0;\n\n for (const target of targets) {\n const files = CodemodFiles.collect({ target });\n const dirty = isWrite ? CodemodFiles.dirty({ target }) : undefined;\n\n if (files.length === 0 && paths.length > 0) {\n console.warn(\n `devz codemod: no source files under \"${relative(root, target) || target}\" (git ignores the path, or it holds none): nothing was read`,\n );\n }\n\n for (const file of files) {\n const source = readFileSync(file, 'utf8');\n\n if (!ASYNC_TEXT.test(source)) {\n continue;\n }\n\n const shown = relative(root, file).split('\\\\').join('/');\n const result = AsyncStyle.fix({ linter, config, checks, source, filename: shown });\n\n for (const report of result.reports) {\n reportCount += 1;\n console.warn(\n `${shown}:${report.line}:${report.column} ${report.ruleId ?? 'parse error'} ${report.message}`,\n );\n }\n\n if (!result.changed) {\n continue;\n }\n\n if (dirty?.has(file)) {\n refusedCount += 1;\n console.warn(\n `refused ${shown} has local changes (commit or stash them, then run again)`,\n );\n continue;\n }\n\n changedCount += 1;\n fixCount += result.fixes;\n console.log(`${isWrite ? 'changed' : 'would change'} ${shown} (${result.fixes} fix(es))`);\n\n if (isWrite) {\n writeFileSync(file, result.output);\n }\n }\n }\n\n console.log(\n `devz codemod async-style: ${changedCount} file(s) ${isWrite ? 'changed' : 'would change'}, ${fixCount} fix(es) ${isWrite ? 'applied' : 'would be applied'}, ${reportCount} left unfixed` +\n (refusedCount > 0 ? `, ${refusedCount} refused` : ''),\n );\n\n if (!isWrite && changedCount > 0) {\n console.log(`Nothing was written. Run again with ${AsyncStyleFlags.WRITE} to apply.`);\n }\n\n if (isWrite && refusedCount > 0) {\n return 1;\n }\n\n return isCheck && (changedCount > 0 || reportCount > 0) ? 1 : 0;\n }\n}\n"]}
1
+ {"version":3,"file":"async-style.js","sourceRoot":"","sources":["../../../src/cli/codemod/async-style.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFileSync, writeFileSync } from 'node:fs';\nimport { relative, resolve } from 'node:path';\nimport type { Linter } from 'eslint';\nimport { CodemodFiles } from './files';\n\nconst USAGE = `Usage: devz codemod async-style [paths...] [--write | --check]\n\nApplies the fixers of three house lint rules, so a repository can sweep them without a lint pass:\n\n venizia/no-inline-await \\`expect(await f())\\` becomes \\`const result = await f(); expect(result)\\`\n venizia/no-return-await \\`return await f()\\` becomes \\`return f()\\` (\\`const result = await f(); return result;\\` in a try)\n venizia/no-await-promise-chain \\`const x = await f().catch(() => null)\\` becomes\n \\`let x; try { x = await f(); } catch (error) { x = null; }\\`\n\nIt changes only what the fixers can change safely, and lists the rest with its file, line and rule:\nan \\`await\\` inside a ternary or logical operand, a loop test, an unbraced \\`if\\` branch, or after a call\nthat may have effects; an awaited \\`.then\\` or \\`.finally\\`, a \\`.catch\\` whose handler has a block body or\ncalls something, and an awaited chain that is not the whole initializer of a \\`const\\` or \\`let\\`; and\nevery venizia/no-silent-catch finding (a handler or catch block that swallows the error), which it never\nfixes: what to log is your decision.\n\n --write write the changed files (without it, nothing is written)\n --check dry run that exits 1 when anything would change or is left unfixed (for CI)\n --all with --write and no path: rewrite every source file under the working directory (--write needs a path or --all)\n --help print this text and exit\n\nWith no flag it lists what would change and writes nothing. Inside a git repository it reads the\nfiles git knows (tracked, or untracked and not ignored), and --write refuses a file with local changes\n(changed, staged or untracked): commit or stash it first, so git can undo the codemod.`;\n\n/** A usage error: nothing was read or written. */\nconst USAGE_ERROR_CODE = 2;\n\n/** A file that can hold an `await` or a `catch`. */\nconst ASYNC_TEXT = /\\b(await|catch)\\b/;\n\nclass AsyncStyleFlags {\n static readonly WRITE = '--write';\n static readonly CHECK = '--check';\n static readonly ALL = '--all';\n static readonly HELP = '--help';\n static readonly HELP_SHORT = '-h';\n\n static readonly SCHEME_SET = new Set<string>([\n this.WRITE,\n this.CHECK,\n this.ALL,\n this.HELP,\n this.HELP_SHORT,\n ]);\n\n static isValid(value: string): boolean {\n return this.SCHEME_SET.has(value);\n }\n}\n\nexport interface IAsyncStyleReport {\n line: number;\n column: number;\n /** The rule id, or `undefined` for a file ESLint could not parse. */\n ruleId: string | null;\n message: string;\n}\n\nexport interface IAsyncStyleResult {\n output: string;\n changed: boolean;\n /** How many findings of the original source carried a fix, once the fixers changed it. */\n fixes: number;\n /** What is still reported after every pass. */\n reports: IAsyncStyleReport[];\n}\n\n/** Runs the async-style rules, and only those, over source text with ESLint's own fixer loop. */\nexport class AsyncStyle {\n /** The `Linter` config: the TypeScript parser and the rules of `asyncStyleConfigs`, at error. */\n static async createConfig(): Promise<Linter.Config[]> {\n const { default: tseslint } = await import('typescript-eslint');\n const { asyncStyleConfigs } = await import('../../linters/house/index.js');\n\n return [\n {\n files: ['**/*.{ts,tsx,mts,cts}'],\n languageOptions: { parser: tseslint.parser },\n // Only the async-style rules run here, so a `// eslint-disable-next-line` for any other rule looks unused and\n // ESLint's fixer would delete it. A codemod never touches a directive.\n linterOptions: { reportUnusedDisableDirectives: 'off' },\n },\n ...asyncStyleConfigs,\n ];\n }\n\n /**\n * The self-check's config: four scope rules, run on the text before and after the fixers. A hoisted name must be\n * declared once, shadow nothing, be used after it is declared and be used at all; when the fixers break one of these\n * the count of that rule goes up, and the file is left as it was.\n */\n static async createChecks(): Promise<Linter.Config[]> {\n const { default: tseslint } = await import('typescript-eslint');\n\n return [\n {\n files: ['**/*.{ts,tsx,mts,cts}'],\n languageOptions: { parser: tseslint.parser },\n linterOptions: { reportUnusedDisableDirectives: 'off' },\n plugins: { '@typescript-eslint': tseslint.plugin as never },\n rules: {\n '@typescript-eslint/no-redeclare': 'error',\n '@typescript-eslint/no-shadow': 'error',\n '@typescript-eslint/no-use-before-define': [\n 'error',\n { functions: false, classes: false },\n ],\n '@typescript-eslint/no-unused-vars': ['error', { args: 'none', caughtErrors: 'none' }],\n },\n },\n ];\n }\n\n /** What got worse between `source` and `output` by the self-check's rules: empty when nothing did. */\n static selfCheck(opts: {\n linter: Linter;\n checks: Linter.Config[];\n source: string;\n output: string;\n filename: string;\n }): string[] {\n const { linter, checks, source, output, filename } = opts;\n const count = (text: string): Map<string, number> => {\n const found = new Map<string, number>();\n for (const message of linter.verify(text, checks, { filename })) {\n const key = message.ruleId ?? 'parse error';\n found.set(key, (found.get(key) ?? 0) + 1);\n }\n\n return found;\n };\n const was = count(source);\n const now = count(output);\n\n return [...now]\n .filter(([rule, total]) => total > (was.get(rule) ?? 0))\n .map(([rule, total]) => `${rule}: ${was.get(rule) ?? 0} before, ${total} after`);\n }\n\n /**\n * Fixes `source` (ESLint applies up to ten passes) and returns the text and what is left. `filename` is relative to\n * the linter's `cwd`. The fixed text is checked against the original (`checks`): if it has more redeclarations,\n * shadowed names, uses before declaration or unused variables than the original, no fix is returned and the file is\n * reported as `self-check`, so a hoisted name that does not match the expression it replaced never reaches a file.\n */\n static fix(opts: {\n linter: Linter;\n config: Linter.Config[];\n checks: Linter.Config[];\n source: string;\n filename: string;\n }): IAsyncStyleResult {\n const { linter, config, checks, source, filename } = opts;\n // A fix can leave a new finding behind (the try/catch of no-await-promise-chain is a silent catch when the\n // fallback is a constant), so the count is of the fixable findings, not of the findings that went away.\n const fixable = linter\n .verify(source, config, { filename })\n .filter((message) => message.fix !== undefined).length;\n const result = linter.verifyAndFix(source, config, { filename });\n\n if (result.output !== source) {\n const broken = AsyncStyle.selfCheck({\n linter,\n checks,\n source,\n output: result.output,\n filename,\n });\n\n if (broken.length > 0) {\n return {\n output: source,\n changed: false,\n fixes: 0,\n reports: [\n {\n line: 1,\n column: 1,\n ruleId: 'self-check',\n message: `the fixers would have made this file worse (${broken.join('; ')}); it is left as it was - fix it by hand`,\n },\n ],\n };\n }\n }\n\n return {\n output: result.output,\n changed: result.output !== source,\n fixes: result.fixed ? fixable : 0,\n reports: result.messages.map((message) => ({\n line: message.line,\n column: message.column,\n ruleId: message.ruleId,\n message: message.message,\n })),\n };\n }\n}\n\n/**\n * `devz codemod async-style [paths...] [--write | --check]`: applies the fixers of `no-inline-await`,\n * `no-return-await` and `no-await-promise-chain`, and lists what is left of those and of `no-silent-catch`.\n * It is a dry run unless `--write` is given. `--check` is the same dry run, and exits 1 when anything would\n * change or is left unfixed.\n */\nexport class AsyncStyleCommand {\n static readonly USAGE = USAGE;\n\n static async run(opts: { root: string; args: string[] }): Promise<number> {\n const { root, args } = opts;\n\n if (args.includes(AsyncStyleFlags.HELP) || args.includes(AsyncStyleFlags.HELP_SHORT)) {\n console.log(USAGE);\n return 0;\n }\n\n const flags = args.filter((argument) => argument.startsWith('-'));\n const paths = args.filter((argument) => !argument.startsWith('-'));\n\n const unknown = flags.filter((flag) => !AsyncStyleFlags.isValid(flag));\n if (unknown.length > 0) {\n console.error(\n `devz codemod: unknown option ${unknown.map((flag) => `\"${flag}\"`).join(', ')}\\n${USAGE}`,\n );\n return USAGE_ERROR_CODE;\n }\n\n const isWrite = flags.includes(AsyncStyleFlags.WRITE);\n const isCheck = flags.includes(AsyncStyleFlags.CHECK);\n\n if (isWrite && isCheck) {\n console.error(\n `devz codemod: ${AsyncStyleFlags.WRITE} and ${AsyncStyleFlags.CHECK} contradict each other\\n${USAGE}`,\n );\n return USAGE_ERROR_CODE;\n }\n\n const refusal = CodemodFiles.refusal({\n command: 'async-style',\n isWrite,\n isAll: flags.includes(AsyncStyleFlags.ALL),\n paths,\n });\n if (refusal) {\n console.error(`${refusal}\\n${USAGE}`);\n return USAGE_ERROR_CODE;\n }\n\n let linter: Linter;\n try {\n const { Linter: EslintLinter } = await import('eslint');\n linter = new EslintLinter({ cwd: root });\n } catch {\n console.error('devz codemod: the \"eslint\" package is required to run the lint fixers');\n return 1;\n }\n\n const config = await AsyncStyle.createConfig();\n const checks = await AsyncStyle.createChecks();\n const targets = (paths.length > 0 ? paths : ['.']).map((path) => resolve(root, path));\n let changedCount = 0;\n let fixCount = 0;\n let reportCount = 0;\n let refusedCount = 0;\n\n for (const target of targets) {\n const files = CodemodFiles.collect({ target });\n const dirty = isWrite ? CodemodFiles.dirty({ target }) : undefined;\n\n if (files.length === 0 && paths.length > 0) {\n console.warn(\n `devz codemod: no source files under \"${relative(root, target) || target}\" (git ignores the path, or it holds none): nothing was read`,\n );\n }\n\n for (const file of files) {\n const source = readFileSync(file, 'utf8');\n\n if (!ASYNC_TEXT.test(source)) {\n continue;\n }\n\n const shown = relative(root, file).split('\\\\').join('/');\n const result = AsyncStyle.fix({ linter, config, checks, source, filename: shown });\n\n for (const report of result.reports) {\n reportCount += 1;\n console.warn(\n `${shown}:${report.line}:${report.column} ${report.ruleId ?? 'parse error'} ${report.message}`,\n );\n }\n\n if (!result.changed) {\n continue;\n }\n\n if (dirty?.has(file)) {\n refusedCount += 1;\n console.warn(\n `refused ${shown} has local changes (commit or stash them, then run again)`,\n );\n continue;\n }\n\n changedCount += 1;\n fixCount += result.fixes;\n console.log(`${isWrite ? 'changed' : 'would change'} ${shown} (${result.fixes} fix(es))`);\n\n if (isWrite) {\n writeFileSync(file, result.output);\n }\n }\n }\n\n console.log(\n `devz codemod async-style: ${changedCount} file(s) ${isWrite ? 'changed' : 'would change'}, ${fixCount} fix(es) ${isWrite ? 'applied' : 'would be applied'}, ${reportCount} left unfixed` +\n (refusedCount > 0 ? `, ${refusedCount} refused` : ''),\n );\n\n if (!isWrite && changedCount > 0) {\n console.log(`Nothing was written. Run again with ${AsyncStyleFlags.WRITE} to apply.`);\n }\n\n if (isWrite && refusedCount > 0) {\n return 1;\n }\n\n return isCheck && (changedCount > 0 || reportCount > 0) ? 1 : 0;\n }\n}\n"]}
@@ -70,7 +70,7 @@ export declare class ConstClasses {
70
70
  }
71
71
  /** `devz codemod const-classes [paths...] [--write | --check]`. */
72
72
  export declare class ConstClassesCommand {
73
- static readonly USAGE = "Usage: devz codemod const-classes [paths...] [--write | --check] [--map <file>] [--emit-map <file>] [--import-from <module>] [--include-tests]\n\nTurns a string-literal union in an exported declaration into a const class (SCHEME_SET, isValid) plus\n`TConstValue<typeof X>`, the shape the venizia/const-class-values lint rule asks for. It handles an\nexported type alias and a property of an exported interface or object type. The values stay the same,\nso every existing literal still type-checks and no call site changes.\n\nIt has to choose names: the class is the type name without its leading T, in the plural; a member is\nthe value in upper snake case. The dry run prints every proposal.\n\n --write write the changed files (without it, nothing is written)\n --check dry run that exits 1 while a union can be converted or is left untouched (for CI)\n --map <file> a JSON file of overrides, keyed by the id the dry run prints\n --emit-map <file> write the proposals as a mapping file to edit, then pass it with --map\n --import-from <m> where TConstValue is imported from (default @venizia/ignis-helpers/common)\n --include-tests also convert test files (skipped by default)\n --help print this text and exit\n\nA mapping entry: { \"src/a.ts#TMode\": { \"class\": \"Modes\", \"members\": { \"read-only\": \"READ_ONLY\" } } },\nor { \"skip\": true }. A union in a parameter, a return type, a generic or a nested type is reported as\nleft untouched: it has no name, so write that class by hand. Key sets (Pick, Omit, Extract, Exclude,\nRecord, mapped types) are not enumerable values and are never touched.";
73
+ static readonly USAGE = "Usage: devz codemod const-classes [paths...] [--write | --check] [--map <file>] [--emit-map <file>] [--import-from <module>] [--include-tests]\n\nTurns a string-literal union in an exported declaration into a const class (SCHEME_SET, isValid) plus\n`TConstValue<typeof X>`, the shape the venizia/const-class-values lint rule asks for. It handles an\nexported type alias and a property of an exported interface or object type. The values stay the same,\nso every existing literal still type-checks and no call site changes.\n\nIt has to choose names: the class is the type name without its leading T, in the plural; a member is\nthe value in upper snake case. The dry run prints every proposal.\n\n --write write the changed files (without it, nothing is written)\n --check dry run that exits 1 while a union can be converted or is left untouched (for CI)\n --all with --write and no path: rewrite every source file under the working directory (--write needs a path or --all)\n --map <file> a JSON file of overrides, keyed by the id the dry run prints\n --emit-map <file> write the proposals as a mapping file to edit, then pass it with --map\n --import-from <m> where TConstValue is imported from (default @venizia/ignis-helpers/common)\n --include-tests also convert test files (skipped by default)\n --help print this text and exit\n\nA mapping entry: { \"src/a.ts#TMode\": { \"class\": \"Modes\", \"members\": { \"read-only\": \"READ_ONLY\" } } },\nor { \"skip\": true }. A union in a parameter, a return type, a generic or a nested type is reported as\nleft untouched: it has no name, so write that class by hand. Key sets (Pick, Omit, Extract, Exclude,\nRecord, mapped types) are not enumerable values and are never touched.";
74
74
  static run(opts: {
75
75
  root: string;
76
76
  args: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"const-classes.d.ts","sourceRoot":"","sources":["../../../src/cli/codemod/const-classes.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAGjC,KAAK,WAAW,GAAG,OAAO,EAAE,CAAC;AAU7B,2EAA2E;AAC3E,MAAM,WAAW,mBAAmB;IAClC,sBAAsB;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oFAAoF;IACpF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,oCAAoC;IACpC,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;AAEjE,MAAM,WAAW,mBAAmB;IAClC,qGAAqG;IACrG,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,uDAAuD;IACvD,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,mBAAmB,EAAE,CAAC;IACjC,SAAS,EAAE,oBAAoB,EAAE,CAAC;CACnC;AA4CD;;;;GAIG;AACH,qBAAa,YAAY;IACvB,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE;QACnB,UAAU,EAAE,WAAW,CAAC;QACxB,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,4FAA4F;QAC5F,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,SAAS,CAAC,EAAE,cAAc,CAAC;QAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,GAAG,iBAAiB;IA4KrB,OAAO,CAAC,MAAM,CAAC,WAAW;IA8C1B,yDAAyD;IACzD,OAAO,CAAC,MAAM,CAAC,aAAa;IAoD5B,OAAO,CAAC,MAAM,CAAC,UAAU;IAQzB,iIAAiI;IACjI,OAAO,CAAC,MAAM,CAAC,SAAS;IA+BxB,gFAAgF;IAChF,OAAO,CAAC,MAAM,CAAC,YAAY;IAuB3B,OAAO,CAAC,MAAM,CAAC,SAAS;IAqCxB,OAAO,CAAC,MAAM,CAAC,WAAW;IAuB1B,sHAAsH;IACtH,OAAO,CAAC,MAAM,CAAC,UAAU;IAkCzB,OAAO,CAAC,MAAM,CAAC,QAAQ;IA2BvB,wJAAwJ;IACxJ,OAAO,CAAC,MAAM,CAAC,UAAU;CAqD1B;AAyDD,mEAAmE;AACnE,qBAAa,mBAAmB;IAC9B,MAAM,CAAC,QAAQ,CAAC,KAAK,ipDAAS;WAEjB,GAAG,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;CA4K1E"}
1
+ {"version":3,"file":"const-classes.d.ts","sourceRoot":"","sources":["../../../src/cli/codemod/const-classes.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAGjC,KAAK,WAAW,GAAG,OAAO,EAAE,CAAC;AAU7B,2EAA2E;AAC3E,MAAM,WAAW,mBAAmB;IAClC,sBAAsB;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oFAAoF;IACpF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,oCAAoC;IACpC,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;AAEjE,MAAM,WAAW,mBAAmB;IAClC,qGAAqG;IACrG,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,uDAAuD;IACvD,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,mBAAmB,EAAE,CAAC;IACjC,SAAS,EAAE,oBAAoB,EAAE,CAAC;CACnC;AA4CD;;;;GAIG;AACH,qBAAa,YAAY;IACvB,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE;QACnB,UAAU,EAAE,WAAW,CAAC;QACxB,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,4FAA4F;QAC5F,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,SAAS,CAAC,EAAE,cAAc,CAAC;QAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,GAAG,iBAAiB;IA4KrB,OAAO,CAAC,MAAM,CAAC,WAAW;IA8C1B,yDAAyD;IACzD,OAAO,CAAC,MAAM,CAAC,aAAa;IAoD5B,OAAO,CAAC,MAAM,CAAC,UAAU;IAQzB,iIAAiI;IACjI,OAAO,CAAC,MAAM,CAAC,SAAS;IA+BxB,gFAAgF;IAChF,OAAO,CAAC,MAAM,CAAC,YAAY;IAuB3B,OAAO,CAAC,MAAM,CAAC,SAAS;IAqCxB,OAAO,CAAC,MAAM,CAAC,WAAW;IAuB1B,sHAAsH;IACtH,OAAO,CAAC,MAAM,CAAC,UAAU;IAkCzB,OAAO,CAAC,MAAM,CAAC,QAAQ;IA2BvB,wJAAwJ;IACxJ,OAAO,CAAC,MAAM,CAAC,UAAU;CAqD1B;AA4DD,mEAAmE;AACnE,qBAAa,mBAAmB;IAC9B,MAAM,CAAC,QAAQ,CAAC,KAAK,yxDAAS;WAEjB,GAAG,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;CAuL1E"}
@@ -429,6 +429,7 @@ the value in upper snake case. The dry run prints every proposal.
429
429
 
430
430
  --write write the changed files (without it, nothing is written)
431
431
  --check dry run that exits 1 while a union can be converted or is left untouched (for CI)
432
+ --all with --write and no path: rewrite every source file under the working directory (--write needs a path or --all)
432
433
  --map <file> a JSON file of overrides, keyed by the id the dry run prints
433
434
  --emit-map <file> write the proposals as a mapping file to edit, then pass it with --map
434
435
  --import-from <m> where TConstValue is imported from (default ${DEFAULT_IMPORT_FROM})
@@ -444,6 +445,7 @@ const USAGE_ERROR_CODE = 2;
444
445
  class ConstClassFlags {
445
446
  static { this.WRITE = '--write'; }
446
447
  static { this.CHECK = '--check'; }
448
+ static { this.ALL = '--all'; }
447
449
  static { this.MAP = '--map'; }
448
450
  static { this.EMIT_MAP = '--emit-map'; }
449
451
  static { this.IMPORT_FROM = '--import-from'; }
@@ -453,6 +455,7 @@ class ConstClassFlags {
453
455
  static { this.SCHEME_SET = new Set([
454
456
  this.WRITE,
455
457
  this.CHECK,
458
+ this.ALL,
456
459
  this.MAP,
457
460
  this.EMIT_MAP,
458
461
  this.IMPORT_FROM,
@@ -507,6 +510,16 @@ class ConstClassesCommand {
507
510
  }
508
511
  const isWrite = flags.includes(ConstClassFlags.WRITE);
509
512
  const isCheck = flags.includes(ConstClassFlags.CHECK);
513
+ const refusal = files_1.CodemodFiles.refusal({
514
+ command: 'const-classes',
515
+ isWrite,
516
+ isAll: flags.includes(ConstClassFlags.ALL),
517
+ paths,
518
+ });
519
+ if (refusal) {
520
+ console.error(`${refusal}\n${USAGE}`);
521
+ return USAGE_ERROR_CODE;
522
+ }
510
523
  const includeTests = flags.includes(ConstClassFlags.INCLUDE_TESTS);
511
524
  if (isWrite && isCheck) {
512
525
  console.error(`devz codemod: ${ConstClassFlags.WRITE} and ${ConstClassFlags.CHECK} contradict each other\n${USAGE}`);