@tokenoftrust/cli 1.4.1 → 1.5.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.
Files changed (53) hide show
  1. package/README.md +5 -0
  2. package/bin/tot.mjs +58 -79
  3. package/package.json +6 -1
  4. package/src/activity.mjs +15 -14
  5. package/src/app-scaffold.mjs +4 -4
  6. package/src/auth.mjs +13 -5
  7. package/src/candidate-state.mjs +3 -3
  8. package/src/commands/accept.mjs +65 -38
  9. package/src/commands/app/dev.mjs +8 -4
  10. package/src/commands/app/index.mjs +3 -3
  11. package/src/commands/app/scaffold.mjs +1 -1
  12. package/src/commands/branches.mjs +4 -3
  13. package/src/commands/cleanup.mjs +7 -11
  14. package/src/commands/clone.mjs +23 -20
  15. package/src/commands/dev.mjs +42 -24
  16. package/src/commands/doctor.mjs +4 -4
  17. package/src/commands/git-credential.mjs +2 -2
  18. package/src/commands/go-live.mjs +9 -5
  19. package/src/commands/grants.mjs +7 -5
  20. package/src/commands/hotfix.mjs +1 -1
  21. package/src/commands/ideas.mjs +2 -2
  22. package/src/commands/link.mjs +2 -2
  23. package/src/commands/login.mjs +5 -6
  24. package/src/commands/pr.mjs +33 -19
  25. package/src/commands/preview-build.mjs +6 -6
  26. package/src/commands/preview-doctor.mjs +225 -0
  27. package/src/commands/preview-retry-evidence.mjs +156 -0
  28. package/src/commands/preview.mjs +19 -3
  29. package/src/commands/revert.mjs +5 -5
  30. package/src/commands/rollback.mjs +18 -16
  31. package/src/commands/ship.mjs +29 -12
  32. package/src/commands/start.mjs +61 -51
  33. package/src/commands/submit.mjs +360 -50
  34. package/src/commands/sync.mjs +2 -2
  35. package/src/commands/validate.mjs +4 -3
  36. package/src/commands/whoami.mjs +1 -1
  37. package/src/dev-heartbeat.mjs +3 -2
  38. package/src/dev-logs.mjs +2 -2
  39. package/src/errors.mjs +11 -4
  40. package/src/git-credential.mjs +94 -21
  41. package/src/last-tenant.mjs +1 -1
  42. package/src/mcp.mjs +6 -1
  43. package/src/merge-doctor-report.mjs +208 -0
  44. package/src/oauth.mjs +18 -14
  45. package/src/obstacle-beacon.cjs +2 -2
  46. package/src/obstacle.mjs +1 -1
  47. package/src/plan.mjs +16 -21
  48. package/src/sample.mjs +4 -4
  49. package/src/validate.mjs +135 -15
  50. package/src/vendor/private-apps-devkit.mjs +3 -3
  51. package/src/viewer-session.mjs +118 -0
  52. package/template/private-app/README.md +12 -6
  53. package/src/commands/retire.mjs +0 -203
@@ -1,203 +0,0 @@
1
- /**
2
- * `tot retire --tenant <t> --pr <N>` — OPERATOR retire/GC (unit U7).
3
- *
4
- * Evict a candidate PR's hosted preview — its `ReviewEnvironment` projection +
5
- * its immutable site version — to RECLAIM SPACE. It calls the
6
- * session-authenticated storefront endpoint `POST /api/preview/retire` (unit
7
- * U7), which deletes the candidate record + prunes the tenant index and evicts
8
- * the candidate version (guarded: it refuses any version a channel points at).
9
- *
10
- * DISTINCT FROM `tot accept` / reject: retire touches NO change lifecycle — the
11
- * change stays open. It is REVERSIBLE-BY-REBUILD: because U1 build-on-demand
12
- * (`tot preview build`) + the U2 fallback page can rematerialize the PR head on
13
- * request, a retired PR degrades to "not built yet", NOT a dead 404.
14
- *
15
- * IDEMPOTENT — retiring an already-absent candidate is a clean success
16
- * (nothing to evict). Unlike `tot preview build`, retire needs NO head sha: it
17
- * targets an EXISTING candidate by `--pr` (→ `pr-<N>`) or an explicit
18
- * `--change-id`.
19
- *
20
- * AUTH — the storefront endpoint accepts the headless Bearer-operator-secret
21
- * path (`resolveOwnerSession` fallback). This verb sends `Authorization: Bearer
22
- * <secret>` (from `PREVIEW_RECONCILE_SECRET` / `GRANTS_ADMIN_SECRET` /
23
- * `TOT_OPERATOR_SECRET`, or `--secret`), `X-Tot-Owner: <tenant>`, and
24
- * `X-Tot-Capability: ship-on-behalf` — mirroring `tot preview build` and the
25
- * `/admin` AdminPublishTab retire call.
26
- *
27
- * SELF-DECLARING — it prints the EXACT plan (which PR → which tenant → evict +
28
- * rebuildable) and confirms before acting (`--yes` to skip; a non-TTY without
29
- * `--yes` aborts rather than acting silently). Retire is INERT re: shared
30
- * channels — it flips no preview/live channel and touches no live pointer — so
31
- * this is a teardown, not a deploy. The plan itself is built by the SHARED plan
32
- * module (`../plan.mjs`, unit U10) — the same affordance every other mutating
33
- * operator verb (build/accept/ship) and the `/admin` confirm dialog use, per
34
- * decision `operator-verb-and-hosting-model`.
35
- *
36
- * Dependency-free (global fetch + the shared plan module).
37
- */
38
- import { planForAction, printPlanAndConfirm } from "../plan.mjs";
39
-
40
- const DEFAULT_STOREFRONT_URL = "https://storefront.tokenoftrust.store";
41
-
42
- /** Parse `tot retire` argv. Pure — unit-testable. */
43
- export function parseRetireArgs(argv) {
44
- const a = {
45
- tenant: null,
46
- pr: null,
47
- changeId: null,
48
- url: null,
49
- secret: null,
50
- yes: false,
51
- help: false,
52
- };
53
- for (let i = 0; i < argv.length; i++) {
54
- const t = argv[i];
55
- if (t === "--tenant") a.tenant = argv[++i];
56
- else if (t === "--pr") a.pr = argv[++i];
57
- else if (t === "--change-id") a.changeId = argv[++i];
58
- else if (t === "--url") a.url = argv[++i];
59
- else if (t === "--secret") a.secret = argv[++i];
60
- else if (t === "--yes" || t === "-y") a.yes = true;
61
- else if (t === "--help" || t === "-h") a.help = true;
62
- }
63
- return a;
64
- }
65
-
66
- export function renderUsage() {
67
- return `tot retire — operator retire/GC: evict a candidate PR's hosted preview
68
-
69
- Usage:
70
- tot retire --tenant <appDomain> --pr <N> [options]
71
- tot retire --tenant <appDomain> --change-id <id> [options]
72
-
73
- Evicts the candidate preview for PR #N of <tenant> — its review environment +
74
- immutable version — to reclaim space. REVERSIBLE: rebuild it any time with
75
- \`tot preview build\` (the change stays open; this is NOT reject). It flips NO
76
- shared channel and is NOT go-live.
77
-
78
- Options:
79
- --tenant <appDomain> Target tenant (e.g. tokenoftrust.com). Defaults to the
80
- current checkout's tenant when run inside one.
81
- --pr <N> PR number to retire (identifies the candidate pr-<N>).
82
- --change-id <id> Optional explicit candidate id (defaults to pr-<N>).
83
- --url <origin> Storefront origin. Defaults to $TOT_STOREFRONT_URL or
84
- ${DEFAULT_STOREFRONT_URL}.
85
- --secret <s> Operator secret. Prefer the env vars below.
86
- --yes, -y Skip the confirmation prompt.
87
- --help, -h Show this help.
88
-
89
- Auth (operator secret, from env, first found):
90
- PREVIEW_RECONCILE_SECRET, GRANTS_ADMIN_SECRET, TOT_OPERATOR_SECRET`;
91
- }
92
-
93
- /**
94
- * @param {string[]} argv
95
- * @param {any} ctx — detected CLI context (ctx.tenant when in a checkout)
96
- */
97
- export async function run(argv, ctx) {
98
- const args = parseRetireArgs(argv);
99
- if (args.help) {
100
- console.log(renderUsage());
101
- return 0;
102
- }
103
-
104
- const base = (args.url || process.env.TOT_STOREFRONT_URL || process.env.STOREFRONT_BASE_URL || DEFAULT_STOREFRONT_URL)
105
- .trim()
106
- .replace(/\/+$/, "");
107
-
108
- const tenant = (args.tenant || ctx?.tenant || "").trim();
109
- if (!tenant) {
110
- console.error(
111
- "✗ no target tenant.\n\n → next: pass --tenant <appDomain> (e.g. --tenant tokenoftrust.com), " +
112
- "or run inside a store checkout.",
113
- );
114
- return 2;
115
- }
116
-
117
- const prRaw = args.pr;
118
- const pr = prRaw != null && `${prRaw}`.trim() && Number.isFinite(Number(prRaw)) ? Number(prRaw) : null;
119
- const changeId = (args.changeId || "").trim() || null;
120
- if (pr == null && !changeId) {
121
- console.error(
122
- "✗ no candidate to retire.\n\n → next: pass --pr <N> (the PR number to retire), or --change-id <id>.",
123
- );
124
- return 2;
125
- }
126
-
127
- const secret = (args.secret || process.env.PREVIEW_RECONCILE_SECRET || process.env.GRANTS_ADMIN_SECRET || process.env.TOT_OPERATOR_SECRET || "").trim();
128
- if (!secret) {
129
- console.error(
130
- "✗ no operator secret.\n\n → next: set PREVIEW_RECONCILE_SECRET (or GRANTS_ADMIN_SECRET / " +
131
- "TOT_OPERATOR_SECRET) in the environment.",
132
- );
133
- return 2;
134
- }
135
-
136
- // Self-declaring: state the EXACT plan before acting (the shared plan module).
137
- const planLines = planForAction({
138
- action: "retire",
139
- tenant,
140
- pr,
141
- changeId,
142
- endpoint: `${base}/api/preview/retire`,
143
- });
144
- const { confirmed, reason } = await printPlanAndConfirm(planLines, {
145
- yes: args.yes,
146
- question: "Retire this preview now?",
147
- });
148
- if (!confirmed) {
149
- if (reason === "non-tty") {
150
- console.error("✗ refusing to retire without confirmation on a non-TTY.\n\n → next: re-run with --yes.");
151
- return 1;
152
- }
153
- console.log("Aborted — nothing was retired.");
154
- return 1;
155
- }
156
-
157
- const body = {
158
- ...(pr != null ? { pr } : {}),
159
- ...(changeId ? { changeId } : {}),
160
- };
161
-
162
- let res;
163
- try {
164
- res = await fetch(`${base}/api/preview/retire`, {
165
- method: "POST",
166
- headers: {
167
- "content-type": "application/json",
168
- authorization: `Bearer ${secret}`,
169
- "x-tot-owner": tenant,
170
- "x-tot-capability": "ship-on-behalf",
171
- },
172
- body: JSON.stringify(body),
173
- });
174
- } catch (e) {
175
- console.error(`✗ could not reach ${base}: ${e?.message || e}\n\n → next: check --url / your network.`);
176
- return 1;
177
- }
178
-
179
- let data = {};
180
- try {
181
- data = await res.json();
182
- } catch {
183
- /* non-JSON error body */
184
- }
185
-
186
- const label = pr != null ? `PR #${pr}` : changeId;
187
-
188
- if (res.ok && data?.ok) {
189
- if (data.evicted) {
190
- console.log(`✓ Retired ${label} for ${tenant} — the preview was evicted (rebuildable on demand).`);
191
- } else {
192
- console.log(`✓ Retire ${label} for ${tenant}: nothing to evict (already retired).`);
193
- }
194
- console.log("\n → rebuild any time: `tot preview build --tenant " + tenant + (pr != null ? " --pr " + pr : "") + "`\n");
195
- return 0;
196
- }
197
-
198
- // A refusal (e.g. 409 version-pinned) or an auth/other failure.
199
- const message = data?.error || `HTTP ${res.status}`;
200
- console.error(`✗ Retire did not succeed (HTTP ${res.status}).`);
201
- console.error(` • ${message}`);
202
- return 1;
203
- }