@vincentt-xr/cli 0.15.1 → 0.16.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/dist/cli/args.js +1 -0
- package/dist/cli/index.js +177 -4
- package/dist/cli/usage-text.js +27 -0
- package/dist/cli/verb-surface.d.ts +3 -1
- package/dist/cli/verb-surface.js +12 -0
- package/dist/domain/api.d.ts +51 -0
- package/dist/domain/api.js +145 -0
- package/dist/domain/command.d.ts +75 -0
- package/dist/domain/command.js +467 -0
- package/dist/domain/copy.d.ts +334 -0
- package/dist/domain/copy.js +952 -0
- package/dist/domain/hostname.d.ts +57 -0
- package/dist/domain/hostname.js +116 -0
- package/dist/domain/names.d.ts +78 -0
- package/dist/domain/names.js +113 -0
- package/dist/domain/routedVerbs.d.ts +10 -0
- package/dist/domain/routedVerbs.js +30 -0
- package/dist/domain/wire.d.ts +140 -0
- package/dist/domain/wire.js +174 -0
- package/dist/dx/clusters.js +10 -1
- package/dist/dx/command.d.ts +13 -4
- package/dist/dx/command.js +26 -5
- package/dist/dx/report.d.ts +39 -7
- package/dist/dx/report.js +71 -20
- package/dist/dx/send.d.ts +22 -4
- package/dist/dx/send.js +29 -8
- package/dist/dx/skeleton.js +19 -0
- package/dist/dx/unbiddenVerbs.d.ts +25 -0
- package/dist/dx/unbiddenVerbs.js +39 -0
- package/dist/lib/exitCodes.d.ts +24 -0
- package/dist/lib/exitCodes.js +28 -0
- package/dist/lib/initCommand.d.ts +3 -1
- package/dist/lib/initCommand.js +10 -0
- package/dist/lib/initFrom.js +12 -2
- package/dist/lib/lifecycle.d.ts +5 -1
- package/dist/lib/projects.d.ts +8 -1
- package/dist/lib/projects.js +21 -4
- package/dist/platform/allowlist.d.ts +36 -0
- package/dist/platform/allowlist.js +34 -0
- package/dist/platform/behind.d.ts +41 -0
- package/dist/platform/behind.js +82 -0
- package/dist/platform/command.d.ts +16 -0
- package/dist/platform/command.js +90 -0
- package/dist/platform/footer.d.ts +60 -0
- package/dist/platform/footer.js +101 -0
- package/dist/platform/notice.d.ts +29 -0
- package/dist/platform/notice.js +66 -0
- package/dist/platform/offSwitch.d.ts +19 -0
- package/dist/platform/offSwitch.js +21 -0
- package/dist/platform/outdated.d.ts +25 -0
- package/dist/platform/outdated.js +246 -0
- package/dist/platform/releaseCache.d.ts +69 -0
- package/dist/platform/releaseCache.js +140 -0
- package/dist/platform/releaseFetch.d.ts +51 -0
- package/dist/platform/releaseFetch.js +150 -0
- package/dist/platform/resolve.d.ts +30 -0
- package/dist/platform/resolve.js +128 -0
- package/dist/platform/templateUpgrade.d.ts +56 -0
- package/dist/platform/templateUpgrade.js +476 -0
- package/dist/platform/upgradeReport.d.ts +132 -0
- package/dist/platform/upgradeReport.js +258 -0
- package/dist/platform/versionSummary.d.ts +82 -0
- package/dist/platform/versionSummary.js +181 -0
- package/dist/platform/zonePath.d.ts +154 -0
- package/dist/platform/zonePath.js +286 -0
- package/dist/platform/zones.d.ts +64 -0
- package/dist/platform/zones.js +79 -0
- package/dist/publish/api.d.ts +15 -4
- package/dist/publish/api.js +19 -5
- package/dist/publish/command.d.ts +10 -0
- package/dist/publish/command.js +7 -1
- package/dist/publish/copy.d.ts +14 -1
- package/dist/publish/copy.js +32 -3
- package/dist/scaffold/index.d.ts +50 -3
- package/dist/scaffold/index.js +61 -1
- package/dist/shared/config.d.ts +16 -0
- package/dist/shared/config.js +9 -1
- package/package.json +1 -1
- package/dist/dx/qa.f7.fixtures.d.ts +0 -73
- package/dist/dx/qa.f7.fixtures.js +0 -58
- package/dist/publish/qa.f4.fixtures.d.ts +0 -25
- package/dist/publish/qa.f4.fixtures.js +0 -140
- package/dist/relay/qa.f12.fixtures.d.ts +0 -154
- package/dist/relay/qa.f12.fixtures.js +0 -358
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// The f16 custom-domain wire shapes, and the ONE parser every renderer reads
|
|
2
|
+
// through.
|
|
3
|
+
//
|
|
4
|
+
// EVERY field is read defensively, for the reason lib/diagnostics.ts and
|
|
5
|
+
// library/wire.ts both record: the CLI and the API are versioned separately on a
|
|
6
|
+
// creator's machine, so a field a renderer reads unguarded is a field whose
|
|
7
|
+
// removal from the API crashes the command. A missing optional field degrades to
|
|
8
|
+
// a missing LINE, never to an exception.
|
|
9
|
+
//
|
|
10
|
+
// Two fields are read with more care than the rest, and the care is the point:
|
|
11
|
+
//
|
|
12
|
+
// • `records` is PRINTED VERBATIM. No client constructs a record
|
|
13
|
+
// (implementation.md § Wire fields). The parser therefore carries values
|
|
14
|
+
// through untouched and derives exactly one thing — the relative name — which
|
|
15
|
+
// it derives from the FQDN the API sent rather than by assembling a name from
|
|
16
|
+
// parts.
|
|
17
|
+
// • `observed` is A STRING, NEVER STRUCTURED. The wire says so explicitly, and
|
|
18
|
+
// the reason is that structure invites a client to re-render and lose the
|
|
19
|
+
// fidelity that is the whole point of the diagnosis ruling. It is read as a
|
|
20
|
+
// string and printed as a string; there is no parse step to lose it in.
|
|
21
|
+
/**
|
|
22
|
+
* The closed state enum (implementation.md § Wire fields).
|
|
23
|
+
*
|
|
24
|
+
* `DomainState` is a union of the seven the API can send. An unrecognized state
|
|
25
|
+
* is NOT coerced into one of these — `parseDomain` keeps the raw string, because
|
|
26
|
+
* a newer API sending an eighth state must render as itself rather than as a
|
|
27
|
+
* plausible neighbour. Rendering a state we do not know as `pending` would tell a
|
|
28
|
+
* creator nothing is wrong when something might be.
|
|
29
|
+
*/
|
|
30
|
+
export const DOMAIN_STATES = [
|
|
31
|
+
"pending",
|
|
32
|
+
"verified",
|
|
33
|
+
"live",
|
|
34
|
+
"failing",
|
|
35
|
+
"held",
|
|
36
|
+
"cert_failed",
|
|
37
|
+
"detached",
|
|
38
|
+
];
|
|
39
|
+
export function isDomainState(s) {
|
|
40
|
+
return DOMAIN_STATES.includes(s);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The closed findings enum (how-it-works.md Scenario 4). Each member carries an
|
|
44
|
+
* `observed` string.
|
|
45
|
+
*
|
|
46
|
+
* The severity is NOT on the wire as a field the CLI trusts blindly — see
|
|
47
|
+
* `findingSeverity` in copy.ts, which maps the enum to blocking/advisory. That
|
|
48
|
+
* mapping lives with the copy because the exit code follows the glyph, and the
|
|
49
|
+
* glyph is a copy decision.
|
|
50
|
+
*/
|
|
51
|
+
// TAKEN FROM THE PRODUCER, not from the design record's prose: these are the
|
|
52
|
+
// exact strings `domain.DomainFinding` emits (api internal/domain/customdomain.go).
|
|
53
|
+
// The record's Scenario 4 describes the same set in narrative form and does not
|
|
54
|
+
// spell every constant, so the Go constants are authoritative for the wire.
|
|
55
|
+
export const FINDING_CODES = [
|
|
56
|
+
"TOKEN_MISSING",
|
|
57
|
+
"TOKEN_MISMATCH",
|
|
58
|
+
"TOKEN_STALE_PRESENT",
|
|
59
|
+
"WRONG_RECORD_TYPE",
|
|
60
|
+
"CNAME_WRONG_TARGET",
|
|
61
|
+
"CONFLICTING_RECORDS",
|
|
62
|
+
"UNEXPECTED_AAAA",
|
|
63
|
+
"PROXY_IN_FRONT",
|
|
64
|
+
"CAA_BLOCKS_ISSUANCE",
|
|
65
|
+
"STALE_ACME_CHALLENGE",
|
|
66
|
+
// D-44. The producer spells this WITHOUT a `CERT_` prefix; an earlier draft of
|
|
67
|
+
// this client invented one and would have rendered the finding as an unknown
|
|
68
|
+
// code — printing it, but with the enum string as its title and no remedy.
|
|
69
|
+
"VALIDATION_RECORD_GONE",
|
|
70
|
+
"MALFORMED_ZONE",
|
|
71
|
+
];
|
|
72
|
+
/**
|
|
73
|
+
* D-44's finding, as a NAMED CONSTANT rather than a literal at each site.
|
|
74
|
+
*
|
|
75
|
+
* Two places branch on it — the severity table and the record re-print — and
|
|
76
|
+
* they were briefly spelled differently, which printed the finding while
|
|
77
|
+
* silently omitting the record that IS its remedy. A shared symbol makes that
|
|
78
|
+
* particular disagreement unexpressible.
|
|
79
|
+
*/
|
|
80
|
+
export const VALIDATION_RECORD_GONE = "VALIDATION_RECORD_GONE";
|
|
81
|
+
function str(o, k) {
|
|
82
|
+
return typeof o[k] === "string" ? o[k] : undefined;
|
|
83
|
+
}
|
|
84
|
+
function num(o, k) {
|
|
85
|
+
return typeof o[k] === "number" ? o[k] : undefined;
|
|
86
|
+
}
|
|
87
|
+
function obj(raw) {
|
|
88
|
+
return typeof raw === "object" && raw !== null ? raw : {};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Parse one record. `type`, `name` and `value` are all required: a record
|
|
92
|
+
* missing any of the three cannot be created at a registrar, and printing a
|
|
93
|
+
* partial row would send a creator to paste a blank into a form.
|
|
94
|
+
*/
|
|
95
|
+
export function parseRecord(raw) {
|
|
96
|
+
const o = obj(raw);
|
|
97
|
+
const type = str(o, "type");
|
|
98
|
+
const name = str(o, "name");
|
|
99
|
+
const value = str(o, "value");
|
|
100
|
+
if (!type || !name || !value)
|
|
101
|
+
return undefined;
|
|
102
|
+
return { type, name, value, ttl: num(o, "ttl") };
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Parse one finding. A finding with no `observed` is DROPPED rather than
|
|
106
|
+
* rendered, because the shape's whole contract is that it reports what was seen
|
|
107
|
+
* — and a finding that restates the instructions is the thing the design refuses.
|
|
108
|
+
* Dropping is visible: `parseDomain` counts what it dropped.
|
|
109
|
+
*/
|
|
110
|
+
export function parseFinding(raw) {
|
|
111
|
+
const o = obj(raw);
|
|
112
|
+
const code = str(o, "code");
|
|
113
|
+
const observed = str(o, "observed");
|
|
114
|
+
if (!code || observed === undefined)
|
|
115
|
+
return undefined;
|
|
116
|
+
return {
|
|
117
|
+
code,
|
|
118
|
+
observed,
|
|
119
|
+
expected: str(o, "expected"),
|
|
120
|
+
severity: str(o, "severity"),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Parse a domain row.
|
|
125
|
+
*
|
|
126
|
+
* `domain` is the only structurally required field: without a hostname there is
|
|
127
|
+
* nothing to render a row about. Everything else degrades to an absent line.
|
|
128
|
+
*/
|
|
129
|
+
export function parseDomain(raw) {
|
|
130
|
+
const o = obj(raw);
|
|
131
|
+
const domain = str(o, "domain");
|
|
132
|
+
if (!domain)
|
|
133
|
+
return undefined;
|
|
134
|
+
const rawRecords = Array.isArray(o.records) ? o.records : [];
|
|
135
|
+
const records = rawRecords
|
|
136
|
+
.map(parseRecord)
|
|
137
|
+
.filter((r) => r !== undefined);
|
|
138
|
+
const rawFindings = Array.isArray(o.findings) ? o.findings : [];
|
|
139
|
+
const findings = rawFindings
|
|
140
|
+
.map(parseFinding)
|
|
141
|
+
.filter((f) => f !== undefined);
|
|
142
|
+
const rawState = str(o, "state") ?? "";
|
|
143
|
+
// `holdsAt` is null unless `failing`. A JSON null must read as ABSENT, not as
|
|
144
|
+
// the string "null" — `str` already refuses a non-string, which is the guard.
|
|
145
|
+
return {
|
|
146
|
+
domain: {
|
|
147
|
+
domain,
|
|
148
|
+
rawState,
|
|
149
|
+
state: isDomainState(rawState) ? rawState : undefined,
|
|
150
|
+
projectId: str(o, "projectId"),
|
|
151
|
+
projectName: str(o, "projectName"),
|
|
152
|
+
records,
|
|
153
|
+
findings,
|
|
154
|
+
holdsAt: str(o, "holdsAt"),
|
|
155
|
+
attachedAt: str(o, "attachedAt"),
|
|
156
|
+
permanentAddress: str(o, "permanentAddress"),
|
|
157
|
+
},
|
|
158
|
+
droppedFindings: rawFindings.length - findings.length,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/** Parse the list envelope. A row that cannot be parsed is skipped, never faked. */
|
|
162
|
+
export function parseDomainList(raw) {
|
|
163
|
+
const o = obj(raw);
|
|
164
|
+
const rows = Array.isArray(o.domains) ? o.domains : [];
|
|
165
|
+
return {
|
|
166
|
+
domains: rows
|
|
167
|
+
.map(parseDomain)
|
|
168
|
+
.filter((p) => p !== undefined)
|
|
169
|
+
.map((p) => p.domain),
|
|
170
|
+
count: num(o, "count"),
|
|
171
|
+
limit: num(o, "limit"),
|
|
172
|
+
planName: str(o, "planName"),
|
|
173
|
+
};
|
|
174
|
+
}
|
package/dist/dx/clusters.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* Detection is deliberately loose. A false positive costs a line in a report a
|
|
16
16
|
* human is already reading; a miss costs a DX gap nobody ever hears about.
|
|
17
17
|
*/
|
|
18
|
+
import { EXIT } from "../lib/exitCodes.js";
|
|
18
19
|
import { verbOf } from "./skeleton.js";
|
|
19
20
|
/**
|
|
20
21
|
* Non-zero exits a verb DEFINES as ordinary outcomes, not failures.
|
|
@@ -26,11 +27,19 @@ import { verbOf } from "./skeleton.js";
|
|
|
26
27
|
* fight-with-the-tool that never happened, and the nudge narrates the intended
|
|
27
28
|
* loop as friction — on stderr, which in this loop is a caller's own input.
|
|
28
29
|
*
|
|
30
|
+
* `create` and `init` exit 3 when the scaffold landed but no account was on the
|
|
31
|
+
* machine to reserve an address. The creator holds a working project, so
|
|
32
|
+
* counting it as a fault would make the nudge narrate a delivered project as a
|
|
33
|
+
* fight with the tool. `LOOPING_VERBS` deliberately does NOT gain these two:
|
|
34
|
+
* re-running create without signing in first IS real repetition.
|
|
35
|
+
*
|
|
29
36
|
* A verb's neutral exits are ITS OWN. Nothing here generalizes 64/65 across the
|
|
30
37
|
* CLI; another verb adding a non-zero-but-fine exit adds a row.
|
|
31
38
|
*/
|
|
32
39
|
const NEUTRAL_EXITS = {
|
|
33
|
-
feedback: new Set([
|
|
40
|
+
feedback: new Set([EXIT.WINDOW_ELAPSED, EXIT.CHANNEL_ENDED]),
|
|
41
|
+
create: new Set([EXIT.DELIVERED_NOT_REGISTERED]),
|
|
42
|
+
init: new Set([EXIT.DELIVERED_NOT_REGISTERED]),
|
|
34
43
|
};
|
|
35
44
|
/**
|
|
36
45
|
* Verbs whose contract is "run it again". Repeating them is not a caller failing
|
package/dist/dx/command.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { ConsentDecision } from "./consent.js";
|
|
|
2
2
|
import type { Enablement } from "./enablement.js";
|
|
3
3
|
import type { JournalEntry } from "./journal.js";
|
|
4
4
|
import type { WireReport } from "./send.js";
|
|
5
|
+
import { type BuiltWith } from "../platform/resolve.js";
|
|
5
6
|
export interface DxDeps {
|
|
6
7
|
entries: () => JournalEntry[];
|
|
7
8
|
print: (line: string) => void;
|
|
@@ -159,13 +160,21 @@ export interface DxSendDeps extends DxDeps {
|
|
|
159
160
|
apiUrl: string;
|
|
160
161
|
pat: string;
|
|
161
162
|
}) => Promise<Enablement>;
|
|
163
|
+
/**
|
|
164
|
+
* The version set. Injectable so a fixed set — or a REJECTING promise, the arm
|
|
165
|
+
* proving a fault omits — can be driven with no filesystem. Defaults to the one
|
|
166
|
+
* shared resolver against `cwd`.
|
|
167
|
+
*/
|
|
168
|
+
builtWith?: () => Promise<BuiltWith | undefined>;
|
|
169
|
+
cwd?: string;
|
|
162
170
|
}
|
|
163
171
|
/**
|
|
164
172
|
* `vincentt dx --send`.
|
|
165
173
|
*
|
|
166
|
-
* The order is the consent model and is not an implementation detail
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
174
|
+
* The order is the consent model and is not an implementation detail. A creator
|
|
175
|
+
* who cannot see what leaves their machine before it leaves has not agreed to
|
|
176
|
+
* anything:
|
|
177
|
+
*
|
|
178
|
+
* friction → GATE (three arms) → auth → RESOLVE → build → SHOW → ask → post
|
|
170
179
|
*/
|
|
171
180
|
export declare function dxSendCommand(deps: DxSendDeps, options: DxOptions): Promise<number>;
|
package/dist/dx/command.js
CHANGED
|
@@ -2,6 +2,7 @@ import { findClusters, rankClusters } from "./clusters.js";
|
|
|
2
2
|
import { enablementLine } from "./enablement.js";
|
|
3
3
|
import { journalEnabled, journalPath, readJournal } from "./journal.js";
|
|
4
4
|
import { buildReport, renderPayload } from "./send.js";
|
|
5
|
+
import { resolveBuiltWith } from "../platform/resolve.js";
|
|
5
6
|
const DEFAULT_DAYS = 7;
|
|
6
7
|
export function parseDxOptions(args) {
|
|
7
8
|
const i = args.indexOf("--days");
|
|
@@ -321,10 +322,11 @@ export async function dxDisableCommand(deps) {
|
|
|
321
322
|
/**
|
|
322
323
|
* `vincentt dx --send`.
|
|
323
324
|
*
|
|
324
|
-
* The order is the consent model and is not an implementation detail
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
325
|
+
* The order is the consent model and is not an implementation detail. A creator
|
|
326
|
+
* who cannot see what leaves their machine before it leaves has not agreed to
|
|
327
|
+
* anything:
|
|
328
|
+
*
|
|
329
|
+
* friction → GATE (three arms) → auth → RESOLVE → build → SHOW → ask → post
|
|
328
330
|
*/
|
|
329
331
|
export async function dxSendCommand(deps, options) {
|
|
330
332
|
if (!deps.enabled()) {
|
|
@@ -416,7 +418,26 @@ export async function dxSendCommand(deps, options) {
|
|
|
416
418
|
deps.print("Not signed in. Run `vincentt login` to send a report.");
|
|
417
419
|
return 1;
|
|
418
420
|
}
|
|
419
|
-
|
|
421
|
+
// RESOLVED AFTER THE GATE AND IMMEDIATELY BEFORE THE BUILD.
|
|
422
|
+
//
|
|
423
|
+
// This is deliberately the INVERSE of publish, which resolves AFTER its
|
|
424
|
+
// confirmation (`publish/command.ts`), and the two must not be "unified": on
|
|
425
|
+
// publish the version set is never shown, so resolving before the prompt would
|
|
426
|
+
// mean a REFUSED publish had read the creator's `node_modules` for nothing.
|
|
427
|
+
// Here the set IS part of what is being consented to, so a value not resolved
|
|
428
|
+
// cannot be shown and a value not shown cannot be consented to. One rule covers
|
|
429
|
+
// both: resolve at the last moment before the value is needed, and never
|
|
430
|
+
// earlier. On publish that moment is the POST; here it is the SHOW.
|
|
431
|
+
//
|
|
432
|
+
// The gate is already passed, so a refused send has read nothing at all.
|
|
433
|
+
//
|
|
434
|
+
// The `.catch` belongs HERE and not in the resolver: the resolver's `undefined`
|
|
435
|
+
// already means "nothing to say", so swallowing inside it would make a genuine
|
|
436
|
+
// fault indistinguishable from a legitimate empty tree inside the sole
|
|
437
|
+
// producer. At this call site the meaning is exact — a fault yields the same
|
|
438
|
+
// omission an empty project yields, and neither can fail a send.
|
|
439
|
+
const builtWith = await (deps.builtWith ?? (() => resolveBuiltWith(deps.cwd ?? process.cwd())))().catch(() => undefined);
|
|
440
|
+
const report = buildReport(clusters, deps.cliVersion(), options.note, builtWith);
|
|
420
441
|
// Above the payload, on the unattended path only: name that this is enabled,
|
|
421
442
|
// how old the grant is, WHERE it was granted, and that nothing is going to ask.
|
|
422
443
|
const unattended = !interactive && enablement.enabled;
|
package/dist/dx/report.d.ts
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import type { Enablement } from "./enablement.js";
|
|
23
23
|
import type { JournalEntry } from "./journal.js";
|
|
24
|
+
import { type BuiltWith } from "../platform/resolve.js";
|
|
24
25
|
/** An observation as it goes on the wire. Mirrors the API's dxObservationRequest. */
|
|
25
26
|
export interface WireObservation {
|
|
26
27
|
note: string;
|
|
@@ -29,6 +30,14 @@ export interface WireObservation {
|
|
|
29
30
|
export interface WireObservationReport {
|
|
30
31
|
cliVersion: string;
|
|
31
32
|
platform: string;
|
|
33
|
+
/**
|
|
34
|
+
* What the CLI resolved on this machine at report time. On the ROOT, not on
|
|
35
|
+
* the observation: it is a fact about the machine that produced the report,
|
|
36
|
+
* not about any one carrier, and putting it per-carrier would both be false
|
|
37
|
+
* and multiply it on the wire by the carrier count. Absent when nothing
|
|
38
|
+
* resolved.
|
|
39
|
+
*/
|
|
40
|
+
builtWith?: BuiltWith;
|
|
32
41
|
findings: never[];
|
|
33
42
|
observations: WireObservation[];
|
|
34
43
|
}
|
|
@@ -62,14 +71,28 @@ export declare function parseReportOptions(args: string[]): ReportOptions;
|
|
|
62
71
|
* this run, and none of those is a reason to refuse to send what someone typed.
|
|
63
72
|
*/
|
|
64
73
|
export declare function lastContext(entries: JournalEntry[]): string | undefined;
|
|
65
|
-
|
|
74
|
+
/**
|
|
75
|
+
* The wire payload. PURE and SYNCHRONOUS, which is the property its tests rest
|
|
76
|
+
* on — `builtWith` is a PARAMETER, not a dep, so the async resolution and the
|
|
77
|
+
* `.catch(() => undefined)` that makes a fault an omission stay the caller's.
|
|
78
|
+
*/
|
|
79
|
+
export declare function buildObservationReport(note: string, cliVersion: string, context?: string, builtWith?: BuiltWith): WireObservationReport;
|
|
66
80
|
/**
|
|
67
81
|
* Render the payload for the creator to read BEFORE it is sent.
|
|
68
82
|
*
|
|
69
|
-
* As in `send.ts`, this is the payload formatted,
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* it
|
|
83
|
+
* As in `send.ts`, this is the payload formatted, WITH EXACTLY ONE FIELD
|
|
84
|
+
* SUMMARIZED: `builtWith` renders as a single `versions:` line at its own
|
|
85
|
+
* position inside the block, and no second field may join it. The exemption's
|
|
86
|
+
* reasoning and the three properties bounding it live in
|
|
87
|
+
* `platform/versionSummary.ts` rather than being restated at each screen, so the
|
|
88
|
+
* two cannot come to describe it differently.
|
|
89
|
+
*
|
|
90
|
+
* `renderPayload` (`send.ts`) is the other consent screen and carries the
|
|
91
|
+
* identical exemption. NEITHER MAY BE UPDATED WITHOUT THE OTHER.
|
|
92
|
+
*
|
|
93
|
+
* The note warning is unconditional here, where it is conditional there: an
|
|
94
|
+
* observation is ALWAYS a note, so there is no path through this screen that
|
|
95
|
+
* does not need it.
|
|
73
96
|
*/
|
|
74
97
|
export declare function renderObservation(report: WireObservationReport): string[];
|
|
75
98
|
export interface ReportDeps {
|
|
@@ -92,11 +115,20 @@ export interface ReportDeps {
|
|
|
92
115
|
apiUrl: string;
|
|
93
116
|
pat: string;
|
|
94
117
|
}) => Promise<Enablement>;
|
|
118
|
+
/**
|
|
119
|
+
* The version set. Injectable so a fixed set — or a REJECTING promise, the arm
|
|
120
|
+
* proving a fault omits — can be driven with no filesystem. Defaults to the one
|
|
121
|
+
* shared resolver against `cwd`.
|
|
122
|
+
*/
|
|
123
|
+
builtWith?: () => Promise<BuiltWith | undefined>;
|
|
124
|
+
cwd?: string;
|
|
95
125
|
}
|
|
96
126
|
/**
|
|
97
127
|
* `vincentt report`.
|
|
98
128
|
*
|
|
99
|
-
* Same order as `dx --send`, for the same reason
|
|
100
|
-
*
|
|
129
|
+
* Same order as `dx --send`, for the same reason — a creator who cannot see what
|
|
130
|
+
* leaves their machine before it leaves has not agreed to anything:
|
|
131
|
+
*
|
|
132
|
+
* bound-check → GATE (three arms) → auth → RESOLVE → build → SHOW → ask → post
|
|
101
133
|
*/
|
|
102
134
|
export declare function reportCommand(deps: ReportDeps, options: ReportOptions): Promise<number>;
|
package/dist/dx/report.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { enablementLine } from "./enablement.js";
|
|
2
2
|
import { DXSendError, platformTag } from "./send.js";
|
|
3
|
+
import { resolveBuiltWith } from "../platform/resolve.js";
|
|
4
|
+
import { renderPayloadWithVersionSummary, summarizeBuiltWith, versionDisclosureLines, } from "../platform/versionSummary.js";
|
|
3
5
|
/** Matches the server's MaxDXNote. Checked here so a refusal costs no round trip. */
|
|
4
6
|
export const MAX_NOTE = 2000;
|
|
5
7
|
/**
|
|
@@ -39,38 +41,64 @@ export function lastContext(entries) {
|
|
|
39
41
|
}
|
|
40
42
|
return undefined;
|
|
41
43
|
}
|
|
42
|
-
|
|
44
|
+
/**
|
|
45
|
+
* The wire payload. PURE and SYNCHRONOUS, which is the property its tests rest
|
|
46
|
+
* on — `builtWith` is a PARAMETER, not a dep, so the async resolution and the
|
|
47
|
+
* `.catch(() => undefined)` that makes a fault an omission stay the caller's.
|
|
48
|
+
*/
|
|
49
|
+
export function buildObservationReport(note, cliVersion, context, builtWith) {
|
|
43
50
|
const o = { note: note.trim() };
|
|
44
51
|
if (context !== undefined && context !== "")
|
|
45
52
|
o.context = context;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
// THE SUMMARY IS THE AUTHORITY ON WHETHER THE FIELD IS SENT — a version set the
|
|
54
|
+
// consent screen cannot render may not go on the wire. See
|
|
55
|
+
// `summarizeBuiltWith`.
|
|
56
|
+
const sendable = summarizeBuiltWith(builtWith);
|
|
57
|
+
// Always present and always empty: the server accepts a report with either
|
|
58
|
+
// carrier, and sending the key explicitly says "no findings" rather than
|
|
59
|
+
// leaving the shape to be inferred from an absent field.
|
|
60
|
+
//
|
|
61
|
+
// `builtWith` sits between `platform` and `findings` — the position the consent
|
|
62
|
+
// screen summarizes it at, and a sibling of the other two machine facts.
|
|
63
|
+
return sendable === undefined
|
|
64
|
+
? { cliVersion, platform: platformTag(), findings: [], observations: [o] }
|
|
65
|
+
: {
|
|
66
|
+
cliVersion,
|
|
67
|
+
platform: platformTag(),
|
|
68
|
+
builtWith: sendable,
|
|
69
|
+
findings: [],
|
|
70
|
+
observations: [o],
|
|
71
|
+
};
|
|
55
72
|
}
|
|
56
73
|
/**
|
|
57
74
|
* Render the payload for the creator to read BEFORE it is sent.
|
|
58
75
|
*
|
|
59
|
-
* As in `send.ts`, this is the payload formatted,
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* it
|
|
76
|
+
* As in `send.ts`, this is the payload formatted, WITH EXACTLY ONE FIELD
|
|
77
|
+
* SUMMARIZED: `builtWith` renders as a single `versions:` line at its own
|
|
78
|
+
* position inside the block, and no second field may join it. The exemption's
|
|
79
|
+
* reasoning and the three properties bounding it live in
|
|
80
|
+
* `platform/versionSummary.ts` rather than being restated at each screen, so the
|
|
81
|
+
* two cannot come to describe it differently.
|
|
82
|
+
*
|
|
83
|
+
* `renderPayload` (`send.ts`) is the other consent screen and carries the
|
|
84
|
+
* identical exemption. NEITHER MAY BE UPDATED WITHOUT THE OTHER.
|
|
85
|
+
*
|
|
86
|
+
* The note warning is unconditional here, where it is conditional there: an
|
|
87
|
+
* observation is ALWAYS a note, so there is no path through this screen that
|
|
88
|
+
* does not need it.
|
|
63
89
|
*/
|
|
64
90
|
export function renderObservation(report) {
|
|
65
91
|
return [
|
|
66
92
|
"This is what would be sent:",
|
|
67
93
|
"",
|
|
68
|
-
...
|
|
69
|
-
.split("\n")
|
|
70
|
-
.map((l) => ` ${l}`),
|
|
94
|
+
...renderPayloadWithVersionSummary(report, report.builtWith).map((l) => ` ${l}`),
|
|
71
95
|
"",
|
|
72
96
|
" YOUR NOTE IS SENT EXACTLY AS WRITTEN — check it for paths, URLs,",
|
|
73
97
|
" tokens, or customer names before sending.",
|
|
98
|
+
// Entry 2: it explains the one anomalous line above, so it sits directly
|
|
99
|
+
// under the warning where the reader's eye lands next — above the context
|
|
100
|
+
// line, which would otherwise make them hunt for the explanation.
|
|
101
|
+
...versionDisclosureLines(report.builtWith),
|
|
74
102
|
" The context line is the shape of your last command, with values removed.",
|
|
75
103
|
" Your journal stays on this machine.",
|
|
76
104
|
];
|
|
@@ -78,8 +106,10 @@ export function renderObservation(report) {
|
|
|
78
106
|
/**
|
|
79
107
|
* `vincentt report`.
|
|
80
108
|
*
|
|
81
|
-
* Same order as `dx --send`, for the same reason
|
|
82
|
-
*
|
|
109
|
+
* Same order as `dx --send`, for the same reason — a creator who cannot see what
|
|
110
|
+
* leaves their machine before it leaves has not agreed to anything:
|
|
111
|
+
*
|
|
112
|
+
* bound-check → GATE (three arms) → auth → RESOLVE → build → SHOW → ask → post
|
|
83
113
|
*/
|
|
84
114
|
export async function reportCommand(deps, options) {
|
|
85
115
|
const note = options.note.trim();
|
|
@@ -126,7 +156,28 @@ export async function reportCommand(deps, options) {
|
|
|
126
156
|
return 1;
|
|
127
157
|
}
|
|
128
158
|
const context = options.noContext ? undefined : lastContext(deps.entries());
|
|
129
|
-
|
|
159
|
+
// RESOLVED AFTER THE GATE AND IMMEDIATELY BEFORE THE BUILD.
|
|
160
|
+
//
|
|
161
|
+
// This is deliberately the INVERSE of publish, which resolves AFTER its
|
|
162
|
+
// confirmation (`publish/command.ts`), and the two must not be "unified": on
|
|
163
|
+
// publish the version set is never shown, so it rides along with bytes the
|
|
164
|
+
// creator already decided to upload and resolving before the prompt would mean
|
|
165
|
+
// a REFUSED publish had read their `node_modules` for nothing. Here the set IS
|
|
166
|
+
// part of what is being consented to, so a value not resolved cannot be shown
|
|
167
|
+
// and a value not shown cannot be consented to — resolving after the ask would
|
|
168
|
+
// transmit a field the creator never saw. One rule covers both: resolve at the
|
|
169
|
+
// last moment before the value is needed, and never earlier. On publish that
|
|
170
|
+
// moment is the POST; here it is the SHOW.
|
|
171
|
+
//
|
|
172
|
+
// The gate is already passed, so a refused report has read nothing at all.
|
|
173
|
+
//
|
|
174
|
+
// The `.catch` belongs HERE and not in the resolver: the resolver's `undefined`
|
|
175
|
+
// already means "nothing to say", so swallowing inside it would make a genuine
|
|
176
|
+
// fault indistinguishable from a legitimate empty tree inside the sole
|
|
177
|
+
// producer. At this call site the meaning is exact — a fault yields the same
|
|
178
|
+
// omission an empty project yields, and neither can fail a report.
|
|
179
|
+
const builtWith = await (deps.builtWith ?? (() => resolveBuiltWith(deps.cwd ?? process.cwd())))().catch(() => undefined);
|
|
180
|
+
const report = buildObservationReport(note, deps.cliVersion(), context, builtWith);
|
|
130
181
|
const unattended = !interactive && enablement.enabled;
|
|
131
182
|
if (unattended) {
|
|
132
183
|
const line = enablementLine(enablement, deps.now());
|
package/dist/dx/send.d.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* that needs server-side scrubbing was built wrong.
|
|
17
17
|
*/
|
|
18
18
|
import type { Cluster } from "./clusters.js";
|
|
19
|
+
import type { BuiltWith } from "../platform/resolve.js";
|
|
19
20
|
/** A finding as it goes on the wire. Mirrors the API's dxFindingRequest. */
|
|
20
21
|
export interface WireFinding {
|
|
21
22
|
kind: "error" | "repetition";
|
|
@@ -29,6 +30,13 @@ export interface WireFinding {
|
|
|
29
30
|
export interface WireReport {
|
|
30
31
|
cliVersion: string;
|
|
31
32
|
platform: string;
|
|
33
|
+
/**
|
|
34
|
+
* What the CLI resolved on this machine at send time. A sibling of
|
|
35
|
+
* `cliVersion` and `platform` at the ROOT: it is a fact about the machine that
|
|
36
|
+
* produced the report, exactly as those two are, and it describes the whole
|
|
37
|
+
* report rather than any one finding. Absent when nothing resolved.
|
|
38
|
+
*/
|
|
39
|
+
builtWith?: BuiltWith;
|
|
32
40
|
findings: WireFinding[];
|
|
33
41
|
}
|
|
34
42
|
/**
|
|
@@ -45,14 +53,24 @@ export declare function platformTag(): string;
|
|
|
45
53
|
* function reads counts and timings, never values. If a future journal field
|
|
46
54
|
* carries content, it must not be added to this mapping without the same
|
|
47
55
|
* scrutiny the skeleton got.
|
|
56
|
+
*
|
|
57
|
+
* `builtWith` is a PARAMETER, not a dep: this function stays synchronous and
|
|
58
|
+
* pure, which is the property its tests rest on. The async resolution — and the
|
|
59
|
+
* `.catch(() => undefined)` that makes a fault an omission — is the caller's.
|
|
48
60
|
*/
|
|
49
|
-
export declare function buildReport(clusters: Cluster[], cliVersion: string, note?: string): WireReport;
|
|
61
|
+
export declare function buildReport(clusters: Cluster[], cliVersion: string, note?: string, builtWith?: BuiltWith): WireReport;
|
|
50
62
|
/**
|
|
51
63
|
* Render the payload for the creator to read BEFORE it is sent.
|
|
52
64
|
*
|
|
53
|
-
* This is
|
|
54
|
-
*
|
|
55
|
-
*
|
|
65
|
+
* This is the payload, formatted, WITH EXACTLY ONE FIELD SUMMARIZED: `builtWith`
|
|
66
|
+
* renders as a single `versions:` line at its own position inside the block.
|
|
67
|
+
* Every other field is its literal bytes, and no second field may join it. The
|
|
68
|
+
* exemption's reasoning and the three properties that bound it are in
|
|
69
|
+
* `platform/versionSummary.ts` — read there rather than restated here, so the two
|
|
70
|
+
* consent screens cannot come to describe it differently.
|
|
71
|
+
*
|
|
72
|
+
* `renderObservation` (`report.ts`) is the other consent screen and carries the
|
|
73
|
+
* identical exemption. NEITHER MAY BE UPDATED WITHOUT THE OTHER.
|
|
56
74
|
*/
|
|
57
75
|
export declare function renderPayload(report: WireReport): string[];
|
|
58
76
|
export interface SendResult {
|
package/dist/dx/send.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { renderPayloadWithVersionSummary, summarizeBuiltWith, versionDisclosureLines, } from "../platform/versionSummary.js";
|
|
1
2
|
/**
|
|
2
3
|
* The server bounds a report at 50 findings and refuses the whole thing past
|
|
3
4
|
* that, so the worst clusters are sent rather than the request rejected. They
|
|
@@ -16,8 +17,12 @@ export function platformTag() {
|
|
|
16
17
|
* function reads counts and timings, never values. If a future journal field
|
|
17
18
|
* carries content, it must not be added to this mapping without the same
|
|
18
19
|
* scrutiny the skeleton got.
|
|
20
|
+
*
|
|
21
|
+
* `builtWith` is a PARAMETER, not a dep: this function stays synchronous and
|
|
22
|
+
* pure, which is the property its tests rest on. The async resolution — and the
|
|
23
|
+
* `.catch(() => undefined)` that makes a fault an omission — is the caller's.
|
|
19
24
|
*/
|
|
20
|
-
export function buildReport(clusters, cliVersion, note) {
|
|
25
|
+
export function buildReport(clusters, cliVersion, note, builtWith) {
|
|
21
26
|
const findings = clusters.slice(0, MAX_SENT_FINDINGS).map((c) => {
|
|
22
27
|
const classes = [
|
|
23
28
|
...new Set(c.entries.map((e) => e.err).filter((e) => e !== null)),
|
|
@@ -38,23 +43,35 @@ export function buildReport(clusters, cliVersion, note) {
|
|
|
38
43
|
if (note !== undefined && note.trim() !== "" && findings.length > 0) {
|
|
39
44
|
findings[0].note = note.trim();
|
|
40
45
|
}
|
|
41
|
-
|
|
46
|
+
// THE SUMMARY IS THE AUTHORITY ON WHETHER THE FIELD IS SENT. A version set the
|
|
47
|
+
// summary cannot render is one the creator would never see, and a field shown
|
|
48
|
+
// nowhere may not go on the wire — see `summarizeBuiltWith`.
|
|
49
|
+
const sendable = summarizeBuiltWith(builtWith);
|
|
50
|
+
// `builtWith` sits between `platform` and `findings`, which is where the
|
|
51
|
+
// consent screen renders its summary and where the api reads it.
|
|
52
|
+
return sendable === undefined
|
|
53
|
+
? { cliVersion, platform: platformTag(), findings }
|
|
54
|
+
: { cliVersion, platform: platformTag(), builtWith: sendable, findings };
|
|
42
55
|
}
|
|
43
56
|
/**
|
|
44
57
|
* Render the payload for the creator to read BEFORE it is sent.
|
|
45
58
|
*
|
|
46
|
-
* This is
|
|
47
|
-
*
|
|
48
|
-
*
|
|
59
|
+
* This is the payload, formatted, WITH EXACTLY ONE FIELD SUMMARIZED: `builtWith`
|
|
60
|
+
* renders as a single `versions:` line at its own position inside the block.
|
|
61
|
+
* Every other field is its literal bytes, and no second field may join it. The
|
|
62
|
+
* exemption's reasoning and the three properties that bound it are in
|
|
63
|
+
* `platform/versionSummary.ts` — read there rather than restated here, so the two
|
|
64
|
+
* consent screens cannot come to describe it differently.
|
|
65
|
+
*
|
|
66
|
+
* `renderObservation` (`report.ts`) is the other consent screen and carries the
|
|
67
|
+
* identical exemption. NEITHER MAY BE UPDATED WITHOUT THE OTHER.
|
|
49
68
|
*/
|
|
50
69
|
export function renderPayload(report) {
|
|
51
70
|
const hasNote = report.findings.some((f) => f.note !== undefined && f.note !== "");
|
|
52
71
|
const lines = [
|
|
53
72
|
"This is what would be sent:",
|
|
54
73
|
"",
|
|
55
|
-
...
|
|
56
|
-
.split("\n")
|
|
57
|
-
.map((l) => ` ${l}`),
|
|
74
|
+
...renderPayloadWithVersionSummary(report, report.builtWith).map((l) => ` ${l}`),
|
|
58
75
|
"",
|
|
59
76
|
];
|
|
60
77
|
// The generated fields carry shape only. The NOTE does not, and saying
|
|
@@ -67,6 +84,10 @@ export function renderPayload(report) {
|
|
|
67
84
|
else {
|
|
68
85
|
lines.push(" No paths, no flag values, no error messages, and nothing about which", " project this was.");
|
|
69
86
|
}
|
|
87
|
+
// Entry 2 of the disclosure stack: it explains the one anomalous line on the
|
|
88
|
+
// screen above, so it goes directly under the scope sentence a reader's eye
|
|
89
|
+
// lands on next. Absent entirely when nothing resolved.
|
|
90
|
+
lines.push(...versionDisclosureLines(report.builtWith));
|
|
70
91
|
lines.push(" Your journal stays on this machine.");
|
|
71
92
|
return lines;
|
|
72
93
|
}
|