monkeypatchlist 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +61 -0
  3. package/README.md +183 -2
  4. package/catalog/catalog.json +552211 -0
  5. package/catalog/catalog.schema.json +787 -0
  6. package/catalog/changelog.json +3 -0
  7. package/dist/bin/monkeypatchlist.d.ts +2 -0
  8. package/dist/bin/monkeypatchlist.js +3 -0
  9. package/dist/catalog.d.ts +561 -0
  10. package/dist/catalog.js +58 -0
  11. package/dist/cli.d.ts +84 -0
  12. package/dist/cli.js +470 -0
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.js +1 -0
  15. package/dist/lockfile-input.d.ts +9 -0
  16. package/dist/lockfile-input.js +91 -0
  17. package/dist/packages/analysis/binding.d.ts +2 -0
  18. package/dist/packages/analysis/binding.js +5 -0
  19. package/dist/packages/analysis/index.d.ts +10 -0
  20. package/dist/packages/analysis/index.js +13 -0
  21. package/dist/packages/analysis/job.d.ts +33 -0
  22. package/dist/packages/analysis/job.js +85 -0
  23. package/dist/packages/analysis/lib/absence-tags.d.ts +34 -0
  24. package/dist/packages/analysis/lib/absence-tags.js +340 -0
  25. package/dist/packages/analysis/lib/ast.d.ts +28 -0
  26. package/dist/packages/analysis/lib/ast.js +64 -0
  27. package/dist/packages/analysis/lib/binding.d.ts +36 -0
  28. package/dist/packages/analysis/lib/binding.js +66 -0
  29. package/dist/packages/analysis/lib/budget.d.ts +80 -0
  30. package/dist/packages/analysis/lib/budget.js +31 -0
  31. package/dist/packages/analysis/lib/catch-arms.d.ts +19 -0
  32. package/dist/packages/analysis/lib/catch-arms.js +351 -0
  33. package/dist/packages/analysis/lib/chunk-loads.d.ts +160 -0
  34. package/dist/packages/analysis/lib/chunk-loads.js +882 -0
  35. package/dist/packages/analysis/lib/chunk-registry.d.ts +45 -0
  36. package/dist/packages/analysis/lib/chunk-registry.js +334 -0
  37. package/dist/packages/analysis/lib/code-routes.d.ts +52 -0
  38. package/dist/packages/analysis/lib/code-routes.js +921 -0
  39. package/dist/packages/analysis/lib/collisions.d.ts +22 -0
  40. package/dist/packages/analysis/lib/collisions.js +103 -0
  41. package/dist/packages/analysis/lib/definitions.d.ts +76 -0
  42. package/dist/packages/analysis/lib/definitions.js +782 -0
  43. package/dist/packages/analysis/lib/early-exits.d.ts +39 -0
  44. package/dist/packages/analysis/lib/early-exits.js +161 -0
  45. package/dist/packages/analysis/lib/effect.d.ts +138 -0
  46. package/dist/packages/analysis/lib/effect.js +800 -0
  47. package/dist/packages/analysis/lib/global-object.d.ts +581 -0
  48. package/dist/packages/analysis/lib/global-object.js +8240 -0
  49. package/dist/packages/analysis/lib/keys.d.ts +33 -0
  50. package/dist/packages/analysis/lib/keys.js +82 -0
  51. package/dist/packages/analysis/lib/line-starts.d.ts +41 -0
  52. package/dist/packages/analysis/lib/line-starts.js +79 -0
  53. package/dist/packages/analysis/lib/link-group.d.ts +51 -0
  54. package/dist/packages/analysis/lib/link-group.js +487 -0
  55. package/dist/packages/analysis/lib/module-links.d.ts +117 -0
  56. package/dist/packages/analysis/lib/module-links.js +1060 -0
  57. package/dist/packages/analysis/lib/nested-code.d.ts +38 -0
  58. package/dist/packages/analysis/lib/nested-code.js +200 -0
  59. package/dist/packages/analysis/lib/numeric.d.ts +16 -0
  60. package/dist/packages/analysis/lib/numeric.js +216 -0
  61. package/dist/packages/analysis/lib/once-flags.d.ts +12 -0
  62. package/dist/packages/analysis/lib/once-flags.js +190 -0
  63. package/dist/packages/analysis/lib/parameter-arguments.d.ts +12 -0
  64. package/dist/packages/analysis/lib/parameter-arguments.js +34 -0
  65. package/dist/packages/analysis/lib/parse.d.ts +10 -0
  66. package/dist/packages/analysis/lib/parse.js +39 -0
  67. package/dist/packages/analysis/lib/partial.d.ts +60 -0
  68. package/dist/packages/analysis/lib/partial.js +518 -0
  69. package/dist/packages/analysis/lib/print.d.ts +3 -0
  70. package/dist/packages/analysis/lib/print.js +150 -0
  71. package/dist/packages/analysis/lib/reachability.d.ts +137 -0
  72. package/dist/packages/analysis/lib/reachability.js +1295 -0
  73. package/dist/packages/analysis/lib/resolver.d.ts +13 -0
  74. package/dist/packages/analysis/lib/resolver.js +2886 -0
  75. package/dist/packages/analysis/lib/restores.d.ts +42 -0
  76. package/dist/packages/analysis/lib/restores.js +482 -0
  77. package/dist/packages/analysis/lib/rewritten-deletes.d.ts +6 -0
  78. package/dist/packages/analysis/lib/rewritten-deletes.js +142 -0
  79. package/dist/packages/analysis/lib/scope.d.ts +110 -0
  80. package/dist/packages/analysis/lib/scope.js +443 -0
  81. package/dist/packages/analysis/lib/script-loads.d.ts +56 -0
  82. package/dist/packages/analysis/lib/script-loads.js +359 -0
  83. package/dist/packages/analysis/lib/summaries.d.ts +177 -0
  84. package/dist/packages/analysis/lib/summaries.js +498 -0
  85. package/dist/packages/analysis/lib/types.d.ts +632 -0
  86. package/dist/packages/analysis/lib/types.js +128 -0
  87. package/dist/packages/analysis/lib/wrap.d.ts +34 -0
  88. package/dist/packages/analysis/lib/wrap.js +407 -0
  89. package/dist/packages/analysis/lib/writes.d.ts +358 -0
  90. package/dist/packages/analysis/lib/writes.js +1612 -0
  91. package/dist/packages/analysis/visit-order.d.ts +3 -0
  92. package/dist/packages/analysis/visit-order.js +23 -0
  93. package/dist/packages/attribution/index.d.ts +233 -0
  94. package/dist/packages/attribution/index.js +444 -0
  95. package/dist/packages/attribution/lib/containing-package.d.ts +76 -0
  96. package/dist/packages/attribution/lib/containing-package.js +304 -0
  97. package/dist/packages/attribution/lib/first-party.d.ts +37 -0
  98. package/dist/packages/attribution/lib/first-party.js +153 -0
  99. package/dist/packages/attribution/lib/lockfile-graph.d.ts +99 -0
  100. package/dist/packages/attribution/lib/lockfile-graph.js +158 -0
  101. package/dist/packages/attribution/lib/lockfile.d.ts +33 -0
  102. package/dist/packages/attribution/lib/lockfile.js +330 -0
  103. package/dist/packages/attribution/lib/path-rules.d.ts +29 -0
  104. package/dist/packages/attribution/lib/path-rules.js +84 -0
  105. package/dist/packages/attribution/lib/pnpm.d.ts +4 -0
  106. package/dist/packages/attribution/lib/pnpm.js +133 -0
  107. package/dist/packages/attribution/lib/remote-url.d.ts +7 -0
  108. package/dist/packages/attribution/lib/remote-url.js +24 -0
  109. package/dist/packages/attribution/lib/source-map.d.ts +51 -0
  110. package/dist/packages/attribution/lib/source-map.js +125 -0
  111. package/dist/packages/attribution/lib/yaml.d.ts +9 -0
  112. package/dist/packages/attribution/lib/yaml.js +277 -0
  113. package/dist/packages/attribution/lib/yarn.d.ts +7 -0
  114. package/dist/packages/attribution/lib/yarn.js +227 -0
  115. package/dist/packages/baseline/index.d.ts +45 -0
  116. package/dist/packages/baseline/index.js +41 -0
  117. package/dist/packages/baseline/lib/error.d.ts +15 -0
  118. package/dist/packages/baseline/lib/error.js +25 -0
  119. package/dist/packages/baseline/lib/format.d.ts +49 -0
  120. package/dist/packages/baseline/lib/format.js +22 -0
  121. package/dist/packages/baseline/lib/migrations.d.ts +19 -0
  122. package/dist/packages/baseline/lib/migrations.js +7 -0
  123. package/dist/packages/baseline/lib/previous.d.ts +23 -0
  124. package/dist/packages/baseline/lib/previous.js +64 -0
  125. package/dist/packages/baseline/lib/read.d.ts +39 -0
  126. package/dist/packages/baseline/lib/read.js +219 -0
  127. package/dist/packages/baseline/lib/report.d.ts +25 -0
  128. package/dist/packages/baseline/lib/report.js +104 -0
  129. package/dist/packages/baseline/lib/skew.d.ts +51 -0
  130. package/dist/packages/baseline/lib/skew.js +75 -0
  131. package/dist/packages/compare/index.d.ts +120 -0
  132. package/dist/packages/compare/index.js +274 -0
  133. package/dist/packages/compare/lib/catalog-notes.d.ts +73 -0
  134. package/dist/packages/compare/lib/catalog-notes.js +224 -0
  135. package/dist/packages/completeness/index.d.ts +246 -0
  136. package/dist/packages/completeness/index.js +249 -0
  137. package/dist/packages/confidence/index.d.ts +54 -0
  138. package/dist/packages/confidence/index.js +71 -0
  139. package/dist/packages/config/index.d.ts +107 -0
  140. package/dist/packages/config/index.js +95 -0
  141. package/dist/packages/config/lib/command-line.d.ts +23 -0
  142. package/dist/packages/config/lib/command-line.js +239 -0
  143. package/dist/packages/config/lib/config-file.d.ts +11 -0
  144. package/dist/packages/config/lib/config-file.js +119 -0
  145. package/dist/packages/config/lib/scan-config-id.d.ts +28 -0
  146. package/dist/packages/config/lib/scan-config-id.js +54 -0
  147. package/dist/packages/config/lib/schema.d.ts +2 -0
  148. package/dist/packages/config/lib/schema.js +31 -0
  149. package/dist/packages/config/lib/types.d.ts +64 -0
  150. package/dist/packages/config/lib/types.js +6 -0
  151. package/dist/packages/config/lib/urls.d.ts +6 -0
  152. package/dist/packages/config/lib/urls.js +9 -0
  153. package/dist/packages/discovery/index.d.ts +277 -0
  154. package/dist/packages/discovery/index.js +1950 -0
  155. package/dist/packages/discovery/lib/import-map.d.ts +68 -0
  156. package/dist/packages/discovery/lib/import-map.js +180 -0
  157. package/dist/packages/discovery/lib/requests.d.ts +58 -0
  158. package/dist/packages/discovery/lib/requests.js +591 -0
  159. package/dist/packages/discovery/lib/scopes.d.ts +6 -0
  160. package/dist/packages/discovery/lib/scopes.js +286 -0
  161. package/dist/packages/discovery/lib/source-maps.d.ts +56 -0
  162. package/dist/packages/discovery/lib/source-maps.js +209 -0
  163. package/dist/packages/discovery/lib/swept-code.d.ts +27 -0
  164. package/dist/packages/discovery/lib/swept-code.js +107 -0
  165. package/dist/packages/discovery/lib/syntax.d.ts +17 -0
  166. package/dist/packages/discovery/lib/syntax.js +39 -0
  167. package/dist/packages/findings/index.d.ts +106 -0
  168. package/dist/packages/findings/index.js +254 -0
  169. package/dist/packages/findings/lib/scope.d.ts +38 -0
  170. package/dist/packages/findings/lib/scope.js +110 -0
  171. package/dist/packages/parallel/index.d.ts +2 -0
  172. package/dist/packages/parallel/index.js +5 -0
  173. package/dist/packages/parallel/lib/protocol.d.ts +15 -0
  174. package/dist/packages/parallel/lib/protocol.js +2 -0
  175. package/dist/packages/parallel/lib/runner.d.ts +36 -0
  176. package/dist/packages/parallel/lib/runner.js +96 -0
  177. package/dist/packages/parallel/lib/schedule.d.ts +16 -0
  178. package/dist/packages/parallel/lib/schedule.js +52 -0
  179. package/dist/packages/parallel/lib/worker.d.ts +1 -0
  180. package/dist/packages/parallel/lib/worker.js +18 -0
  181. package/dist/packages/policy/index.d.ts +36 -0
  182. package/dist/packages/policy/index.js +22 -0
  183. package/dist/packages/report/explain.d.ts +26 -0
  184. package/dist/packages/report/explain.js +99 -0
  185. package/dist/packages/report/index.d.ts +210 -0
  186. package/dist/packages/report/index.js +388 -0
  187. package/dist/packages/report/terminal.d.ts +11 -0
  188. package/dist/packages/report/terminal.js +331 -0
  189. package/dist/packages/source-store/fixture.d.ts +22 -0
  190. package/dist/packages/source-store/fixture.js +58 -0
  191. package/dist/packages/source-store/index.d.ts +278 -0
  192. package/dist/packages/source-store/index.js +322 -0
  193. package/dist/packages/source-store/lib/addresses.d.ts +6 -0
  194. package/dist/packages/source-store/lib/addresses.js +91 -0
  195. package/dist/packages/source-store/lib/connection.d.ts +15 -0
  196. package/dist/packages/source-store/lib/connection.js +135 -0
  197. package/dist/packages/source-store/lib/content-types.d.ts +16 -0
  198. package/dist/packages/source-store/lib/content-types.js +75 -0
  199. package/dist/packages/source-store/lib/data-urls.d.ts +10 -0
  200. package/dist/packages/source-store/lib/data-urls.js +85 -0
  201. package/dist/packages/source-store/lib/deadline.d.ts +18 -0
  202. package/dist/packages/source-store/lib/deadline.js +44 -0
  203. package/dist/packages/source-store/lib/exchange.d.ts +29 -0
  204. package/dist/packages/source-store/lib/exchange.js +112 -0
  205. package/dist/packages/source-store/lib/integrity.d.ts +19 -0
  206. package/dist/packages/source-store/lib/integrity.js +86 -0
  207. package/dist/packages/source-store/lib/outcomes.d.ts +12 -0
  208. package/dist/packages/source-store/lib/outcomes.js +22 -0
  209. package/dist/packages/source-store/lib/proxy.d.ts +11 -0
  210. package/dist/packages/source-store/lib/proxy.js +53 -0
  211. package/dist/packages/source-store/lib/session.d.ts +12 -0
  212. package/dist/packages/source-store/lib/session.js +139 -0
  213. package/dist/packages/source-store/lib/urls.d.ts +6 -0
  214. package/dist/packages/source-store/lib/urls.js +15 -0
  215. package/dist/packages/source-store/resolve.d.ts +59 -0
  216. package/dist/packages/source-store/resolve.js +45 -0
  217. package/dist/scan.d.ts +65 -0
  218. package/dist/scan.js +592 -0
  219. package/package.json +95 -4
  220. package/schema/config.schema.json +133 -0
  221. package/schema/report.schema.json +1905 -0
@@ -0,0 +1,444 @@
1
+ // Attribution (§19, module 5): adds where each occurrence's code came from (§10). It never changes the resolver's fields,
2
+ // and knows nothing of how a target was resolved. It never reads chunk names.
3
+ import { attributionConfidence } from "../confidence/index.js";
4
+ import { sourceKind } from "./lib/containing-package.js";
5
+ import { firstPartyRoots } from "./lib/first-party.js";
6
+ import { importerOf, introducerReasons, nameAtVersion, readLockfile, workspaceOf, } from "./lib/lockfile.js";
7
+ import { pathRuling } from "./lib/path-rules.js";
8
+ import { normalizeRemoteUrl } from "./lib/remote-url.js";
9
+ import { lineStarts, originalAt, position, readMap, tokenAppears, } from "./lib/source-map.js";
10
+ export { introducerReasons };
11
+ export { lockfileManifests } from "./lib/lockfile.js";
12
+ /** How the code behind a finding got into its artifact (§10 Provenance). */
13
+ export const provenances = ['first-party', 'bundled-dependency', 'remote', 'inline', 'unknown'];
14
+ /**
15
+ * Where a package's version came from (§10 Version), strongest first: a store path, a lockfile keyed by install path,
16
+ * then the weak content signals: the package's `package.json` in `sourcesContent`, an in-package `VERSION`
17
+ * constant, a banner.
18
+ */
19
+ export const versionSources = ['store-path', 'lockfile', 'package-json', 'version-constant', 'banner'];
20
+ /** The strong version sources, the only ones that feed `packages` and the `previousVersion` note (§10, §11). */
21
+ export const strongVersionSources = new Set(['store-path', 'lockfile']);
22
+ /**
23
+ * Attributes each occurrence (§10):
24
+ *
25
+ * - Inline code is the entry document's (`inline`), and has high attribution confidence.
26
+ * - Remote code keys `url:` with its URL normalized by the catalog's rules (§11 Remote URL normalization), provenance
27
+ * `remote`, and is `high` on that key (§9). A verified map still gives its occurrences original locations, looked up
28
+ * as for local code, and a package the map names is evidence only (`evidence.package`).
29
+ * - Local code without a source map is `unknown`, with `no-map`.
30
+ * - Local code with a verified map is looked up there at the occurrence's lookup point, with the line reading that
31
+ * verified the map. A position that is unmapped, or maps to a synthetic or bundler-runtime source, is `unknown` with
32
+ * `no-map`. Otherwise the token check runs, and a position that fails it is discarded: `unknown`, with
33
+ * `token-check-failed` and the rejected candidate. A kept position's source path names the containing package, which
34
+ * keys `pkg:<name>` with provenance `bundled-dependency`. A source with an unrecognized scheme is `unknown` with
35
+ * `foreign-scheme`, and a scoped package under Yarn's pnpm linker `unknown` with `yarn-slug`. A source under no
36
+ * `node_modules` segment inside its namespace's inferred root is first-party, keyed `app:<root-relative path>`
37
+ * (`first-party-inferred` when the root lies above the bundler base), unless its map's ignore list demotes it to
38
+ * `unknown` (`ignore-list-demoted`). One outside the root is escaped: `unknown` with `escaped-source`. Before the
39
+ * root decides, the catalog's framework path rules are tried, then its escape-only shapes: a source a rule
40
+ * matches keys the package the rule names (`bundled-dependency`, `framework-path-rule`, with a `path-rule` step),
41
+ * and one an escape-only shape matches is escaped, wherever the root is.
42
+ */
43
+ export function attribute(occurrences, artifacts, rules) {
44
+ return attribution(artifacts, rules, null).attribute(occurrences);
45
+ }
46
+ /** Attributes each occurrence of an ineffective write (§3 Record), as `attribute` attributes a finding's. */
47
+ export function attributeIneffective(writes, artifacts, rules) {
48
+ return attribution(artifacts, rules, null).attributeIneffective(writes);
49
+ }
50
+ /**
51
+ * The attribution of a scan over `artifacts` (§10), with the lockfile it was given, or null. The lockfile only adds
52
+ * evidence, never a key (ADR 0006):
53
+ *
54
+ * - A lockfile that can't be read or parsed, or a format version R1 doesn't read (`package-lock.json` v1), gives
55
+ * `no-lockfile`, with the format version as evidence, as no lockfile does.
56
+ * - A lockfile with no installed copy of a package at the version a store path in the scan's maps gives it is stale
57
+ * for the whole scan: `lockfile-stale`, and it derives nothing.
58
+ * - Otherwise a package's install path gives it its lockfile version (§10 Version), and a package path the lockfile
59
+ * says isn't installed is vendored, in the installed package around it (§10 Provenance). First-party code gets its
60
+ * workspace as evidence.
61
+ * - The importer is picked as `importerOf` says; with none, `importer-unresolved`, and no introducers.
62
+ */
63
+ export function attribution(artifacts, rules, lockfileInput) {
64
+ const byId = new Map(artifacts.map((artifact) => [artifact.id ?? artifact.url, artifact]));
65
+ const verified = new Map();
66
+ const verifiedMapOf = (artifact) => {
67
+ const id = artifact.id ?? artifact.url;
68
+ let map = verified.get(id);
69
+ if (map === undefined)
70
+ verified.set(id, (map = verifiedMap(artifact)));
71
+ return map;
72
+ };
73
+ // One root per bundler namespace, inferred from every local-build map in the scan (§10 Provenance).
74
+ const local = artifacts.flatMap((artifact) => {
75
+ const map = artifact.origin === 'local-build' ? verifiedMapOf(artifact) : null;
76
+ return map === null ? [] : [map];
77
+ });
78
+ const roots = firstPartyRoots(local);
79
+ // The curated path data decides before the root does (§10 Escaped sources).
80
+ const classify = (path, directory) => {
81
+ const ruling = pathRuling(path, rules);
82
+ if (ruling.kind === 'rule')
83
+ return ruling;
84
+ if (ruling.kind === 'escaped')
85
+ return { kind: 'outside' };
86
+ return roots(path, directory);
87
+ };
88
+ const { lock, introducers } = lockState(lockfileInput, local, classify);
89
+ const each = (write) => {
90
+ const artifact = byId.get(write.artifact);
91
+ if (artifact === undefined)
92
+ throw new Error(`an occurrence names an unknown artifact ${write.artifact}`);
93
+ const { url, hash, origin } = artifact;
94
+ const { lookup, trace, ...owned } = write;
95
+ // A helper write's lookup point may be a call in another artifact of the write's origin (§10): that artifact's
96
+ // map is read there.
97
+ const at = lookup.artifact === undefined ? artifact : byId.get(lookup.artifact);
98
+ if (at === undefined)
99
+ throw new Error(`an occurrence's lookup names an unknown artifact ${lookup.artifact}`);
100
+ const found = attributionOf(artifact, lookup, verifiedMapOf, classify, rules.remoteUrlNormalization, lock, at);
101
+ const attributed = {
102
+ // `artifact` is replaced by the artifact's identity.
103
+ ...owned,
104
+ artifact: { url, hash, origin },
105
+ attribution: found.attribution,
106
+ original: found.original,
107
+ evidence: found.evidence,
108
+ // A token check at a lookup point in another artifact names it (§14).
109
+ trace: [
110
+ ...trace,
111
+ ...found.trace.map((step) => (at === artifact || step.kind !== 'token-check' ? step : { ...step, artifact: at.url })),
112
+ ],
113
+ };
114
+ return { attributed, found };
115
+ };
116
+ return {
117
+ attribute: (occurrences) => occurrences.map((occurrence) => {
118
+ const { attributed, found } = each(occurrence);
119
+ const { confidence, ...rest } = attributed;
120
+ // Introducers apply only to bundled-dependency code (§10 Record).
121
+ const introducedBy = found.attribution.provenance === 'bundled-dependency' ? found.introducedBy : undefined;
122
+ return {
123
+ ...rest,
124
+ ...(introducedBy === undefined ? {} : { introducedBy }),
125
+ confidence: { ...confidence, attribution: attributionConfidence(found.reasons) },
126
+ };
127
+ }),
128
+ attributeIneffective: (writes) => writes.map((write) => each(write).attributed),
129
+ introducers,
130
+ };
131
+ }
132
+ /** The scan's lockfile state, and why introducers are unknown (§10 Unknown is not empty). */
133
+ function lockState(input, maps, classify) {
134
+ const unknown = (reason, evidence) => ({ lock: null, introducers: { reason, evidence } });
135
+ if (input === null)
136
+ return unknown('no-lockfile', null);
137
+ const read = readLockfile(input.path, input.text, input.manifests ?? {});
138
+ if ('unusable' in read)
139
+ return unknown('no-lockfile', read.unusable);
140
+ const { lockfile } = read;
141
+ // Any disagreement with a store-path version makes the whole lockfile stale.
142
+ const disagreements = new Set();
143
+ const firstParty = new Set();
144
+ for (const map of maps) {
145
+ map.kinds.forEach((kind, i) => {
146
+ if (kind.kind === 'package') {
147
+ // A store path gives a version to its own package, or, for vendored code (a store-name mismatch), to the
148
+ // vendoring package whose store entry holds it.
149
+ const stored = [
150
+ ...(kind.version === null || kind.vendoredBy !== null ? [] : [`${kind.name}@${kind.version}`]),
151
+ ...(kind.vendoredBy === null ? [] : [kind.vendoredBy]),
152
+ ];
153
+ for (const each of stored) {
154
+ const at = each.lastIndexOf('@');
155
+ if (lockfile.named(each.slice(0, at), each.slice(at + 1)).length === 0) {
156
+ disagreements.add(`a store path gives ${each}, which the lockfile doesn't install`);
157
+ }
158
+ }
159
+ }
160
+ if (kind.kind === 'other' && map.map.sources[i]?.ignored !== true) {
161
+ const classified = classify(kind.path, map.directory);
162
+ if (classified.kind === 'first-party')
163
+ firstParty.add(classified.path);
164
+ }
165
+ });
166
+ }
167
+ if (disagreements.size > 0)
168
+ return unknown('lockfile-stale', [...disagreements].sort(compareText)[0]);
169
+ // A missing or unreadable manifest leaves the direct dependencies unknown.
170
+ if (lockfile.unresolved !== null) {
171
+ return { lock: { lockfile, importer: null }, introducers: { reason: 'importer-unresolved', evidence: lockfile.unresolved } };
172
+ }
173
+ const importer = importerOf(lockfile, input.importer, [...firstParty]);
174
+ if (importer === null) {
175
+ const why = input.importer !== null
176
+ ? `the importer ${input.importer} is no workspace of the lockfile`
177
+ : firstParty.size === 0
178
+ ? 'no first-party source picks one of the lockfile\'s workspaces'
179
+ : `the first-party sources lie in several workspaces: ${[
180
+ ...new Set([...firstParty].map((path) => workspaceOf(lockfile, path)?.folder ?? '')),
181
+ ]
182
+ .map((folder) => (folder === '' ? '.' : folder))
183
+ .sort(compareText)
184
+ .join(', ')}`;
185
+ return { lock: { lockfile, importer: null }, introducers: { reason: 'importer-unresolved', evidence: why } };
186
+ }
187
+ return { lock: { lockfile, importer }, introducers: { reason: null, evidence: null, confidence: lockfile.confidence } };
188
+ }
189
+ const compareText = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
190
+ function verifiedMap({ text, map }) {
191
+ if (map === null)
192
+ return null;
193
+ const read = readMap(map.raw);
194
+ if (read === null)
195
+ return null;
196
+ const lineSeparator = /[\u2028\u2029]/.test(text);
197
+ const kinds = read.sources.map((source) => sourceKind(source.raw, source.sourceRoot));
198
+ const directory = map.base === null ? null : map.base.split('/').slice(0, -1).filter((segment) => segment !== '');
199
+ const starts = lineStarts(text, map.reading);
200
+ return { map: read, starts, method: map.method, lineSeparator, kinds, directory, weakVersions: new Map() };
201
+ }
202
+ const unknown = { key: 'unknown', provenance: 'unknown' };
203
+ /** A found attribution with `reasons`, and no original location, evidence or trace step unless `more` gives them. */
204
+ function found(attribution, reasons, more = {}) {
205
+ return { attribution, original: null, evidence: {}, trace: [], reasons, ...more };
206
+ }
207
+ /** The attribution of an occurrence in `artifact`, looked up at `lookup` in `at` (§10). */
208
+ function attributionOf(artifact, lookup, verifiedMapOf, classify, normalization, lock, at = artifact) {
209
+ if (artifact.origin === 'inline')
210
+ return found({ key: 'inline', provenance: 'inline' }, []);
211
+ // The map of the artifact the lookup point is in: the write's own, or another of its origin's.
212
+ const verified = verifiedMapOf(at);
213
+ if (artifact.origin !== 'local-build') {
214
+ // Remote code keys `url:` with its normalized URL, and is `high` on it (§9, §11).
215
+ const remote = { key: `url:${normalizeRemoteUrl(artifact.url, normalization)}`, provenance: 'remote' };
216
+ if (verified === null)
217
+ return found(remote, []);
218
+ // Its verified map still gives original locations, and a package the map names is evidence only (§9). The
219
+ // lockfile describes the scanned application's install, not remote code, so it adds nothing here (§10).
220
+ const mapped = mappedAttribution(verified, lookup, classify, null);
221
+ const named = mapped.attribution.package;
222
+ const evidence = named === undefined ? mapped.evidence : { ...mapped.evidence, package: named };
223
+ return found(remote, [], { original: mapped.original, evidence, trace: mapped.trace });
224
+ }
225
+ if (verified === null)
226
+ return found(unknown, ['no-map']);
227
+ return mappedAttribution(verified, lookup, classify, lock);
228
+ }
229
+ /** The attribution of an occurrence looked up at `lookup` in an artifact's verified map (§10). */
230
+ function mappedAttribution(verified, lookup, classify, lock) {
231
+ // Raw U+2028/U+2029 can shift the positions a map gives (§5, §9).
232
+ const shifted = verified.lineSeparator ? ['line-separator'] : [];
233
+ const { line, column } = position(verified.starts, lookup.generated.start);
234
+ const mapped = originalAt(verified.map, line, column);
235
+ if (mapped === null)
236
+ return found(unknown, [...shifted, 'no-map']);
237
+ const { source } = mapped;
238
+ const kind = verified.kinds[mapped.index] ?? sourceKind(null, null);
239
+ // A synthetic or bundler-runtime source counts as unmapped, and its raw source is kept as evidence (§10).
240
+ if (kind.kind === 'synthetic')
241
+ return found(unknown, [...shifted, 'no-map'], { evidence: { source: source.raw } });
242
+ // Only a null or empty entry has no raw source, and both are synthetic.
243
+ const candidate = { source: source.raw ?? '', line: mapped.line + 1, column: mapped.column };
244
+ const result = lookup.token === null || source.content === null
245
+ ? 'n/a'
246
+ : tokenAppears(source.content, mapped.line, lookup.token)
247
+ ? 'passed'
248
+ : 'failed';
249
+ const trace = [tokenCheckStep(result, lookup, candidate)];
250
+ if (result === 'failed') {
251
+ return found(unknown, [...shifted, 'token-check-failed'], { evidence: { rejected: candidate }, trace });
252
+ }
253
+ // Without the token check, the map's position is capped at medium, and a mirrored sidecar is too (§10).
254
+ const unverified = result === 'n/a'
255
+ ? ['token-check-n/a', ...(verified.method === 'external-sidecar' ? ['sidecar-not-token-verified'] : [])]
256
+ : [];
257
+ const kept = { original: candidate, trace };
258
+ const reasons = [...unverified, ...shifted];
259
+ switch (kind.kind) {
260
+ case 'foreign-scheme':
261
+ return found(unknown, [...reasons, 'foreign-scheme'], kept);
262
+ case 'yarn-slug':
263
+ return found(unknown, [...reasons, 'yarn-slug'], { ...kept, evidence: { slug: kind.slug } });
264
+ // A composed path that climbs out of its package inside `node_modules` is escaped, wherever the root is.
265
+ case 'escaped':
266
+ return found(unknown, [...reasons, 'escaped-source'], kept);
267
+ case 'package': {
268
+ const { name, subpath } = kind;
269
+ // A package's `dist` file may hide vendored code (§9 `prebundled-dist`).
270
+ const dist = subpath.slice(0, -1).includes('dist');
271
+ const attribution = { key: `pkg:${name}`, provenance: 'bundled-dependency', package: name };
272
+ const installed = lock === null ? null : installedAs(lock.lockfile, kind);
273
+ const vendoredBy = kind.vendoredBy ?? installed?.vendoredBy ?? null;
274
+ // Only a lockfile keyed by install path is a version source (§10 Version).
275
+ const version = versionOf(verified, kind, lock?.lockfile.keyedByInstallPath === true ? (installed?.node ?? null) : null);
276
+ // Vendored code, a store-name mismatch or a package the lockfile says isn't installed, still names the package
277
+ // its path names (§10 Provenance; ADR 0006).
278
+ const vendored = vendoredBy === null && installed?.vendored !== true ? [] : ['vendored'];
279
+ const introducedBy = lock?.importer == null || installed === null || installed.from.length === 0
280
+ ? undefined
281
+ : lock.lockfile.introducers(installed.from, lock.importer);
282
+ return {
283
+ ...found(attribution, [...reasons, ...(dist ? ['prebundled-dist'] : []), ...vendored], {
284
+ ...kept,
285
+ evidence: { ...(vendoredBy === null ? {} : { vendoredBy }), ...(version === null ? {} : { version }) },
286
+ }),
287
+ ...(introducedBy === undefined ? {} : { introducedBy }),
288
+ };
289
+ }
290
+ case 'other': {
291
+ const classified = classify(kind.path, verified.directory);
292
+ switch (classified.kind) {
293
+ // A source outside the inferred root is escaped (§10 Escaped sources), and so is an absolute path that no
294
+ // root prefixes.
295
+ case 'outside':
296
+ return found(unknown, [...reasons, 'escaped-source'], kept);
297
+ // An escaped source a framework path rule matches is a file of the package the rule names, with no version,
298
+ // and never `high` (§10 Framework path rules).
299
+ case 'rule': {
300
+ const attribution = { key: `pkg:${classified.package}`, provenance: 'bundled-dependency', package: classified.package };
301
+ const step = {
302
+ kind: 'path-rule',
303
+ rule: classified.rule,
304
+ package: classified.package,
305
+ source: candidate.source,
306
+ text: `the framework path rule ${classified.rule} names ${classified.package} for the escaped source ${candidate.source}`,
307
+ };
308
+ return found(attribution, [...reasons, 'framework-path-rule'], {
309
+ ...kept,
310
+ evidence: { pathRule: classified.rule },
311
+ trace: [...trace, step],
312
+ });
313
+ }
314
+ // A fetched map's relative entry: remote code, keyed by its URL (§11), not by a root.
315
+ case 'unplaced':
316
+ return found(unknown, reasons, kept);
317
+ case 'first-party': {
318
+ // `ignoreList` only demotes: a listed first-party source is `unknown` (§10).
319
+ if (source.ignored)
320
+ return found(unknown, [...reasons, 'ignore-list-demoted'], kept);
321
+ const attribution = { key: `app:${classified.path}`, provenance: 'first-party' };
322
+ // Its workspace is evidence, which only a lockfile gives (§10 Provenance; ADR 0006).
323
+ const workspace = lock === null ? undefined : workspaceOf(lock.lockfile, classified.path);
324
+ const named = workspace?.name == null ? null : workspace.version === null ? workspace.name : `${workspace.name}@${workspace.version}`;
325
+ return found(attribution, [...reasons, ...(classified.inferred ? ['first-party-inferred'] : [])], {
326
+ ...kept,
327
+ ...(named === null ? {} : { evidence: { workspace: named } }),
328
+ });
329
+ }
330
+ }
331
+ }
332
+ }
333
+ }
334
+ /**
335
+ * How a fresh lockfile installs a package source (§10): the node its install path matches, the nodes the walk starts
336
+ * from, and whether it is vendored, with the vendoring package when the lockfile names one.
337
+ *
338
+ * - A store path's package is the lockfile's copies of it at the store version.
339
+ * - Vendored code by a store-name mismatch walks from the vendoring package's copies (§10 Walk).
340
+ * - Otherwise the install path is matched, by its longest suffix that is a lockfile key. A path the lockfile doesn't
341
+ * install is vendored: in the installed package whose directory holds it, which the walk starts from, or in none
342
+ * the lockfile names.
343
+ */
344
+ function installedAs(lockfile, kind) {
345
+ if (kind.vendoredBy !== null) {
346
+ const at = kind.vendoredBy.lastIndexOf('@');
347
+ const from = lockfile.named(kind.vendoredBy.slice(0, at), kind.vendoredBy.slice(at + 1));
348
+ return { node: null, from, vendored: true, vendoredBy: null };
349
+ }
350
+ if (kind.version !== null)
351
+ return { node: null, from: lockfile.named(kind.name, kind.version), vendored: false, vendoredBy: null };
352
+ const placed = lockfile.place(kind.directory);
353
+ switch (placed.kind) {
354
+ case 'installed':
355
+ return { node: placed.nodes[0] ?? null, from: placed.nodes, vendored: false, vendoredBy: null };
356
+ case 'vendored': {
357
+ const [first] = placed.around;
358
+ return { node: null, from: placed.around, vendored: true, vendoredBy: first === undefined ? null : nameAtVersion(first) };
359
+ }
360
+ case 'unplaced':
361
+ return { node: null, from: [], vendored: false, vendoredBy: null };
362
+ }
363
+ }
364
+ /**
365
+ * A package's version, from its strongest source (§10 Version): the store path it is read from, or else the version a
366
+ * fresh lockfile installs at its install path, or else a weak content
367
+ * signal among its own files in the map, the sources in the same package directory: its `package.json` naming it, then
368
+ * an in-package `VERSION` constant, then a banner naming it. A signal is taken from the first source, in the map's
369
+ * order, that gives one.
370
+ */
371
+ function versionOf(verified, kind, installed) {
372
+ if (kind.version !== null)
373
+ return { version: kind.version, versionSource: 'store-path' };
374
+ if (installed !== null && installed.version !== '')
375
+ return { version: installed.version, versionSource: 'lockfile' };
376
+ const key = `${kind.path.namespace}\0${kind.directory.join('/')}`;
377
+ let weak = verified.weakVersions.get(key);
378
+ if (weak === undefined)
379
+ verified.weakVersions.set(key, (weak = weakVersion(verified, kind, key)));
380
+ return weak;
381
+ }
382
+ const semver = String.raw `\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?`;
383
+ const wholeSemver = new RegExp(`^${semver}$`);
384
+ /** An in-package `VERSION` constant: `VERSION`, `SDK_VERSION` … assigned or keyed a version string. */
385
+ const versionConstant = new RegExp(String.raw `\b(?:[A-Z][A-Z0-9]*_)*VERSION\s*[:=]\s*(['"\x60])(${semver})\1`);
386
+ /** A source's leading block comment, where a banner is. */
387
+ const leadingComment = /^\s*\/\*[\s\S]*?\*\//;
388
+ /** The weak version of the package `kind`, from the content of its own files in the map (§10 Version), or null. */
389
+ function weakVersion(verified, kind, key) {
390
+ const own = verified.map.sources.flatMap((source, i) => {
391
+ const each = verified.kinds[i];
392
+ if (each?.kind !== 'package' || source.content === null || each.name !== kind.name)
393
+ return [];
394
+ return `${each.path.namespace}\0${each.directory.join('/')}` === key ? [{ subpath: each.subpath, content: source.content }] : [];
395
+ });
396
+ for (const { subpath, content } of own) {
397
+ if (subpath.length !== 1 || subpath[0] !== 'package.json')
398
+ continue;
399
+ let manifest;
400
+ try {
401
+ manifest = JSON.parse(content);
402
+ }
403
+ catch {
404
+ continue;
405
+ }
406
+ if (typeof manifest !== 'object' || manifest === null)
407
+ continue;
408
+ const { name, version } = manifest;
409
+ if (name === kind.name && typeof version === 'string' && wholeSemver.test(version)) {
410
+ return { version, versionSource: 'package-json' };
411
+ }
412
+ }
413
+ for (const { content } of own) {
414
+ const found = versionConstant.exec(content);
415
+ if (found !== null)
416
+ return { version: found[2], versionSource: 'version-constant' };
417
+ }
418
+ const named = kind.name.replace(/[.*+?^${}()|[\]\\/]/g, '\\$&');
419
+ const banner = new RegExp(String.raw `(?:^|[\s*!])${named}(?:\s+|@)v?(${semver})(?![0-9A-Za-z.+-])`);
420
+ for (const { content } of own) {
421
+ const comment = leadingComment.exec(content)?.[0];
422
+ const found = comment === undefined ? null : banner.exec(comment);
423
+ if (found !== null && found !== undefined)
424
+ return { version: found[1], versionSource: 'banner' };
425
+ }
426
+ return null;
427
+ }
428
+ /** The `token-check` step for a check at `lookup` of `candidate` (§14). */
429
+ function tokenCheckStep(result, lookup, candidate) {
430
+ const { token } = lookup;
431
+ const at = `${candidate.source}:${candidate.line}:${candidate.column}`;
432
+ const text = result === 'passed'
433
+ ? `\`${token}\` appears within one line of ${at}, where the map puts the write`
434
+ : result === 'failed'
435
+ ? `\`${token}\` does not appear within one line of ${at}, where the map puts the write, so that position is ` +
436
+ 'discarded'
437
+ : token === null
438
+ ? `the write has no token at its site, so the token check does not apply to ${at}`
439
+ : `the map has no source content for ${candidate.source}, so the token check does not apply`;
440
+ return { kind: 'token-check', result, token, lookup: location(lookup.generated), candidate, text };
441
+ }
442
+ function location({ start, end, line, column }) {
443
+ return { start, end, line, column };
444
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * What a raw `sources` entry names:
3
+ * - `synthetic`: a synthetic or bundler-runtime source, which counts as unmapped (`no-map`)
4
+ * - `foreign-scheme`: a source with a scheme §10 doesn't recognize, which keys `unknown` whether or not it names a
5
+ * package
6
+ * - `package`: a file of the package `name`, with the path segments inside the package after it, the segments of the
7
+ * package's own directory (which hold its other files), the version its store path gives, or null (§10 Version), and
8
+ * the vendoring package (`name@version`) when the path lies inside another package's store entry, or null (§10
9
+ * Provenance, vendored code)
10
+ * - `yarn-slug`: a scoped package under Yarn's pnpm linker, which names it only by the lossy `slug`
11
+ * - `escaped`: a composed-map path that climbs out of its own package inside `node_modules` (`node_modules/@x/src/…`,
12
+ * `node_modules/src/…`, `node_modules/node_modules/…`), which is escaped wherever the inferred root is
13
+ * - `other`: a path under no `node_modules` segment: first-party or escaped code
14
+ *
15
+ * Each but the first two carries the normalized `path` it was read from, which the inferred root is read from and
16
+ * first-party code is named by (§10 Provenance, §11).
17
+ */
18
+ export type SourceKind = {
19
+ kind: 'synthetic';
20
+ } | {
21
+ kind: 'foreign-scheme';
22
+ } | {
23
+ kind: 'package';
24
+ name: string;
25
+ subpath: string[];
26
+ path: SourcePath;
27
+ directory: string[];
28
+ version: string | null;
29
+ vendoredBy: string | null;
30
+ } | {
31
+ kind: 'yarn-slug';
32
+ slug: string;
33
+ path: SourcePath;
34
+ } | {
35
+ kind: 'escaped';
36
+ path: SourcePath;
37
+ } | {
38
+ kind: 'other';
39
+ path: SourcePath;
40
+ };
41
+ /**
42
+ * A source's normalized path (§10 Source schemes), with what it is relative to:
43
+ * - `relative`: a path with no scheme, relative to its map's directory (a relative `sourceRoot` joined)
44
+ * - `absolute`: a `file://`, absolute or drive-letter path, whose drive letter is its first segment
45
+ * - `webpack:<ns>`, `webpack-internal`, `turbopack` or `vite`: a path relative to that bundler's base (the webpack
46
+ * context, or the project root), within the bundler namespace it names
47
+ *
48
+ * `segments` has `.` and `x/..` collapsed, and keeps leading `..` segments.
49
+ */
50
+ export interface SourcePath {
51
+ namespace: string;
52
+ segments: string[];
53
+ }
54
+ /**
55
+ * The relative `sourceRoot` a map's entries may be joined to (§10 step 1), or null: a URL or an absolute or
56
+ * drive-letter root is ignored, and so is an empty or non-string one.
57
+ */
58
+ export declare function relativeSourceRoot(sourceRoot: unknown): string | null;
59
+ /**
60
+ * What a raw `sources` entry names, under a map's relative `sourceRoot` (§10 Containing package):
61
+ *
62
+ * 1. **Normalize.** A null or empty entry, or one holding `\0` (a Rollup virtual module), is synthetic. A BOM or a
63
+ * `)]}'` prefix is stripped, then a recognized scheme; any other scheme is foreign. A synthetic source is then
64
+ * recognized on the entry, before any root is joined: `webpack/runtime/*`, `webpack/bootstrap`, `<anon>`,
65
+ * `turbopack:///[turbopack]/…`, `ignored|…` and `external "x"`, and an entry whose resource path is empty.
66
+ * Only an entry that had no scheme and isn't absolute is joined to the root, as a string, with a `/` between them
67
+ * unless the root ends with one. Loader prefixes (up to the last `!`), a `?query` (webpack's `?hash`
68
+ * included) and `*` suffixes are stripped, so the resource path decides. The path is percent-decoded, `\` becomes
69
+ * `/`, and `.` and `x/..` segments collapse (leading `..` stay). A `file://`, absolute or drive-letter path is
70
+ * `absolute`, whatever scheme wraps it.
71
+ * 2. **Read the name** from the segment after the last `node_modules` segment that a dot segment doesn't follow, or the
72
+ * two segments after it for a scoped package. Under Yarn's pnpm linker the package is in
73
+ * `node_modules/.store/<slug>-npm-<version>-<hash>/package/` (or `<slug>-virtual-<hash>`): an unscoped slug is its
74
+ * name, and a scoped one is lossy. A path that climbs out of its package inside `node_modules` names none.
75
+ */
76
+ export declare function sourceKind(source: string | null, sourceRoot: string | null): SourceKind;