@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.buildManifest = exports.assertBrowserEsmEntry = exports.deriveEntries = exports.assertNoWorkspaceExternal = void 0;
3
+ exports.PurityManifest = void 0;
4
4
  const node_fs_1 = require("node:fs");
5
5
  const node_path_1 = require("node:path");
6
6
  const DEFAULT_WORKSPACE_SCOPE = '@venizia/';
@@ -10,30 +10,6 @@ const purityError = (message) => new Error(message);
10
10
  /** `types` describes the entry, it is not a runtime target; `./package.json` is not code. */
11
11
  const NON_RUNTIME_CONDITION = 'types';
12
12
  const NON_CODE_SUBPATH = './package.json';
13
- /**
14
- * A code-level guard, not just the JSDoc on `external`: it is an escape hatch from this gate, and a
15
- * hatch with no lock is one bad edit away from hiding a real leak - `external: ['@venizia/ignis-helpers']`
16
- * on a red entry would silently turn it green by removing the exact root-barrel import this gate
17
- * exists to catch from the module graph, rather than proving it absent.
18
- *
19
- * A plain `Error`, not `getError`: the engine runs from the repository root through `devz`, outside
20
- * every workspace package, and has no logger to resolve.
21
- */
22
- const assertNoWorkspaceExternal = (manifest, workspaceScope = DEFAULT_WORKSPACE_SCOPE) => {
23
- for (const row of manifest) {
24
- const specifiers = row.external ?? [];
25
- for (const specifier of specifiers) {
26
- if (!specifier.startsWith(workspaceScope)) {
27
- continue;
28
- }
29
- throw purityError(`[purity][manifest] Entry '${row.label}' externalises '${specifier}', a workspace package ` +
30
- `| \`external\` may only exempt THIRD-PARTY packaging, never the repository's own code - doing so ` +
31
- `would hide the exact leak this gate exists to catch`);
32
- }
33
- }
34
- return manifest;
35
- };
36
- exports.assertNoWorkspaceExternal = assertNoWorkspaceExternal;
37
13
  const readExportsMap = (opts) => {
38
14
  const manifestPath = (0, node_path_1.join)(opts.root, 'packages', opts.package, 'package.json');
39
15
  const manifest = JSON.parse((0, node_fs_1.readFileSync)(manifestPath, 'utf8'));
@@ -71,107 +47,137 @@ const buildLabel = (opts) => {
71
47
  const base = `${packageName}${suffix}`;
72
48
  return isSoleTarget ? base : `${base} [${condition}]`;
73
49
  };
74
- /**
75
- * Exported for the tests, which have to drive a claim that no longer matches its package's
76
- * `exports` map - the staleness guard is the whole point of deriving, and it cannot be asserted
77
- * through a built manifest, which by construction is never stale.
78
- */
79
- const deriveEntries = (opts) => {
80
- const { claim, root } = opts;
81
- const exportsMap = readExportsMap({ root, package: claim.package });
82
- const published = Object.keys(exportsMap).filter((subpath) => subpath !== NON_CODE_SUBPATH);
83
- const subpathsList = claim.subpaths ?? [];
84
- for (const subpath of subpathsList) {
85
- if (published.includes(subpath)) {
86
- continue;
50
+ /** Derives the rows of the purity gate from the `exports` map of each claimed package, then runs the guards on them. */
51
+ class PurityManifest {
52
+ /**
53
+ * A code-level guard, not just the JSDoc on `external`: it is an escape hatch from this gate, and a
54
+ * hatch with no lock is one bad edit away from hiding a real leak - `external: ['@venizia/ignis-helpers']`
55
+ * on a red entry would silently turn it green by removing the exact root-barrel import this gate
56
+ * exists to catch from the module graph, rather than proving it absent.
57
+ *
58
+ * A plain `Error`, not `getError`: the engine runs from the repository root through `devz`, outside
59
+ * every workspace package, and has no logger to resolve.
60
+ */
61
+ static assertNoWorkspaceExternal(opts) {
62
+ const { manifest, workspaceScope = DEFAULT_WORKSPACE_SCOPE } = opts;
63
+ for (const row of manifest) {
64
+ const specifiers = row.external ?? [];
65
+ for (const specifier of specifiers) {
66
+ if (!specifier.startsWith(workspaceScope)) {
67
+ continue;
68
+ }
69
+ throw purityError(`[purity][manifest] Entry '${row.label}' externalises '${specifier}', a workspace package ` +
70
+ `| \`external\` may only exempt THIRD-PARTY packaging, never the repository's own code - doing so ` +
71
+ `would hide the exact leak this gate exists to catch`);
72
+ }
87
73
  }
88
- throw purityError(`[purity][manifest] '${claim.package}' claims '${subpath}', which its \`exports\` map no longer ` +
89
- `publishes | Published: ${published.join(', ')} - a renamed sub-path must not silently drop its row`);
74
+ return manifest;
90
75
  }
91
- const claimExternalKeys = Object.keys(claim.external ?? {});
92
- for (const subpath of claimExternalKeys) {
93
- if (published.includes(subpath)) {
94
- continue;
76
+ /**
77
+ * Public for the tests, which have to drive a claim that no longer matches its package's
78
+ * `exports` map - the staleness guard is the whole point of deriving, and it cannot be asserted
79
+ * through a built manifest, which by construction is never stale.
80
+ */
81
+ static deriveEntries(opts) {
82
+ const { claim, root } = opts;
83
+ const exportsMap = readExportsMap({ root, package: claim.package });
84
+ const published = Object.keys(exportsMap).filter((subpath) => subpath !== NON_CODE_SUBPATH);
85
+ const subpathsList = claim.subpaths ?? [];
86
+ for (const subpath of subpathsList) {
87
+ if (published.includes(subpath)) {
88
+ continue;
89
+ }
90
+ throw purityError(`[purity][manifest] '${claim.package}' claims '${subpath}', which its \`exports\` map no longer ` +
91
+ `publishes | Published: ${published.join(', ')} - a renamed sub-path must not silently drop its row`);
95
92
  }
96
- throw purityError(`[purity][manifest] '${claim.package}' declares \`external\` for '${subpath}', which its ` +
97
- `\`exports\` map no longer publishes | An external that outlives its entry is a waiver ` +
98
- `nobody is reading`);
99
- }
100
- const claimed = claim.subpaths ?? published;
101
- const claimImpureKeys = Object.keys(claim.impure ?? {});
102
- for (const subpath of claimImpureKeys) {
103
- if (claimed.includes(subpath)) {
104
- continue;
93
+ const claimExternalKeys = Object.keys(claim.external ?? {});
94
+ for (const subpath of claimExternalKeys) {
95
+ if (published.includes(subpath)) {
96
+ continue;
97
+ }
98
+ throw purityError(`[purity][manifest] '${claim.package}' declares \`external\` for '${subpath}', which its ` +
99
+ `\`exports\` map no longer publishes | An external that outlives its entry is a waiver ` +
100
+ `nobody is reading`);
105
101
  }
106
- throw purityError(`[purity][manifest] '${claim.package}' waives '${subpath}' as impure, but that sub-path is not ` +
107
- `claimed | Claimed: ${claimed.join(', ')} - a waiver against nothing is one nobody is reading`);
108
- }
109
- const entries = [];
110
- for (const subpath of claimed) {
111
- const targets = listConditionTargets(exportsMap[subpath]);
112
- const conditions = claim.impure?.[subpath] ?? [];
113
- for (const condition of conditions) {
114
- if (targets.some((target) => target.condition === condition)) {
102
+ const claimed = claim.subpaths ?? published;
103
+ const claimImpureKeys = Object.keys(claim.impure ?? {});
104
+ for (const subpath of claimImpureKeys) {
105
+ if (claimed.includes(subpath)) {
115
106
  continue;
116
107
  }
117
- throw purityError(`[purity][manifest] '${claim.package}' waives '${subpath}' under condition '${condition}', ` +
118
- `which its \`exports\` map does not publish | Published: ${targets.map((t) => t.condition).join(', ')}`);
108
+ throw purityError(`[purity][manifest] '${claim.package}' waives '${subpath}' as impure, but that sub-path is not ` +
109
+ `claimed | Claimed: ${claimed.join(', ')} - a waiver against nothing is one nobody is reading`);
119
110
  }
120
- for (const { condition, file } of targets) {
121
- entries.push({
122
- label: buildLabel({
111
+ const entries = [];
112
+ for (const subpath of claimed) {
113
+ const targets = listConditionTargets(exportsMap[subpath]);
114
+ const conditions = claim.impure?.[subpath] ?? [];
115
+ for (const condition of conditions) {
116
+ if (targets.some((target) => target.condition === condition)) {
117
+ continue;
118
+ }
119
+ throw purityError(`[purity][manifest] '${claim.package}' waives '${subpath}' under condition '${condition}', ` +
120
+ `which its \`exports\` map does not publish | Published: ${targets.map((t) => t.condition).join(', ')}`);
121
+ }
122
+ for (const { condition, file } of targets) {
123
+ entries.push({
124
+ label: buildLabel({
125
+ package: claim.package,
126
+ subpath,
127
+ condition,
128
+ isSoleTarget: targets.length === 1,
129
+ }),
123
130
  package: claim.package,
124
- subpath,
125
- condition,
126
- isSoleTarget: targets.length === 1,
127
- }),
128
- package: claim.package,
129
- entry: (0, node_path_1.join)('packages', claim.package, file),
130
- ...(claim.external?.[subpath] ? { external: claim.external[subpath] } : {}),
131
- ...(claim.impure?.[subpath]?.includes(condition) ? { expectImpure: true } : {}),
132
- });
131
+ entry: (0, node_path_1.join)('packages', claim.package, file),
132
+ ...(claim.external?.[subpath] ? { external: claim.external[subpath] } : {}),
133
+ ...(claim.impure?.[subpath]?.includes(condition) ? { expectImpure: true } : {}),
134
+ });
135
+ }
133
136
  }
137
+ return entries;
134
138
  }
135
- return entries;
136
- };
137
- exports.deriveEntries = deriveEntries;
138
- /**
139
- * A browser claim is only honest if a browser bundler can actually consume the entry. A CommonJS
140
- * entry forces every consumer into a workaround, so the claim carries the obligation: every claimed
141
- * sub-path must publish its runtime entries below `esmEntryPrefix`. Checked here rather than trusted
142
- * to a comment, for the same reason as {@link assertNoWorkspaceExternal} - the failure is otherwise
143
- * invisible until someone tries to bundle the package downstream.
144
- */
145
- const assertBrowserEsmEntry = (opts) => {
146
- const { claims, root, esmEntryPrefix } = opts;
147
- for (const claim of claims) {
148
- const exportsMap = readExportsMap({ root, package: claim.package });
149
- const claimed = claim.subpaths ?? Object.keys(exportsMap).filter((s) => s !== NON_CODE_SUBPATH);
150
- for (const subpath of claimed) {
151
- const target = exportsMap[subpath];
152
- const runtimeTargets = typeof target === 'object' && target !== null
153
- ? Object.entries(target)
154
- .filter(([condition]) => condition !== NON_RUNTIME_CONDITION)
155
- .map(([, file]) => file)
156
- : [];
157
- if (runtimeTargets.length > 0 &&
158
- runtimeTargets.every((file) => file.startsWith(esmEntryPrefix))) {
159
- continue;
139
+ /**
140
+ * A browser claim is only honest if a browser bundler can actually consume the entry. A CommonJS
141
+ * entry forces every consumer into a workaround, so the claim carries the obligation: every claimed
142
+ * sub-path must publish its runtime entries below `esmEntryPrefix`. Checked here rather than trusted
143
+ * to a comment, for the same reason as {@link PurityManifest.assertNoWorkspaceExternal} - the failure is otherwise
144
+ * invisible until someone tries to bundle the package downstream.
145
+ */
146
+ static assertBrowserEsmEntry(opts) {
147
+ const { claims, root, esmEntryPrefix } = opts;
148
+ for (const claim of claims) {
149
+ const exportsMap = readExportsMap({ root, package: claim.package });
150
+ const claimed = claim.subpaths ?? Object.keys(exportsMap).filter((s) => s !== NON_CODE_SUBPATH);
151
+ for (const subpath of claimed) {
152
+ const target = exportsMap[subpath];
153
+ const runtimeTargets = typeof target === 'object' && target !== null
154
+ ? Object.entries(target)
155
+ .filter(([condition]) => condition !== NON_RUNTIME_CONDITION)
156
+ .map(([, file]) => file)
157
+ : [];
158
+ if (runtimeTargets.length > 0 &&
159
+ runtimeTargets.every((file) => file.startsWith(esmEntryPrefix))) {
160
+ continue;
161
+ }
162
+ throw purityError(`[purity][manifest] '${claim.package}' claims browser purity for '${subpath}' but its runtime ` +
163
+ `entry is not in ${esmEntryPrefix.replace(/^\.\//, '')} | A browser bundler needs an ES module, so the claim ` +
164
+ `cannot be consumed - build the package as ES modules`);
160
165
  }
161
- throw purityError(`[purity][manifest] '${claim.package}' claims browser purity for '${subpath}' but its runtime ` +
162
- `entry is not in ${esmEntryPrefix.replace(/^\.\//, '')} | A browser bundler needs an ES module, so the claim ` +
163
- `cannot be consumed - build the package as ES modules`);
164
166
  }
167
+ return claims;
165
168
  }
166
- return claims;
167
- };
168
- exports.assertBrowserEsmEntry = assertBrowserEsmEntry;
169
- /** The rows of a whole repository: every claim derived from its package's `exports`, then the guards. */
170
- const buildManifest = (opts) => {
171
- const { claims, root, options = {} } = opts;
172
- const { esmEntryPrefix, workspaceScope = DEFAULT_WORKSPACE_SCOPE } = options;
173
- const checked = esmEntryPrefix === undefined ? claims : (0, exports.assertBrowserEsmEntry)({ claims, root, esmEntryPrefix });
174
- return (0, exports.assertNoWorkspaceExternal)(checked.flatMap((claim) => (0, exports.deriveEntries)({ claim, root })), workspaceScope);
175
- };
176
- exports.buildManifest = buildManifest;
169
+ /** The rows of a whole repository: every claim derived from its package's `exports`, then the guards. */
170
+ static build(opts) {
171
+ const { claims, root, options = {} } = opts;
172
+ const { esmEntryPrefix, workspaceScope = DEFAULT_WORKSPACE_SCOPE } = options;
173
+ const checked = esmEntryPrefix === undefined
174
+ ? claims
175
+ : PurityManifest.assertBrowserEsmEntry({ claims, root, esmEntryPrefix });
176
+ return PurityManifest.assertNoWorkspaceExternal({
177
+ manifest: checked.flatMap((claim) => PurityManifest.deriveEntries({ claim, root })),
178
+ workspaceScope,
179
+ });
180
+ }
181
+ }
182
+ exports.PurityManifest = PurityManifest;
177
183
  //# sourceMappingURL=manifest.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../../src/checks/purity/manifest.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport interface IPurityEntry {\n label: string;\n package: string;\n /** Repository-relative path to the built file the `exports` map points at. */\n entry: string;\n /**\n * Specifiers to pass through as `bun build --external`. Only for a third-party dependency whose\n * OWN packaging is out of this gate's remit - e.g. one that relies on its `package.json` `browser`\n * field remap, which the downstream app's bundler (Vite, ...) honours and `bun build` does not.\n * Never for the repository's own source: an external hides that file from the module graph\n * entirely, so using it on our own code would let a real leak through unseen rather than prove\n * anything pure. Enforced by {@link assertNoWorkspaceExternal}, not left to this comment alone.\n */\n external?: string[];\n /**\n * This row is EXPECTED to be impure, so failing is its passing state. Derived from\n * {@link IPurityClaim.impure} - never authored on a row directly.\n */\n expectImpure?: boolean;\n}\n\n/**\n * A package whose published entry points carry a browser-purity claim.\n *\n * The ROW SET IS NEVER AUTHORED. It is read from the package's own `exports` map, so a sub-path\n * added to a manifest is probed the moment it is published.\n */\nexport interface IPurityClaim {\n /** Directory under `packages/`, and the token `devz check purity <token>` filters on. */\n package: string;\n /**\n * Sub-paths (as spelled in `exports`) when the claim covers only PART of the published surface.\n * Omit it and the whole surface is claimed. Every sub-path named here must still exist in\n * `exports`; a rename fails the derivation loudly rather than quietly dropping a row.\n */\n subpaths?: string[];\n /** `external` for one sub-path - see {@link IPurityEntry.external}. Keyed as spelled in `exports`. */\n external?: Record<string, string[]>;\n /**\n * Entries that CANNOT be browser-pure, keyed as spelled in `exports`, valued by the conditions\n * that are impure. The waiver is exact in BOTH directions: a listed entry that turns out to be\n * pure fails as loudly as an unlisted one that is not. Deriving still owns the ROW SET, so a\n * sub-path added later is claimed pure by default and has to earn its line here.\n */\n impure?: Record<string, string[]>;\n}\n\n/** What a repository may tune about the engine; everything else is the engine's contract. */\nexport interface IPurityOptions {\n /**\n * When set, every claimed sub-path must publish its runtime entries below this prefix (for\n * example `./dist/esm/`): a browser bundler needs an ES module, so a CommonJS-only entry makes\n * the claim unconsumable. Omit it for a repository that publishes a single build.\n */\n esmEntryPrefix?: string;\n\n /** Package-name prefix of this repository's own packages; an `external` may never start with it. Default `@venizia/`. */\n workspaceScope?: string;\n}\n\nconst DEFAULT_WORKSPACE_SCOPE = '@venizia/';\n\n/** The one place this engine 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 purityError = (message: string): Error => new Error(message);\n\n/** `types` describes the entry, it is not a runtime target; `./package.json` is not code. */\nconst NON_RUNTIME_CONDITION = 'types';\nconst NON_CODE_SUBPATH = './package.json';\n\n/**\n * A code-level guard, not just the JSDoc on `external`: it is an escape hatch from this gate, and a\n * hatch with no lock is one bad edit away from hiding a real leak - `external: ['@venizia/ignis-helpers']`\n * on a red entry would silently turn it green by removing the exact root-barrel import this gate\n * exists to catch from the module graph, rather than proving it absent.\n *\n * A plain `Error`, not `getError`: the engine runs from the repository root through `devz`, outside\n * every workspace package, and has no logger to resolve.\n */\nexport const assertNoWorkspaceExternal = (\n manifest: IPurityEntry[],\n workspaceScope: string = DEFAULT_WORKSPACE_SCOPE,\n): IPurityEntry[] => {\n for (const row of manifest) {\n const specifiers = row.external ?? [];\n for (const specifier of specifiers) {\n if (!specifier.startsWith(workspaceScope)) {\n continue;\n }\n\n throw purityError(\n `[purity][manifest] Entry '${row.label}' externalises '${specifier}', a workspace package ` +\n `| \\`external\\` may only exempt THIRD-PARTY packaging, never the repository's own code - doing so ` +\n `would hide the exact leak this gate exists to catch`,\n );\n }\n }\n\n return manifest;\n};\n\ntype TExportsTarget = string | Record<string, string>;\n\nconst readExportsMap = (opts: {\n root: string;\n package: string;\n}): Record<string, TExportsTarget> => {\n const manifestPath = join(opts.root, 'packages', opts.package, 'package.json');\n const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) as {\n exports?: Record<string, TExportsTarget>;\n };\n\n if (!manifest.exports) {\n throw purityError(\n `[purity][manifest] '${opts.package}' claims browser purity but publishes no \\`exports\\` map ` +\n `| There is nothing to derive rows from - remove the claim or add the map`,\n );\n }\n\n return manifest.exports;\n};\n\n/**\n * `exports` may spell one sub-path as several conditions pointing at different builds - the dual\n * CJS/ESM packages do exactly that - and each is a separately resolvable file that a consumer can\n * end up importing, so each is probed. `default` normally repeats `require`, so files are deduped\n * and the FIRST condition naming a file is the one that labels it.\n */\nconst listConditionTargets = (\n target: TExportsTarget,\n): Array<{ condition: string; file: string }> => {\n if (typeof target === 'string') {\n return [{ condition: 'default', file: target }];\n }\n\n const seen = new Set<string>();\n const targets: Array<{ condition: string; file: string }> = [];\n\n const targetEntries = Object.entries(target);\n for (const [condition, file] of targetEntries) {\n if (condition === NON_RUNTIME_CONDITION || seen.has(file)) {\n continue;\n }\n\n seen.add(file);\n targets.push({ condition, file });\n }\n\n return targets;\n};\n\nconst buildLabel = (opts: {\n package: string;\n subpath: string;\n condition: string;\n isSoleTarget: boolean;\n}): string => {\n const { package: packageName, subpath, condition, isSoleTarget } = opts;\n const suffix = subpath === '.' ? '' : subpath.slice(1);\n const base = `${packageName}${suffix}`;\n\n return isSoleTarget ? base : `${base} [${condition}]`;\n};\n\n/**\n * Exported for the tests, which have to drive a claim that no longer matches its package's\n * `exports` map - the staleness guard is the whole point of deriving, and it cannot be asserted\n * through a built manifest, which by construction is never stale.\n */\nexport const deriveEntries = (opts: { claim: IPurityClaim; root: string }): IPurityEntry[] => {\n const { claim, root } = opts;\n const exportsMap = readExportsMap({ root, package: claim.package });\n const published = Object.keys(exportsMap).filter((subpath) => subpath !== NON_CODE_SUBPATH);\n\n const subpathsList = claim.subpaths ?? [];\n for (const subpath of subpathsList) {\n if (published.includes(subpath)) {\n continue;\n }\n\n throw purityError(\n `[purity][manifest] '${claim.package}' claims '${subpath}', which its \\`exports\\` map no longer ` +\n `publishes | Published: ${published.join(', ')} - a renamed sub-path must not silently drop its row`,\n );\n }\n\n const claimExternalKeys = Object.keys(claim.external ?? {});\n for (const subpath of claimExternalKeys) {\n if (published.includes(subpath)) {\n continue;\n }\n\n throw purityError(\n `[purity][manifest] '${claim.package}' declares \\`external\\` for '${subpath}', which its ` +\n `\\`exports\\` map no longer publishes | An external that outlives its entry is a waiver ` +\n `nobody is reading`,\n );\n }\n\n const claimed = claim.subpaths ?? published;\n\n const claimImpureKeys = Object.keys(claim.impure ?? {});\n for (const subpath of claimImpureKeys) {\n if (claimed.includes(subpath)) {\n continue;\n }\n\n throw purityError(\n `[purity][manifest] '${claim.package}' waives '${subpath}' as impure, but that sub-path is not ` +\n `claimed | Claimed: ${claimed.join(', ')} - a waiver against nothing is one nobody is reading`,\n );\n }\n\n const entries: IPurityEntry[] = [];\n\n for (const subpath of claimed) {\n const targets = listConditionTargets(exportsMap[subpath]!);\n\n const conditions = claim.impure?.[subpath] ?? [];\n for (const condition of conditions) {\n if (targets.some((target) => target.condition === condition)) {\n continue;\n }\n\n throw purityError(\n `[purity][manifest] '${claim.package}' waives '${subpath}' under condition '${condition}', ` +\n `which its \\`exports\\` map does not publish | Published: ${targets.map((t) => t.condition).join(', ')}`,\n );\n }\n\n for (const { condition, file } of targets) {\n entries.push({\n label: buildLabel({\n package: claim.package,\n subpath,\n condition,\n isSoleTarget: targets.length === 1,\n }),\n package: claim.package,\n entry: join('packages', claim.package, file),\n ...(claim.external?.[subpath] ? { external: claim.external[subpath] } : {}),\n ...(claim.impure?.[subpath]?.includes(condition) ? { expectImpure: true } : {}),\n });\n }\n }\n\n return entries;\n};\n\n/**\n * A browser claim is only honest if a browser bundler can actually consume the entry. A CommonJS\n * entry forces every consumer into a workaround, so the claim carries the obligation: every claimed\n * sub-path must publish its runtime entries below `esmEntryPrefix`. Checked here rather than trusted\n * to a comment, for the same reason as {@link assertNoWorkspaceExternal} - the failure is otherwise\n * invisible until someone tries to bundle the package downstream.\n */\nexport const assertBrowserEsmEntry = (opts: {\n claims: IPurityClaim[];\n root: string;\n esmEntryPrefix: string;\n}): IPurityClaim[] => {\n const { claims, root, esmEntryPrefix } = opts;\n\n for (const claim of claims) {\n const exportsMap = readExportsMap({ root, package: claim.package });\n const claimed = claim.subpaths ?? Object.keys(exportsMap).filter((s) => s !== NON_CODE_SUBPATH);\n\n for (const subpath of claimed) {\n const target = exportsMap[subpath];\n\n const runtimeTargets =\n typeof target === 'object' && target !== null\n ? Object.entries(target)\n .filter(([condition]) => condition !== NON_RUNTIME_CONDITION)\n .map(([, file]) => file)\n : [];\n\n if (\n runtimeTargets.length > 0 &&\n runtimeTargets.every((file) => file.startsWith(esmEntryPrefix))\n ) {\n continue;\n }\n\n throw purityError(\n `[purity][manifest] '${claim.package}' claims browser purity for '${subpath}' but its runtime ` +\n `entry is not in ${esmEntryPrefix.replace(/^\\.\\//, '')} | A browser bundler needs an ES module, so the claim ` +\n `cannot be consumed - build the package as ES modules`,\n );\n }\n }\n\n return claims;\n};\n\n/** The rows of a whole repository: every claim derived from its package's `exports`, then the guards. */\nexport const buildManifest = (opts: {\n claims: IPurityClaim[];\n root: string;\n options?: IPurityOptions;\n}): IPurityEntry[] => {\n const { claims, root, options = {} } = opts;\n const { esmEntryPrefix, workspaceScope = DEFAULT_WORKSPACE_SCOPE } = options;\n\n const checked =\n esmEntryPrefix === undefined ? claims : assertBrowserEsmEntry({ claims, root, esmEntryPrefix });\n\n return assertNoWorkspaceExternal(\n checked.flatMap((claim) => deriveEntries({ claim, root })),\n workspaceScope,\n );\n};\n"]}
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../../src/checks/purity/manifest.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport interface IPurityEntry {\n label: string;\n package: string;\n /** Repository-relative path to the built file the `exports` map points at. */\n entry: string;\n /**\n * Specifiers to pass through as `bun build --external`. Only for a third-party dependency whose\n * OWN packaging is out of this gate's remit - e.g. one that relies on its `package.json` `browser`\n * field remap, which the downstream app's bundler (Vite, ...) honours and `bun build` does not.\n * Never for the repository's own source: an external hides that file from the module graph\n * entirely, so using it on our own code would let a real leak through unseen rather than prove\n * anything pure. Enforced by {@link PurityManifest.assertNoWorkspaceExternal}, not left to this comment alone.\n */\n external?: string[];\n /**\n * This row is EXPECTED to be impure, so failing is its passing state. Derived from\n * {@link IPurityClaim.impure} - never authored on a row directly.\n */\n expectImpure?: boolean;\n}\n\n/**\n * A package whose published entry points carry a browser-purity claim.\n *\n * The ROW SET IS NEVER AUTHORED. It is read from the package's own `exports` map, so a sub-path\n * added to a manifest is probed the moment it is published.\n */\nexport interface IPurityClaim {\n /** Directory under `packages/`, and the token `devz check purity <token>` filters on. */\n package: string;\n /**\n * Sub-paths (as spelled in `exports`) when the claim covers only PART of the published surface.\n * Omit it and the whole surface is claimed. Every sub-path named here must still exist in\n * `exports`; a rename fails the derivation loudly rather than quietly dropping a row.\n */\n subpaths?: string[];\n /** `external` for one sub-path - see {@link IPurityEntry.external}. Keyed as spelled in `exports`. */\n external?: Record<string, string[]>;\n /**\n * Entries that CANNOT be browser-pure, keyed as spelled in `exports`, valued by the conditions\n * that are impure. The waiver is exact in BOTH directions: a listed entry that turns out to be\n * pure fails as loudly as an unlisted one that is not. Deriving still owns the ROW SET, so a\n * sub-path added later is claimed pure by default and has to earn its line here.\n */\n impure?: Record<string, string[]>;\n}\n\n/** What a repository may tune about the engine; everything else is the engine's contract. */\nexport interface IPurityOptions {\n /**\n * When set, every claimed sub-path must publish its runtime entries below this prefix (for\n * example `./dist/esm/`): a browser bundler needs an ES module, so a CommonJS-only entry makes\n * the claim unconsumable. Omit it for a repository that publishes a single build.\n */\n esmEntryPrefix?: string;\n\n /** Package-name prefix of this repository's own packages; an `external` may never start with it. Default `@venizia/`. */\n workspaceScope?: string;\n}\n\nconst DEFAULT_WORKSPACE_SCOPE = '@venizia/';\n\n/** The one place this engine 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 purityError = (message: string): Error => new Error(message);\n\n/** `types` describes the entry, it is not a runtime target; `./package.json` is not code. */\nconst NON_RUNTIME_CONDITION = 'types';\nconst NON_CODE_SUBPATH = './package.json';\n\ntype TExportsTarget = string | Record<string, string>;\n\nconst readExportsMap = (opts: {\n root: string;\n package: string;\n}): Record<string, TExportsTarget> => {\n const manifestPath = join(opts.root, 'packages', opts.package, 'package.json');\n const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) as {\n exports?: Record<string, TExportsTarget>;\n };\n\n if (!manifest.exports) {\n throw purityError(\n `[purity][manifest] '${opts.package}' claims browser purity but publishes no \\`exports\\` map ` +\n `| There is nothing to derive rows from - remove the claim or add the map`,\n );\n }\n\n return manifest.exports;\n};\n\n/**\n * `exports` may spell one sub-path as several conditions pointing at different builds - the dual\n * CJS/ESM packages do exactly that - and each is a separately resolvable file that a consumer can\n * end up importing, so each is probed. `default` normally repeats `require`, so files are deduped\n * and the FIRST condition naming a file is the one that labels it.\n */\nconst listConditionTargets = (\n target: TExportsTarget,\n): Array<{ condition: string; file: string }> => {\n if (typeof target === 'string') {\n return [{ condition: 'default', file: target }];\n }\n\n const seen = new Set<string>();\n const targets: Array<{ condition: string; file: string }> = [];\n\n const targetEntries = Object.entries(target);\n for (const [condition, file] of targetEntries) {\n if (condition === NON_RUNTIME_CONDITION || seen.has(file)) {\n continue;\n }\n\n seen.add(file);\n targets.push({ condition, file });\n }\n\n return targets;\n};\n\nconst buildLabel = (opts: {\n package: string;\n subpath: string;\n condition: string;\n isSoleTarget: boolean;\n}): string => {\n const { package: packageName, subpath, condition, isSoleTarget } = opts;\n const suffix = subpath === '.' ? '' : subpath.slice(1);\n const base = `${packageName}${suffix}`;\n\n return isSoleTarget ? base : `${base} [${condition}]`;\n};\n\n/** Derives the rows of the purity gate from the `exports` map of each claimed package, then runs the guards on them. */\nexport class PurityManifest {\n /**\n * A code-level guard, not just the JSDoc on `external`: it is an escape hatch from this gate, and a\n * hatch with no lock is one bad edit away from hiding a real leak - `external: ['@venizia/ignis-helpers']`\n * on a red entry would silently turn it green by removing the exact root-barrel import this gate\n * exists to catch from the module graph, rather than proving it absent.\n *\n * A plain `Error`, not `getError`: the engine runs from the repository root through `devz`, outside\n * every workspace package, and has no logger to resolve.\n */\n static assertNoWorkspaceExternal(opts: {\n manifest: IPurityEntry[];\n workspaceScope?: string;\n }): IPurityEntry[] {\n const { manifest, workspaceScope = DEFAULT_WORKSPACE_SCOPE } = opts;\n\n for (const row of manifest) {\n const specifiers = row.external ?? [];\n for (const specifier of specifiers) {\n if (!specifier.startsWith(workspaceScope)) {\n continue;\n }\n\n throw purityError(\n `[purity][manifest] Entry '${row.label}' externalises '${specifier}', a workspace package ` +\n `| \\`external\\` may only exempt THIRD-PARTY packaging, never the repository's own code - doing so ` +\n `would hide the exact leak this gate exists to catch`,\n );\n }\n }\n\n return manifest;\n }\n\n /**\n * Public for the tests, which have to drive a claim that no longer matches its package's\n * `exports` map - the staleness guard is the whole point of deriving, and it cannot be asserted\n * through a built manifest, which by construction is never stale.\n */\n static deriveEntries(opts: { claim: IPurityClaim; root: string }): IPurityEntry[] {\n const { claim, root } = opts;\n const exportsMap = readExportsMap({ root, package: claim.package });\n const published = Object.keys(exportsMap).filter((subpath) => subpath !== NON_CODE_SUBPATH);\n\n const subpathsList = claim.subpaths ?? [];\n for (const subpath of subpathsList) {\n if (published.includes(subpath)) {\n continue;\n }\n\n throw purityError(\n `[purity][manifest] '${claim.package}' claims '${subpath}', which its \\`exports\\` map no longer ` +\n `publishes | Published: ${published.join(', ')} - a renamed sub-path must not silently drop its row`,\n );\n }\n\n const claimExternalKeys = Object.keys(claim.external ?? {});\n for (const subpath of claimExternalKeys) {\n if (published.includes(subpath)) {\n continue;\n }\n\n throw purityError(\n `[purity][manifest] '${claim.package}' declares \\`external\\` for '${subpath}', which its ` +\n `\\`exports\\` map no longer publishes | An external that outlives its entry is a waiver ` +\n `nobody is reading`,\n );\n }\n\n const claimed = claim.subpaths ?? published;\n\n const claimImpureKeys = Object.keys(claim.impure ?? {});\n for (const subpath of claimImpureKeys) {\n if (claimed.includes(subpath)) {\n continue;\n }\n\n throw purityError(\n `[purity][manifest] '${claim.package}' waives '${subpath}' as impure, but that sub-path is not ` +\n `claimed | Claimed: ${claimed.join(', ')} - a waiver against nothing is one nobody is reading`,\n );\n }\n\n const entries: IPurityEntry[] = [];\n\n for (const subpath of claimed) {\n const targets = listConditionTargets(exportsMap[subpath]!);\n\n const conditions = claim.impure?.[subpath] ?? [];\n for (const condition of conditions) {\n if (targets.some((target) => target.condition === condition)) {\n continue;\n }\n\n throw purityError(\n `[purity][manifest] '${claim.package}' waives '${subpath}' under condition '${condition}', ` +\n `which its \\`exports\\` map does not publish | Published: ${targets.map((t) => t.condition).join(', ')}`,\n );\n }\n\n for (const { condition, file } of targets) {\n entries.push({\n label: buildLabel({\n package: claim.package,\n subpath,\n condition,\n isSoleTarget: targets.length === 1,\n }),\n package: claim.package,\n entry: join('packages', claim.package, file),\n ...(claim.external?.[subpath] ? { external: claim.external[subpath] } : {}),\n ...(claim.impure?.[subpath]?.includes(condition) ? { expectImpure: true } : {}),\n });\n }\n }\n\n return entries;\n }\n\n /**\n * A browser claim is only honest if a browser bundler can actually consume the entry. A CommonJS\n * entry forces every consumer into a workaround, so the claim carries the obligation: every claimed\n * sub-path must publish its runtime entries below `esmEntryPrefix`. Checked here rather than trusted\n * to a comment, for the same reason as {@link PurityManifest.assertNoWorkspaceExternal} - the failure is otherwise\n * invisible until someone tries to bundle the package downstream.\n */\n static assertBrowserEsmEntry(opts: {\n claims: IPurityClaim[];\n root: string;\n esmEntryPrefix: string;\n }): IPurityClaim[] {\n const { claims, root, esmEntryPrefix } = opts;\n\n for (const claim of claims) {\n const exportsMap = readExportsMap({ root, package: claim.package });\n const claimed =\n claim.subpaths ?? Object.keys(exportsMap).filter((s) => s !== NON_CODE_SUBPATH);\n\n for (const subpath of claimed) {\n const target = exportsMap[subpath];\n\n const runtimeTargets =\n typeof target === 'object' && target !== null\n ? Object.entries(target)\n .filter(([condition]) => condition !== NON_RUNTIME_CONDITION)\n .map(([, file]) => file)\n : [];\n\n if (\n runtimeTargets.length > 0 &&\n runtimeTargets.every((file) => file.startsWith(esmEntryPrefix))\n ) {\n continue;\n }\n\n throw purityError(\n `[purity][manifest] '${claim.package}' claims browser purity for '${subpath}' but its runtime ` +\n `entry is not in ${esmEntryPrefix.replace(/^\\.\\//, '')} | A browser bundler needs an ES module, so the claim ` +\n `cannot be consumed - build the package as ES modules`,\n );\n }\n }\n\n return claims;\n }\n\n /** The rows of a whole repository: every claim derived from its package's `exports`, then the guards. */\n static build(opts: {\n claims: IPurityClaim[];\n root: string;\n options?: IPurityOptions;\n }): IPurityEntry[] {\n const { claims, root, options = {} } = opts;\n const { esmEntryPrefix, workspaceScope = DEFAULT_WORKSPACE_SCOPE } = options;\n\n const checked =\n esmEntryPrefix === undefined\n ? claims\n : PurityManifest.assertBrowserEsmEntry({ claims, root, esmEntryPrefix });\n\n return PurityManifest.assertNoWorkspaceExternal({\n manifest: checked.flatMap((claim) => PurityManifest.deriveEntries({ claim, root })),\n workspaceScope,\n });\n }\n}\n"]}
@@ -41,28 +41,32 @@ export type TProbeSeverity = Extract<(typeof ProbeSeverities)[keyof typeof Probe
41
41
  * gate cry wolf on a dependency; the unguarded `process.` and `globalThis.process.` forms stay
42
42
  * fatal, and those are the ones that are unambiguously browser-breaking.
43
43
  *
44
- * Exported for `probe.test.ts` to assert against directly, alongside {@link stripBundlerPathComments}.
44
+ * Exported for `probe.test.ts` to assert against directly, alongside {@link PurityProbe.stripBundlerPathComments}.
45
45
  */
46
46
  export declare const GLOBAL_PATTERNS: Array<{
47
47
  name: string;
48
48
  pattern: RegExp;
49
49
  severity: TProbeSeverity;
50
50
  }>;
51
- /**
52
- * Exported so `probe.test.ts` can assert against the literal collision string directly, without
53
- * spawning `bun build` - a fixture whose OWN entry-path comment happens to reproduce the collision
54
- * is not something a filename choice can be relied on to keep reproducing.
55
- */
56
- export declare const stripBundlerPathComments: (text: string) => string;
57
- /**
58
- * The CLI, not `Bun.build()`: the in-process bundler resolves this workspace's symlinked
59
- * dependencies to directories and dies on them.
60
- */
61
- export declare const probeEntry: (opts: {
62
- entry: string;
63
- cwd: string;
64
- external?: string[];
65
- /** Defaults to {@link PURITY_SCRATCH_DIR} under `cwd`. */
66
- scratchDir?: string;
67
- }) => Promise<IPurityResult>;
51
+ export declare class PurityProbe {
52
+ /**
53
+ * Public so `probe.test.ts` can assert against the literal collision string directly, without
54
+ * spawning `bun build` - a fixture whose OWN entry-path comment happens to reproduce the collision
55
+ * is not something a filename choice can be relied on to keep reproducing.
56
+ */
57
+ static stripBundlerPathComments(opts: {
58
+ text: string;
59
+ }): string;
60
+ /**
61
+ * The CLI, not `Bun.build()`: the in-process bundler resolves this workspace's symlinked
62
+ * dependencies to directories and dies on them.
63
+ */
64
+ static probeEntry(opts: {
65
+ entry: string;
66
+ cwd: string;
67
+ external?: string[];
68
+ /** Defaults to {@link PURITY_SCRATCH_DIR} under `cwd`. */
69
+ scratchDir?: string;
70
+ }): Promise<IPurityResult>;
71
+ }
68
72
  //# sourceMappingURL=probe.d.ts.map
@@ -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;AAaF;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,GAAI,MAAM,MAAM,KAAG,MAEvD,CAAC;AAsIF;;;GAGG;AACH,eAAO,MAAM,UAAU,GAAU,MAAM;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,KAAG,OAAO,CAAC,aAAa,CA6GxB,CAAC"}
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"}