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,210 @@
1
+ import { type ChangeRecord, type Realm, type Surface } from '../../catalog.ts';
2
+ import type { Effect, GeneratedLocation, IneffectiveReason, InstallContext, Locks, Mechanism, Reachability, ReachabilityBasis, RealmInstance, Strictness, Target, Throws } from '../analysis/index.ts';
3
+ import type { ArtifactIdentity, Attribution, AttributedTraceStep, AttributionEvidence, OriginalLocation, PackageVersion } from '../attribution/index.ts';
4
+ import type { Baseline, BaselineNote } from '../baseline/index.ts';
5
+ import type { BaselineState, ChangedReason, ExplainingRecord, FindingNote, MatchedBy } from '../compare/index.ts';
6
+ import { type Completeness, type SourceMapPairing } from '../completeness/index.ts';
7
+ import type { Confidence } from '../confidence/index.ts';
8
+ import type { Entry, FailOnState, Mapping, Mode, SurfaceScope } from '../config/index.ts';
9
+ import { type IneffectiveWriteRecord, type OutOfScopeBySurface } from '../findings/index.ts';
10
+ import type { Gate, PolicedFinding, PolicySlot } from '../policy/index.ts';
11
+ import type { Retrieval, RunRetrieval } from '../source-store/index.ts';
12
+ /** The report format's version (§15). Removing, renaming or changing the meaning of a field bumps it. */
13
+ export declare const reportVersion = 1;
14
+ export interface Tool {
15
+ monkeypatchlistVersion: string;
16
+ catalogVersion: number;
17
+ analyzerVersion: string;
18
+ fingerprintScheme: number;
19
+ }
20
+ /** The scanned application (§16 Application name). */
21
+ export interface Application {
22
+ name: string;
23
+ }
24
+ export interface ReportConfig {
25
+ scanConfigId: string;
26
+ surface: SurfaceScope;
27
+ mode: Mode;
28
+ entries: Entry[];
29
+ mappings: Mapping[];
30
+ /** The source-map directories, as declared, in the order they were searched (§2 Source maps). */
31
+ sourceMaps: string[];
32
+ /** The lockfile as given, and the `lockfile.importer` setting, or null when none was given (§10, §16). */
33
+ lockfile: {
34
+ path: string;
35
+ importer: string | null;
36
+ } | null;
37
+ failOn: FailOnState[];
38
+ }
39
+ /** The baseline a scan was compared with (§15): its path as given, its header, and scan-level notes. */
40
+ export interface ReportBaseline {
41
+ path: string;
42
+ header: Omit<Baseline, 'entries'>;
43
+ /** Scan-level notes: version skew (§4). */
44
+ notes: BaselineNote[];
45
+ }
46
+ export interface ReportArtifact {
47
+ url: string;
48
+ path: string | null;
49
+ /** Null when the artifact's bytes were never read. */
50
+ hash: string | null;
51
+ origin: ArtifactIdentity['origin'];
52
+ discovery: 'reference' | 'injected' | 'sweep';
53
+ sourceType: 'script' | 'module';
54
+ realm: Realm;
55
+ realmBasis: 'reference' | 'assumed';
56
+ /** Null for an artifact only the sweep found. */
57
+ parent: string | null;
58
+ sourceMap: SourceMapPairing;
59
+ retrieval: Retrieval;
60
+ /** The artifact's module linking (§6). */
61
+ moduleLinking: {
62
+ idioms: string[];
63
+ modules: number;
64
+ unlinked: number;
65
+ linkGroups: number;
66
+ };
67
+ }
68
+ export interface ReportOccurrence {
69
+ artifact: ArtifactIdentity;
70
+ generated: GeneratedLocation;
71
+ /** The original location a source map resolves, or null (§8). */
72
+ original: OriginalLocation | null;
73
+ mechanism: Mechanism;
74
+ /** Whether the mutation happens, judged by the guards on its path (§7). */
75
+ reachability: Reachability;
76
+ /** How its reachability was judged (§7). */
77
+ reachabilityBasis: ReachabilityBasis;
78
+ /** Whether it runs at load or later, and the install entry a `deferred` one waits on (§7 Install context). */
79
+ installContext: InstallContext;
80
+ installEntry: string | null;
81
+ realmInstance: RealmInstance;
82
+ /** Its attribution evidence (§8, §10). */
83
+ evidence: AttributionEvidence;
84
+ /** Its introducing dependencies (§10), only when a usable lockfile gives them. */
85
+ introducedBy?: string[];
86
+ confidence: Confidence;
87
+ trace: AttributedTraceStep[];
88
+ }
89
+ export interface ReportFinding {
90
+ fingerprint: string;
91
+ targetKey: string;
92
+ target: Target;
93
+ surface: Surface;
94
+ effect: Effect;
95
+ locks: Locks;
96
+ attribution: Attribution;
97
+ /** The union of its occurrences' introducing dependencies (§10), sorted; absent when none has them. */
98
+ introducedBy?: string[];
99
+ /** The most-reaching occurrence's (§7). Null for a baseline entry the scan didn't find. */
100
+ reachability: Reachability | null;
101
+ /** The basis of the occurrences that give its reachability, `call-path` winning (§7). Null as `reachability` is. */
102
+ reachabilityBasis: ReachabilityBasis | null;
103
+ /** `load` if any occurrence is `load` (§7). Null for a baseline entry the scan didn't find. */
104
+ installContext: InstallContext | null;
105
+ /** Null for a baseline entry the scan didn't find: only occurrences give confidence. */
106
+ confidence: Confidence | null;
107
+ baselineState: BaselineState | null;
108
+ matchedBy: MatchedBy | null;
109
+ changedReasons: ChangedReason[];
110
+ notes: FindingNote[];
111
+ /** Set on a finding of a retained target (§4 Catalog versions). */
112
+ catalogStatus?: 'retained';
113
+ /** The catalog changes that could explain this delta (§4 Catalog versions); absent when none does. */
114
+ explainedBy?: ExplainingRecord[];
115
+ policy: PolicySlot;
116
+ occurrences: ReportOccurrence[];
117
+ }
118
+ /**
119
+ * An occurrence of an ineffective write (§3 Record): where it is and where its code came from, as a finding's
120
+ * occurrence, with the effect it intended, its mechanism, the strictness of its code and whether it throws.
121
+ */
122
+ export interface ReportIneffectiveOccurrence {
123
+ artifact: ArtifactIdentity;
124
+ generated: GeneratedLocation;
125
+ original: OriginalLocation | null;
126
+ effect: Effect;
127
+ mechanism: Mechanism;
128
+ reachability: Reachability;
129
+ reachabilityBasis: ReachabilityBasis;
130
+ installContext: InstallContext;
131
+ installEntry: string | null;
132
+ strictness: Strictness;
133
+ throws: Throws;
134
+ realmInstance: RealmInstance;
135
+ attribution: Attribution;
136
+ evidence: AttributionEvidence;
137
+ trace: AttributedTraceStep[];
138
+ }
139
+ /** The ineffective writes to one target for one reason (§3 Record). No fingerprint, baseline state or effect. */
140
+ export interface ReportIneffectiveWrite {
141
+ target: Target;
142
+ surface: Surface;
143
+ reason: IneffectiveReason;
144
+ occurrences: ReportIneffectiveOccurrence[];
145
+ }
146
+ /**
147
+ * How many findings are in each baseline state (§15), all 0 without a baseline, how many findings a `protected` scan
148
+ * left out, per surface (§12), and how many ineffective-write records the scan has (§3).
149
+ */
150
+ export type Summary = Record<BaselineState, number> & {
151
+ outOfScopeBySurface: OutOfScopeBySurface;
152
+ ineffectiveWrites: number;
153
+ };
154
+ /** All wall-clock data, and nothing else that varies between runs (§15). */
155
+ export interface Run {
156
+ startedAt: string;
157
+ durationMs: number;
158
+ /** The wall-clock facts of each fetch, by requested URL (§17 What is recorded). */
159
+ retrievals: Record<string, RunRetrieval>;
160
+ }
161
+ export interface Report {
162
+ reportVersion: typeof reportVersion;
163
+ tool: Tool;
164
+ application: Application;
165
+ config: ReportConfig;
166
+ summary: Summary;
167
+ gate: Gate;
168
+ completeness: Completeness;
169
+ baseline: ReportBaseline | null;
170
+ /** Name → strong version and its source, for each finding's containing package (§10 Version, §11): the baseline's map. */
171
+ packages: Record<string, PackageVersion>;
172
+ findings: ReportFinding[];
173
+ ineffectiveWrites: ReportIneffectiveWrite[];
174
+ artifacts: ReportArtifact[];
175
+ run: Run;
176
+ }
177
+ export interface ReportInput {
178
+ tool: Tool;
179
+ application: Application;
180
+ config: ReportConfig;
181
+ gate: Gate;
182
+ /** How many findings a `protected` scan left out, per surface (§12). */
183
+ outOfScopeBySurface: OutOfScopeBySurface;
184
+ completeness: Completeness;
185
+ baseline: {
186
+ path: string;
187
+ baseline: Baseline;
188
+ notes: readonly BaselineNote[];
189
+ } | null;
190
+ packages: Readonly<Record<string, PackageVersion>>;
191
+ findings: readonly PolicedFinding[];
192
+ ineffectiveWrites: readonly IneffectiveWriteRecord[];
193
+ artifacts: readonly ReportArtifact[];
194
+ run: Run;
195
+ }
196
+ /**
197
+ * Builds the JSON report with its keys in a fixed order (§15). Findings are sorted by fingerprint and artifacts by URL.
198
+ */
199
+ export declare function buildReport(input: ReportInput): Report;
200
+ /** The JSON report: 2-space indent, LF line endings and a trailing newline. */
201
+ export declare function renderJson(report: Report): string;
202
+ /** A change record in a line of words (§4 Catalog versions), as `baseline update` prints it. */
203
+ export declare function describeChange(record: ChangeRecord): string;
204
+ /**
205
+ * What `baseline update` accepts into the baseline at `path` (§11 Commands), from the report of its scan against that
206
+ * baseline: the added, changed and removed findings, grouped by state, each with its count and fingerprints, and under
207
+ * each fingerprint the `explainedBy` records it carries (§11 Commands). Existing findings are accepted unchanged, so
208
+ * they aren't listed.
209
+ */
210
+ export declare function renderAccepted(report: Report, path: string): string;
@@ -0,0 +1,388 @@
1
+ // Report (§19, module 9): renders what the modules before it produced, as JSON and for the terminal (§15). Nothing
2
+ // is recomputed here.
3
+ import { realms, surfaces } from "../../catalog.js";
4
+ import { pairingMethods } from "../completeness/index.js";
5
+ import { compareStrings } from "../findings/index.js";
6
+ /** The report format's version (§15). Removing, renaming or changing the meaning of a field bumps it. */
7
+ export const reportVersion = 1;
8
+ /**
9
+ * Builds the JSON report with its keys in a fixed order (§15). Findings are sorted by fingerprint and artifacts by URL.
10
+ */
11
+ export function buildReport(input) {
12
+ const { tool, application, config, run } = input;
13
+ return {
14
+ reportVersion,
15
+ tool: {
16
+ monkeypatchlistVersion: tool.monkeypatchlistVersion,
17
+ catalogVersion: tool.catalogVersion,
18
+ analyzerVersion: tool.analyzerVersion,
19
+ fingerprintScheme: tool.fingerprintScheme,
20
+ },
21
+ application: { name: application.name },
22
+ config: {
23
+ scanConfigId: config.scanConfigId,
24
+ surface: config.surface,
25
+ mode: config.mode,
26
+ entries: config.entries.map(({ file, url }) => ({ file, url })),
27
+ mappings: config.mappings.map(({ url, dir }) => ({ url, dir })),
28
+ sourceMaps: [...config.sourceMaps],
29
+ lockfile: config.lockfile === null ? null : { path: config.lockfile.path, importer: config.lockfile.importer },
30
+ failOn: [...config.failOn],
31
+ },
32
+ summary: summary(input.findings, input.outOfScopeBySurface, input.ineffectiveWrites),
33
+ gate: { failOn: [...input.gate.failOn], result: input.gate.result, exitCode: input.gate.exitCode },
34
+ completeness: completeness(input.completeness),
35
+ baseline: input.baseline === null ? null : baseline(input.baseline),
36
+ packages: packages(input.packages),
37
+ findings: [...input.findings].sort((a, b) => compareStrings(a.fingerprint, b.fingerprint)).map(finding),
38
+ ineffectiveWrites: input.ineffectiveWrites.map(ineffectiveWrite),
39
+ // A URL referenced in two realms is an artifact in each (§2 Realm), listed in the order of `realms`, and one run
40
+ // both as a classic script and as a module is an artifact each way, the classic one first.
41
+ artifacts: [...input.artifacts]
42
+ .sort((a, b) => compareStrings(a.url, b.url) ||
43
+ realms.indexOf(a.realm) - realms.indexOf(b.realm) ||
44
+ Number(a.sourceType === 'module') - Number(b.sourceType === 'module') ||
45
+ // One key URL reached through two fetched URLs is an artifact of each (§11), by the URL it was fetched at.
46
+ compareStrings(a.retrieval.requestedUrl ?? '', b.retrieval.requestedUrl ?? ''))
47
+ .map(artifact),
48
+ run: { startedAt: run.startedAt, durationMs: run.durationMs, retrievals: retrievals(run.retrievals) },
49
+ };
50
+ }
51
+ /** An ineffective-write record (§3 Record), with its keys, and its occurrences' keys, in a fixed order. */
52
+ function ineffectiveWrite(input) {
53
+ const { realm, holder, name, shadows } = input.target;
54
+ return {
55
+ target: { realm, holder, name, ...(shadows === undefined ? {} : { shadows }) },
56
+ surface: input.surface,
57
+ reason: input.reason,
58
+ occurrences: input.occurrences.map((occurrence) => ({
59
+ artifact: { url: occurrence.artifact.url, hash: occurrence.artifact.hash, origin: occurrence.artifact.origin },
60
+ generated: location(occurrence.generated),
61
+ original: occurrence.original === null ? null : original(occurrence.original),
62
+ effect: occurrence.effect,
63
+ mechanism: occurrence.mechanism,
64
+ reachability: occurrence.reachability,
65
+ reachabilityBasis: occurrence.reachabilityBasis,
66
+ installContext: occurrence.installContext,
67
+ installEntry: occurrence.installEntry,
68
+ strictness: occurrence.strictness,
69
+ throws: occurrence.throws,
70
+ realmInstance: occurrence.realmInstance,
71
+ attribution: attribution(occurrence.attribution),
72
+ evidence: evidence(occurrence.evidence),
73
+ trace: occurrence.trace.map(traceStep),
74
+ })),
75
+ };
76
+ }
77
+ function location({ start, end, line, column }) {
78
+ return { start, end, line, column };
79
+ }
80
+ /** A trace step with `kind` first and `text` last (§14). */
81
+ function traceStep({ kind, text, ...fields }) {
82
+ return { kind, ...fields, text };
83
+ }
84
+ function finding(input) {
85
+ const { realm, holder, name, shadows } = input.target;
86
+ return {
87
+ fingerprint: input.fingerprint,
88
+ targetKey: input.targetKey,
89
+ target: { realm, holder, name, ...(shadows === undefined ? {} : { shadows }) },
90
+ surface: input.surface,
91
+ effect: input.effect,
92
+ locks: input.locks,
93
+ attribution: attribution(input.attribution),
94
+ ...(input.introducedBy === undefined ? {} : { introducedBy: [...input.introducedBy] }),
95
+ reachability: input.reachability,
96
+ reachabilityBasis: input.reachabilityBasis,
97
+ installContext: input.installContext,
98
+ confidence: input.confidence === null ? null : confidence(input.confidence),
99
+ baselineState: input.baselineState,
100
+ matchedBy: input.matchedBy,
101
+ changedReasons: input.changedReasons.map((reason) => ({ ...reason })),
102
+ notes: input.notes.map(findingNote),
103
+ ...(input.catalogStatus === undefined ? {} : { catalogStatus: input.catalogStatus }),
104
+ ...(input.explainedBy === undefined ? {} : { explainedBy: input.explainedBy.map(explainingRecord) }),
105
+ policy: input.policy,
106
+ occurrences: input.occurrences.map((occurrence) => ({
107
+ artifact: { url: occurrence.artifact.url, hash: occurrence.artifact.hash, origin: occurrence.artifact.origin },
108
+ generated: location(occurrence.generated),
109
+ original: occurrence.original === null ? null : original(occurrence.original),
110
+ mechanism: occurrence.mechanism,
111
+ reachability: occurrence.reachability,
112
+ reachabilityBasis: occurrence.reachabilityBasis,
113
+ installContext: occurrence.installContext,
114
+ installEntry: occurrence.installEntry,
115
+ realmInstance: occurrence.realmInstance,
116
+ evidence: evidence(occurrence.evidence),
117
+ ...(occurrence.introducedBy === undefined ? {} : { introducedBy: [...occurrence.introducedBy] }),
118
+ confidence: confidence(occurrence.confidence),
119
+ trace: occurrence.trace.map(traceStep),
120
+ })),
121
+ };
122
+ }
123
+ /** A change record with `catalogVersion`, `kind` and any `change` first. */
124
+ function explainingRecord({ catalogVersion, kind, ...record }) {
125
+ return structuredClone({ catalogVersion, kind, ...record });
126
+ }
127
+ /** A finding's note, with `kind` first and its fields in a fixed order (§15). */
128
+ function findingNote(input) {
129
+ switch (input.kind) {
130
+ case 'rekeyed':
131
+ return { kind: input.kind, before: [...input.before], after: input.after };
132
+ case 'shared-target-key':
133
+ return { kind: input.kind, fingerprints: [...input.fingerprints] };
134
+ case 'previous-version':
135
+ return { kind: input.kind, package: input.package, before: input.before, after: input.after };
136
+ case 'reachability-change':
137
+ return { kind: input.kind, before: input.before, after: input.after };
138
+ case 'install-context-change':
139
+ return { kind: input.kind, before: input.before, after: input.after };
140
+ }
141
+ }
142
+ /**
143
+ * A finding's attribution, with its keys in a fixed order: `package` only for a `pkg:` key, and `vendoredBy` only for
144
+ * vendored code (§15).
145
+ */
146
+ function attribution({ key, provenance, package: name, vendoredBy, workspace }) {
147
+ return {
148
+ key,
149
+ provenance,
150
+ ...(name === undefined ? {} : { package: name }),
151
+ ...(vendoredBy === undefined ? {} : { vendoredBy }),
152
+ ...(workspace === undefined ? {} : { workspace }),
153
+ };
154
+ }
155
+ function original({ source, line, column }) {
156
+ return { source, line, column };
157
+ }
158
+ /** An occurrence's attribution evidence, with its keys in a fixed order, each only when it applies. */
159
+ function evidence({ source, rejected, slug, pathRule, package: named, vendoredBy, version, workspace, }) {
160
+ return {
161
+ ...(source === undefined ? {} : { source }),
162
+ ...(rejected === undefined ? {} : { rejected: original(rejected) }),
163
+ ...(slug === undefined ? {} : { slug }),
164
+ ...(pathRule === undefined ? {} : { pathRule }),
165
+ ...(named === undefined ? {} : { package: named }),
166
+ ...(vendoredBy === undefined ? {} : { vendoredBy }),
167
+ ...(version === undefined ? {} : { version: { version: version.version, versionSource: version.versionSource } }),
168
+ ...(workspace === undefined ? {} : { workspace }),
169
+ };
170
+ }
171
+ /** A `packages` map (§11), by name in code-unit order, each entry's keys in a fixed order. */
172
+ function packages(input) {
173
+ return Object.fromEntries(Object.entries(input)
174
+ .sort(([a], [b]) => compareStrings(a, b))
175
+ .map(([name, { version, versionSource }]) => [name, { version, versionSource }]));
176
+ }
177
+ /** The baseline block, with the header's keys in the order the file has them (§11). */
178
+ function baseline(input) {
179
+ const { path, baseline } = input;
180
+ const { formatVersion, scanConfigId, fingerprintScheme, catalogVersion, monkeypatchlistVersion } = baseline;
181
+ const { completeness, surface } = baseline;
182
+ return {
183
+ path,
184
+ header: {
185
+ formatVersion,
186
+ scanConfigId,
187
+ fingerprintScheme,
188
+ catalogVersion,
189
+ monkeypatchlistVersion,
190
+ completeness,
191
+ surface,
192
+ packages: packages(baseline.packages),
193
+ },
194
+ notes: input.notes.map(note),
195
+ };
196
+ }
197
+ /** A scan-level note, with its keys in a fixed order. */
198
+ function note(input) {
199
+ const pair = ({ baseline, scan }) => ({ baseline, scan });
200
+ return {
201
+ kind: input.kind,
202
+ newer: input.newer,
203
+ formatVersion: pair(input.formatVersion),
204
+ fingerprintScheme: pair(input.fingerprintScheme),
205
+ catalogVersion: pair(input.catalogVersion),
206
+ monkeypatchlistVersion: pair(input.monkeypatchlistVersion),
207
+ };
208
+ }
209
+ function summary(findings, outOfScope, ineffective) {
210
+ const count = (state) => findings.filter((finding) => finding.baselineState === state).length;
211
+ return {
212
+ added: count('added'),
213
+ changed: count('changed'),
214
+ existing: count('existing'),
215
+ removed: count('removed'),
216
+ unconfirmed: count('unconfirmed'),
217
+ outOfScopeBySurface: Object.fromEntries(surfaces.map((surface) => [surface, outOfScope[surface]])),
218
+ ineffectiveWrites: ineffective.length,
219
+ };
220
+ }
221
+ function confidence({ target, mutation, attribution }) {
222
+ const dimension = ({ level, reasons }) => ({ level, reasons: [...reasons] });
223
+ return { target: dimension(target), mutation: dimension(mutation), attribution: dimension(attribution) };
224
+ }
225
+ /** The completeness block (§13), with its keys in a fixed order. The aggregator has already sorted the records. */
226
+ function completeness({ level, counters, limitations, excluded, attribution }) {
227
+ const { artifacts, blindSpots, html, statistics } = counters;
228
+ return {
229
+ level,
230
+ counters: {
231
+ artifacts: {
232
+ known: artifacts.known,
233
+ analyzed: artifacts.analyzed,
234
+ failed: {
235
+ 'parse-failure': artifacts.failed['parse-failure'],
236
+ unreadable: artifacts.failed.unreadable,
237
+ 'fetch-failed': artifacts.failed['fetch-failed'],
238
+ 'sri-mismatch': artifacts.failed['sri-mismatch'],
239
+ 'crash-guard': artifacts.failed['crash-guard'],
240
+ },
241
+ skipped: {
242
+ offline: artifacts.skipped.offline,
243
+ refused: artifacts.skipped.refused,
244
+ excluded: artifacts.skipped.excluded,
245
+ nonJs: artifacts.skipped.nonJs,
246
+ },
247
+ sweepOnly: artifacts.sweepOnly,
248
+ otherFiles: artifacts.otherFiles,
249
+ },
250
+ blindSpots: {
251
+ dynamicCode: blindSpots.dynamicCode,
252
+ unresolvedReference: blindSpots.unresolvedReference,
253
+ realmUncertain: blindSpots.realmUncertain,
254
+ unresolvedHolder: blindSpots.unresolvedHolder,
255
+ },
256
+ html: { nomodule: html.nomodule, inlineHandlers: html.inlineHandlers },
257
+ statistics: {
258
+ steps: statistics.steps,
259
+ stepBudget: statistics.stepBudget,
260
+ budgetExhausted: statistics.budgetExhausted,
261
+ caps: { ...statistics.caps },
262
+ pruned: statistics.pruned,
263
+ // The caps the scan ran with, keyed in code-unit order, as the hits are.
264
+ limits: {
265
+ stepsPerByte: statistics.limits.stepsPerByte,
266
+ caps: Object.fromEntries(Object.entries(statistics.limits.caps).sort(([a], [b]) => compareStrings(a, b))),
267
+ },
268
+ crashGuard: statistics.crashGuard,
269
+ },
270
+ },
271
+ limitations: [...limitations],
272
+ excluded: [...excluded],
273
+ attribution: {
274
+ artifacts: {
275
+ withMap: attribution.artifacts.withMap,
276
+ withoutMap: attribution.artifacts.withoutMap,
277
+ withBrokenMap: attribution.artifacts.withBrokenMap,
278
+ },
279
+ pairings: Object.fromEntries(pairingMethods.map((method) => [method, attribution.pairings[method]])),
280
+ staleCandidates: attribution.staleCandidates,
281
+ unreadMaps: {
282
+ refused: attribution.unreadMaps.refused,
283
+ failed: attribution.unreadMaps.failed,
284
+ unreadable: attribution.unreadMaps.unreadable,
285
+ },
286
+ sourceMapDirectories: attribution.sourceMapDirectories.map(({ path, found, paired, stale, orphaned, orphanedMaps }) => ({
287
+ path,
288
+ found,
289
+ paired,
290
+ stale,
291
+ orphaned,
292
+ orphanedMaps: [...orphanedMaps],
293
+ })),
294
+ cappedFindings: { medium: attribution.cappedFindings.medium, low: attribution.cappedFindings.low },
295
+ introducers: {
296
+ reason: attribution.introducers.reason,
297
+ evidence: attribution.introducers.evidence,
298
+ ...(attribution.introducers.confidence === undefined ? {} : { confidence: attribution.introducers.confidence }),
299
+ },
300
+ },
301
+ };
302
+ }
303
+ function artifact(input) {
304
+ const { url, path, hash, origin, discovery, sourceType, realm, realmBasis, parent } = input;
305
+ const sourceMap = { method: input.sourceMap.method, path: input.sourceMap.path, status: input.sourceMap.status };
306
+ const { status, requestedUrl, finalUrl, contentType, sri } = input.retrieval;
307
+ // An integrity check's keys in a fixed order (§15 Determinism).
308
+ const check = sri === 'none' ? sri : { result: sri.result, expected: [...sri.expected], actual: sri.actual };
309
+ const retrieval = { status, requestedUrl, finalUrl, contentType, sri: check };
310
+ return {
311
+ url,
312
+ path,
313
+ hash,
314
+ origin,
315
+ discovery,
316
+ sourceType,
317
+ realm,
318
+ realmBasis,
319
+ parent,
320
+ sourceMap: sourceMap,
321
+ retrieval,
322
+ // Its keys in a fixed order, and its idioms in code-unit order (§15 Determinism).
323
+ moduleLinking: {
324
+ idioms: [...input.moduleLinking.idioms].sort(compareStrings),
325
+ modules: input.moduleLinking.modules,
326
+ unlinked: input.moduleLinking.unlinked,
327
+ linkGroups: input.moduleLinking.linkGroups,
328
+ },
329
+ };
330
+ }
331
+ /** The run block's retrievals, by requested URL in code-unit order, with their keys in a fixed order. */
332
+ function retrievals(input) {
333
+ return Object.fromEntries(Object.entries(input)
334
+ .sort(([a], [b]) => compareStrings(a, b))
335
+ .map(([url, { fetchedAt, etag, lastModified }]) => [url, { fetchedAt, etag, lastModified }]));
336
+ }
337
+ /** The JSON report: 2-space indent, LF line endings and a trailing newline. */
338
+ export function renderJson(report) {
339
+ return `${JSON.stringify(report, null, 2)}\n`;
340
+ }
341
+ /** A change record in a line of words (§4 Catalog versions), as `baseline update` prints it. */
342
+ export function describeChange(record) {
343
+ switch (record.kind) {
344
+ case 'target':
345
+ return `target ${record.change} ${record.name} (${record.realm})`;
346
+ case 'protected-entry': {
347
+ const { interface: name, member } = record.entry;
348
+ return `protected entry ${record.change} ${member === undefined ? name : `${name}.${member}`} (${record.surface})`;
349
+ }
350
+ case 'member-data':
351
+ return `descriptor or assign kind changed on ${record.name} (${record.realm})`;
352
+ case 'holder': {
353
+ const chain = (side) => String(side?.proto ?? null);
354
+ const step = record.change === 'rechained'
355
+ ? `, prototype ${chain(record.before)} → ${chain(record.after)}`
356
+ : `, prototype ${chain(record.after ?? record.before)}`;
357
+ return `holder ${record.change === 'rechained' ? 're-chained' : record.change} ${record.holder}${step} (${record.realm})`;
358
+ }
359
+ case 'immutable-prototype':
360
+ return `immutablePrototype ${record.before} → ${record.after} on ${record.holder} (${record.realm})`;
361
+ case 'framework-path-rule':
362
+ return `framework path rule ${record.id} ${record.change}`;
363
+ case 'url-normalization':
364
+ return `URL normalization rule ${record.id} ${record.change}`;
365
+ }
366
+ }
367
+ /** The baseline states `baseline update` accepts and prints, in the order it prints them (§11 Commands). */
368
+ const acceptedStates = ['added', 'changed', 'removed'];
369
+ /**
370
+ * What `baseline update` accepts into the baseline at `path` (§11 Commands), from the report of its scan against that
371
+ * baseline: the added, changed and removed findings, grouped by state, each with its count and fingerprints, and under
372
+ * each fingerprint the `explainedBy` records it carries (§11 Commands). Existing findings are accepted unchanged, so
373
+ * they aren't listed.
374
+ */
375
+ export function renderAccepted(report, path) {
376
+ const lines = [`Accepted into ${path}`];
377
+ for (const state of acceptedStates) {
378
+ const findings = report.findings.filter((finding) => finding.baselineState === state);
379
+ lines.push(` ${state[0].toUpperCase()}${state.slice(1)} ${findings.length}`);
380
+ for (const finding of findings) {
381
+ lines.push(` ${finding.fingerprint}`);
382
+ for (const record of finding.explainedBy ?? []) {
383
+ lines.push(` explained by catalog ${record.catalogVersion}: ${describeChange(record)}`);
384
+ }
385
+ }
386
+ }
387
+ return `${lines.join('\n')}\n`;
388
+ }
@@ -0,0 +1,11 @@
1
+ import type { Report } from './index.ts';
2
+ export interface TerminalOptions {
3
+ /** ANSI color: only on a TTY, never under `NO_COLOR` or `--no-color` (§15 Formatting). */
4
+ color: boolean;
5
+ /** `--verbose`: existing findings get a card too, instead of only being counted (§16). */
6
+ verbose: boolean;
7
+ /** `--list-ineffective`: each ineffective-write record is listed, instead of only being counted (§16). */
8
+ listIneffective: boolean;
9
+ }
10
+ /** The terminal report for `report` (§15 Terminal). */
11
+ export declare function renderTerminal(report: Report, options?: Partial<TerminalOptions>): string;