@tokenoftrust/cli 2.4.1 → 2.4.2
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/package.json +1 -1
- package/src/commands/submit.mjs +57 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/cli",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.2",
|
|
4
4
|
"description": "Token of Trust developer CLI — clone a tenant store, run it locally with save→reload, and submit it for preview. Installs the `tot` command.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Token of Trust",
|
package/src/commands/submit.mjs
CHANGED
|
@@ -28,11 +28,12 @@
|
|
|
28
28
|
* needed. `--watch` just raises the budget (longer waitMs, more attempts) so an
|
|
29
29
|
* attached dev can stay and watch reconcile+compliance resolve live.
|
|
30
30
|
*
|
|
31
|
-
* Watching through to
|
|
32
|
-
* reconcile resolves cleanly the loop keeps going
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* runs out. A failed reconcile never waits for ship — it can't
|
|
31
|
+
* Watching through to live (E1b): `--watch` also sets untilShipped, so once
|
|
32
|
+
* reconcile resolves cleanly the loop keeps going until the pipeline verdict
|
|
33
|
+
* (`preview_status.verdict`) shows this commit live — on Cloudflare once the
|
|
34
|
+
* pointer flip is proven, on CloudFront once the CI publish is confirmed — or the
|
|
35
|
+
* attempts budget runs out. A failed reconcile never waits for ship — it can't
|
|
36
|
+
* have shipped.
|
|
36
37
|
* Detached devs get the E1 email instead; this is the attached half.
|
|
37
38
|
*
|
|
38
39
|
* Dependency-free (global fetch + `git`).
|
|
@@ -1798,7 +1799,7 @@ export async function run(argv, ctx, {
|
|
|
1798
1799
|
let status;
|
|
1799
1800
|
operation = "preview_status";
|
|
1800
1801
|
if (args.noWait) {
|
|
1801
|
-
status = normalizePreviewStatus(await scoped.callTool("preview_status", { commit: statusSha }));
|
|
1802
|
+
status = normalizePreviewStatus(await scoped.callTool("preview_status", { commit: statusSha }), statusSha);
|
|
1802
1803
|
} else {
|
|
1803
1804
|
// One in-place status line (TTY: a spinner with an elapsed-seconds counter;
|
|
1804
1805
|
// non-TTY: a ~10s heartbeat) instead of a newline per poll — the wait reads
|
|
@@ -1880,10 +1881,10 @@ export async function run(argv, ctx, {
|
|
|
1880
1881
|
/**
|
|
1881
1882
|
* Normalize a `preview_status` tool response to the contract shape the CLI reports
|
|
1882
1883
|
* on: { status, reconcile:{ok,errors}, compliance:{verdict,detail}, previewUrl,
|
|
1883
|
-
* shipped, delivery, dispatched }. A live tool always returns status
|
|
1884
|
+
* shipped, delivery, dispatched, verdict }. A live tool always returns status
|
|
1884
1885
|
* pending|reconciled|failed; anything else (an older MCP without the tool's flat
|
|
1885
1886
|
* fields) normalizes to "unknown" so the CLI degrades visibly instead of pretending
|
|
1886
|
-
* it reconciled. `shipped` (E1b) is null until
|
|
1887
|
+
* it reconciled. `shipped` (E1b) is derived from the verdict: null until this commit is live.
|
|
1887
1888
|
*
|
|
1888
1889
|
* `delivery` is the MCP's reconcile-observability triad for the Gitea webhook that
|
|
1889
1890
|
* fired on this commit ({ target, actual, evidence, drift? }) — or null when NO
|
|
@@ -1893,7 +1894,11 @@ export async function run(argv, ctx, {
|
|
|
1893
1894
|
* read under a different tenant scope than it was pushed to), which is a permanent
|
|
1894
1895
|
* dead-end the CLI must not report as "still reconciling". Pure — unit-tested.
|
|
1895
1896
|
*/
|
|
1896
|
-
|
|
1897
|
+
/**
|
|
1898
|
+
* @param {any} r the `preview_status` response
|
|
1899
|
+
* @param {string|null} [commit] the sha the status was asked for (what `shipped` is decided against)
|
|
1900
|
+
*/
|
|
1901
|
+
export function normalizePreviewStatus(r, commit = null) {
|
|
1897
1902
|
const status = r?.status;
|
|
1898
1903
|
const known = status === "pending" || status === "reconciled" || status === "failed";
|
|
1899
1904
|
const delivery = r?.delivery ?? null;
|
|
@@ -1902,7 +1907,7 @@ export function normalizePreviewStatus(r) {
|
|
|
1902
1907
|
reconcile: r?.reconcile ?? null,
|
|
1903
1908
|
compliance: r?.compliance ?? null,
|
|
1904
1909
|
previewUrl: r?.previewUrl ?? null,
|
|
1905
|
-
shipped: r?.
|
|
1910
|
+
shipped: shippedFromVerdict(r?.verdict ?? null, commit),
|
|
1906
1911
|
delivery,
|
|
1907
1912
|
dispatched: delivery != null,
|
|
1908
1913
|
// The ONE pipeline-status read every surface (console, `preview_status`, `tot
|
|
@@ -1913,6 +1918,26 @@ export function normalizePreviewStatus(r) {
|
|
|
1913
1918
|
};
|
|
1914
1919
|
}
|
|
1915
1920
|
|
|
1921
|
+
/**
|
|
1922
|
+
* Whether the pipeline verdict shows `commit` live, and where: the live release IS this commit, or
|
|
1923
|
+
* the live release is the next release this commit's change was merged into (its membership names
|
|
1924
|
+
* the commit as a change head). Per provider, the verdict's live release is recorded only once
|
|
1925
|
+
* delivery is proven — the Cloudflare pointer flip read back, or the CloudFront CI publish
|
|
1926
|
+
* confirmed. Null when not live (or no verdict). Pure — unit-tested.
|
|
1927
|
+
* @param {any} verdict @param {string|null} commit
|
|
1928
|
+
* @returns {{ sha: string, receiptId: string|null, provider: string|null, provedAt: string|null }|null}
|
|
1929
|
+
*/
|
|
1930
|
+
export function shippedFromVerdict(verdict, commit) {
|
|
1931
|
+
if (!commit || verdict?.kind !== "idle" || !verdict.live?.sha) return null;
|
|
1932
|
+
const live = verdict.live;
|
|
1933
|
+
const merged =
|
|
1934
|
+
verdict.preview?.sha === live.sha &&
|
|
1935
|
+
Array.isArray(verdict.preview?.membership) &&
|
|
1936
|
+
verdict.preview.membership.some((m) => m?.headSha === commit);
|
|
1937
|
+
if (live.sha !== commit && !merged) return null;
|
|
1938
|
+
return { sha: live.sha, receiptId: live.receiptId ?? null, provider: live.provider ?? null, provedAt: live.provedAt ?? null };
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1916
1941
|
/** A submit succeeds only when reconcile has neither failed nor failed to start. */
|
|
1917
1942
|
export function previewSubmitSucceeded(status) {
|
|
1918
1943
|
const knownSuccessState = status?.status === "pending" || status?.status === "reconciled";
|
|
@@ -1971,7 +1996,7 @@ export async function pollPreviewStatus(
|
|
|
1971
1996
|
for (let i = 0; i < attempts; i++) {
|
|
1972
1997
|
const startedAt = now();
|
|
1973
1998
|
const args = waitMs ? { commit, waitMs } : { commit };
|
|
1974
|
-
last = normalizePreviewStatus(await scoped.callTool("preview_status", args));
|
|
1999
|
+
last = normalizePreviewStatus(await scoped.callTool("preview_status", args), commit);
|
|
1975
2000
|
if (last.dispatched) everDispatched = true;
|
|
1976
2001
|
last.everDispatched = everDispatched;
|
|
1977
2002
|
if (onTick) onTick(last, i);
|
|
@@ -2194,6 +2219,25 @@ export function formatVerdictBlock(verdict, supportBundleText = null) {
|
|
|
2194
2219
|
* --json — automation doesn't want a browser popping up). `commit`/`ref`/`verb`
|
|
2195
2220
|
* feed the honest never-dispatched block.
|
|
2196
2221
|
*/
|
|
2222
|
+
/**
|
|
2223
|
+
* The ship line after a watched submit: where this commit is live (per provider) once the verdict
|
|
2224
|
+
* shows it, or that a reviewer still has to accept it. Null when reconcile has not settled. Pure.
|
|
2225
|
+
* @param {{ status?: string, shipped?: { sha: string, receiptId: string|null, provider: string|null, provedAt: string|null }|null }} s
|
|
2226
|
+
*/
|
|
2227
|
+
export function formatShipLine(s) {
|
|
2228
|
+
if (s?.shipped) {
|
|
2229
|
+
const where =
|
|
2230
|
+
s.shipped.provider === "s3-overwrite" ? "CloudFront" : s.shipped.provider === "worker-r2-pointer" ? "Cloudflare" : "your live site";
|
|
2231
|
+
return (
|
|
2232
|
+
`\n ✓ shipped — live at ${s.shipped.sha.slice(0, 9)} on ${where}` +
|
|
2233
|
+
(s.shipped.receiptId ? ` (receipt ${s.shipped.receiptId})` : "") +
|
|
2234
|
+
(s.shipped.provedAt ? `, proven ${s.shipped.provedAt}` : "")
|
|
2235
|
+
);
|
|
2236
|
+
}
|
|
2237
|
+
if (s?.status === "reconciled") return `\n ~ not yet shipped — a reviewer still needs to run change_accept.`;
|
|
2238
|
+
return null;
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2197
2241
|
/**
|
|
2198
2242
|
* @param {any} s @param {string} tenant
|
|
2199
2243
|
* @param {{ open?: boolean, quiet?: boolean, commit?: string|null, ref?: string|null, verb?: string, noChanges?: boolean }} [opts]
|
|
@@ -2249,11 +2293,8 @@ function reportStatus(s, tenant, { open = true, quiet = false, commit = null, re
|
|
|
2249
2293
|
console.log(` compliance: ${s.compliance.verdict}${s.compliance.detail ? ` — ${s.compliance.detail}` : ""}`);
|
|
2250
2294
|
for (const c of s.compliance.checks ?? []) reportComplianceCheck(c);
|
|
2251
2295
|
}
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
} else if (s.status === "reconciled") {
|
|
2255
|
-
console.log(`\n ~ not yet shipped — a reviewer still needs to run change_accept.`);
|
|
2256
|
-
}
|
|
2296
|
+
const shipLine = formatShipLine(s);
|
|
2297
|
+
if (shipLine) console.log(shipLine);
|
|
2257
2298
|
for (const line of formatShareableUrlBlock(s, tenant)) console.log(line);
|
|
2258
2299
|
}
|
|
2259
2300
|
if (s.previewUrl && open && s.status === "reconciled" && openBrowser(s.previewUrl)) {
|