@venizia/dev-configs 0.4.7-0 → 0.4.7-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/LICENSE.md +1 -3
  2. package/README.md +1 -1
  3. package/dist/checks/catalog.d.ts +4 -2
  4. package/dist/checks/catalog.d.ts.map +1 -1
  5. package/dist/checks/catalog.js +72 -70
  6. package/dist/checks/catalog.js.map +1 -1
  7. package/dist/checks/module-cycles.d.ts +11 -9
  8. package/dist/checks/module-cycles.d.ts.map +1 -1
  9. package/dist/checks/module-cycles.js +32 -30
  10. package/dist/checks/module-cycles.js.map +1 -1
  11. package/dist/checks/purity/manifest.d.ts +44 -38
  12. package/dist/checks/purity/manifest.d.ts.map +1 -1
  13. package/dist/checks/purity/manifest.js +121 -115
  14. package/dist/checks/purity/manifest.js.map +1 -1
  15. package/dist/checks/purity/probe.d.ts +22 -18
  16. package/dist/checks/purity/probe.d.ts.map +1 -1
  17. package/dist/checks/purity/probe.js +99 -98
  18. package/dist/checks/purity/probe.js.map +1 -1
  19. package/dist/checks/purity/run.d.ts +18 -16
  20. package/dist/checks/purity/run.d.ts.map +1 -1
  21. package/dist/checks/purity/run.js +90 -86
  22. package/dist/checks/purity/run.js.map +1 -1
  23. package/dist/checks/test-controls.d.ts +17 -15
  24. package/dist/checks/test-controls.d.ts.map +1 -1
  25. package/dist/checks/test-controls.js +71 -70
  26. package/dist/checks/test-controls.js.map +1 -1
  27. package/dist/checks/test-dead-code.d.ts +5 -3
  28. package/dist/checks/test-dead-code.d.ts.map +1 -1
  29. package/dist/checks/test-dead-code.js +23 -21
  30. package/dist/checks/test-dead-code.js.map +1 -1
  31. package/dist/checks/test-mutate.d.ts +12 -7
  32. package/dist/checks/test-mutate.d.ts.map +1 -1
  33. package/dist/checks/test-mutate.js +59 -56
  34. package/dist/checks/test-mutate.js.map +1 -1
  35. package/dist/checks/test-weight.d.ts +11 -8
  36. package/dist/checks/test-weight.d.ts.map +1 -1
  37. package/dist/checks/test-weight.js +98 -94
  38. package/dist/checks/test-weight.js.map +1 -1
  39. package/dist/cli/check.d.ts.map +1 -1
  40. package/dist/cli/check.js +11 -8
  41. package/dist/cli/check.js.map +1 -1
  42. package/dist/cli/codemod/async-style.d.ts +29 -8
  43. package/dist/cli/codemod/async-style.d.ts.map +1 -1
  44. package/dist/cli/codemod/async-style.js +102 -20
  45. package/dist/cli/codemod/async-style.js.map +1 -1
  46. package/dist/cli/codemod/index.d.ts.map +1 -1
  47. package/dist/cli/codemod/index.js +6 -2
  48. package/dist/cli/codemod/index.js.map +1 -1
  49. package/dist/cli/codemod/model-settings.d.ts +7 -5
  50. package/dist/cli/codemod/model-settings.d.ts.map +1 -1
  51. package/dist/cli/codemod/model-settings.js +293 -291
  52. package/dist/cli/codemod/model-settings.js.map +1 -1
  53. package/dist/cli/codemod/namespace-functions/index.d.ts +20 -0
  54. package/dist/cli/codemod/namespace-functions/index.d.ts.map +1 -0
  55. package/dist/cli/codemod/namespace-functions/index.js +182 -0
  56. package/dist/cli/codemod/namespace-functions/index.js.map +1 -0
  57. package/dist/cli/codemod/namespace-functions/logger-scope.d.ts +32 -0
  58. package/dist/cli/codemod/namespace-functions/logger-scope.d.ts.map +1 -0
  59. package/dist/cli/codemod/namespace-functions/logger-scope.js +128 -0
  60. package/dist/cli/codemod/namespace-functions/logger-scope.js.map +1 -0
  61. package/dist/cli/codemod/namespace-functions/mapping.d.ts +66 -0
  62. package/dist/cli/codemod/namespace-functions/mapping.d.ts.map +1 -0
  63. package/dist/cli/codemod/namespace-functions/mapping.js +99 -0
  64. package/dist/cli/codemod/namespace-functions/mapping.js.map +1 -0
  65. package/dist/cli/codemod/namespace-functions/mappings/boot.d.ts +7 -0
  66. package/dist/cli/codemod/namespace-functions/mappings/boot.d.ts.map +1 -0
  67. package/dist/cli/codemod/namespace-functions/mappings/boot.js +28 -0
  68. package/dist/cli/codemod/namespace-functions/mappings/boot.js.map +1 -0
  69. package/dist/cli/codemod/namespace-functions/mappings/connectors.d.ts +4 -0
  70. package/dist/cli/codemod/namespace-functions/mappings/connectors.d.ts.map +1 -0
  71. package/dist/cli/codemod/namespace-functions/mappings/connectors.js +218 -0
  72. package/dist/cli/codemod/namespace-functions/mappings/connectors.js.map +1 -0
  73. package/dist/cli/codemod/namespace-functions/mappings/core-server.d.ts +7 -0
  74. package/dist/cli/codemod/namespace-functions/mappings/core-server.d.ts.map +1 -0
  75. package/dist/cli/codemod/namespace-functions/mappings/core-server.js +162 -0
  76. package/dist/cli/codemod/namespace-functions/mappings/core-server.js.map +1 -0
  77. package/dist/cli/codemod/namespace-functions/mappings/core-worker.d.ts +7 -0
  78. package/dist/cli/codemod/namespace-functions/mappings/core-worker.d.ts.map +1 -0
  79. package/dist/cli/codemod/namespace-functions/mappings/core-worker.js +18 -0
  80. package/dist/cli/codemod/namespace-functions/mappings/core-worker.js.map +1 -0
  81. package/dist/cli/codemod/namespace-functions/mappings/dev-configs.d.ts +7 -0
  82. package/dist/cli/codemod/namespace-functions/mappings/dev-configs.d.ts.map +1 -0
  83. package/dist/cli/codemod/namespace-functions/mappings/dev-configs.js +192 -0
  84. package/dist/cli/codemod/namespace-functions/mappings/dev-configs.js.map +1 -0
  85. package/dist/cli/codemod/namespace-functions/mappings/filter.d.ts +7 -0
  86. package/dist/cli/codemod/namespace-functions/mappings/filter.d.ts.map +1 -0
  87. package/dist/cli/codemod/namespace-functions/mappings/filter.js +30 -0
  88. package/dist/cli/codemod/namespace-functions/mappings/filter.js.map +1 -0
  89. package/dist/cli/codemod/namespace-functions/mappings/helpers.d.ts +16 -0
  90. package/dist/cli/codemod/namespace-functions/mappings/helpers.d.ts.map +1 -0
  91. package/dist/cli/codemod/namespace-functions/mappings/helpers.js +535 -0
  92. package/dist/cli/codemod/namespace-functions/mappings/helpers.js.map +1 -0
  93. package/dist/cli/codemod/namespace-functions/mappings/index.d.ts +4 -0
  94. package/dist/cli/codemod/namespace-functions/mappings/index.d.ts.map +1 -0
  95. package/dist/cli/codemod/namespace-functions/mappings/index.js +25 -0
  96. package/dist/cli/codemod/namespace-functions/mappings/index.js.map +1 -0
  97. package/dist/cli/codemod/namespace-functions/mappings/inversion.d.ts +10 -0
  98. package/dist/cli/codemod/namespace-functions/mappings/inversion.d.ts.map +1 -0
  99. package/dist/cli/codemod/namespace-functions/mappings/inversion.js +81 -0
  100. package/dist/cli/codemod/namespace-functions/mappings/inversion.js.map +1 -0
  101. package/dist/cli/codemod/namespace-functions/mappings/kernel.d.ts +17 -0
  102. package/dist/cli/codemod/namespace-functions/mappings/kernel.d.ts.map +1 -0
  103. package/dist/cli/codemod/namespace-functions/mappings/kernel.js +229 -0
  104. package/dist/cli/codemod/namespace-functions/mappings/kernel.js.map +1 -0
  105. package/dist/cli/codemod/namespace-functions/rewriter.d.ts +61 -0
  106. package/dist/cli/codemod/namespace-functions/rewriter.d.ts.map +1 -0
  107. package/dist/cli/codemod/namespace-functions/rewriter.js +564 -0
  108. package/dist/cli/codemod/namespace-functions/rewriter.js.map +1 -0
  109. package/dist/cli/index.js +2 -1
  110. package/dist/cli/index.js.map +1 -1
  111. package/dist/compilers/bun.d.ts +1 -1
  112. package/dist/compilers/bun.js +1 -1
  113. package/dist/compilers/bun.js.map +1 -1
  114. package/dist/linters/house/await-support.d.ts +17 -3
  115. package/dist/linters/house/await-support.d.ts.map +1 -1
  116. package/dist/linters/house/await-support.js +52 -23
  117. package/dist/linters/house/await-support.js.map +1 -1
  118. package/dist/linters/house/common.d.ts +13 -9
  119. package/dist/linters/house/common.d.ts.map +1 -1
  120. package/dist/linters/house/common.js +18 -15
  121. package/dist/linters/house/common.js.map +1 -1
  122. package/dist/linters/house/configs.d.ts +5 -2
  123. package/dist/linters/house/configs.d.ts.map +1 -1
  124. package/dist/linters/house/configs.js +11 -2
  125. package/dist/linters/house/configs.js.map +1 -1
  126. package/dist/linters/house/const-class-values.rule.d.ts.map +1 -1
  127. package/dist/linters/house/const-class-values.rule.js +27 -19
  128. package/dist/linters/house/const-class-values.rule.js.map +1 -1
  129. package/dist/linters/house/effect-cleanup-consistency.rule.js +3 -3
  130. package/dist/linters/house/effect-cleanup-consistency.rule.js.map +1 -1
  131. package/dist/linters/house/hook-support.d.ts.map +1 -1
  132. package/dist/linters/house/hook-support.js +17 -14
  133. package/dist/linters/house/hook-support.js.map +1 -1
  134. package/dist/linters/house/index.d.ts +3 -0
  135. package/dist/linters/house/index.d.ts.map +1 -1
  136. package/dist/linters/house/index.js +3 -0
  137. package/dist/linters/house/index.js.map +1 -1
  138. package/dist/linters/house/max-params-own.rule.d.ts.map +1 -1
  139. package/dist/linters/house/max-params-own.rule.js +33 -24
  140. package/dist/linters/house/max-params-own.rule.js.map +1 -1
  141. package/dist/linters/house/no-async-hook-callback.rule.js +1 -1
  142. package/dist/linters/house/no-async-hook-callback.rule.js.map +1 -1
  143. package/dist/linters/house/no-await-promise-chain.rule.d.ts +24 -0
  144. package/dist/linters/house/no-await-promise-chain.rule.d.ts.map +1 -0
  145. package/dist/linters/house/no-await-promise-chain.rule.js +298 -0
  146. package/dist/linters/house/no-await-promise-chain.rule.js.map +1 -0
  147. package/dist/linters/house/no-inline-await.rule.d.ts.map +1 -1
  148. package/dist/linters/house/no-inline-await.rule.js +6 -12
  149. package/dist/linters/house/no-inline-await.rule.js.map +1 -1
  150. package/dist/linters/house/no-new-error.rule.js +4 -4
  151. package/dist/linters/house/no-new-error.rule.js.map +1 -1
  152. package/dist/linters/house/no-return-await.rule.d.ts.map +1 -1
  153. package/dist/linters/house/no-return-await.rule.js +11 -11
  154. package/dist/linters/house/no-return-await.rule.js.map +1 -1
  155. package/dist/linters/house/no-silent-catch.rule.d.ts +22 -0
  156. package/dist/linters/house/no-silent-catch.rule.d.ts.map +1 -0
  157. package/dist/linters/house/no-silent-catch.rule.js +88 -0
  158. package/dist/linters/house/no-silent-catch.rule.js.map +1 -0
  159. package/dist/linters/house/plugin.d.ts.map +1 -1
  160. package/dist/linters/house/plugin.js +4 -0
  161. package/dist/linters/house/plugin.js.map +1 -1
  162. package/dist/linters/house/promise-chain-support.d.ts +49 -0
  163. package/dist/linters/house/promise-chain-support.d.ts.map +1 -0
  164. package/dist/linters/house/promise-chain-support.js +188 -0
  165. package/dist/linters/house/promise-chain-support.js.map +1 -0
  166. package/dist/linters/rules/no-restricted-properties.rule.js +1 -1
  167. package/dist/linters/rules/no-restricted-properties.rule.js.map +1 -1
  168. package/dist/testing/rejection.helper.d.ts +25 -20
  169. package/dist/testing/rejection.helper.d.ts.map +1 -1
  170. package/dist/testing/rejection.helper.js +33 -31
  171. package/dist/testing/rejection.helper.js.map +1 -1
  172. package/dist/testing/repo-tmp.d.ts +19 -9
  173. package/dist/testing/repo-tmp.d.ts.map +1 -1
  174. package/dist/testing/repo-tmp.js +50 -48
  175. package/dist/testing/repo-tmp.js.map +1 -1
  176. package/dist/testing/spawn.helper.d.ts +12 -10
  177. package/dist/testing/spawn.helper.d.ts.map +1 -1
  178. package/dist/testing/spawn.helper.js +22 -16
  179. package/dist/testing/spawn.helper.js.map +1 -1
  180. package/dist/testing/tmpdir-preload.js +1 -1
  181. package/dist/testing/tmpdir-preload.js.map +1 -1
  182. package/package.json +2 -3
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.probeEntry = exports.stripBundlerPathComments = exports.GLOBAL_PATTERNS = exports.ProbeSeverities = exports.PURITY_SCRATCH_DIR = void 0;
3
+ exports.PurityProbe = exports.GLOBAL_PATTERNS = exports.ProbeSeverities = exports.PURITY_SCRATCH_DIR = void 0;
4
4
  const node_crypto_1 = require("node:crypto");
5
5
  const promises_1 = require("node:fs/promises");
6
6
  const node_module_1 = require("node:module");
@@ -39,7 +39,7 @@ exports.ProbeSeverities = ProbeSeverities;
39
39
  * gate cry wolf on a dependency; the unguarded `process.` and `globalThis.process.` forms stay
40
40
  * fatal, and those are the ones that are unambiguously browser-breaking.
41
41
  *
42
- * Exported for `probe.test.ts` to assert against directly, alongside {@link stripBundlerPathComments}.
42
+ * Exported for `probe.test.ts` to assert against directly, alongside {@link PurityProbe.stripBundlerPathComments}.
43
43
  */
44
44
  exports.GLOBAL_PATTERNS = [
45
45
  { name: 'process', pattern: /(?<![A-Za-z0-9_$.])process\s*\./, severity: 'error' },
@@ -84,15 +84,6 @@ exports.GLOBAL_PATTERNS = [
84
84
  * information, so it cannot tell a path comment from code - stripped before matching.
85
85
  */
86
86
  const BUNDLER_PATH_COMMENT = /^\/\/ [\w@./-]+\.(?:c|m)?js$/gm;
87
- /**
88
- * Exported so `probe.test.ts` can assert against the literal collision string directly, without
89
- * spawning `bun build` - a fixture whose OWN entry-path comment happens to reproduce the collision
90
- * is not something a filename choice can be relied on to keep reproducing.
91
- */
92
- const stripBundlerPathComments = (text) => {
93
- return text.replace(BUNDLER_PATH_COMMENT, '');
94
- };
95
- exports.stripBundlerPathComments = stripBundlerPathComments;
96
87
  /**
97
88
  * `node:module`'s own list, not a hand-maintained one: a bare `import ... from 'fs'` bundles
98
89
  * identically to `'node:fs'` (Bun stubs both to an empty object), and the metafile records
@@ -177,7 +168,7 @@ const findUnresolvedExternalSpecifiers = (opts) => {
177
168
  };
178
169
  /**
179
170
  * Reads and removes the metafile unconditionally. Returns `undefined` rather than throwing -
180
- * `probeEntry` reports on every entry it is given rather than aborting the run of the other five.
171
+ * `PurityProbe.probeEntry` reports on every entry it is given rather than aborting the run of the other five.
181
172
  */
182
173
  const readMetafile = async (path) => {
183
174
  try {
@@ -193,104 +184,114 @@ const readMetafile = async (path) => {
193
184
  await (0, promises_1.rm)(path, { force: true });
194
185
  }
195
186
  };
196
- /**
197
- * The CLI, not `Bun.build()`: the in-process bundler resolves this workspace's symlinked
198
- * dependencies to directories and dies on them.
199
- */
200
- const probeEntry = async (opts) => {
201
- const { entry, cwd, external = [], scratchDir = (0, node_path_1.join)(cwd, exports.PURITY_SCRATCH_DIR) } = opts;
202
- await (0, promises_1.mkdir)(scratchDir, { recursive: true });
203
- const metafilePath = (0, node_path_1.join)(scratchDir, `purity-${(0, node_crypto_1.randomUUID)()}.json`);
204
- // `--env=disable`: Bun inlines `process.env.NODE_ENV` as a compile-time string constant by
205
- // default, regardless of whether the read is reachable. That erases the very read this probe
206
- // exists to catch, and the erasure depends on the ambient `NODE_ENV` the probe happens to run
207
- // under - the same failure shape as the undetected leak this gate was built to prevent.
208
- const externalArgs = external.flatMap((specifier) => ['--external', specifier]);
209
- const proc = Bun.spawn([
210
- 'bun',
211
- 'build',
212
- entry,
213
- '--target=browser',
214
- '--format=esm',
215
- '--env=disable',
216
- `--metafile=${metafilePath}`,
217
- ...externalArgs,
218
- ], { cwd, stdout: 'pipe', stderr: 'pipe' });
219
- const [stdout, stderr, exitCode] = await Promise.all([
220
- new Response(proc.stdout).text(),
221
- new Response(proc.stderr).text(),
222
- proc.exited,
223
- ]);
224
- if (exitCode !== 0) {
225
- await (0, promises_1.rm)(metafilePath, { force: true });
226
- return {
227
- entry,
228
- ok: false,
229
- sizeBytes: 0,
230
- builtins: [],
231
- globals: [],
232
- guardedGlobals: [],
233
- external,
234
- buildError: stderr.trim(),
235
- };
187
+ class PurityProbe {
188
+ /**
189
+ * Public so `probe.test.ts` can assert against the literal collision string directly, without
190
+ * spawning `bun build` - a fixture whose OWN entry-path comment happens to reproduce the collision
191
+ * is not something a filename choice can be relied on to keep reproducing.
192
+ */
193
+ static stripBundlerPathComments(opts) {
194
+ return opts.text.replace(BUNDLER_PATH_COMMENT, '');
236
195
  }
237
- const metafile = await readMetafile(metafilePath);
238
- if (!metafile) {
239
- return {
196
+ /**
197
+ * The CLI, not `Bun.build()`: the in-process bundler resolves this workspace's symlinked
198
+ * dependencies to directories and dies on them.
199
+ */
200
+ static async probeEntry(opts) {
201
+ const { entry, cwd, external = [], scratchDir = (0, node_path_1.join)(cwd, exports.PURITY_SCRATCH_DIR) } = opts;
202
+ await (0, promises_1.mkdir)(scratchDir, { recursive: true });
203
+ const metafilePath = (0, node_path_1.join)(scratchDir, `purity-${(0, node_crypto_1.randomUUID)()}.json`);
204
+ // `--env=disable`: Bun inlines `process.env.NODE_ENV` as a compile-time string constant by
205
+ // default, regardless of whether the read is reachable. That erases the very read this probe
206
+ // exists to catch, and the erasure depends on the ambient `NODE_ENV` the probe happens to run
207
+ // under - the same failure shape as the undetected leak this gate was built to prevent.
208
+ const externalArgs = external.flatMap((specifier) => ['--external', specifier]);
209
+ const proc = Bun.spawn([
210
+ 'bun',
211
+ 'build',
240
212
  entry,
241
- ok: false,
242
- sizeBytes: stdout.length,
243
- builtins: [],
244
- globals: [],
245
- guardedGlobals: [],
246
- external,
247
- buildError: 'Could not read the bun build metafile - the probe result is not trustworthy',
248
- };
249
- }
250
- const builtins = findBuiltinSpecifiers(metafile);
251
- const reachesDevConfigs = Object.keys(metafile.inputs).some((inputPath) => DEV_CONFIGS_PATH.test(inputPath));
252
- const unresolvedExternals = findUnresolvedExternalSpecifiers({
253
- metafile,
254
- builtins: new Set(builtins),
255
- requestedExternal: new Set(external),
256
- });
257
- const scannableText = (0, exports.stripBundlerPathComments)(stdout);
258
- const matched = exports.GLOBAL_PATTERNS.filter((g) => g.pattern.test(scannableText));
259
- const globals = matched.filter((g) => g.severity === ProbeSeverities.ERROR).map((g) => g.name);
260
- const guardedGlobals = matched
261
- .filter((g) => g.severity === ProbeSeverities.GUARDED)
262
- .map((g) => g.name);
263
- if (builtins.length > 0 || unresolvedExternals.length > 0 || reachesDevConfigs) {
264
- const reasons = [];
265
- if (reachesDevConfigs) {
266
- reasons.push('reaches @venizia/dev-configs, which is Bun-only tooling and never a browser dependency');
213
+ '--target=browser',
214
+ '--format=esm',
215
+ '--env=disable',
216
+ `--metafile=${metafilePath}`,
217
+ ...externalArgs,
218
+ ], { cwd, stdout: 'pipe', stderr: 'pipe' });
219
+ const [stdout, stderr, exitCode] = await Promise.all([
220
+ new Response(proc.stdout).text(),
221
+ new Response(proc.stderr).text(),
222
+ proc.exited,
223
+ ]);
224
+ if (exitCode !== 0) {
225
+ await (0, promises_1.rm)(metafilePath, { force: true });
226
+ return {
227
+ entry,
228
+ ok: false,
229
+ sizeBytes: 0,
230
+ builtins: [],
231
+ globals: [],
232
+ guardedGlobals: [],
233
+ external,
234
+ buildError: stderr.trim(),
235
+ };
267
236
  }
268
- if (builtins.length > 0) {
269
- reasons.push(`node builtin import(s): ${builtins.join(', ')}`);
237
+ const metafile = await readMetafile(metafilePath);
238
+ if (!metafile) {
239
+ return {
240
+ entry,
241
+ ok: false,
242
+ sizeBytes: stdout.length,
243
+ builtins: [],
244
+ globals: [],
245
+ guardedGlobals: [],
246
+ external,
247
+ buildError: 'Could not read the bun build metafile - the probe result is not trustworthy',
248
+ };
270
249
  }
271
- if (unresolvedExternals.length > 0) {
272
- reasons.push(`unresolved external import(s): ${unresolvedExternals.join(', ')}`);
250
+ const builtins = findBuiltinSpecifiers(metafile);
251
+ const reachesDevConfigs = Object.keys(metafile.inputs).some((inputPath) => DEV_CONFIGS_PATH.test(inputPath));
252
+ const unresolvedExternals = findUnresolvedExternalSpecifiers({
253
+ metafile,
254
+ builtins: new Set(builtins),
255
+ requestedExternal: new Set(external),
256
+ });
257
+ const scannableText = PurityProbe.stripBundlerPathComments({ text: stdout });
258
+ const matched = exports.GLOBAL_PATTERNS.filter((g) => g.pattern.test(scannableText));
259
+ const globals = matched.filter((g) => g.severity === ProbeSeverities.ERROR).map((g) => g.name);
260
+ const guardedGlobals = matched
261
+ .filter((g) => g.severity === ProbeSeverities.GUARDED)
262
+ .map((g) => g.name);
263
+ if (builtins.length > 0 || unresolvedExternals.length > 0 || reachesDevConfigs) {
264
+ const reasons = [];
265
+ if (reachesDevConfigs) {
266
+ reasons.push('reaches @venizia/dev-configs, which is Bun-only tooling and never a browser dependency');
267
+ }
268
+ if (builtins.length > 0) {
269
+ reasons.push(`node builtin import(s): ${builtins.join(', ')}`);
270
+ }
271
+ if (unresolvedExternals.length > 0) {
272
+ reasons.push(`unresolved external import(s): ${unresolvedExternals.join(', ')}`);
273
+ }
274
+ return {
275
+ entry,
276
+ ok: false,
277
+ sizeBytes: stdout.length,
278
+ builtins,
279
+ globals,
280
+ guardedGlobals,
281
+ external,
282
+ buildError: reasons.join(' | '),
283
+ };
273
284
  }
274
285
  return {
275
286
  entry,
276
- ok: false,
287
+ ok: globals.length === 0,
277
288
  sizeBytes: stdout.length,
278
289
  builtins,
279
290
  globals,
280
291
  guardedGlobals,
281
292
  external,
282
- buildError: reasons.join(' | '),
283
293
  };
284
294
  }
285
- return {
286
- entry,
287
- ok: globals.length === 0,
288
- sizeBytes: stdout.length,
289
- builtins,
290
- globals,
291
- guardedGlobals,
292
- external,
293
- };
294
- };
295
- exports.probeEntry = probeEntry;
295
+ }
296
+ exports.PurityProbe = PurityProbe;
296
297
  //# sourceMappingURL=probe.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"probe.js","sourceRoot":"","sources":["../../../src/checks/purity/probe.ts"],"names":[],"mappings":"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","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 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\n/**\n * Exported 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 */\nexport const stripBundlerPathComments = (text: string): string => {\n return text.replace(BUNDLER_PATH_COMMENT, '');\n};\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 * `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\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 */\nexport const probeEntry = async (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 = stripBundlerPathComments(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"]}
1
+ {"version":3,"file":"probe.js","sourceRoot":"","sources":["../../../src/checks/purity/probe.ts"],"names":[],"mappings":"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","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"]}
@@ -28,20 +28,22 @@ export interface IPurityReport {
28
28
  waived: number;
29
29
  checked: number;
30
30
  }
31
- /**
32
- * Probes every row of the claims and returns the report. `print` receives the human report line by
33
- * line, in the exact shape the repository scripts printed before the engine moved here.
34
- */
35
- export declare const checkPurity: (opts: {
36
- root: string;
37
- claims: IPurityClaim[];
38
- options?: IPurityOptions;
39
- /** Only the rows of this package; a token that matches nothing is a broken gate and returns `undefined`. */
40
- filter?: string;
41
- /** The claims file, repository-relative, named in the hint printed for a stale waiver. */
42
- claimsFile: string;
43
- print: (line: string) => void;
44
- /** Where the no-match message goes (stderr for the command): it is an error, not a report line. */
45
- printError: (line: string) => void;
46
- }) => Promise<IPurityReport | undefined>;
31
+ export declare class PurityCheck {
32
+ /**
33
+ * Probes every row of the claims and returns the report. `print` receives the human report line by
34
+ * line, in the exact shape the repository scripts printed before the engine moved here.
35
+ */
36
+ static run(opts: {
37
+ root: string;
38
+ claims: IPurityClaim[];
39
+ options?: IPurityOptions;
40
+ /** Only the rows of this package; a token that matches nothing is a broken gate and returns `undefined`. */
41
+ filter?: string;
42
+ /** The claims file, repository-relative, named in the hint printed for a stale waiver. */
43
+ claimsFile: string;
44
+ print: (line: string) => void;
45
+ /** Where the no-match message goes (stderr for the command): it is an error, not a report line. */
46
+ printError: (line: string) => void;
47
+ }): Promise<IPurityReport | undefined>;
48
+ }
47
49
  //# sourceMappingURL=run.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/checks/purity/run.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAgB,cAAc,EAAE,MAAM,YAAY,CAAC;AAI7E,oFAAoF;AACpF,qBAAa,cAAc;IACzB,MAAM,CAAC,QAAQ,CAAC,IAAI,UAAU;IAC9B,MAAM,CAAC,QAAQ,CAAC,IAAI,UAAU;IAC9B,MAAM,CAAC,QAAQ,CAAC,MAAM,YAAY;IAClC,MAAM,CAAC,QAAQ,CAAC,OAAO,aAAa;IACpC,MAAM,CAAC,QAAQ,CAAC,YAAY,kBAAkB;IAE9C,MAAM,CAAC,QAAQ,CAAC,UAAU,cAMvB;IAEH,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,cAAc;CAGvD;AAED,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,OAAO,cAAc,CAAC,EAAE,MAAM,CAAC,CAAC;AAEnG,gHAAgH;AAChH,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,cAAc,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,iBAAiB,EAAE,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAID;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAU,MAAM;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,4GAA4G;IAC5G,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0FAA0F;IAC1F,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,mGAAmG;IACnG,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC,KAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAgHpC,CAAC"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/checks/purity/run.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAgB,cAAc,EAAE,MAAM,YAAY,CAAC;AAI7E,oFAAoF;AACpF,qBAAa,cAAc;IACzB,MAAM,CAAC,QAAQ,CAAC,IAAI,UAAU;IAC9B,MAAM,CAAC,QAAQ,CAAC,IAAI,UAAU;IAC9B,MAAM,CAAC,QAAQ,CAAC,MAAM,YAAY;IAClC,MAAM,CAAC,QAAQ,CAAC,OAAO,aAAa;IACpC,MAAM,CAAC,QAAQ,CAAC,YAAY,kBAAkB;IAE9C,MAAM,CAAC,QAAQ,CAAC,UAAU,cAMvB;IAEH,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,cAAc;CAGvD;AAED,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,OAAO,cAAc,CAAC,EAAE,MAAM,CAAC,CAAC;AAEnG,gHAAgH;AAChH,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,cAAc,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,iBAAiB,EAAE,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAID,qBAAa,WAAW;IACtB;;;OAGG;WACU,GAAG,CAAC,IAAI,EAAE;QACrB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,YAAY,EAAE,CAAC;QACvB,OAAO,CAAC,EAAE,cAAc,CAAC;QACzB,4GAA4G;QAC5G,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,0FAA0F;QAC1F,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;QAC9B,mGAAmG;QACnG,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;KACpC,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC;CAsHvC"}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.checkPurity = exports.PurityVerdicts = void 0;
3
+ exports.PurityCheck = exports.PurityVerdicts = void 0;
4
4
  const node_fs_1 = require("node:fs");
5
5
  const node_path_1 = require("node:path");
6
6
  const manifest_1 = require("./manifest");
@@ -25,97 +25,101 @@ class PurityVerdicts {
25
25
  }
26
26
  exports.PurityVerdicts = PurityVerdicts;
27
27
  const formatList = (values) => values.join(', ') || 'none';
28
- /**
29
- * Probes every row of the claims and returns the report. `print` receives the human report line by
30
- * line, in the exact shape the repository scripts printed before the engine moved here.
31
- */
32
- const checkPurity = async (opts) => {
33
- const { root, claims, options, filter, claimsFile, print, printError } = opts;
34
- const manifest = (0, manifest_1.buildManifest)({ claims, root, options });
35
- const rows = filter ? manifest.filter((row) => row.package === filter) : manifest;
36
- // Non-zero, not a friendly note: a mistyped `package` or a renamed directory would otherwise turn
37
- // a release-blocking gate into a silent no-op that still reports success.
38
- if (rows.length === 0) {
39
- const known = [...new Set(manifest.map((row) => row.package))].sort().join(', ');
40
- printError(`[purity] No manifest entry matches "${filter}" | Known packages: ${known} - a filter that ` +
41
- `matches nothing is a broken gate, not an empty one.`);
42
- return undefined;
43
- }
44
- const labelWidth = Math.max(...rows.map((row) => row.label.length));
45
- const verdicts = [];
46
- let failed = 0;
47
- let waived = 0;
48
- for (const row of rows) {
49
- const entry = (0, node_path_1.join)(root, row.entry);
50
- // The `exports` map is the source of these rows, so a missing file is either an unbuilt package
51
- // or a manifest pointing at something the build never emits - both fatal, and neither is worth
52
- // reporting as a bundler error twenty lines long.
53
- if (!(0, node_fs_1.existsSync)(entry)) {
54
- failed += 1;
55
- print(` ✗ ${row.label.padEnd(labelWidth)} MISSING ${row.entry}`);
56
- print(` The package's \`exports\` map points here and nothing is built - run \`make ${row.package}\``);
57
- verdicts.push({ label: row.label, entry: row.entry, verdict: PurityVerdicts.MISSING });
58
- continue;
28
+ class PurityCheck {
29
+ /**
30
+ * Probes every row of the claims and returns the report. `print` receives the human report line by
31
+ * line, in the exact shape the repository scripts printed before the engine moved here.
32
+ */
33
+ static async run(opts) {
34
+ const { root, claims, options, filter, claimsFile, print, printError } = opts;
35
+ const manifest = manifest_1.PurityManifest.build({ claims, root, options });
36
+ const rows = filter
37
+ ? manifest.filter((row) => row.package === filter)
38
+ : manifest;
39
+ // Non-zero, not a friendly note: a mistyped `package` or a renamed directory would otherwise turn
40
+ // a release-blocking gate into a silent no-op that still reports success.
41
+ if (rows.length === 0) {
42
+ const known = [...new Set(manifest.map((row) => row.package))].sort().join(', ');
43
+ printError(`[purity] No manifest entry matches "${filter}" | Known packages: ${known} - a filter that ` +
44
+ `matches nothing is a broken gate, not an empty one.`);
45
+ return undefined;
59
46
  }
60
- const result = await (0, probe_1.probeEntry)({
61
- entry,
62
- cwd: root,
63
- external: row.external,
64
- });
65
- const detail = {
66
- label: row.label,
67
- entry: row.entry,
68
- sizeBytes: result.sizeBytes,
69
- builtins: result.builtins,
70
- globals: result.globals,
71
- guardedGlobals: result.guardedGlobals,
72
- external: result.external,
73
- // The bundler's message names absolute paths; the verdict must read the same on any machine.
74
- ...(result.buildError ? { buildError: result.buildError.replaceAll(root, '<root>') } : {}),
75
- };
76
- // Guarded reads are printed on the passing path too: they are safe, but a reviewer needs to
77
- // see that the entry is clean by construction and not merely by the shape of one expression.
78
- const guarded = result.guardedGlobals.length > 0 ? ` | guarded: ${result.guardedGlobals.join(', ')}` : '';
79
- // Same reasoning as `guarded`, printed on both paths: an entry that externalises a dependency
80
- // measures less than one that does not, and that must stay visible next to the verdict.
81
- const external = result.external.length > 0 ? ` | external: ${result.external.join(', ')}` : '';
82
- // Checked before the normal verdict, because for a waived row the two outcomes swap meaning.
83
- if (row.expectImpure) {
84
- // A waived entry that comes back pure fails: the waiver now describes something untrue, and
85
- // the reader who trusts it next will be trusting it about a different entry.
86
- if (result.ok) {
47
+ const labelWidth = Math.max(...rows.map((row) => row.label.length));
48
+ const verdicts = [];
49
+ let failed = 0;
50
+ let waived = 0;
51
+ for (const row of rows) {
52
+ const entry = (0, node_path_1.join)(root, row.entry);
53
+ // The `exports` map is the source of these rows, so a missing file is either an unbuilt package
54
+ // or a manifest pointing at something the build never emits - both fatal, and neither is worth
55
+ // reporting as a bundler error twenty lines long.
56
+ if (!(0, node_fs_1.existsSync)(entry)) {
87
57
  failed += 1;
88
- print(` ✗ ${row.label.padEnd(labelWidth)} STALE WAIVER - this entry is browser-pure now`);
89
- print(` Drop it from \`impure\` in ${claimsFile} so the gate measures it again`);
90
- verdicts.push({ ...detail, verdict: PurityVerdicts.STALE_WAIVER });
58
+ print(` ✗ ${row.label.padEnd(labelWidth)} MISSING ${row.entry}`);
59
+ print(` The package's \`exports\` map points here and nothing is built - run \`make ${row.package}\``);
60
+ verdicts.push({ label: row.label, entry: row.entry, verdict: PurityVerdicts.MISSING });
91
61
  continue;
92
62
  }
93
- waived += 1;
94
- print(` ~ ${row.label.padEnd(labelWidth)} waived | builtins: ${formatList(result.builtins)} | globals: ${formatList(result.globals)}${external}`);
95
- verdicts.push({ ...detail, verdict: PurityVerdicts.WAIVED });
96
- continue;
63
+ const result = await probe_1.PurityProbe.probeEntry({
64
+ entry,
65
+ cwd: root,
66
+ external: row.external,
67
+ });
68
+ const detail = {
69
+ label: row.label,
70
+ entry: row.entry,
71
+ sizeBytes: result.sizeBytes,
72
+ builtins: result.builtins,
73
+ globals: result.globals,
74
+ guardedGlobals: result.guardedGlobals,
75
+ external: result.external,
76
+ // The bundler's message names absolute paths; the verdict must read the same on any machine.
77
+ ...(result.buildError ? { buildError: result.buildError.replaceAll(root, '<root>') } : {}),
78
+ };
79
+ // Guarded reads are printed on the passing path too: they are safe, but a reviewer needs to
80
+ // see that the entry is clean by construction and not merely by the shape of one expression.
81
+ const guarded = result.guardedGlobals.length > 0 ? ` | guarded: ${result.guardedGlobals.join(', ')}` : '';
82
+ // Same reasoning as `guarded`, printed on both paths: an entry that externalises a dependency
83
+ // measures less than one that does not, and that must stay visible next to the verdict.
84
+ const external = result.external.length > 0 ? ` | external: ${result.external.join(', ')}` : '';
85
+ // Checked before the normal verdict, because for a waived row the two outcomes swap meaning.
86
+ if (row.expectImpure) {
87
+ // A waived entry that comes back pure fails: the waiver now describes something untrue, and
88
+ // the reader who trusts it next will be trusting it about a different entry.
89
+ if (result.ok) {
90
+ failed += 1;
91
+ print(` ✗ ${row.label.padEnd(labelWidth)} STALE WAIVER - this entry is browser-pure now`);
92
+ print(` Drop it from \`impure\` in ${claimsFile} so the gate measures it again`);
93
+ verdicts.push({ ...detail, verdict: PurityVerdicts.STALE_WAIVER });
94
+ continue;
95
+ }
96
+ waived += 1;
97
+ print(` ~ ${row.label.padEnd(labelWidth)} waived | builtins: ${formatList(result.builtins)} | globals: ${formatList(result.globals)}${external}`);
98
+ verdicts.push({ ...detail, verdict: PurityVerdicts.WAIVED });
99
+ continue;
100
+ }
101
+ if (result.ok) {
102
+ print(` ✓ ${row.label.padEnd(labelWidth)} ${(result.sizeBytes / 1024).toFixed(1)} KB${guarded}${external}`);
103
+ verdicts.push({ ...detail, verdict: PurityVerdicts.PASS });
104
+ continue;
105
+ }
106
+ failed += 1;
107
+ print(` ✗ ${row.label.padEnd(labelWidth)} builtins: ${formatList(result.builtins)} | globals: ${formatList(result.globals)}${guarded}${external}`);
108
+ if (result.buildError) {
109
+ print(` ${result.buildError.split('\n')[0]}`);
110
+ }
111
+ verdicts.push({ ...detail, verdict: PurityVerdicts.FAIL });
97
112
  }
98
- if (result.ok) {
99
- print(` ✓ ${row.label.padEnd(labelWidth)} ${(result.sizeBytes / 1024).toFixed(1)} KB${guarded}${external}`);
100
- verdicts.push({ ...detail, verdict: PurityVerdicts.PASS });
101
- continue;
113
+ const note = waived > 0 ? ` (${waived} waived as known-impure)` : '';
114
+ if (failed > 0) {
115
+ print(`\n[purity] ${failed}/${rows.length} entries are not browser-pure${note}.`);
102
116
  }
103
- failed += 1;
104
- print(` ✗ ${row.label.padEnd(labelWidth)} builtins: ${formatList(result.builtins)} | globals: ${formatList(result.globals)}${guarded}${external}`);
105
- if (result.buildError) {
106
- print(` ${result.buildError.split('\n')[0]}`);
117
+ else {
118
+ const checked = rows.length - waived;
119
+ print(`\n[purity] ${checked}/${checked} entries are browser-pure${note}.`);
107
120
  }
108
- verdicts.push({ ...detail, verdict: PurityVerdicts.FAIL });
121
+ return { rows: verdicts, failed, waived, checked: rows.length - waived };
109
122
  }
110
- const note = waived > 0 ? ` (${waived} waived as known-impure)` : '';
111
- if (failed > 0) {
112
- print(`\n[purity] ${failed}/${rows.length} entries are not browser-pure${note}.`);
113
- }
114
- else {
115
- const checked = rows.length - waived;
116
- print(`\n[purity] ${checked}/${checked} entries are browser-pure${note}.`);
117
- }
118
- return { rows: verdicts, failed, waived, checked: rows.length - waived };
119
- };
120
- exports.checkPurity = checkPurity;
123
+ }
124
+ exports.PurityCheck = PurityCheck;
121
125
  //# sourceMappingURL=run.js.map