@slicedearth/whoisleuth-cli 2.2.0 → 2.4.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/NOTICE +6 -0
- package/README.md +20 -15
- package/bin/whoisleuth.mjs +8 -1
- package/cli/archive-content-digest.mjs +14 -0
- package/cli/archive-inspect.mjs +20 -53
- package/cli/arguments.mjs +454 -1748
- package/cli/artifact-validation/case-response.mjs +66 -35
- package/cli/artifact-validation/investigation-capsule.mjs +52 -31
- package/cli/artifact-validation/lookup-evidence.mjs +12 -6
- package/cli/artifact-validation/signed-review.mjs +112 -72
- package/cli/artifact-validation/structure-primitives.mjs +1 -105
- package/cli/artifact-verify.mjs +90 -24
- package/cli/assurance-command-runner.mjs +39 -4
- package/cli/bagit-folder.mjs +57 -0
- package/cli/bagit-review.mjs +23 -0
- package/cli/bounded-json.mjs +1 -1
- package/cli/bulk-checkpoint.mjs +34 -3
- package/cli/bulk-command-runner.mjs +2 -0
- package/cli/bulk-output.mjs +29 -3
- package/cli/case-command.mjs +195 -0
- package/cli/case-pack.mjs +66 -111
- package/cli/command-argument-grammar.mjs +215 -0
- package/cli/command-reference.mjs +342 -285
- package/cli/completion.mjs +303 -80
- package/cli/config-profile.mjs +7 -5
- package/cli/ct-event-intake.mjs +12 -12
- package/cli/discover.mjs +4 -3
- package/cli/discovery-observation-snapshot.mjs +59 -18
- package/cli/discovery-scan-command-runner.mjs +21 -0
- package/cli/discovery-scan.mjs +13 -4
- package/cli/discovery-workflow.mjs +1 -1
- package/cli/domain-control-monitor.mjs +50 -20
- package/cli/domain-control-observations.mjs +149 -102
- package/cli/errors.mjs +33 -1
- package/cli/evidence-command-runner.mjs +16 -5
- package/cli/evidence-signing.mjs +2 -2
- package/cli/external-observation-mapping.mjs +7 -8
- package/cli/formatters/terminal-lookup.mjs +18 -46
- package/cli/formatters/terminal-shared.mjs +5 -3
- package/cli/formatters/terminal-technology.mjs +114 -0
- package/cli/inline-command-families.mjs +2 -0
- package/cli/input.mjs +116 -0
- package/cli/interactive-launcher.mjs +2 -160
- package/cli/interchange-report.mjs +18 -8
- package/cli/investigation-artifacts.mjs +176 -0
- package/cli/investigation-checkpoint.mjs +10 -0
- package/cli/investigation-folder.mjs +114 -0
- package/cli/investigation-manifest.mjs +1 -112
- package/cli/investigation-package-review.mjs +103 -0
- package/cli/investigation-plan.mjs +32 -19
- package/cli/investigation-run.mjs +188 -33
- package/cli/local-document-checkpoint.mjs +170 -0
- package/cli/lookalike-calibration.mjs +7 -2
- package/cli/lookup-brief.mjs +4 -2
- package/cli/lookup-browser-view.mjs +1 -1
- package/cli/lookup-browser.mjs +5 -12
- package/cli/lookup-command-runner.mjs +17 -6
- package/cli/lookup-diff.mjs +37 -3
- package/cli/lookup-plan.mjs +11 -5
- package/cli/lookup-reconcile.mjs +10 -8
- package/cli/mail-header-review.mjs +467 -0
- package/cli/mail-review.mjs +66 -14
- package/cli/manual.mjs +1 -0
- package/cli/network-command-runner.mjs +2 -0
- package/cli/output-file.mjs +31 -5
- package/cli/registry-doctor.mjs +3 -1
- package/cli/registry-support.mjs +1 -1
- package/cli/retained-artifact-diff.mjs +2 -2
- package/cli/review-command-runner.mjs +70 -0
- package/cli/risk-calibration.mjs +19 -17
- package/cli/runner.mjs +62 -7
- package/cli/sharing-review.mjs +21 -34
- package/cli/signer-trust.mjs +104 -0
- package/cli/source-reliability.mjs +74 -83
- package/cli/terminal-input.mjs +168 -0
- package/cli/workflow-command-runner.mjs +68 -13
- package/docs/cli-reference.md +77 -24
- package/docs/cli.md +300 -25
- package/lib/abort.mjs +4 -1
- package/lib/availability.mjs +161 -112
- package/lib/bounded-file.mjs +3 -1
- package/lib/bounded-html-document.mjs +125 -0
- package/lib/bounded-json.mjs +51 -8
- package/lib/browser-library-profile.mjs +42 -51
- package/lib/browser-library-worker.mjs +112 -0
- package/lib/client-behavior-profile.mjs +3 -3
- package/lib/ct-search.mjs +11 -4
- package/lib/dns-convergence-review.mjs +1 -0
- package/lib/dns-delegation-health.mjs +24 -14
- package/lib/dns-inheritance-review.mjs +217 -0
- package/lib/dns-intelligence.mjs +73 -26
- package/lib/dns-mx.mjs +1 -1
- package/lib/dnssec-chain-validation.mjs +6 -0
- package/lib/dnssec-evidence-validation.mjs +11 -3
- package/lib/domain-change-packet.mjs +49 -16
- package/lib/domain-change-review.mjs +14 -8
- package/lib/domain-control-flight-recorder.mjs +221 -117
- package/lib/domain-control-manifest.mjs +129 -69
- package/lib/domain-posture-analysis.mjs +20 -7
- package/lib/domain-posture-parsers.mjs +201 -8
- package/lib/domain-posture.mjs +147 -29
- package/lib/evidence-export-privacy.mjs +561 -0
- package/lib/evidence-export.mjs +11 -543
- package/lib/evidence-report.mjs +5 -3
- package/lib/favicon.mjs +117 -41
- package/lib/generated/cisa-kev-catalog.mjs +12 -4
- package/lib/generated/registrar-standing-catalogue.mjs +7 -7
- package/lib/generated/retire-browser-catalog.mjs +2335 -940
- package/lib/hostname.mjs +2 -38
- package/lib/html-signals.mjs +111 -108
- package/lib/http-evidence-bounds.mjs +2 -1
- package/lib/http-intelligence.mjs +39 -26
- package/lib/local-geoip-evidence.mjs +19 -3
- package/lib/lookup-cache.mjs +34 -11
- package/lib/lookup-child-profile-contract.mjs +24 -15
- package/lib/lookup-diagnostics.mjs +24 -1
- package/lib/lookup-progress.mjs +30 -13
- package/lib/lookup-response-contract.mjs +1048 -0
- package/lib/lookup-response.mjs +8 -10
- package/lib/lookup-source-progress.mjs +40 -14
- package/lib/lookup.mjs +48 -27
- package/lib/nameserver-preflight-review.mjs +1 -0
- package/lib/observed-network-context.mjs +43 -27
- package/lib/outbound-request-bounds.mjs +4 -1
- package/lib/page-fingerprints.mjs +69 -133
- package/lib/page-role-profile.mjs +12 -8
- package/lib/perceptual-hash.mjs +43 -14
- package/lib/rdap-bootstrap.mjs +72 -22
- package/lib/rdap-capabilities.mjs +1 -1
- package/lib/rdap-client.mjs +15 -7
- package/lib/rdap-normalization.mjs +33 -18
- package/lib/rdap-search-workbench.mjs +1 -1
- package/lib/rdap-transport.mjs +47 -20
- package/lib/rdap-types.mjs +2 -1
- package/lib/rdap.mjs +18 -3
- package/lib/registrable-domain.mjs +21 -1
- package/lib/registrar-standing-contract.mjs +1 -0
- package/lib/registry-capabilities.mjs +5 -1
- package/lib/registry-capability-catalogue.mjs +2 -1
- package/lib/registry-dates.mjs +2 -190
- package/lib/registry-insights.mjs +85 -34
- package/lib/response-policy.mjs +86 -48
- package/lib/risk-calibration-summary.mjs +18 -6
- package/lib/rpki-evidence.mjs +8 -5
- package/lib/safe-fetch.mjs +54 -47
- package/lib/security-txt.mjs +42 -7
- package/lib/service-binding-dns.mjs +8 -0
- package/lib/smtp-transport-review.mjs +27 -15
- package/lib/sslbl-certificates.generated.mjs +136 -135
- package/lib/static-html-analysis.mjs +178 -151
- package/lib/structured-data-identity.mjs +1 -0
- package/lib/technology-markup.mjs +100 -0
- package/lib/threat-intelligence-manifests.mjs +11 -139
- package/lib/threat-intelligence-runtime.mjs +24 -52
- package/lib/threat-intelligence-targets.mjs +6 -34
- package/lib/threat-intelligence-types.mjs +39 -0
- package/lib/typosquat-generator.mjs +19 -15
- package/lib/urlhaus-intelligence.mjs +8 -3
- package/lib/urlscan-intelligence.mjs +1 -1
- package/lib/website-security-posture.mjs +30 -6
- package/lib/website-technology.mjs +153 -45
- package/lib/whois-authority.mjs +14 -2
- package/lib/whois-chain.mjs +9 -2
- package/lib/whois-common-formats.mjs +5 -5
- package/lib/whois-contracts.mjs +2 -1
- package/lib/whois-dialects.mjs +2 -2
- package/lib/whois-parser-support.mjs +8 -1
- package/lib/whois-transport.mjs +23 -7
- package/lib/zone-intent-review.mjs +16 -6
- package/package.json +2 -1
- package/packages/cases/case-attachment-model.mjs +83 -0
- package/packages/cases/case-evidence-model.mjs +156 -62
- package/packages/cases/case-export-input.mjs +53 -0
- package/packages/cases/case-migration-model.mjs +127 -44
- package/packages/cases/case-model.mjs +1 -0
- package/packages/cases/case-recheck-model.mjs +91 -0
- package/packages/cases/case-record-contracts.mjs +5 -20
- package/packages/cases/case-record-core.mjs +12 -22
- package/packages/cases/case-record-decisions.mjs +72 -0
- package/packages/cases/case-record-model.mjs +1 -0
- package/packages/cases/case-record-operations.mjs +95 -31
- package/packages/cases/case-record-projection.mjs +221 -0
- package/packages/cases/case-report.mjs +70 -54
- package/packages/cases/case-response-actions.mjs +654 -0
- package/packages/cases/case-response-model.mjs +64 -1115
- package/packages/cases/case-response-outcomes.mjs +317 -0
- package/packages/cases/case-response-packet-render.mjs +153 -0
- package/packages/cases/case-response-packet-types.mjs +1 -0
- package/packages/cases/case-response-packet-vocabulary.mjs +48 -0
- package/packages/cases/case-response-packet.mjs +74 -572
- package/packages/cases/case-response-records.mjs +94 -0
- package/packages/cases/case-response-review-inputs.mjs +371 -0
- package/packages/cases/case-response-values.mjs +85 -0
- package/packages/cases/case-review-package.mjs +45 -0
- package/packages/cases/case-selection.mjs +46 -0
- package/packages/cases/case-storage-model.mjs +31 -2
- package/packages/cases/response-route-freshness.mjs +16 -0
- package/packages/comparison/capture-context.mjs +61 -0
- package/packages/comparison/comparison-ledger-bulk.mjs +13 -11
- package/packages/comparison/page-similarity.mjs +5 -2
- package/packages/comparison/relationship-evidence.mjs +85 -8
- package/packages/comparison/relationship-provenance.mjs +93 -0
- package/packages/contracts/analyst-interchange.mjs +216 -0
- package/packages/contracts/analyst-review-state-contract.mjs +11 -0
- package/packages/contracts/analyst-review-state.mjs +5 -4
- package/packages/contracts/case-portability.mjs +366 -83
- package/packages/contracts/case-views-contract.mjs +12 -0
- package/packages/contracts/case-views.mjs +26 -0
- package/packages/contracts/cli-command-semantics.mjs +94 -0
- package/packages/contracts/cli-lookup.mjs +0 -1
- package/packages/contracts/domain-control-flight-recorder.mjs +80 -42
- package/packages/contracts/domain-control-manifest.mjs +127 -73
- package/packages/contracts/domain-control-monitor.mjs +48 -45
- package/packages/contracts/domain-control-review.mjs +179 -121
- package/packages/contracts/domain-name.mjs +21 -0
- package/packages/contracts/external-observation-interchange.mjs +2 -1
- package/packages/contracts/extracted-domain-lifecycle.mjs +11 -32
- package/packages/contracts/http-url.mjs +2 -0
- package/packages/contracts/investigation-package-limits.mjs +24 -0
- package/packages/contracts/investigation-portability.mjs +56 -27
- package/packages/contracts/investigation-run.mjs +16 -0
- package/packages/contracts/ip-address.mjs +34 -0
- package/packages/contracts/lookup-evidence.mjs +35 -0
- package/packages/contracts/offline-comparison.mjs +1 -2
- package/packages/contracts/page-fingerprints.mjs +15 -0
- package/packages/contracts/risk-calibration.mjs +15 -11
- package/packages/contracts/schema-compatibility.mjs +1 -1
- package/packages/contracts/schema-lifecycle.mjs +83 -153
- package/packages/contracts/selected-file-limits.mjs +6 -0
- package/packages/contracts/vulnerability-identifiers.mjs +5 -0
- package/packages/contracts/web-capture.mjs +10 -0
- package/packages/contracts/workspace-portability.mjs +183 -211
- package/packages/evidence/artifact-integrity.mjs +8 -2
- package/packages/evidence/artifact-structure.mjs +105 -0
- package/packages/evidence/decision-fact.mjs +4 -1
- package/packages/evidence/domain-control-runtime.mjs +201 -33
- package/packages/evidence/domain-posture-context.mjs +79 -0
- package/packages/evidence/image-regions.mjs +56 -0
- package/packages/evidence/latest-observations.mjs +28 -0
- package/packages/evidence/lookup-target.mjs +104 -0
- package/packages/evidence/observation.mjs +16 -1
- package/packages/evidence/passphrase-encryption.mjs +44 -0
- package/packages/evidence/record-identity.mjs +72 -0
- package/packages/evidence/registry-dates.mjs +190 -0
- package/packages/evidence/retained-file.mjs +69 -0
- package/packages/evidence/review-age.mjs +14 -0
- package/packages/interchange/bagit.mjs +342 -0
- package/packages/interchange/bounded-zip-extraction.mjs +278 -0
- package/packages/interchange/crc32.mjs +17 -0
- package/packages/interchange/external-findings-import.mjs +92 -51
- package/packages/interchange/web-capture-import.mjs +382 -0
- package/packages/investigation/bulk-mail-exposure.mjs +2 -1
- package/packages/investigation/investigation-bagit.mjs +14 -0
- package/packages/investigation/investigation-capsule.mjs +33 -5
- package/packages/investigation/investigation-manifest.mjs +200 -0
- package/packages/investigation/investigation-package-crypto.mjs +98 -0
- package/packages/investigation/investigation-package.mjs +200 -0
- package/packages/investigation/lookup-asset-graph-inputs.mjs +38 -0
- package/packages/investigation/lookup-asset-graph.mjs +193 -165
- package/packages/investigation/lookup-claim-readiness.mjs +18 -0
- package/packages/investigation/lookup-investigation-brief.mjs +3 -3
- package/packages/investigation/lookup-task-guidance.mjs +58 -0
- package/packages/monitoring/analyst-review-state.mjs +38 -90
- package/packages/workspace/brand-profile-model.mjs +276 -22
- package/packages/workspace/bulk-columns.mjs +17 -0
- package/packages/workspace/bulk-review-model.mjs +23 -11
- package/packages/workspace/bulk-session-model.mjs +100 -24
- package/packages/workspace/bulk-sort.mjs +1 -0
- package/packages/workspace/case-views.mjs +102 -0
- package/packages/workspace/detection-rule-model.mjs +70 -22
- package/packages/workspace/hostile-input.mjs +29 -8
- package/packages/workspace/investigation-template-model.mjs +19 -4
- package/packages/workspace/page-baseline.mjs +18 -15
- package/packages/workspace/relationship-observation-model.mjs +41 -23
- package/packages/workspace/watchlist-history.mjs +2 -2
- package/packages/workspace/watchlist-store.mjs +8 -2
- package/packages/workspace/website-snapshot-model.mjs +164 -26
- package/packages/workspace/workspace-archive-crypto.mjs +41 -61
- package/packages/workspace/workspace-archive.mjs +87 -21
- package/third-party-notices.txt +29 -2
- package/cli/evidence-command-arguments.mjs +0 -156
- package/packages/investigation/bulk-review-export.mjs +0 -63
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { CliUsageError } from "./errors.mjs";
|
|
2
|
+
import { commandDefinition, } from "./command-reference.mjs";
|
|
3
|
+
function humanList(values, conjunction = 'or') {
|
|
4
|
+
if (values.length < 2)
|
|
5
|
+
return values[0] ?? '';
|
|
6
|
+
if (values.length === 2)
|
|
7
|
+
return `${values[0]} ${conjunction} ${values[1]}`;
|
|
8
|
+
return `${values.slice(0, -1).join(', ')}, ${conjunction} ${values.at(-1)}`;
|
|
9
|
+
}
|
|
10
|
+
function valueError(specification) {
|
|
11
|
+
if (specification.valueKind === 'file') {
|
|
12
|
+
return new CliUsageError(`${specification.option} requires one bounded file path.`);
|
|
13
|
+
}
|
|
14
|
+
if (specification.valueKind === 'integer') {
|
|
15
|
+
const baseRange = specification.integerRanges[0];
|
|
16
|
+
return new CliUsageError(baseRange
|
|
17
|
+
? `${specification.option} requires an integer from ${baseRange.minimum} to ${baseRange.maximum}.`
|
|
18
|
+
: `${specification.option} requires one bounded integer.`);
|
|
19
|
+
}
|
|
20
|
+
if (specification.values.length > 0) {
|
|
21
|
+
return new CliUsageError(`${specification.option} requires ${humanList(specification.values)}.`);
|
|
22
|
+
}
|
|
23
|
+
return new CliUsageError(`${specification.option} requires one bounded value.`);
|
|
24
|
+
}
|
|
25
|
+
function parseTokens(command, argv) {
|
|
26
|
+
const specificationByOption = new Map(commandDefinition(command).grammar.options.map((specification) => [specification.option, specification]));
|
|
27
|
+
const options = new Map();
|
|
28
|
+
const positionals = [];
|
|
29
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
30
|
+
const argument = argv[index];
|
|
31
|
+
if (argument === '--') {
|
|
32
|
+
positionals.push(...argv.slice(index + 1));
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
if (!argument.startsWith('-')) {
|
|
36
|
+
positionals.push(argument);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const specification = specificationByOption.get(argument);
|
|
40
|
+
if (!specification || specification.metaAction !== null) {
|
|
41
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
42
|
+
}
|
|
43
|
+
const existing = options.get(argument);
|
|
44
|
+
if (existing && specification.occurrence === 'once') {
|
|
45
|
+
throw new CliUsageError(`${argument} may be supplied only once.`);
|
|
46
|
+
}
|
|
47
|
+
if (existing && specification.occurrence === 'idempotent')
|
|
48
|
+
continue;
|
|
49
|
+
let value = '';
|
|
50
|
+
if (specification.arity === 1) {
|
|
51
|
+
const candidate = argv[++index];
|
|
52
|
+
if (candidate === undefined || candidate === ''
|
|
53
|
+
|| (!specification.acceptsOptionLikeValue && candidate.startsWith('-'))) {
|
|
54
|
+
throw valueError(specification);
|
|
55
|
+
}
|
|
56
|
+
value = candidate;
|
|
57
|
+
}
|
|
58
|
+
if (existing)
|
|
59
|
+
existing.push(value);
|
|
60
|
+
else
|
|
61
|
+
options.set(argument, [value]);
|
|
62
|
+
}
|
|
63
|
+
return { options, positionals };
|
|
64
|
+
}
|
|
65
|
+
function selectedIntegerRange(specification, options) {
|
|
66
|
+
return specification.integerRanges
|
|
67
|
+
.filter((range) => range.whenOptionPresent === null || options.has(range.whenOptionPresent))
|
|
68
|
+
.at(-1) ?? null;
|
|
69
|
+
}
|
|
70
|
+
function validateOptionValues(command, options) {
|
|
71
|
+
for (const specification of commandDefinition(command).grammar.options) {
|
|
72
|
+
const values = options.get(specification.option);
|
|
73
|
+
if (!values || specification.arity === 0)
|
|
74
|
+
continue;
|
|
75
|
+
if (specification.valueKind === 'enum') {
|
|
76
|
+
if (values.some((value) => !specification.values.includes(value)))
|
|
77
|
+
throw valueError(specification);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (specification.valueKind === 'integer') {
|
|
81
|
+
const range = selectedIntegerRange(specification, options);
|
|
82
|
+
if (!range)
|
|
83
|
+
throw new Error(`Integer option ${command} ${specification.option} has no applicable range.`);
|
|
84
|
+
for (const value of values) {
|
|
85
|
+
if (!/^\d+$/u.test(value))
|
|
86
|
+
throw valueError(specification);
|
|
87
|
+
const parsed = Number(value);
|
|
88
|
+
if (!Number.isSafeInteger(parsed) || parsed < range.minimum || parsed > range.maximum) {
|
|
89
|
+
throw new CliUsageError(`${specification.option} must be from ${range.minimum} to ${range.maximum}.`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (specification.valueKind === 'policy_list') {
|
|
95
|
+
for (const value of values) {
|
|
96
|
+
const policies = [...new Set(value.split(',').map((item) => item.trim().toLowerCase()).filter(Boolean))];
|
|
97
|
+
if (!policies.length || policies.some((policy) => !specification.values.includes(policy))) {
|
|
98
|
+
throw new CliUsageError(`${specification.option} for ${command} supports: ${specification.values.join(', ')}.`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function allocatePositionals(command, rawPositionals) {
|
|
105
|
+
const specifications = commandDefinition(command).grammar.positionals;
|
|
106
|
+
const maximum = specifications.reduce((sum, specification) => sum + specification.maximum, 0);
|
|
107
|
+
if (rawPositionals.length > maximum) {
|
|
108
|
+
throw new CliUsageError(`${command} accepts at most ${maximum} positional value${maximum === 1 ? '' : 's'}.`);
|
|
109
|
+
}
|
|
110
|
+
const allocated = {};
|
|
111
|
+
let offset = 0;
|
|
112
|
+
for (const [specificationIndex, specification] of specifications.entries()) {
|
|
113
|
+
const remainingMinimum = specifications.slice(specificationIndex + 1)
|
|
114
|
+
.reduce((sum, later) => sum + later.minimum, 0);
|
|
115
|
+
const available = Math.max(0, rawPositionals.length - offset - remainingMinimum);
|
|
116
|
+
const count = Math.min(specification.maximum, available);
|
|
117
|
+
const values = Object.freeze(rawPositionals.slice(offset, offset + count));
|
|
118
|
+
offset += count;
|
|
119
|
+
if (values.length < specification.minimum) {
|
|
120
|
+
throw new CliUsageError(`${command} requires ${specification.minimum} ${specification.name} value${specification.minimum === 1 ? '' : 's'}.`);
|
|
121
|
+
}
|
|
122
|
+
if (specification.valueKind === 'enum'
|
|
123
|
+
&& values.some((value) => !specification.values.includes(value))) {
|
|
124
|
+
throw new CliUsageError(`${command} ${specification.name} must be ${humanList(specification.values)}.`);
|
|
125
|
+
}
|
|
126
|
+
allocated[specification.name] = values;
|
|
127
|
+
}
|
|
128
|
+
return Object.freeze(allocated);
|
|
129
|
+
}
|
|
130
|
+
function present(options, option) {
|
|
131
|
+
return options.has(option);
|
|
132
|
+
}
|
|
133
|
+
function validateConstraint(command, constraint, options) {
|
|
134
|
+
if (constraint.kind === 'required') {
|
|
135
|
+
const missing = constraint.options.filter((option) => !present(options, option));
|
|
136
|
+
if (missing.length > 0)
|
|
137
|
+
throw new CliUsageError(`${command} requires ${missing.join(', ')}.`);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (constraint.kind === 'mutually_exclusive') {
|
|
141
|
+
const selected = constraint.options.filter((option) => present(options, option));
|
|
142
|
+
if (selected.length > 1) {
|
|
143
|
+
throw new CliUsageError(`${selected.join(' and ')} are mutually exclusive and may be supplied only once.`);
|
|
144
|
+
}
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (constraint.kind === 'excludes_all') {
|
|
148
|
+
if (!present(options, constraint.option))
|
|
149
|
+
return;
|
|
150
|
+
const selected = constraint.excludedOptions.filter((option) => present(options, option));
|
|
151
|
+
if (selected.length > 0) {
|
|
152
|
+
throw new CliUsageError(`${constraint.option} cannot be combined with ${selected.join(', ')}.`);
|
|
153
|
+
}
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (constraint.kind === 'value_excludes') {
|
|
157
|
+
if (!options.get(constraint.option)?.includes(constraint.value))
|
|
158
|
+
return;
|
|
159
|
+
const selected = constraint.excludedOptions.filter((option) => present(options, option));
|
|
160
|
+
if (selected.length > 0) {
|
|
161
|
+
throw new CliUsageError(`${constraint.option} ${constraint.value} cannot be combined with ${selected.join(', ')}.`);
|
|
162
|
+
}
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (!present(options, constraint.option))
|
|
166
|
+
return;
|
|
167
|
+
const selected = constraint.requiredOptions.filter((option) => present(options, option));
|
|
168
|
+
if (constraint.kind === 'requires_all' && selected.length !== constraint.requiredOptions.length) {
|
|
169
|
+
throw new CliUsageError(`${constraint.option} requires ${constraint.requiredOptions.join(' and ')}.`);
|
|
170
|
+
}
|
|
171
|
+
if (constraint.kind === 'requires_any' && selected.length === 0) {
|
|
172
|
+
throw new CliUsageError(`${constraint.option} requires ${constraint.requiredOptions.join(' or ')}.`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function validatePositionalRequirements(command, allocated, options) {
|
|
176
|
+
for (const specification of commandDefinition(command).grammar.positionals) {
|
|
177
|
+
if (specification.requiredWhenOptions.some((option) => options.has(option))
|
|
178
|
+
&& (allocated[specification.name]?.length ?? 0) === 0) {
|
|
179
|
+
throw new CliUsageError(`${specification.requiredWhenOptions.join(' or ')} requires a positional ${specification.name}.`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function freezeOptions(options) {
|
|
184
|
+
return new Map([...options].map(([option, values]) => [option, Object.freeze([...values])]));
|
|
185
|
+
}
|
|
186
|
+
export function parseCommandArguments(command, argv) {
|
|
187
|
+
const parsed = parseTokens(command, argv);
|
|
188
|
+
const options = freezeOptions(parsed.options);
|
|
189
|
+
validateOptionValues(command, options);
|
|
190
|
+
const allocated = allocatePositionals(command, parsed.positionals);
|
|
191
|
+
validatePositionalRequirements(command, allocated, options);
|
|
192
|
+
for (const constraint of commandDefinition(command).grammar.constraints) {
|
|
193
|
+
validateConstraint(command, constraint, options);
|
|
194
|
+
}
|
|
195
|
+
for (const option of ['--config', '--profile']) {
|
|
196
|
+
const specification = commandDefinition(command).grammar.options.find((candidate) => candidate.option === option);
|
|
197
|
+
if (specification?.scope === 'common' && options.has(option)) {
|
|
198
|
+
throw new CliUsageError(`${option} must be resolved before command parsing.`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const allPositionals = Object.freeze([...parsed.positionals]);
|
|
202
|
+
return Object.freeze({
|
|
203
|
+
command,
|
|
204
|
+
allPositionals,
|
|
205
|
+
hasOption: (option) => options.has(option),
|
|
206
|
+
optionValue: (option) => options.get(option)?.at(-1) ?? null,
|
|
207
|
+
optionValues: (option) => options.get(option) ?? Object.freeze([]),
|
|
208
|
+
integerOption: (option) => {
|
|
209
|
+
const value = options.get(option)?.at(-1);
|
|
210
|
+
return value === undefined ? null : Number(value);
|
|
211
|
+
},
|
|
212
|
+
positionalValue: (name) => allocated[name]?.[0] ?? null,
|
|
213
|
+
positionalValues: (name) => allocated[name] ?? Object.freeze([]),
|
|
214
|
+
});
|
|
215
|
+
}
|