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,107 @@
|
|
|
1
|
+
import { type BaselineCommand, type Entry, type FailOnState, type Mapping, type Mode, type SurfaceScope } from './lib/types.ts';
|
|
2
|
+
export { differingScanConfigFields } from './lib/scan-config-id.ts';
|
|
3
|
+
export { failOnStates, UsageError, type Entry, type FailOnState, type Mapping, type Mode, type SurfaceScope, } from './lib/types.ts';
|
|
4
|
+
/**
|
|
5
|
+
* The default baseline file (§16): the one `baseline init` writes and `baseline update` rewrites (`--out`), and the one
|
|
6
|
+
* a scan compares with under `--fail-on` (`--baseline`). It is relative to the working directory.
|
|
7
|
+
*/
|
|
8
|
+
export declare const defaultBaselineFile = "monkeypatchlist.baseline.json";
|
|
9
|
+
/**
|
|
10
|
+
* A validated scan configuration: `scan`'s, which names the baseline it compares with, or a baseline command's, which
|
|
11
|
+
* names the baseline file it writes. `baseline update` also compares with that file before it rewrites it.
|
|
12
|
+
*/
|
|
13
|
+
export type ScanConfig = CommonConfig & ({
|
|
14
|
+
command: 'scan';
|
|
15
|
+
/**
|
|
16
|
+
* `--baseline`: the baseline to compare with, relative to the working directory, or null for none (§11): a
|
|
17
|
+
* baseline file, or a full JSON scan report (ADR 0020). Under `--fail-on` it defaults to `monkeypatchlist.baseline.json`.
|
|
18
|
+
*/
|
|
19
|
+
baseline: string | null;
|
|
20
|
+
/**
|
|
21
|
+
* `--previous`: a previous JSON scan report, relative to the working directory, or null for none (§16). Its
|
|
22
|
+
* values feed the reachability and install-context notes, overriding a report baseline's; it needs a baseline.
|
|
23
|
+
*/
|
|
24
|
+
previous: string | null;
|
|
25
|
+
/** `--fail-on`: the baseline states that fail the gate, in the order of `failOnStates`; none runs no gate. */
|
|
26
|
+
failOn: FailOnState[];
|
|
27
|
+
} | {
|
|
28
|
+
command: BaselineCommand;
|
|
29
|
+
/** `--out`: the baseline file it writes, relative to the working directory. `baseline update` rewrites it. */
|
|
30
|
+
out: string;
|
|
31
|
+
/**
|
|
32
|
+
* `--allow-downgrade` (§4 Version skew, §16): `baseline init` replaces, and `baseline update` rewrites, a
|
|
33
|
+
* baseline written by a newer catalog or monkeypatchlist version.
|
|
34
|
+
*/
|
|
35
|
+
allowDowngrade: boolean;
|
|
36
|
+
});
|
|
37
|
+
interface CommonConfig {
|
|
38
|
+
/** The build directory, as given. */
|
|
39
|
+
buildDir: string;
|
|
40
|
+
/** The application name (§16): the report header and the terminal title. It is not identity. */
|
|
41
|
+
name: string;
|
|
42
|
+
entries: Entry[];
|
|
43
|
+
mappings: Mapping[];
|
|
44
|
+
/**
|
|
45
|
+
* `--source-maps`: the source-map directories (§2 Source maps), relative to the working directory, in the order they
|
|
46
|
+
* are searched: the flags' order, or the config array's when no flag is given. They are not in the scan-config id
|
|
47
|
+
* (§16).
|
|
48
|
+
*/
|
|
49
|
+
sourceMaps: string[];
|
|
50
|
+
/** The surface the scan reports, baselines and gates (§12). The findings module applies it. */
|
|
51
|
+
surface: SurfaceScope;
|
|
52
|
+
/** `--mode`: the remote retrieval mode (§17). Only `resolve` fetches remote references. */
|
|
53
|
+
mode: Mode;
|
|
54
|
+
/**
|
|
55
|
+
* `--lockfile` and `lockfile.importer` (§10 Introducing dependencies): the lockfile, relative to the working
|
|
56
|
+
* directory, and the workspace of the scanned application in it, or null when the first-party source paths pick it.
|
|
57
|
+
* Null when no lockfile is given. It isn't in the scan-config id (§16).
|
|
58
|
+
*/
|
|
59
|
+
lockfile: {
|
|
60
|
+
path: string;
|
|
61
|
+
importer: string | null;
|
|
62
|
+
} | null;
|
|
63
|
+
/** `--json`: a path to write the JSON report to, `-` for stdout instead of the terminal report, or null. */
|
|
64
|
+
json: string | null;
|
|
65
|
+
/** The scan-config id (§16): what the baseline records, to be compared only with scans of the same configuration. */
|
|
66
|
+
scanConfigId: string;
|
|
67
|
+
/**
|
|
68
|
+
* `--allow-incomplete` (§13, §16): an `incomplete` scan under `--fail-on` warns instead of exiting 3, and `baseline
|
|
69
|
+
* init` and `baseline update` write one. After a fired crash guard, `baseline init` still refuses, and `baseline
|
|
70
|
+
* update` leaves the baseline unchanged (§13). On `scan`, it also makes an `incomplete` report readable as the
|
|
71
|
+
* baseline, read as `baseline update --allow-incomplete` would write it (§11).
|
|
72
|
+
*/
|
|
73
|
+
allowIncomplete: boolean;
|
|
74
|
+
/** `--verbose` (§16): the terminal report cards existing findings too. Only `scan` takes the flag. */
|
|
75
|
+
verbose: boolean;
|
|
76
|
+
/** `--list-ineffective` (§16): the terminal report lists the ineffective writes. Only `scan` takes the flag. */
|
|
77
|
+
listIneffective: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* False under `--no-color` (§16). The terminal report is colored only when this is true, stdout is a TTY and
|
|
80
|
+
* `NO_COLOR` is unset or empty (§15 Formatting); the CLI judges the last two.
|
|
81
|
+
*/
|
|
82
|
+
color: boolean;
|
|
83
|
+
}
|
|
84
|
+
export declare const usage: string;
|
|
85
|
+
/**
|
|
86
|
+
* The scan configuration for `monkeypatchlist <argv…>` run in `cwd`, or a `UsageError`. It reads the config file `--config`
|
|
87
|
+
* names, or `monkeypatchlist.config.json` in `cwd` if there is one, and the flags override it field by field (§16). Its paths
|
|
88
|
+
* are relative to `cwd`, except entry files and mapping directories, which are relative to the build directory.
|
|
89
|
+
*/
|
|
90
|
+
export declare function loadConfig(argv: readonly string[], options: {
|
|
91
|
+
cwd: string;
|
|
92
|
+
}): ScanConfig;
|
|
93
|
+
/** What `monkeypatchlist explain` reads (§16 Commands): the finding asked for, and the saved JSON report it is found in. */
|
|
94
|
+
export interface ExplainConfig {
|
|
95
|
+
command: 'explain';
|
|
96
|
+
/** The fingerprint or target, as given. */
|
|
97
|
+
query: string;
|
|
98
|
+
/** The saved JSON report, relative to the working directory: `--report`, or the config file's `report`. */
|
|
99
|
+
report: string;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The configuration for `monkeypatchlist explain <argv…>` run in `cwd` (the arguments after `explain`), or a `UsageError`.
|
|
103
|
+
* Only `report` is read from the config file; explain reads no build directory, so the scan settings don't apply.
|
|
104
|
+
*/
|
|
105
|
+
export declare function loadExplainConfig(argv: readonly string[], options: {
|
|
106
|
+
cwd: string;
|
|
107
|
+
}): ExplainConfig;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Config (§19, module 1): CLI flags and `monkeypatchlist.config.json` into a validated scan configuration. It knows nothing
|
|
2
|
+
// about artifacts.
|
|
3
|
+
import { basename, resolve } from 'node:path';
|
|
4
|
+
import { parseCommandLine, parseExplainCommandLine } from "./lib/command-line.js";
|
|
5
|
+
import { readConfigFile } from "./lib/config-file.js";
|
|
6
|
+
import { scanConfigId } from "./lib/scan-config-id.js";
|
|
7
|
+
import { failOnStates, UsageError, } from "./lib/types.js";
|
|
8
|
+
export { differingScanConfigFields } from "./lib/scan-config-id.js";
|
|
9
|
+
export { failOnStates, UsageError, } from "./lib/types.js";
|
|
10
|
+
/**
|
|
11
|
+
* The default baseline file (§16): the one `baseline init` writes and `baseline update` rewrites (`--out`), and the one
|
|
12
|
+
* a scan compares with under `--fail-on` (`--baseline`). It is relative to the working directory.
|
|
13
|
+
*/
|
|
14
|
+
export const defaultBaselineFile = 'monkeypatchlist.baseline.json';
|
|
15
|
+
const flags = '[--config <path>] [--map <urlPrefix>=<dir>]... [--entry <file>[@<url>]]... [--source-maps <dir>]... ' +
|
|
16
|
+
'[--surface protected|all] [--mode offline|resolve] [--lockfile <path>] [--json <path|->] [--no-color]';
|
|
17
|
+
export const usage = [
|
|
18
|
+
`usage: monkeypatchlist scan <buildDir> ${flags} [--baseline <path>] [--previous <report.json>] [--fail-on added,changed] [--allow-incomplete] [--verbose] [--list-ineffective]`,
|
|
19
|
+
` monkeypatchlist baseline init <buildDir> ${flags} [--out <path>] [--allow-incomplete] [--allow-downgrade]`,
|
|
20
|
+
` monkeypatchlist baseline update <buildDir> ${flags} [--out <path>] [--allow-incomplete] [--allow-downgrade]`,
|
|
21
|
+
' monkeypatchlist explain <fingerprint|target> --report <file> [--config <path>] [--no-color]',
|
|
22
|
+
].join('\n');
|
|
23
|
+
/**
|
|
24
|
+
* The scan configuration for `monkeypatchlist <argv…>` run in `cwd`, or a `UsageError`. It reads the config file `--config`
|
|
25
|
+
* names, or `monkeypatchlist.config.json` in `cwd` if there is one, and the flags override it field by field (§16). Its paths
|
|
26
|
+
* are relative to `cwd`, except entry files and mapping directories, which are relative to the build directory.
|
|
27
|
+
*/
|
|
28
|
+
export function loadConfig(argv, options) {
|
|
29
|
+
const { cwd } = options;
|
|
30
|
+
const commandLine = parseCommandLine(argv);
|
|
31
|
+
const file = readConfigFile(commandLine.config, cwd) ?? {};
|
|
32
|
+
const settings = { ...file, ...commandLine.settings };
|
|
33
|
+
// `--lockfile` sets only the path: the file's importer stays (§16 Flags).
|
|
34
|
+
const lockfile = { ...file.lockfile, ...commandLine.settings.lockfile };
|
|
35
|
+
const { mappings = [] } = settings;
|
|
36
|
+
const [mapping] = mappings;
|
|
37
|
+
if (mapping === undefined) {
|
|
38
|
+
throw new UsageError('no URL mapping given: pass --map <urlPrefix>=<dir>, or give mappings in the config file');
|
|
39
|
+
}
|
|
40
|
+
// An entry document's URL defaults to the first mapping's URL (§16).
|
|
41
|
+
const entries = (settings.entries ?? [{ file: 'index.html' }]).map(({ file, url }) => ({
|
|
42
|
+
file,
|
|
43
|
+
url: url ?? mapping.url,
|
|
44
|
+
}));
|
|
45
|
+
entries.forEach(({ file, url }, i) => {
|
|
46
|
+
if (entries.findIndex((other) => other.file === file && other.url === url) < i) {
|
|
47
|
+
throw new UsageError(`entry document '${file}' at ${url} is given twice`);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
// The application name defaults to the build directory's basename (§16). A filesystem root has none.
|
|
51
|
+
const buildDir = resolve(cwd, commandLine.buildDir);
|
|
52
|
+
// The surface defaults to protected (§12).
|
|
53
|
+
const surface = settings.surface ?? 'protected';
|
|
54
|
+
const common = {
|
|
55
|
+
buildDir: commandLine.buildDir,
|
|
56
|
+
name: settings.name ?? (basename(buildDir) || buildDir),
|
|
57
|
+
entries,
|
|
58
|
+
mappings,
|
|
59
|
+
sourceMaps: settings.sourceMaps ?? [],
|
|
60
|
+
surface,
|
|
61
|
+
// The mode defaults to offline (§17): no command touches the network unless `--mode resolve` is given.
|
|
62
|
+
mode: settings.mode ?? 'offline',
|
|
63
|
+
lockfile: lockfile.path === undefined ? null : { path: lockfile.path, importer: lockfile.importer ?? null },
|
|
64
|
+
json: settings.json ?? null,
|
|
65
|
+
scanConfigId: scanConfigId({ entries, mappings, surface }),
|
|
66
|
+
allowIncomplete: settings.allowIncomplete ?? false,
|
|
67
|
+
// Only `scan` reads `verbose` and `listIneffective` (§16), from its flags or the file.
|
|
68
|
+
verbose: commandLine.command === 'scan' && (settings.verbose ?? false),
|
|
69
|
+
listIneffective: commandLine.command === 'scan' && (settings.listIneffective ?? false),
|
|
70
|
+
color: !(settings.noColor ?? false),
|
|
71
|
+
};
|
|
72
|
+
if (commandLine.command !== 'scan') {
|
|
73
|
+
const { command } = commandLine;
|
|
74
|
+
const allowDowngrade = settings.allowDowngrade ?? false;
|
|
75
|
+
return { command, ...common, out: settings.out ?? defaultBaselineFile, allowDowngrade };
|
|
76
|
+
}
|
|
77
|
+
const failOn = failOnStates.filter((state) => settings.failOn?.includes(state));
|
|
78
|
+
// Under `--fail-on`, the baseline defaults to `monkeypatchlist.baseline.json` (§16): a gate never runs without one.
|
|
79
|
+
const baseline = settings.baseline ?? (failOn.length > 0 ? defaultBaselineFile : null);
|
|
80
|
+
return { command: 'scan', ...common, baseline, previous: settings.previous ?? null, failOn };
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The configuration for `monkeypatchlist explain <argv…>` run in `cwd` (the arguments after `explain`), or a `UsageError`.
|
|
84
|
+
* Only `report` is read from the config file; explain reads no build directory, so the scan settings don't apply.
|
|
85
|
+
*/
|
|
86
|
+
export function loadExplainConfig(argv, options) {
|
|
87
|
+
const commandLine = parseExplainCommandLine(argv);
|
|
88
|
+
// The config file is read and validated as for every command; the flag overrides its key (§16).
|
|
89
|
+
const file = readConfigFile(commandLine.config, options.cwd);
|
|
90
|
+
const report = commandLine.report ?? file?.report;
|
|
91
|
+
if (report === undefined) {
|
|
92
|
+
throw new UsageError('explain needs the saved JSON report to read: pass --report <file>');
|
|
93
|
+
}
|
|
94
|
+
return { command: 'explain', query: commandLine.query, report };
|
|
95
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type Command, type Settings } from './types.ts';
|
|
2
|
+
export interface CommandLine {
|
|
3
|
+
command: Command;
|
|
4
|
+
/** The build directory, as given. */
|
|
5
|
+
buildDir: string;
|
|
6
|
+
/** `--config <path>`, as given. */
|
|
7
|
+
config: string | undefined;
|
|
8
|
+
/** The settings the flags give. */
|
|
9
|
+
settings: Settings;
|
|
10
|
+
}
|
|
11
|
+
/** Parses `monkeypatchlist <argv…>`, or throws a `UsageError`. */
|
|
12
|
+
export declare function parseCommandLine(argv: readonly string[]): CommandLine;
|
|
13
|
+
/** `monkeypatchlist explain <fingerprint|target> [--report <file>] [--config <path>]`, as given. */
|
|
14
|
+
export interface ExplainCommandLine {
|
|
15
|
+
/** The fingerprint or target to explain. */
|
|
16
|
+
query: string;
|
|
17
|
+
/** `--config <path>`, as given. */
|
|
18
|
+
config: string | undefined;
|
|
19
|
+
/** `--report <file>`, as given, relative to the working directory. */
|
|
20
|
+
report: string | undefined;
|
|
21
|
+
}
|
|
22
|
+
/** Parses the arguments after `monkeypatchlist explain`, or throws a `UsageError`. */
|
|
23
|
+
export declare function parseExplainCommandLine(argv: readonly string[]): ExplainCommandLine;
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// The command line (§16 Commands, Flags).
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import { failOnStates, UsageError, } from "./types.js";
|
|
4
|
+
import { httpUrl } from "./urls.js";
|
|
5
|
+
/** Parses `monkeypatchlist <argv…>`, or throws a `UsageError`. */
|
|
6
|
+
export function parseCommandLine(argv) {
|
|
7
|
+
const { command, rest } = parseCommand(argv);
|
|
8
|
+
let parsed;
|
|
9
|
+
try {
|
|
10
|
+
parsed = parseArgs({
|
|
11
|
+
args: [...rest],
|
|
12
|
+
allowPositionals: true,
|
|
13
|
+
strict: true,
|
|
14
|
+
options: {
|
|
15
|
+
config: { type: 'string', multiple: true },
|
|
16
|
+
entry: { type: 'string', multiple: true },
|
|
17
|
+
map: { type: 'string', multiple: true },
|
|
18
|
+
'source-maps': { type: 'string', multiple: true },
|
|
19
|
+
surface: { type: 'string', multiple: true },
|
|
20
|
+
mode: { type: 'string', multiple: true },
|
|
21
|
+
lockfile: { type: 'string', multiple: true },
|
|
22
|
+
json: { type: 'string', multiple: true },
|
|
23
|
+
out: { type: 'string', multiple: true },
|
|
24
|
+
baseline: { type: 'string', multiple: true },
|
|
25
|
+
previous: { type: 'string', multiple: true },
|
|
26
|
+
'fail-on': { type: 'string', multiple: true },
|
|
27
|
+
'allow-incomplete': { type: 'boolean' },
|
|
28
|
+
'allow-downgrade': { type: 'boolean' },
|
|
29
|
+
verbose: { type: 'boolean' },
|
|
30
|
+
'list-ineffective': { type: 'boolean' },
|
|
31
|
+
'no-color': { type: 'boolean' },
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
throw new UsageError(error.message);
|
|
37
|
+
}
|
|
38
|
+
const { positionals, values } = parsed;
|
|
39
|
+
if (positionals.length !== 1) {
|
|
40
|
+
throw new UsageError(positionals.length === 0 ? 'no build directory given' : `unexpected argument '${positionals[1]}'`);
|
|
41
|
+
}
|
|
42
|
+
const settings = {};
|
|
43
|
+
if (values.map !== undefined)
|
|
44
|
+
settings.mappings = mappings(values.map.map(parseMapping));
|
|
45
|
+
if (values.entry !== undefined)
|
|
46
|
+
settings.entries = values.entry.map(parseEntry);
|
|
47
|
+
if (values['source-maps'] !== undefined)
|
|
48
|
+
settings.sourceMaps = sourceMaps(values['source-maps']);
|
|
49
|
+
const surface = one('--surface', values.surface);
|
|
50
|
+
if (surface !== undefined)
|
|
51
|
+
settings.surface = parseSurface(surface);
|
|
52
|
+
const mode = one('--mode', values.mode);
|
|
53
|
+
if (mode !== undefined)
|
|
54
|
+
settings.mode = parseMode(mode);
|
|
55
|
+
// `--lockfile` sets `lockfile.path`; the importer is only a config key (§16 Flags).
|
|
56
|
+
const lockfile = one('--lockfile', values.lockfile);
|
|
57
|
+
if (lockfile === '')
|
|
58
|
+
throw new UsageError('--lockfile needs a path');
|
|
59
|
+
if (lockfile !== undefined)
|
|
60
|
+
settings.lockfile = { path: lockfile };
|
|
61
|
+
const json = one('--json', values.json);
|
|
62
|
+
if (json !== undefined)
|
|
63
|
+
settings.json = json;
|
|
64
|
+
// `--out` names the file a baseline command writes.
|
|
65
|
+
if (command === 'scan' && values.out !== undefined) {
|
|
66
|
+
throw new UsageError("--out is a baseline flag: scan doesn't write a baseline");
|
|
67
|
+
}
|
|
68
|
+
const out = one('--out', values.out);
|
|
69
|
+
if (out !== undefined)
|
|
70
|
+
settings.out = out;
|
|
71
|
+
// `--baseline` names the baseline a scan compares with, `--previous` the previous report its notes compare with, and
|
|
72
|
+
// `--fail-on` gates on the baseline.
|
|
73
|
+
for (const flag of ['baseline', 'previous', 'fail-on']) {
|
|
74
|
+
if (command !== 'scan' && values[flag] !== undefined) {
|
|
75
|
+
throw new UsageError(`--${flag} is a scan flag: ${command} accepts the whole finding set`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const baseline = one('--baseline', values.baseline);
|
|
79
|
+
if (baseline !== undefined)
|
|
80
|
+
settings.baseline = baseline;
|
|
81
|
+
const previous = one('--previous', values.previous);
|
|
82
|
+
if (previous !== undefined)
|
|
83
|
+
settings.previous = previous;
|
|
84
|
+
const failOn = one('--fail-on', values['fail-on']);
|
|
85
|
+
if (failOn !== undefined)
|
|
86
|
+
settings.failOn = parseFailOn(failOn);
|
|
87
|
+
// `--allow-incomplete` downgrades exit code 3 to a warning, and lets `baseline init` and `baseline update` write
|
|
88
|
+
// (§16), except after a fired crash guard (§13). On `scan` it also lets an incomplete report be the baseline (§11).
|
|
89
|
+
if (values['allow-incomplete'] === true)
|
|
90
|
+
settings.allowIncomplete = true;
|
|
91
|
+
// `--allow-downgrade` lets `baseline init` and `baseline update` replace a baseline written by a newer version (§4
|
|
92
|
+
// Version skew, §16).
|
|
93
|
+
if (values['allow-downgrade'] === true) {
|
|
94
|
+
if (command === 'scan')
|
|
95
|
+
throw new UsageError("--allow-downgrade is a baseline flag: scan writes no baseline");
|
|
96
|
+
settings.allowDowngrade = true;
|
|
97
|
+
}
|
|
98
|
+
// `--verbose` and `--list-ineffective` are scan flags (§16): what a baseline command accepts is the whole finding set.
|
|
99
|
+
for (const flag of ['verbose', 'list-ineffective']) {
|
|
100
|
+
if (command !== 'scan' && values[flag] === true)
|
|
101
|
+
throw new UsageError(`--${flag} is a scan flag`);
|
|
102
|
+
}
|
|
103
|
+
if (values.verbose === true)
|
|
104
|
+
settings.verbose = true;
|
|
105
|
+
if (values['list-ineffective'] === true)
|
|
106
|
+
settings.listIneffective = true;
|
|
107
|
+
if (values['no-color'] === true)
|
|
108
|
+
settings.noColor = true;
|
|
109
|
+
return { command, buildDir: positionals[0], config: one('--config', values.config), settings };
|
|
110
|
+
}
|
|
111
|
+
/** The command (`scan`, `baseline init` or `baseline update`), and the arguments after it. */
|
|
112
|
+
function parseCommand(argv) {
|
|
113
|
+
const [command, ...rest] = argv;
|
|
114
|
+
if (command === 'scan')
|
|
115
|
+
return { command, rest };
|
|
116
|
+
if (command === 'baseline') {
|
|
117
|
+
const [subcommand, ...after] = rest;
|
|
118
|
+
if (subcommand === 'init' || subcommand === 'update')
|
|
119
|
+
return { command: `baseline ${subcommand}`, rest: after };
|
|
120
|
+
throw new UsageError(subcommand === undefined ? 'no baseline command given' : `unknown command 'baseline ${subcommand}'`);
|
|
121
|
+
}
|
|
122
|
+
throw new UsageError(command === undefined ? 'no command given' : `unknown command '${command}'`);
|
|
123
|
+
}
|
|
124
|
+
/** The one value of a flag that may be given once. */
|
|
125
|
+
function one(flag, values) {
|
|
126
|
+
if (values !== undefined && values.length > 1)
|
|
127
|
+
throw new UsageError(`${flag} can be given only once`);
|
|
128
|
+
return values?.[0];
|
|
129
|
+
}
|
|
130
|
+
/** Parses `added,changed`: a comma-separated list of the states the gate fails on, each named once. */
|
|
131
|
+
function parseFailOn(value) {
|
|
132
|
+
const states = [];
|
|
133
|
+
for (const state of value.split(',')) {
|
|
134
|
+
if (state === 'removed' || state === 'unconfirmed') {
|
|
135
|
+
throw new UsageError(`--fail-on can't gate on ${state}: only added and changed can`);
|
|
136
|
+
}
|
|
137
|
+
if (!isFailOnState(state)) {
|
|
138
|
+
throw new UsageError(`--fail-on takes added and changed, separated by commas, got '${value}'`);
|
|
139
|
+
}
|
|
140
|
+
if (states.includes(state))
|
|
141
|
+
throw new UsageError(`--fail-on names ${state} twice`);
|
|
142
|
+
states.push(state);
|
|
143
|
+
}
|
|
144
|
+
return states;
|
|
145
|
+
}
|
|
146
|
+
function isFailOnState(state) {
|
|
147
|
+
return failOnStates.includes(state);
|
|
148
|
+
}
|
|
149
|
+
/** Parses `protected|all`. */
|
|
150
|
+
function parseSurface(value) {
|
|
151
|
+
if (value === 'protected' || value === 'all')
|
|
152
|
+
return value;
|
|
153
|
+
throw new UsageError(`--surface must be protected or all, got '${value}'`);
|
|
154
|
+
}
|
|
155
|
+
/** Parses `offline|resolve`. */
|
|
156
|
+
function parseMode(value) {
|
|
157
|
+
if (value === 'offline' || value === 'resolve')
|
|
158
|
+
return value;
|
|
159
|
+
throw new UsageError(`--mode must be offline or resolve, got '${value}'`);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Parses `<file>[@<url>]`. The URL starts after the first `@` that begins an absolute URL, so a file name may hold an
|
|
163
|
+
* `@` of its own. It must be an `http:` or `https:` URL.
|
|
164
|
+
*/
|
|
165
|
+
function parseEntry(value) {
|
|
166
|
+
for (let at = value.indexOf('@'); at !== -1; at = value.indexOf('@', at + 1)) {
|
|
167
|
+
if (URL.parse(value.slice(at + 1)) === null)
|
|
168
|
+
continue;
|
|
169
|
+
const file = value.slice(0, at);
|
|
170
|
+
if (file === '')
|
|
171
|
+
throw new UsageError(`--entry needs a file before @<url>, got '${value}'`);
|
|
172
|
+
const url = httpUrl(value.slice(at + 1));
|
|
173
|
+
if ('problem' in url)
|
|
174
|
+
throw new UsageError(`--entry URL ${url.problem}`);
|
|
175
|
+
return { file, url: url.href };
|
|
176
|
+
}
|
|
177
|
+
if (value === '')
|
|
178
|
+
throw new UsageError('--entry needs a file');
|
|
179
|
+
return { file: value };
|
|
180
|
+
}
|
|
181
|
+
/** The `--map` flags, with no URL prefix mapped twice: under longest-prefix-wins, the second would never be used. */
|
|
182
|
+
function mappings(list) {
|
|
183
|
+
list.forEach(({ url }, i) => {
|
|
184
|
+
if (list.findIndex((other) => other.url === url) < i)
|
|
185
|
+
throw new UsageError(`--map maps '${url}' twice`);
|
|
186
|
+
});
|
|
187
|
+
return list;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* The `--source-maps` flags, in the order given, which is the order the directories are searched in (§2 Source maps).
|
|
191
|
+
* A directory given twice would only be searched again.
|
|
192
|
+
*/
|
|
193
|
+
function sourceMaps(list) {
|
|
194
|
+
list.forEach((dir, i) => {
|
|
195
|
+
if (dir === '')
|
|
196
|
+
throw new UsageError('--source-maps needs a directory');
|
|
197
|
+
if (list.indexOf(dir) < i)
|
|
198
|
+
throw new UsageError(`--source-maps names '${dir}' twice`);
|
|
199
|
+
});
|
|
200
|
+
return list;
|
|
201
|
+
}
|
|
202
|
+
/** Parses `<urlPrefix>=<dir>`. The prefix is an absolute `http:` or `https:` URL. */
|
|
203
|
+
function parseMapping(value) {
|
|
204
|
+
const split = value.indexOf('=');
|
|
205
|
+
if (split <= 0 || split === value.length - 1)
|
|
206
|
+
throw new UsageError(`--map needs <urlPrefix>=<dir>, got '${value}'`);
|
|
207
|
+
const url = httpUrl(value.slice(0, split));
|
|
208
|
+
if ('problem' in url)
|
|
209
|
+
throw new UsageError(`--map URL prefix ${url.problem}`);
|
|
210
|
+
return { url: url.href, dir: value.slice(split + 1) };
|
|
211
|
+
}
|
|
212
|
+
/** Parses the arguments after `monkeypatchlist explain`, or throws a `UsageError`. */
|
|
213
|
+
export function parseExplainCommandLine(argv) {
|
|
214
|
+
let parsed;
|
|
215
|
+
try {
|
|
216
|
+
parsed = parseArgs({
|
|
217
|
+
args: [...argv],
|
|
218
|
+
allowPositionals: true,
|
|
219
|
+
strict: true,
|
|
220
|
+
options: {
|
|
221
|
+
config: { type: 'string', multiple: true },
|
|
222
|
+
report: { type: 'string', multiple: true },
|
|
223
|
+
// Every command takes `--no-color` (§16); explain's output has no color to turn off.
|
|
224
|
+
'no-color': { type: 'boolean' },
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
throw new UsageError(error.message);
|
|
230
|
+
}
|
|
231
|
+
const { positionals, values } = parsed;
|
|
232
|
+
if (positionals.length !== 1) {
|
|
233
|
+
throw new UsageError(positionals.length === 0 ? 'explain needs a fingerprint or target' : `unexpected argument '${positionals[1]}'`);
|
|
234
|
+
}
|
|
235
|
+
const report = one('--report', values.report);
|
|
236
|
+
if (report === '')
|
|
237
|
+
throw new UsageError('--report needs a file');
|
|
238
|
+
return { query: positionals[0], config: one('--config', values.config), report };
|
|
239
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type Settings } from './types.ts';
|
|
2
|
+
/** The file used when `--config` is not given, if it exists in the working directory. */
|
|
3
|
+
export declare const defaultConfigFile = "monkeypatchlist.config.json";
|
|
4
|
+
/**
|
|
5
|
+
* Reads the config file: `path` relative to `cwd`, or `monkeypatchlist.config.json` in `cwd` if it exists. Returns its
|
|
6
|
+
* settings, or undefined when there is no file. Throws a `UsageError` naming the failing path when the file is invalid.
|
|
7
|
+
*
|
|
8
|
+
* The file's own paths are relative to its directory, and come back relative to `cwd`, as a flag's are. Entry files and
|
|
9
|
+
* mapping directories stay relative to the build directory.
|
|
10
|
+
*/
|
|
11
|
+
export declare function readConfigFile(path: string | undefined, cwd: string): Settings | undefined;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// The configuration file (§16): `monkeypatchlist.config.json`, or the file `--config` names. It is JSON, only ever parsed.
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, extname, isAbsolute, join, resolve } from 'node:path';
|
|
4
|
+
import { schemaProblems } from "./schema.js";
|
|
5
|
+
import { UsageError } from "./types.js";
|
|
6
|
+
import { httpUrl } from "./urls.js";
|
|
7
|
+
/** The file used when `--config` is not given, if it exists in the working directory. */
|
|
8
|
+
export const defaultConfigFile = 'monkeypatchlist.config.json';
|
|
9
|
+
/**
|
|
10
|
+
* Reads the config file: `path` relative to `cwd`, or `monkeypatchlist.config.json` in `cwd` if it exists. Returns its
|
|
11
|
+
* settings, or undefined when there is no file. Throws a `UsageError` naming the failing path when the file is invalid.
|
|
12
|
+
*
|
|
13
|
+
* The file's own paths are relative to its directory, and come back relative to `cwd`, as a flag's are. Entry files and
|
|
14
|
+
* mapping directories stay relative to the build directory.
|
|
15
|
+
*/
|
|
16
|
+
export function readConfigFile(path, cwd) {
|
|
17
|
+
const file = path ?? defaultConfigFile;
|
|
18
|
+
// A CI gate for platform integrity must not execute its own configuration, so a JS or TS config file is never
|
|
19
|
+
// loaded. Only a `.json` file is read, and it is only ever parsed.
|
|
20
|
+
if (extname(file).toLowerCase() !== '.json') {
|
|
21
|
+
throw new UsageError(`config file '${file}' is not a .json file: monkeypatchlist never loads JS or TS configuration`);
|
|
22
|
+
}
|
|
23
|
+
let text;
|
|
24
|
+
try {
|
|
25
|
+
text = readFileSync(resolve(cwd, file), 'utf8');
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
const code = error.code;
|
|
29
|
+
// Only a missing default file means "no config file". Any other failure is a config error (exit 2), not exit 4.
|
|
30
|
+
if (path === undefined && code === 'ENOENT')
|
|
31
|
+
return undefined;
|
|
32
|
+
throw new UsageError(`config file '${file}' can't be read: ${code ?? error}`);
|
|
33
|
+
}
|
|
34
|
+
let value;
|
|
35
|
+
try {
|
|
36
|
+
// A leading UTF-8 byte order mark is dropped.
|
|
37
|
+
value = JSON.parse(text.startsWith('\uFEFF') ? text.slice(1) : text);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
throw new UsageError(`config file '${file}' is not valid JSON: ${error.message}`);
|
|
41
|
+
}
|
|
42
|
+
const problems = schemaProblems(value);
|
|
43
|
+
if (problems.length > 0)
|
|
44
|
+
throw invalid(file, problems);
|
|
45
|
+
const config = value;
|
|
46
|
+
// URLs are checked and canonicalized as the flags' are.
|
|
47
|
+
const canonical = (url, pointer) => {
|
|
48
|
+
const parsed = httpUrl(url);
|
|
49
|
+
if ('problem' in parsed)
|
|
50
|
+
throw invalid(file, [`${pointer}: ${parsed.problem}`]);
|
|
51
|
+
return parsed.href;
|
|
52
|
+
};
|
|
53
|
+
const fromFile = (relative) => (isAbsolute(relative) ? relative : join(dirname(file), relative));
|
|
54
|
+
const settings = {};
|
|
55
|
+
if (config.name !== undefined)
|
|
56
|
+
settings.name = config.name;
|
|
57
|
+
if (config.entries !== undefined) {
|
|
58
|
+
settings.entries = config.entries.map((entry, i) => entry.url === undefined ? { file: entry.file } : { file: entry.file, url: canonical(entry.url, `/entries/${i}/url`) });
|
|
59
|
+
}
|
|
60
|
+
if (config.mappings !== undefined) {
|
|
61
|
+
const mappings = config.mappings.map(({ url, dir }, i) => ({ url: canonical(url, `/mappings/${i}/url`), dir }));
|
|
62
|
+
// The longest matching prefix wins, so two mappings for one prefix would leave one unused.
|
|
63
|
+
mappings.forEach(({ url }, i) => {
|
|
64
|
+
const first = mappings.findIndex((other) => other.url === url);
|
|
65
|
+
if (first < i)
|
|
66
|
+
throw invalid(file, [`/mappings/${i}/url: '${url}' is already mapped by /mappings/${first}`]);
|
|
67
|
+
});
|
|
68
|
+
settings.mappings = mappings;
|
|
69
|
+
}
|
|
70
|
+
if (config.sourceMaps !== undefined) {
|
|
71
|
+
const sourceMaps = config.sourceMaps.map(fromFile);
|
|
72
|
+
// Two entries that name one directory relative to the file (`maps`, `./maps`) would only search it again, as a
|
|
73
|
+
// flag given twice would.
|
|
74
|
+
sourceMaps.forEach((dir, i) => {
|
|
75
|
+
const first = sourceMaps.indexOf(dir);
|
|
76
|
+
if (first < i)
|
|
77
|
+
throw invalid(file, [`/sourceMaps/${i}: '${dir}' is already declared by /sourceMaps/${first}`]);
|
|
78
|
+
});
|
|
79
|
+
settings.sourceMaps = sourceMaps;
|
|
80
|
+
}
|
|
81
|
+
if (config.surface !== undefined)
|
|
82
|
+
settings.surface = config.surface;
|
|
83
|
+
if (config.mode !== undefined)
|
|
84
|
+
settings.mode = config.mode;
|
|
85
|
+
if (config.lockfile !== undefined) {
|
|
86
|
+
// The lockfile's path is relative to the file, as every path in it is; the importer is a workspace of the lockfile.
|
|
87
|
+
const { path, importer } = config.lockfile;
|
|
88
|
+
settings.lockfile = {
|
|
89
|
+
...(path === undefined ? {} : { path: fromFile(path) }),
|
|
90
|
+
...(importer === undefined ? {} : { importer }),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (config.json !== undefined)
|
|
94
|
+
settings.json = config.json === '-' ? '-' : fromFile(config.json);
|
|
95
|
+
if (config.out !== undefined)
|
|
96
|
+
settings.out = fromFile(config.out);
|
|
97
|
+
if (config.baseline !== undefined)
|
|
98
|
+
settings.baseline = fromFile(config.baseline);
|
|
99
|
+
if (config.previous !== undefined)
|
|
100
|
+
settings.previous = fromFile(config.previous);
|
|
101
|
+
if (config.failOn !== undefined)
|
|
102
|
+
settings.failOn = config.failOn;
|
|
103
|
+
if (config.allowIncomplete !== undefined)
|
|
104
|
+
settings.allowIncomplete = config.allowIncomplete;
|
|
105
|
+
if (config.allowDowngrade !== undefined)
|
|
106
|
+
settings.allowDowngrade = config.allowDowngrade;
|
|
107
|
+
if (config.report !== undefined)
|
|
108
|
+
settings.report = fromFile(config.report);
|
|
109
|
+
if (config.verbose !== undefined)
|
|
110
|
+
settings.verbose = config.verbose;
|
|
111
|
+
if (config.listIneffective !== undefined)
|
|
112
|
+
settings.listIneffective = config.listIneffective;
|
|
113
|
+
if (config.noColor !== undefined)
|
|
114
|
+
settings.noColor = config.noColor;
|
|
115
|
+
return settings;
|
|
116
|
+
}
|
|
117
|
+
function invalid(file, problems) {
|
|
118
|
+
return new UsageError(`config file '${file}' is invalid:\n${problems.map((problem) => ` ${problem}`).join('\n')}`);
|
|
119
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Entry, Mapping, SurfaceScope } from './types.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The fields the id is made of, in the order of its parts. The mode, the source-map directories and the lockfile are
|
|
4
|
+
* not among them: remote gaps already become `unconfirmed`, and maps and lockfile are optional evidence (ADR 0006).
|
|
5
|
+
*/
|
|
6
|
+
export declare const scanConfigFields: readonly ["entries", "mappings", "surface", "realms"];
|
|
7
|
+
/**
|
|
8
|
+
* The scan-config id: one 12-hex-digit part per field of `scanConfigFields`, joined with `.`, so that two ids name the
|
|
9
|
+
* fields that differ (§16). Each part is the start of the SHA-256 of the field's canonical JSON:
|
|
10
|
+
*
|
|
11
|
+
* - entries: `[file, url]` pairs, sorted
|
|
12
|
+
* - mappings: `[url, dir]` pairs, sorted
|
|
13
|
+
* - surface: the scope
|
|
14
|
+
* - realms: the realm set, sorted. R1 has no setting that narrows it, so every scan covers all four realms (§2).
|
|
15
|
+
*
|
|
16
|
+
* Only what decides the findings counts: the order of entries and mappings doesn't (the longest mapping prefix wins),
|
|
17
|
+
* and a path is normalized, without a trailing `/`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function scanConfigId(config: {
|
|
20
|
+
entries: readonly Entry[];
|
|
21
|
+
mappings: readonly Mapping[];
|
|
22
|
+
surface: SurfaceScope;
|
|
23
|
+
}): string;
|
|
24
|
+
/**
|
|
25
|
+
* The fields in which two scan-config ids differ, in the order of `scanConfigFields`: none when they are the same id.
|
|
26
|
+
* An id that doesn't have one part per field can't be read part by part, so it differs in every field.
|
|
27
|
+
*/
|
|
28
|
+
export declare function differingScanConfigFields(a: string, b: string): (typeof scanConfigFields)[number][];
|