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.
- package/LICENSE +202 -0
- package/NOTICE +61 -0
- package/README.md +183 -2
- package/catalog/catalog.json +552211 -0
- package/catalog/catalog.schema.json +787 -0
- package/catalog/changelog.json +3 -0
- package/dist/bin/monkeypatchlist.d.ts +2 -0
- package/dist/bin/monkeypatchlist.js +3 -0
- package/dist/catalog.d.ts +561 -0
- package/dist/catalog.js +58 -0
- package/dist/cli.d.ts +84 -0
- package/dist/cli.js +470 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/lockfile-input.d.ts +9 -0
- package/dist/lockfile-input.js +91 -0
- package/dist/packages/analysis/binding.d.ts +2 -0
- package/dist/packages/analysis/binding.js +5 -0
- package/dist/packages/analysis/index.d.ts +10 -0
- package/dist/packages/analysis/index.js +13 -0
- package/dist/packages/analysis/job.d.ts +33 -0
- package/dist/packages/analysis/job.js +85 -0
- package/dist/packages/analysis/lib/absence-tags.d.ts +34 -0
- package/dist/packages/analysis/lib/absence-tags.js +340 -0
- package/dist/packages/analysis/lib/ast.d.ts +28 -0
- package/dist/packages/analysis/lib/ast.js +64 -0
- package/dist/packages/analysis/lib/binding.d.ts +36 -0
- package/dist/packages/analysis/lib/binding.js +66 -0
- package/dist/packages/analysis/lib/budget.d.ts +80 -0
- package/dist/packages/analysis/lib/budget.js +31 -0
- package/dist/packages/analysis/lib/catch-arms.d.ts +19 -0
- package/dist/packages/analysis/lib/catch-arms.js +351 -0
- package/dist/packages/analysis/lib/chunk-loads.d.ts +160 -0
- package/dist/packages/analysis/lib/chunk-loads.js +882 -0
- package/dist/packages/analysis/lib/chunk-registry.d.ts +45 -0
- package/dist/packages/analysis/lib/chunk-registry.js +334 -0
- package/dist/packages/analysis/lib/code-routes.d.ts +52 -0
- package/dist/packages/analysis/lib/code-routes.js +921 -0
- package/dist/packages/analysis/lib/collisions.d.ts +22 -0
- package/dist/packages/analysis/lib/collisions.js +103 -0
- package/dist/packages/analysis/lib/definitions.d.ts +76 -0
- package/dist/packages/analysis/lib/definitions.js +782 -0
- package/dist/packages/analysis/lib/early-exits.d.ts +39 -0
- package/dist/packages/analysis/lib/early-exits.js +161 -0
- package/dist/packages/analysis/lib/effect.d.ts +138 -0
- package/dist/packages/analysis/lib/effect.js +800 -0
- package/dist/packages/analysis/lib/global-object.d.ts +581 -0
- package/dist/packages/analysis/lib/global-object.js +8240 -0
- package/dist/packages/analysis/lib/keys.d.ts +33 -0
- package/dist/packages/analysis/lib/keys.js +82 -0
- package/dist/packages/analysis/lib/line-starts.d.ts +41 -0
- package/dist/packages/analysis/lib/line-starts.js +79 -0
- package/dist/packages/analysis/lib/link-group.d.ts +51 -0
- package/dist/packages/analysis/lib/link-group.js +487 -0
- package/dist/packages/analysis/lib/module-links.d.ts +117 -0
- package/dist/packages/analysis/lib/module-links.js +1060 -0
- package/dist/packages/analysis/lib/nested-code.d.ts +38 -0
- package/dist/packages/analysis/lib/nested-code.js +200 -0
- package/dist/packages/analysis/lib/numeric.d.ts +16 -0
- package/dist/packages/analysis/lib/numeric.js +216 -0
- package/dist/packages/analysis/lib/once-flags.d.ts +12 -0
- package/dist/packages/analysis/lib/once-flags.js +190 -0
- package/dist/packages/analysis/lib/parameter-arguments.d.ts +12 -0
- package/dist/packages/analysis/lib/parameter-arguments.js +34 -0
- package/dist/packages/analysis/lib/parse.d.ts +10 -0
- package/dist/packages/analysis/lib/parse.js +39 -0
- package/dist/packages/analysis/lib/partial.d.ts +60 -0
- package/dist/packages/analysis/lib/partial.js +518 -0
- package/dist/packages/analysis/lib/print.d.ts +3 -0
- package/dist/packages/analysis/lib/print.js +150 -0
- package/dist/packages/analysis/lib/reachability.d.ts +137 -0
- package/dist/packages/analysis/lib/reachability.js +1295 -0
- package/dist/packages/analysis/lib/resolver.d.ts +13 -0
- package/dist/packages/analysis/lib/resolver.js +2886 -0
- package/dist/packages/analysis/lib/restores.d.ts +42 -0
- package/dist/packages/analysis/lib/restores.js +482 -0
- package/dist/packages/analysis/lib/rewritten-deletes.d.ts +6 -0
- package/dist/packages/analysis/lib/rewritten-deletes.js +142 -0
- package/dist/packages/analysis/lib/scope.d.ts +110 -0
- package/dist/packages/analysis/lib/scope.js +443 -0
- package/dist/packages/analysis/lib/script-loads.d.ts +56 -0
- package/dist/packages/analysis/lib/script-loads.js +359 -0
- package/dist/packages/analysis/lib/summaries.d.ts +177 -0
- package/dist/packages/analysis/lib/summaries.js +498 -0
- package/dist/packages/analysis/lib/types.d.ts +632 -0
- package/dist/packages/analysis/lib/types.js +128 -0
- package/dist/packages/analysis/lib/wrap.d.ts +34 -0
- package/dist/packages/analysis/lib/wrap.js +407 -0
- package/dist/packages/analysis/lib/writes.d.ts +358 -0
- package/dist/packages/analysis/lib/writes.js +1612 -0
- package/dist/packages/analysis/visit-order.d.ts +3 -0
- package/dist/packages/analysis/visit-order.js +23 -0
- package/dist/packages/attribution/index.d.ts +233 -0
- package/dist/packages/attribution/index.js +444 -0
- package/dist/packages/attribution/lib/containing-package.d.ts +76 -0
- package/dist/packages/attribution/lib/containing-package.js +304 -0
- package/dist/packages/attribution/lib/first-party.d.ts +37 -0
- package/dist/packages/attribution/lib/first-party.js +153 -0
- package/dist/packages/attribution/lib/lockfile-graph.d.ts +99 -0
- package/dist/packages/attribution/lib/lockfile-graph.js +158 -0
- package/dist/packages/attribution/lib/lockfile.d.ts +33 -0
- package/dist/packages/attribution/lib/lockfile.js +330 -0
- package/dist/packages/attribution/lib/path-rules.d.ts +29 -0
- package/dist/packages/attribution/lib/path-rules.js +84 -0
- package/dist/packages/attribution/lib/pnpm.d.ts +4 -0
- package/dist/packages/attribution/lib/pnpm.js +133 -0
- package/dist/packages/attribution/lib/remote-url.d.ts +7 -0
- package/dist/packages/attribution/lib/remote-url.js +24 -0
- package/dist/packages/attribution/lib/source-map.d.ts +51 -0
- package/dist/packages/attribution/lib/source-map.js +125 -0
- package/dist/packages/attribution/lib/yaml.d.ts +9 -0
- package/dist/packages/attribution/lib/yaml.js +277 -0
- package/dist/packages/attribution/lib/yarn.d.ts +7 -0
- package/dist/packages/attribution/lib/yarn.js +227 -0
- package/dist/packages/baseline/index.d.ts +45 -0
- package/dist/packages/baseline/index.js +41 -0
- package/dist/packages/baseline/lib/error.d.ts +15 -0
- package/dist/packages/baseline/lib/error.js +25 -0
- package/dist/packages/baseline/lib/format.d.ts +49 -0
- package/dist/packages/baseline/lib/format.js +22 -0
- package/dist/packages/baseline/lib/migrations.d.ts +19 -0
- package/dist/packages/baseline/lib/migrations.js +7 -0
- package/dist/packages/baseline/lib/previous.d.ts +23 -0
- package/dist/packages/baseline/lib/previous.js +64 -0
- package/dist/packages/baseline/lib/read.d.ts +39 -0
- package/dist/packages/baseline/lib/read.js +219 -0
- package/dist/packages/baseline/lib/report.d.ts +25 -0
- package/dist/packages/baseline/lib/report.js +104 -0
- package/dist/packages/baseline/lib/skew.d.ts +51 -0
- package/dist/packages/baseline/lib/skew.js +75 -0
- package/dist/packages/compare/index.d.ts +120 -0
- package/dist/packages/compare/index.js +274 -0
- package/dist/packages/compare/lib/catalog-notes.d.ts +73 -0
- package/dist/packages/compare/lib/catalog-notes.js +224 -0
- package/dist/packages/completeness/index.d.ts +246 -0
- package/dist/packages/completeness/index.js +249 -0
- package/dist/packages/confidence/index.d.ts +54 -0
- package/dist/packages/confidence/index.js +71 -0
- package/dist/packages/config/index.d.ts +107 -0
- package/dist/packages/config/index.js +95 -0
- package/dist/packages/config/lib/command-line.d.ts +23 -0
- package/dist/packages/config/lib/command-line.js +239 -0
- package/dist/packages/config/lib/config-file.d.ts +11 -0
- package/dist/packages/config/lib/config-file.js +119 -0
- package/dist/packages/config/lib/scan-config-id.d.ts +28 -0
- package/dist/packages/config/lib/scan-config-id.js +54 -0
- package/dist/packages/config/lib/schema.d.ts +2 -0
- package/dist/packages/config/lib/schema.js +31 -0
- package/dist/packages/config/lib/types.d.ts +64 -0
- package/dist/packages/config/lib/types.js +6 -0
- package/dist/packages/config/lib/urls.d.ts +6 -0
- package/dist/packages/config/lib/urls.js +9 -0
- package/dist/packages/discovery/index.d.ts +277 -0
- package/dist/packages/discovery/index.js +1950 -0
- package/dist/packages/discovery/lib/import-map.d.ts +68 -0
- package/dist/packages/discovery/lib/import-map.js +180 -0
- package/dist/packages/discovery/lib/requests.d.ts +58 -0
- package/dist/packages/discovery/lib/requests.js +591 -0
- package/dist/packages/discovery/lib/scopes.d.ts +6 -0
- package/dist/packages/discovery/lib/scopes.js +286 -0
- package/dist/packages/discovery/lib/source-maps.d.ts +56 -0
- package/dist/packages/discovery/lib/source-maps.js +209 -0
- package/dist/packages/discovery/lib/swept-code.d.ts +27 -0
- package/dist/packages/discovery/lib/swept-code.js +107 -0
- package/dist/packages/discovery/lib/syntax.d.ts +17 -0
- package/dist/packages/discovery/lib/syntax.js +39 -0
- package/dist/packages/findings/index.d.ts +106 -0
- package/dist/packages/findings/index.js +254 -0
- package/dist/packages/findings/lib/scope.d.ts +38 -0
- package/dist/packages/findings/lib/scope.js +110 -0
- package/dist/packages/parallel/index.d.ts +2 -0
- package/dist/packages/parallel/index.js +5 -0
- package/dist/packages/parallel/lib/protocol.d.ts +15 -0
- package/dist/packages/parallel/lib/protocol.js +2 -0
- package/dist/packages/parallel/lib/runner.d.ts +36 -0
- package/dist/packages/parallel/lib/runner.js +96 -0
- package/dist/packages/parallel/lib/schedule.d.ts +16 -0
- package/dist/packages/parallel/lib/schedule.js +52 -0
- package/dist/packages/parallel/lib/worker.d.ts +1 -0
- package/dist/packages/parallel/lib/worker.js +18 -0
- package/dist/packages/policy/index.d.ts +36 -0
- package/dist/packages/policy/index.js +22 -0
- package/dist/packages/report/explain.d.ts +26 -0
- package/dist/packages/report/explain.js +99 -0
- package/dist/packages/report/index.d.ts +210 -0
- package/dist/packages/report/index.js +388 -0
- package/dist/packages/report/terminal.d.ts +11 -0
- package/dist/packages/report/terminal.js +331 -0
- package/dist/packages/source-store/fixture.d.ts +22 -0
- package/dist/packages/source-store/fixture.js +58 -0
- package/dist/packages/source-store/index.d.ts +278 -0
- package/dist/packages/source-store/index.js +322 -0
- package/dist/packages/source-store/lib/addresses.d.ts +6 -0
- package/dist/packages/source-store/lib/addresses.js +91 -0
- package/dist/packages/source-store/lib/connection.d.ts +15 -0
- package/dist/packages/source-store/lib/connection.js +135 -0
- package/dist/packages/source-store/lib/content-types.d.ts +16 -0
- package/dist/packages/source-store/lib/content-types.js +75 -0
- package/dist/packages/source-store/lib/data-urls.d.ts +10 -0
- package/dist/packages/source-store/lib/data-urls.js +85 -0
- package/dist/packages/source-store/lib/deadline.d.ts +18 -0
- package/dist/packages/source-store/lib/deadline.js +44 -0
- package/dist/packages/source-store/lib/exchange.d.ts +29 -0
- package/dist/packages/source-store/lib/exchange.js +112 -0
- package/dist/packages/source-store/lib/integrity.d.ts +19 -0
- package/dist/packages/source-store/lib/integrity.js +86 -0
- package/dist/packages/source-store/lib/outcomes.d.ts +12 -0
- package/dist/packages/source-store/lib/outcomes.js +22 -0
- package/dist/packages/source-store/lib/proxy.d.ts +11 -0
- package/dist/packages/source-store/lib/proxy.js +53 -0
- package/dist/packages/source-store/lib/session.d.ts +12 -0
- package/dist/packages/source-store/lib/session.js +139 -0
- package/dist/packages/source-store/lib/urls.d.ts +6 -0
- package/dist/packages/source-store/lib/urls.js +15 -0
- package/dist/packages/source-store/resolve.d.ts +59 -0
- package/dist/packages/source-store/resolve.js +45 -0
- package/dist/scan.d.ts +65 -0
- package/dist/scan.js +592 -0
- package/package.json +95 -4
- package/schema/config.schema.json +133 -0
- package/schema/report.schema.json +1905 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import type { Mapping } from '../config/index.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Whether an artifact's bytes were read (`ok`), or why not: a local file can't be read (`unreadable`), a remote one
|
|
4
|
+
* isn't fetched offline (`offline-skipped`), the safety floor refused it (`remote-refused`), its fetch failed
|
|
5
|
+
* (`fetch-failed`), or its bytes don't match their integrity value (`sri-mismatch`, §17 Outcomes).
|
|
6
|
+
*/
|
|
7
|
+
export type RetrievalStatus = 'ok' | 'unreadable' | 'offline-skipped' | 'remote-refused' | 'fetch-failed' | 'sri-mismatch';
|
|
8
|
+
/**
|
|
9
|
+
* The Subresource Integrity check of fetched bytes (§17 Integrity): `none` when no integrity value was checked, or
|
|
10
|
+
* the check.
|
|
11
|
+
*/
|
|
12
|
+
export type Sri = 'none' | SriCheck;
|
|
13
|
+
export { alternativeIntegrity } from './lib/integrity.ts';
|
|
14
|
+
/**
|
|
15
|
+
* An integrity check: whether the bytes `match` their integrity value, and both digests. `expected` holds the value's
|
|
16
|
+
* digests in the strongest algorithm it names, which is the one used, in its order, as `<algorithm>-<base64>`. `actual`
|
|
17
|
+
* is the bytes' digest in that algorithm. For a value with alternatives that matches one, the check is that
|
|
18
|
+
* alternative's; for one that matches none, `expected` holds every alternative's digests and `actual` is the bytes'
|
|
19
|
+
* digest in the strongest algorithm any of them names.
|
|
20
|
+
*/
|
|
21
|
+
export interface SriCheck {
|
|
22
|
+
result: 'match' | 'mismatch';
|
|
23
|
+
expected: string[];
|
|
24
|
+
actual: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* What an artifact records of how its bytes were retrieved (§17 What is recorded). It holds no wall-clock data: that
|
|
28
|
+
* goes in the report's `run` block (`RunRetrieval`).
|
|
29
|
+
*
|
|
30
|
+
* - `requestedUrl`: the URL the reference requested, which the artifact's URL, origin and attribution key come from,
|
|
31
|
+
* except for code a redirected script references: that is requested against the script's final URL, but its
|
|
32
|
+
* artifact's URL, origin and key come from the URL resolved against the script's own requested URL (§11, §17).
|
|
33
|
+
* Null for an inline script, which is part of its entry document and never requested.
|
|
34
|
+
* - `finalUrl`: the URL the retrieval ended at, after any redirects. A build file's or a `data:` URL's is the requested
|
|
35
|
+
* URL. Null when nothing answered: offline, a fetch that got no response, and an inline script.
|
|
36
|
+
* - `contentType`: the `Content-Type` of the response, as served, or a `data:` URL's MIME type. Null for a build file,
|
|
37
|
+
* an inline script, and a fetch with no response or no such header.
|
|
38
|
+
* - `sri`: the Subresource Integrity check of the bytes (§17 Integrity). `none` when no integrity value was checked:
|
|
39
|
+
* the reference carries none, the bytes are a build file's or an inline script's, which are never fetched, or the
|
|
40
|
+
* check never ran: offline, a failed fetch, or a refusal before it. A refusal made after it passed, for a missing
|
|
41
|
+
* JavaScript MIME type, keeps the check (§17 Content types).
|
|
42
|
+
*/
|
|
43
|
+
export interface Retrieval {
|
|
44
|
+
status: RetrievalStatus;
|
|
45
|
+
requestedUrl: string | null;
|
|
46
|
+
finalUrl: string | null;
|
|
47
|
+
contentType: string | null;
|
|
48
|
+
sri: Sri;
|
|
49
|
+
}
|
|
50
|
+
/** The wall-clock facts of one fetch, which only the report's `run` block holds (§15, §17 What is recorded). */
|
|
51
|
+
export interface RunRetrieval {
|
|
52
|
+
/** When the fetch finished, as an ISO 8601 UTC timestamp. */
|
|
53
|
+
fetchedAt: string;
|
|
54
|
+
/** The final response's `ETag`, or null without one. */
|
|
55
|
+
etag: string | null;
|
|
56
|
+
/** The final response's `Last-Modified`, or null without one. */
|
|
57
|
+
lastModified: string | null;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The response a fetch ended with, after any redirects: its URL, the headers that are recorded, and the one that names
|
|
61
|
+
* its source map: `SourceMap`, or else the deprecated `X-SourceMap` (§2 Source maps), or null without either.
|
|
62
|
+
*/
|
|
63
|
+
export interface FetchedResponse {
|
|
64
|
+
finalUrl: string;
|
|
65
|
+
contentType: string | null;
|
|
66
|
+
etag: string | null;
|
|
67
|
+
lastModified: string | null;
|
|
68
|
+
sourceMap: string | null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* What a fetcher returns for a remote URL (§17 Outcomes): the bytes of a 2xx response, with their integrity check,
|
|
72
|
+
* `offline-skipped`, or why there are none, with the response when there was one: `remote-refused` when the safety
|
|
73
|
+
* floor refuses the fetch, `fetch-failed` when it fails, and `sri-mismatch` when the bytes don't match the request's
|
|
74
|
+
* integrity value, with the check that failed. A refusal made after the bytes passed their integrity check, for a
|
|
75
|
+
* content type that needs checking only then (§17 Content types), carries that check; `none` or left out otherwise. A
|
|
76
|
+
* fetcher reports these as data; one that rejects is a bug (exit 4).
|
|
77
|
+
*/
|
|
78
|
+
export type FetchResult = {
|
|
79
|
+
status: 'offline-skipped';
|
|
80
|
+
} | {
|
|
81
|
+
status: 'ok';
|
|
82
|
+
response: FetchedResponse;
|
|
83
|
+
bytes: Uint8Array;
|
|
84
|
+
sri: Sri;
|
|
85
|
+
} | {
|
|
86
|
+
status: 'remote-refused';
|
|
87
|
+
detail: string;
|
|
88
|
+
response: FetchedResponse | null;
|
|
89
|
+
sri?: Sri;
|
|
90
|
+
} | {
|
|
91
|
+
status: 'fetch-failed';
|
|
92
|
+
detail: string;
|
|
93
|
+
response: FetchedResponse | null;
|
|
94
|
+
} | {
|
|
95
|
+
status: 'sri-mismatch';
|
|
96
|
+
detail: string;
|
|
97
|
+
response: FetchedResponse;
|
|
98
|
+
sri: SriCheck;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* How a reference asks for a remote URL, which the safety floor judges it by (§17):
|
|
102
|
+
*
|
|
103
|
+
* - `destination`: what the bytes are fetched as, which decides the MIME types a response may have (§17 Content
|
|
104
|
+
* types). `script` is a classic script of the window, from a `script` element or a `<link rel=preload as=script>`.
|
|
105
|
+
* `module` is a module script, however it is loaded: a module `script` element, a `modulepreload`, an import or a
|
|
106
|
+
* dynamic import. `worker` is a classic script of a worker: a worker's script, or one `importScripts` loads. `map` is
|
|
107
|
+
* a source map.
|
|
108
|
+
* - `document`: the URL of the entry document the reference was made for. `http:` may be fetched only for an `http:`
|
|
109
|
+
* document (§17 Schemes).
|
|
110
|
+
* - `integrity`: the integrity metadata the reference carries (§17 Integrity), which the bytes are checked against:
|
|
111
|
+
* an element's `integrity` attribute, or its URL's entry in an import map's `integrity` section. None when left out
|
|
112
|
+
* or empty.
|
|
113
|
+
* - `imported`: whether `importScripts` loads the script, rather than it being a worker's own. A browser runs a script
|
|
114
|
+
* `importScripts` loads only with a JavaScript MIME type, whatever its URL's scheme, where a worker's own classic
|
|
115
|
+
* script from a URL that isn't HTTP(S), such as a `data:` URL, needs none (HTML § Fetch a classic worker-imported
|
|
116
|
+
* script, § Fetch a classic worker script). An HTTP(S) response is checked the same either way.
|
|
117
|
+
*/
|
|
118
|
+
export interface FetchRequest {
|
|
119
|
+
destination: 'script' | 'module' | 'worker' | 'map';
|
|
120
|
+
document: string;
|
|
121
|
+
integrity?: string;
|
|
122
|
+
imported?: boolean;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Reads remote URLs (§19 Source store and retrieval): the offline fetcher here, the fixture fetcher (`fixture.ts`) for
|
|
126
|
+
* tests, and the resolve fetcher (`resolve.ts`), which enforces §17's safety floor.
|
|
127
|
+
*/
|
|
128
|
+
export interface Fetcher {
|
|
129
|
+
fetch(url: string, request: FetchRequest): Promise<FetchResult>;
|
|
130
|
+
}
|
|
131
|
+
/** The offline fetcher (§17): every call returns `offline-skipped`. */
|
|
132
|
+
export declare const offlineFetcher: Fetcher;
|
|
133
|
+
/**
|
|
134
|
+
* The bytes of a URL or build file, or why there are none, and its retrieval record. `path` is build-relative and
|
|
135
|
+
* POSIX, and null for a remote URL. `sourceMap` is the header of a fetched response that names its source map
|
|
136
|
+
* (`FetchedResponse`), and null for a build file, which has no headers. A build file that can't be read says why in
|
|
137
|
+
* `detail`: the error's code, or that its URL names no path in the build directory. Whether a file is there to read
|
|
138
|
+
* is the listing's to say (`list()`), not the error's (§2 Source maps). A `data:` URL is `refused` when a browser
|
|
139
|
+
* would not run the script it holds (§2 Other script URLs): it has no retrieval record, because nothing of it is an
|
|
140
|
+
* artifact.
|
|
141
|
+
*/
|
|
142
|
+
export type ReadResult = {
|
|
143
|
+
status: 'ok';
|
|
144
|
+
url: string;
|
|
145
|
+
path: string | null;
|
|
146
|
+
bytes: Uint8Array;
|
|
147
|
+
retrieval: Retrieval;
|
|
148
|
+
sourceMap: string | null;
|
|
149
|
+
} | {
|
|
150
|
+
status: 'unreadable';
|
|
151
|
+
url: string;
|
|
152
|
+
path: string;
|
|
153
|
+
detail: string;
|
|
154
|
+
retrieval: Retrieval;
|
|
155
|
+
} | {
|
|
156
|
+
status: 'offline-skipped';
|
|
157
|
+
url: string;
|
|
158
|
+
retrieval: Retrieval;
|
|
159
|
+
} | {
|
|
160
|
+
status: 'remote-refused' | 'fetch-failed' | 'sri-mismatch';
|
|
161
|
+
url: string;
|
|
162
|
+
detail: string;
|
|
163
|
+
retrieval: Retrieval;
|
|
164
|
+
} | {
|
|
165
|
+
status: 'refused';
|
|
166
|
+
url: string;
|
|
167
|
+
detail: string;
|
|
168
|
+
};
|
|
169
|
+
/**
|
|
170
|
+
* The regular files of the build directory (§2 Sweep), sorted by path: each with the URL a mapping serves it at, or
|
|
171
|
+
* null when none does. `directories` are the directories the sweep meets, sorted: the build directory (`.`) and
|
|
172
|
+
* every directory under it, a link to one included, even one it doesn't list, as a loop or because it can't. A
|
|
173
|
+
* directory that can't be listed, or a symbolic link whose target can't be reached (`link`), is named in `unreadable`,
|
|
174
|
+
* with why, and whether a mapping serves its path or a path under it (`served`); the files under it are missing.
|
|
175
|
+
*/
|
|
176
|
+
export interface Listing {
|
|
177
|
+
files: {
|
|
178
|
+
path: string;
|
|
179
|
+
url: string | null;
|
|
180
|
+
}[];
|
|
181
|
+
directories: string[];
|
|
182
|
+
unreadable: {
|
|
183
|
+
path: string;
|
|
184
|
+
detail: string;
|
|
185
|
+
link: boolean;
|
|
186
|
+
served: boolean;
|
|
187
|
+
}[];
|
|
188
|
+
}
|
|
189
|
+
export interface SourceStore {
|
|
190
|
+
/**
|
|
191
|
+
* Reads a URL: a local file when a mapping covers it (the longest prefix wins), otherwise through the fetcher, as
|
|
192
|
+
* `request` asks for it. A `data:` URL holds its bytes, and is never fetched (§2 Other script URLs).
|
|
193
|
+
*/
|
|
194
|
+
read(url: string, request: FetchRequest): Promise<ReadResult>;
|
|
195
|
+
/** Reads a build-relative file, such as an entry document, that has the URL `url`. */
|
|
196
|
+
readFile(path: string, url: string): Promise<ReadResult>;
|
|
197
|
+
/** Whether a URL mapping covers a URL, so that `read` reads it from the build directory rather than the fetcher. */
|
|
198
|
+
isMapped(url: string): boolean;
|
|
199
|
+
/** Whether a URL mapping's URL lies under a URL, as `https://app.example/static/` lies under `https://app.example/`. */
|
|
200
|
+
mapsUnder(url: string): boolean;
|
|
201
|
+
/**
|
|
202
|
+
* The build-relative path a URL mapping reads `url` from (the longest prefix wins), or null when no mapping covers it
|
|
203
|
+
* or its path is malformed or lies outside the build directory.
|
|
204
|
+
*/
|
|
205
|
+
buildPath(url: string): string | null;
|
|
206
|
+
/**
|
|
207
|
+
* The wall-clock facts of every fetch so far, by requested URL: the report's `run.retrievals`. A URL the fetcher
|
|
208
|
+
* skipped offline was never fetched, so it has none, and nor has one the safety floor refused before any response.
|
|
209
|
+
* A URL read twice keeps the facts of its first fetch.
|
|
210
|
+
*/
|
|
211
|
+
retrievals(): Record<string, RunRetrieval>;
|
|
212
|
+
/**
|
|
213
|
+
* Lists every regular file in the build directory, with the URL a mapping serves it at. When several mappings serve
|
|
214
|
+
* it, the one with the most specific directory wins, then the URL first in code-unit order. Symbolic links to files
|
|
215
|
+
* and directories are followed, wherever they lead, and a file reached through one has the path of the link. A
|
|
216
|
+
* directory whose real path is that of a directory it lies in is a loop, and is not listed again. A dangling link or
|
|
217
|
+
* a link cycle leads to nothing, and gives nothing; a link whose target can't be reached is `unreadable` (§2 Sweep).
|
|
218
|
+
* The directories it walks are listed too, since a directory at a source map's path is there (§2 Source maps).
|
|
219
|
+
*/
|
|
220
|
+
list(): Promise<Listing>;
|
|
221
|
+
/**
|
|
222
|
+
* Lists the source-map directory declared as `dir` (§2 Source maps), as `list()` lists the build directory: it
|
|
223
|
+
* follows symbolic links to files and directories, with loop protection by real path, and a dangling link, a link
|
|
224
|
+
* cycle, a directory it can't list and a link whose target it can't reach give nothing, so the files behind them are
|
|
225
|
+
* missing. It is `unreadable` only when the directory itself can't be listed: it is missing, is no directory, or its
|
|
226
|
+
* entries can't be read.
|
|
227
|
+
*/
|
|
228
|
+
listSourceMaps(dir: string): Promise<SourceMapListing>;
|
|
229
|
+
/**
|
|
230
|
+
* What the build-relative path `path` leads to, following symbolic links: its device and inode, which two spellings of
|
|
231
|
+
* one file share (§2 Source maps), or null when nothing can be found there or it lies outside the build directory.
|
|
232
|
+
*/
|
|
233
|
+
buildIdentity(path: string): Promise<string | null>;
|
|
234
|
+
/** Reads the file at `path`, relative to the source-map directory declared as `dir`. */
|
|
235
|
+
readSourceMap(dir: string, path: string): Promise<SourceMapRead>;
|
|
236
|
+
/**
|
|
237
|
+
* What the path `path`, relative to the source-map directory declared as `dir`, leads to, following symbolic links:
|
|
238
|
+
* its device and inode, which two spellings of one file share (§2 Source maps), or null when nothing can be found
|
|
239
|
+
* there.
|
|
240
|
+
*/
|
|
241
|
+
sourceMapIdentity(dir: string, path: string): Promise<string | null>;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* A source-map directory's regular files and its directories, by POSIX path relative to it, each sorted, or why it
|
|
245
|
+
* can't be listed. The directories are the directory itself (`.`) and every one under it the listing meets, a link to
|
|
246
|
+
* one included, even one it doesn't list as a loop, but not one it can't list.
|
|
247
|
+
*/
|
|
248
|
+
export type SourceMapListing = {
|
|
249
|
+
status: 'ok';
|
|
250
|
+
files: string[];
|
|
251
|
+
directories: string[];
|
|
252
|
+
} | {
|
|
253
|
+
status: 'unreadable';
|
|
254
|
+
detail: string;
|
|
255
|
+
};
|
|
256
|
+
/** The bytes of a file in a source-map directory, or why they can't be read. */
|
|
257
|
+
export type SourceMapRead = {
|
|
258
|
+
status: 'ok';
|
|
259
|
+
bytes: Uint8Array;
|
|
260
|
+
} | {
|
|
261
|
+
status: 'unreadable';
|
|
262
|
+
detail: string;
|
|
263
|
+
};
|
|
264
|
+
export declare function createSourceStore(options: {
|
|
265
|
+
buildDir: string;
|
|
266
|
+
mappings: Mapping[];
|
|
267
|
+
fetcher: Fetcher;
|
|
268
|
+
/** The wall clock, read when each fetch finishes. */
|
|
269
|
+
now: () => Date;
|
|
270
|
+
/** The source-map directories (§2 Source maps): each one's absolute path, by its name as declared. */
|
|
271
|
+
sourceMapDirs?: ReadonlyMap<string, string>;
|
|
272
|
+
}): SourceStore;
|
|
273
|
+
/**
|
|
274
|
+
* A store that reads each thing once (§19 References only the resolver can see): a scan's rounds discover the artifact
|
|
275
|
+
* graph again, and each round must see the bytes, fetches and listings the first one saw, so that a URL is fetched once
|
|
276
|
+
* per scan and the rounds can't disagree.
|
|
277
|
+
*/
|
|
278
|
+
export declare function onceStore(store: SourceStore): SourceStore;
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
// Source store (§19, module 2): the only module that reads bytes. It reads local files through the URL→path mappings,
|
|
2
|
+
// and remote URLs through a `Fetcher`. It knows nothing of JavaScript semantics.
|
|
3
|
+
import { readdir, readFile, realpath, stat } from 'node:fs/promises';
|
|
4
|
+
import { isAbsolute, join, posix } from 'node:path';
|
|
5
|
+
import { readDataUrl } from "./lib/data-urls.js";
|
|
6
|
+
export { alternativeIntegrity } from "./lib/integrity.js";
|
|
7
|
+
/** The offline fetcher (§17): every call returns `offline-skipped`. */
|
|
8
|
+
export const offlineFetcher = {
|
|
9
|
+
fetch: async () => ({ status: 'offline-skipped' }),
|
|
10
|
+
};
|
|
11
|
+
export function createSourceStore(options) {
|
|
12
|
+
const { buildDir, mappings, fetcher, now, sourceMapDirs = new Map() } = options;
|
|
13
|
+
// The longest matching prefix wins (§16). Config rejects two mappings for one prefix.
|
|
14
|
+
const byLength = [...mappings].sort((a, b) => b.url.length - a.url.length);
|
|
15
|
+
const fetches = new Map();
|
|
16
|
+
async function readBuildFile(path, url) {
|
|
17
|
+
const normalized = posix.normalize(path);
|
|
18
|
+
if (isOutside(normalized))
|
|
19
|
+
return unreadable(url, normalized, 'outside the build directory');
|
|
20
|
+
try {
|
|
21
|
+
const bytes = await readFile(join(buildDir, normalized));
|
|
22
|
+
return { status: 'ok', url, path: normalized, bytes, retrieval: buildFileRetrieval(url, 'ok'), sourceMap: null };
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
return unreadable(url, normalized, errorCode(error));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function fetchRemote(url, request) {
|
|
29
|
+
const result = await fetcher.fetch(url, request);
|
|
30
|
+
if (result.status === 'offline-skipped') {
|
|
31
|
+
return { status: 'offline-skipped', url, retrieval: remoteRetrieval(url, result.status, null, 'none') };
|
|
32
|
+
}
|
|
33
|
+
const { response } = result;
|
|
34
|
+
if (!fetches.has(url) && (result.status !== 'remote-refused' || response !== null)) {
|
|
35
|
+
fetches.set(url, {
|
|
36
|
+
fetchedAt: now().toISOString(),
|
|
37
|
+
etag: response?.etag ?? null,
|
|
38
|
+
lastModified: response?.lastModified ?? null,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
const sri = result.status === 'fetch-failed' ? 'none' : (result.sri ?? 'none');
|
|
42
|
+
const retrieval = remoteRetrieval(url, result.status, response, sri);
|
|
43
|
+
if (result.status === 'ok') {
|
|
44
|
+
return { status: 'ok', url, path: null, bytes: result.bytes, retrieval, sourceMap: result.response.sourceMap };
|
|
45
|
+
}
|
|
46
|
+
return { status: result.status, url, detail: result.detail, retrieval };
|
|
47
|
+
}
|
|
48
|
+
/** The URL without its query and fragment, and the mapping that covers it, if any. */
|
|
49
|
+
function mapped(url) {
|
|
50
|
+
const bare = new URL(url);
|
|
51
|
+
bare.search = '';
|
|
52
|
+
bare.hash = '';
|
|
53
|
+
return { bare, mapping: byLength.find((candidate) => bare.href.startsWith(candidate.url)) };
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
readFile: readBuildFile,
|
|
57
|
+
isMapped: (url) => mapped(url).mapping !== undefined,
|
|
58
|
+
mapsUnder: (url) => mappings.some((mapping) => mapping.url.startsWith(url)),
|
|
59
|
+
async read(url, request) {
|
|
60
|
+
if (url.startsWith('data:'))
|
|
61
|
+
return readDataUrl(url, request);
|
|
62
|
+
const { mapping } = mapped(url);
|
|
63
|
+
if (mapping === undefined)
|
|
64
|
+
return fetchRemote(url, request);
|
|
65
|
+
// The path `buildPath` gives, so that the path read is the path discovery judges.
|
|
66
|
+
const path = pathOf(url);
|
|
67
|
+
if (path === undefined)
|
|
68
|
+
return unreadable(url, mapping.dir, 'malformed percent-encoding in the URL path');
|
|
69
|
+
return readBuildFile(path, url);
|
|
70
|
+
},
|
|
71
|
+
retrievals: () => Object.fromEntries(fetches),
|
|
72
|
+
buildPath(url) {
|
|
73
|
+
const path = pathOf(url);
|
|
74
|
+
return path === undefined || isOutside(path) ? null : path;
|
|
75
|
+
},
|
|
76
|
+
async list() {
|
|
77
|
+
const listing = { files: [], directories: ['.'], unreadable: [] };
|
|
78
|
+
await walkTree(buildDir, {
|
|
79
|
+
file: (path) => listing.files.push({ path, url: urlOf(path) }),
|
|
80
|
+
directory: (path) => listing.directories.push(path),
|
|
81
|
+
// A directory the sweep can't list, or a link whose target it can't reach. A link may lead to a file, so a
|
|
82
|
+
// mapping that serves its own path serves it too.
|
|
83
|
+
unreachable: (path, error, link) => listing.unreadable.push({
|
|
84
|
+
path,
|
|
85
|
+
detail: errorCode(error),
|
|
86
|
+
link,
|
|
87
|
+
served: (link && urlOf(path) !== null) || servesUnder(path),
|
|
88
|
+
}),
|
|
89
|
+
});
|
|
90
|
+
listing.files.sort(byPath);
|
|
91
|
+
listing.directories.sort();
|
|
92
|
+
listing.unreadable.sort(byPath);
|
|
93
|
+
return listing;
|
|
94
|
+
},
|
|
95
|
+
async listSourceMaps(dir) {
|
|
96
|
+
const files = [];
|
|
97
|
+
const directories = new Set(['.']);
|
|
98
|
+
let failed = undefined;
|
|
99
|
+
await walkTree(sourceMapDirectory(dir), {
|
|
100
|
+
file: (path) => files.push(path),
|
|
101
|
+
directory: (path) => directories.add(path),
|
|
102
|
+
// Only the directory itself failing to list makes it unreadable. What can't be reached under it gives nothing,
|
|
103
|
+
// so the maps there don't pair, and a directory it can't list isn't held, as in the build directory (§2 Source
|
|
104
|
+
// maps).
|
|
105
|
+
unreachable: (path, error, link) => {
|
|
106
|
+
if (path === '.')
|
|
107
|
+
failed = error;
|
|
108
|
+
else if (!link)
|
|
109
|
+
directories.delete(path);
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
if (failed !== undefined)
|
|
113
|
+
return { status: 'unreadable', detail: errorCode(failed) };
|
|
114
|
+
const order = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
115
|
+
return { status: 'ok', files: files.sort(order), directories: [...directories].sort(order) };
|
|
116
|
+
},
|
|
117
|
+
async readSourceMap(dir, path) {
|
|
118
|
+
const normalized = posix.normalize(path);
|
|
119
|
+
if (isOutside(normalized))
|
|
120
|
+
return { status: 'unreadable', detail: 'outside the source-map directory' };
|
|
121
|
+
try {
|
|
122
|
+
return { status: 'ok', bytes: await readFile(join(sourceMapDirectory(dir), normalized)) };
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
return { status: 'unreadable', detail: errorCode(error) };
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
sourceMapIdentity: (dir, path) => identityUnder(sourceMapDirectory(dir), path),
|
|
129
|
+
buildIdentity: (path) => identityUnder(buildDir, path),
|
|
130
|
+
};
|
|
131
|
+
/** The absolute path of the source-map directory declared as `dir`. */
|
|
132
|
+
function sourceMapDirectory(dir) {
|
|
133
|
+
const path = sourceMapDirs.get(dir);
|
|
134
|
+
if (path === undefined)
|
|
135
|
+
throw new Error(`no source-map directory was declared as '${dir}'`);
|
|
136
|
+
return path;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Whether a mapping serves a path under the build-relative directory `dir`: its own directory is under `dir`, or
|
|
140
|
+
* `dir` is under it, and the URLs it gives the paths under the deeper of the two map back to them, which a longer
|
|
141
|
+
* prefix may keep it from doing (§16). A longer prefix that claims only some names under the directory leaves the
|
|
142
|
+
* others served.
|
|
143
|
+
*/
|
|
144
|
+
function servesUnder(dir) {
|
|
145
|
+
return mappings.some((mapping) => {
|
|
146
|
+
const own = directoryOf(mapping);
|
|
147
|
+
const base = isWithin(dir, own) ? dir : isWithin(own, dir) ? own : undefined;
|
|
148
|
+
if (base === undefined)
|
|
149
|
+
return false;
|
|
150
|
+
const rest = base === own ? '' : own === '.' ? `${base}/` : `${base.slice(own.length + 1)}/`;
|
|
151
|
+
const url = urlUnder(mapping, rest);
|
|
152
|
+
const read = url === null ? undefined : pathOf(url.href);
|
|
153
|
+
return read !== undefined && (posix.normalize(read).replace(/\/+$/, '') || '.') === base;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* The URL a build file is served at, or null when no mapping serves it. A mapping serves a file under its directory
|
|
158
|
+
* when the URL it gives the file maps back to that file, which a longer prefix may keep it from doing (§16). When
|
|
159
|
+
* several serve it, the one with the most specific directory wins, then the URL first in code-unit order, so that the
|
|
160
|
+
* order of the mappings never matters.
|
|
161
|
+
*/
|
|
162
|
+
function urlOf(path) {
|
|
163
|
+
const served = [];
|
|
164
|
+
for (const mapping of mappings) {
|
|
165
|
+
const dir = directoryOf(mapping);
|
|
166
|
+
const rest = dir === '.' ? path : path.startsWith(`${dir}/`) ? path.slice(dir.length + 1) : undefined;
|
|
167
|
+
if (rest === undefined)
|
|
168
|
+
continue;
|
|
169
|
+
const url = urlUnder(mapping, rest);
|
|
170
|
+
if (url !== null && pathOf(url.href) === path)
|
|
171
|
+
served.push({ url: url.href, depth: dir === '.' ? 0 : dir.length });
|
|
172
|
+
}
|
|
173
|
+
served.sort((a, b) => b.depth - a.depth || (a.url < b.url ? -1 : a.url > b.url ? 1 : 0));
|
|
174
|
+
return served[0]?.url ?? null;
|
|
175
|
+
}
|
|
176
|
+
/** The build-relative path a mapped URL reads, or undefined when no mapping covers it or its path is malformed. */
|
|
177
|
+
function pathOf(url) {
|
|
178
|
+
const { bare, mapping } = mapped(url);
|
|
179
|
+
if (mapping === undefined)
|
|
180
|
+
return undefined;
|
|
181
|
+
try {
|
|
182
|
+
return posix.normalize(posix.join(mapping.dir, decodeURIComponent(bare.href.slice(mapping.url.length))));
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const byPath = (a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
190
|
+
/** A mapping's build-relative directory, normalized, with no trailing slash: `.` for the build directory itself. */
|
|
191
|
+
const directoryOf = (mapping) => posix.normalize(mapping.dir).replace(/\/+$/, '');
|
|
192
|
+
/** Whether the build-relative path `path` is the directory `dir` or lies under it. */
|
|
193
|
+
const isWithin = (path, dir) => dir === '.' || path === dir || path.startsWith(`${dir}/`);
|
|
194
|
+
/**
|
|
195
|
+
* The URL `mapping` gives `rest`, a path under its directory, or null when that is no URL. What a URL would read as a
|
|
196
|
+
* delimiter or a percent-encoding is escaped; the URL parser encodes the rest.
|
|
197
|
+
*/
|
|
198
|
+
function urlUnder(mapping, rest) {
|
|
199
|
+
const escaped = rest.replace(/[%?#\\]/g, (character) => encodeURIComponent(character));
|
|
200
|
+
return URL.parse(`${mapping.url.endsWith('/') ? mapping.url : `${mapping.url}/`}${escaped}`);
|
|
201
|
+
}
|
|
202
|
+
const errorCode = (error) => error.code ?? String(error);
|
|
203
|
+
/**
|
|
204
|
+
* What a directory entry is: a regular file, a directory, or neither. A symbolic link is what it leads to (§2 Sweep),
|
|
205
|
+
* and neither when it leads nowhere: its target is missing, or the links form a cycle. It throws when what the link
|
|
206
|
+
* leads to can't be told, as when its target's directory can't be searched.
|
|
207
|
+
*/
|
|
208
|
+
async function kindOf(entry, path) {
|
|
209
|
+
if (!entry.isSymbolicLink())
|
|
210
|
+
return entry.isDirectory() ? 'directory' : entry.isFile() ? 'file' : undefined;
|
|
211
|
+
let target;
|
|
212
|
+
try {
|
|
213
|
+
target = await stat(path);
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
if (['ENOENT', 'ENOTDIR', 'ELOOP'].includes(error.code ?? ''))
|
|
217
|
+
return undefined;
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
return target.isDirectory() ? 'directory' : target.isFile() ? 'file' : undefined;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Walks the tree under the absolute directory `root`, as the sweep lists the build directory (§2 Sweep), naming each
|
|
224
|
+
* entry by its POSIX path relative to `root`. Symbolic links to files and directories are followed, wherever they
|
|
225
|
+
* lead, and an entry reached through one has the path of the link. A directory whose real path is that of a directory
|
|
226
|
+
* it lies in is a loop, and is not listed again. A dangling link or a link cycle leads to nothing, and gives nothing.
|
|
227
|
+
*/
|
|
228
|
+
async function walkTree(root, visitor) {
|
|
229
|
+
// Each directory to list, with the real paths of the directories it lies in, which a loop leads back to.
|
|
230
|
+
const directories = [{ path: '.', within: [] }];
|
|
231
|
+
for (let directory = directories.pop(); directory !== undefined; directory = directories.pop()) {
|
|
232
|
+
const { path: dir, within } = directory;
|
|
233
|
+
let real;
|
|
234
|
+
let entries;
|
|
235
|
+
try {
|
|
236
|
+
real = await realpath(join(root, dir));
|
|
237
|
+
if (within.includes(real))
|
|
238
|
+
continue;
|
|
239
|
+
entries = await readdir(join(root, dir), { withFileTypes: true });
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
visitor.unreachable(dir, error, false);
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
for (const entry of entries) {
|
|
246
|
+
const path = dir === '.' ? entry.name : `${dir}/${entry.name}`;
|
|
247
|
+
let kind;
|
|
248
|
+
try {
|
|
249
|
+
kind = await kindOf(entry, join(root, path));
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
visitor.unreachable(path, error, true);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (kind === 'directory') {
|
|
256
|
+
visitor.directory(path);
|
|
257
|
+
directories.push({ path, within: [...within, real] });
|
|
258
|
+
}
|
|
259
|
+
else if (kind === 'file')
|
|
260
|
+
visitor.file(path);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* What the path `path`, relative to the absolute directory `root`, leads to, following symbolic links: its device and
|
|
266
|
+
* inode, or null when nothing can be found there or it lies outside `root`.
|
|
267
|
+
*/
|
|
268
|
+
async function identityUnder(root, path) {
|
|
269
|
+
const normalized = posix.normalize(path);
|
|
270
|
+
if (isOutside(normalized))
|
|
271
|
+
return null;
|
|
272
|
+
try {
|
|
273
|
+
const { dev, ino } = await stat(join(root, normalized), { bigint: true });
|
|
274
|
+
return `${dev}:${ino}`;
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/** Whether a normalized build-relative path leads outside the build directory. */
|
|
281
|
+
const isOutside = (path) => isAbsolute(path) || path === '..' || path.startsWith('../');
|
|
282
|
+
/** A build file at `path`, for the URL `url`, that can't be read, and why. */
|
|
283
|
+
function unreadable(url, path, detail) {
|
|
284
|
+
return { status: 'unreadable', url, path, detail, retrieval: buildFileRetrieval(url, 'unreadable') };
|
|
285
|
+
}
|
|
286
|
+
/** The retrieval record of a build file: read through its URL mapping, with no redirects and no response headers. */
|
|
287
|
+
function buildFileRetrieval(url, status) {
|
|
288
|
+
return { status, requestedUrl: url, finalUrl: url, contentType: null, sri: 'none' };
|
|
289
|
+
}
|
|
290
|
+
/** The retrieval record of a remote URL, from what its fetch returned. */
|
|
291
|
+
function remoteRetrieval(url, status, response, sri) {
|
|
292
|
+
const finalUrl = response?.finalUrl ?? null;
|
|
293
|
+
return { status, requestedUrl: url, finalUrl, contentType: response?.contentType ?? null, sri };
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* A store that reads each thing once (§19 References only the resolver can see): a scan's rounds discover the artifact
|
|
297
|
+
* graph again, and each round must see the bytes, fetches and listings the first one saw, so that a URL is fetched once
|
|
298
|
+
* per scan and the rounds can't disagree.
|
|
299
|
+
*/
|
|
300
|
+
export function onceStore(store) {
|
|
301
|
+
const cache = new Map();
|
|
302
|
+
const once = (key, read) => {
|
|
303
|
+
const id = JSON.stringify(key);
|
|
304
|
+
let found = cache.get(id);
|
|
305
|
+
if (found === undefined)
|
|
306
|
+
cache.set(id, (found = read()));
|
|
307
|
+
return found;
|
|
308
|
+
};
|
|
309
|
+
return {
|
|
310
|
+
read: (url, request) => once(['read', url, request], () => store.read(url, request)),
|
|
311
|
+
readFile: (path, url) => once(['readFile', path, url], () => store.readFile(path, url)),
|
|
312
|
+
isMapped: (url) => store.isMapped(url),
|
|
313
|
+
mapsUnder: (url) => store.mapsUnder(url),
|
|
314
|
+
buildPath: (url) => store.buildPath(url),
|
|
315
|
+
retrievals: () => store.retrievals(),
|
|
316
|
+
list: () => once(['list'], () => store.list()),
|
|
317
|
+
listSourceMaps: (dir) => once(['listSourceMaps', dir], () => store.listSourceMaps(dir)),
|
|
318
|
+
buildIdentity: (path) => once(['buildIdentity', path], () => store.buildIdentity(path)),
|
|
319
|
+
readSourceMap: (dir, path) => once(['readSourceMap', dir, path], () => store.readSourceMap(dir, path)),
|
|
320
|
+
sourceMapIdentity: (dir, path) => once(['sourceMapIdentity', dir, path], () => store.sourceMapIdentity(dir, path)),
|
|
321
|
+
};
|
|
322
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why the floor refuses to connect to `address`, as the kind of address it is, or null when it doesn't. Refused:
|
|
3
|
+
* loopback, private, link-local, unique-local, carrier-grade NAT, multicast, cloud-metadata and unspecified addresses,
|
|
4
|
+
* and an IPv6 address that carries a refused IPv4 one (IPv4-mapped, IPv4-compatible, NAT64 or 6to4).
|
|
5
|
+
*/
|
|
6
|
+
export declare function refusedAddress(scoped: string): string | null;
|