@tokenoftrust/cli 4.3.0 → 4.4.1
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/cli",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.1",
|
|
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/pr.mjs
CHANGED
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
import { createMcpClient } from "../mcp.mjs";
|
|
37
37
|
import { establishSession, AuthUnavailableError } from "../auth.mjs";
|
|
38
38
|
import { fail } from "../errors.mjs";
|
|
39
|
+
import { previewPrUrl } from "../preview-url.mjs";
|
|
39
40
|
import { scopeToTenant } from "../tenant-scope.mjs";
|
|
40
41
|
import { repoNameFromRemote, currentBranch } from "./submit.mjs";
|
|
41
42
|
// `tot pr list` reuses `tot ship --pr`'s operator-secret transport helpers verbatim
|
|
@@ -53,7 +54,7 @@ const DEFAULT_STOREFRONT_URL = "https://storefront.tokenoftrust.store";
|
|
|
53
54
|
const SUBCOMMANDS = ["list", "view", "close"];
|
|
54
55
|
|
|
55
56
|
/**
|
|
56
|
-
* The storefront-owned, shareable
|
|
57
|
+
* The storefront-owned, shareable `/<tenant>/preview/pr/<N>` link — NEVER the
|
|
57
58
|
* forge/Gitea `url` (2026-08-18 incident: a raw forge PR URL reached an
|
|
58
59
|
* owner). `candidate_status` (the local-checkout MCP tool) has no
|
|
59
60
|
* `previewUrl` field at all, unlike the operator `GET /api/changes` path — so
|
|
@@ -66,8 +67,7 @@ const SUBCOMMANDS = ["list", "view", "close"];
|
|
|
66
67
|
*/
|
|
67
68
|
export function buildPreviewUrl(storefrontUrl, tenant, prNumber) {
|
|
68
69
|
if (typeof prNumber !== "number" || !tenant) return null;
|
|
69
|
-
|
|
70
|
-
return `${base}/preview/${tenant}/pr/${prNumber}`;
|
|
70
|
+
return previewPrUrl(storefrontUrl || DEFAULT_STOREFRONT_URL, tenant, prNumber);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
const USAGE = `tot pr — see and manage candidate PRs
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
*/
|
|
31
31
|
import { execFileSync } from "node:child_process";
|
|
32
32
|
import { planForAction, printPlanAndConfirm } from "../plan.mjs";
|
|
33
|
+
import { previewPrUrl } from "../preview-url.mjs";
|
|
33
34
|
|
|
34
35
|
const DEFAULT_STOREFRONT_URL = "https://storefront.tokenoftrust.store";
|
|
35
36
|
|
|
@@ -147,7 +148,7 @@ export async function run(argv, ctx) {
|
|
|
147
148
|
return 2;
|
|
148
149
|
}
|
|
149
150
|
|
|
150
|
-
const previewUrl = pr != null ?
|
|
151
|
+
const previewUrl = pr != null ? previewPrUrl(base, tenant, pr) : null;
|
|
151
152
|
|
|
152
153
|
// Self-declaring: state the EXACT plan before acting (the shared plan module).
|
|
153
154
|
const planLines = planForAction({
|
|
@@ -39,6 +39,7 @@ import { fail } from "../errors.mjs";
|
|
|
39
39
|
import { scopeToTenant } from "../tenant-scope.mjs";
|
|
40
40
|
import { isInteractive, promptYesNo } from "../prompt.mjs";
|
|
41
41
|
import { startProgress } from "../progress.mjs";
|
|
42
|
+
import { previewRevUrl } from "../preview-url.mjs";
|
|
42
43
|
import { resolveViewerTransport } from "../viewer-session.mjs";
|
|
43
44
|
|
|
44
45
|
const DEFAULT_MCP_URL = "https://mcp.tokenoftrust.com";
|
|
@@ -145,7 +146,7 @@ export function normalizeRollbackResult(r) {
|
|
|
145
146
|
// ─── Revision links (pure) ───────────────────────────────────────────────
|
|
146
147
|
|
|
147
148
|
/**
|
|
148
|
-
* The immutable
|
|
149
|
+
* The immutable `/<tenant>/preview/rev/<sha>/` deep-link for a promoted
|
|
149
150
|
* `versionId` — this route, and per `gitea-only-tenant-content-authority` the
|
|
150
151
|
* `versionId` recorded by a promote IS the tenant Gitea sha, so no extra lookup
|
|
151
152
|
* is needed to build it. `null` when there's no `base` (storefront origin) or no
|
|
@@ -158,7 +159,7 @@ export function normalizeRollbackResult(r) {
|
|
|
158
159
|
*/
|
|
159
160
|
export function revisionUrl(base, tenant, versionId) {
|
|
160
161
|
if (!base || !versionId) return null;
|
|
161
|
-
return
|
|
162
|
+
return previewRevUrl(base, tenant, versionId);
|
|
162
163
|
}
|
|
163
164
|
|
|
164
165
|
// ─── Candidate selection + rendering (pure) ──────────────────────────────────────
|
package/src/commands/submit.mjs
CHANGED
|
@@ -57,6 +57,7 @@ import { startProgress } from "../progress.mjs";
|
|
|
57
57
|
import { CliError, fail } from "../errors.mjs";
|
|
58
58
|
import { isBrokerUnavailable, waitOutBrokerOutage } from "../broker-outage.mjs";
|
|
59
59
|
import { emitActivity } from "../activity.mjs";
|
|
60
|
+
import { previewPrUrl } from "../preview-url.mjs";
|
|
60
61
|
import {
|
|
61
62
|
diagnosticReference,
|
|
62
63
|
invocationDegradedResult,
|
|
@@ -2096,7 +2097,7 @@ function reportComplianceCheck(c) {
|
|
|
2096
2097
|
* formatShareableUrlBlock's job, above — the two are deliberately redundant:
|
|
2097
2098
|
* this one is available immediately but "building", that one is authoritative
|
|
2098
2099
|
* once reconcile actually lands). Same route shape as the server-minted one
|
|
2099
|
-
* (
|
|
2100
|
+
* (`/<tenant>/preview/pr/<N>`) by construction — see
|
|
2100
2101
|
* docs/architecture/preview-candidate-workflow.md — so the link doesn't change
|
|
2101
2102
|
* out from under the reviewer once the build completes; it just starts
|
|
2102
2103
|
* resolving.
|
|
@@ -2108,7 +2109,7 @@ function reportComplianceCheck(c) {
|
|
|
2108
2109
|
* @returns {string}
|
|
2109
2110
|
*/
|
|
2110
2111
|
export function shareablePrUrl(base, tenant, prNumber) {
|
|
2111
|
-
return
|
|
2112
|
+
return previewPrUrl(base, tenant, prNumber) ?? "";
|
|
2112
2113
|
}
|
|
2113
2114
|
|
|
2114
2115
|
/**
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one builder of preview URLs. A store's preview lives under the store, never under a
|
|
3
|
+
* `/preview/<store>` prefix:
|
|
4
|
+
*
|
|
5
|
+
* <origin>/<store>/preview the store's current shared preview
|
|
6
|
+
* <origin>/<store>/preview/pr/<n> candidate PR n
|
|
7
|
+
* <origin>/<store>/preview/rev/<sha>/ an immutable revision
|
|
8
|
+
*
|
|
9
|
+
* Every producer (Worker, CLI) calls these; `apps/storefront/src/pages/preview/preview-url-routes.test.ts`
|
|
10
|
+
* resolves each shape against the page route table. `packages/cli/src/preview-url.mjs` is a
|
|
11
|
+
* byte-identical copy (a CLI test pins it).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** The canonical shapes, for messages that name the right URL. */
|
|
15
|
+
export const PREVIEW_URL_SHAPE = "/<store>/preview[/pr/<n>]";
|
|
16
|
+
|
|
17
|
+
function trimOrigin(origin) {
|
|
18
|
+
return String(origin).trim().replace(/\/+$/, "");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** `/<store>/preview` with an optional sub-path (`/pr/5`). */
|
|
22
|
+
export function previewPath(store, subPath = "") {
|
|
23
|
+
return `/${encodeURIComponent(store)}/preview${subPath}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function previewUrl(origin, store) {
|
|
27
|
+
return `${trimOrigin(origin)}${previewPath(store)}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** `null` when `prNumber` is not a positive integer. */
|
|
31
|
+
export function previewPrUrl(origin, store, prNumber) {
|
|
32
|
+
const n = Number(prNumber);
|
|
33
|
+
if (!Number.isInteger(n) || n < 1) return null;
|
|
34
|
+
return `${trimOrigin(origin)}${previewPath(store, `/pr/${n}`)}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function previewRevUrl(origin, store, versionId) {
|
|
38
|
+
return `${trimOrigin(origin)}${previewPath(store, `/rev/${encodeURIComponent(versionId)}/`)}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The canonical location for a legacy `/preview/<store>[/pr/<n>[/...]]` request, or `null` when
|
|
43
|
+
* the path is not that shape. `isStore` decides whether the first segment names a store
|
|
44
|
+
* (the caller owns what a store id looks like), so `/preview/pr/5`, `/preview/rev/...` and the
|
|
45
|
+
* other first-segment routes are never read as a store.
|
|
46
|
+
*/
|
|
47
|
+
export function legacyPreviewLocation(pathname, search, isStore) {
|
|
48
|
+
const m = /^\/preview\/([^/]+)(\/.*)?$/.exec(pathname);
|
|
49
|
+
if (!m) return null;
|
|
50
|
+
const store = m[1];
|
|
51
|
+
if (!/^[A-Za-z0-9.-]+$/.test(store) || !isStore(store)) return null;
|
|
52
|
+
const rest = (m[2] ?? "").replace(/\/+$/, "");
|
|
53
|
+
return `/${store}/preview${rest}${search ?? ""}`;
|
|
54
|
+
}
|