@tokenoftrust/cli 2.4.10 → 2.4.11

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": "2.4.10",
3
+ "version": "2.4.11",
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",
@@ -36,6 +36,7 @@ import { fail } from "../errors.mjs";
36
36
  import { planForAction, printPlanAndConfirm } from "../plan.mjs";
37
37
  import { startProgress } from "../progress.mjs";
38
38
  import { openBrowser } from "../open.mjs";
39
+ import { missingGrantsOf, refusalHint } from "../admission-hint.mjs";
39
40
  // Reuse `tot ship`'s operator-secret precedence + live-url derivation verbatim so
40
41
  // ship + hotfix speak ONE operator-auth contract.
41
42
  import { resolveOperatorSecret, liveUrlFor } from "./ship.mjs";
@@ -245,18 +246,27 @@ export async function runHotfix(
245
246
  return 1;
246
247
  }
247
248
  const planData = await readJsonSafe(planRes);
248
- if (!planRes.ok && planData?.ok !== false) {
249
+ if (planRes.status === 401 || planRes.status === 403) {
250
+ // The admission rule's own refusal (body shape `{ ok:false, error,
251
+ // admission:{ missing } }`) — name exactly what's missing instead of a
252
+ // generic "check the operator secret" when the server named one.
249
253
  const msg = planData?.error || `HTTP ${planRes.status}`;
254
+ const missing = missingGrantsOf(planData);
250
255
  console.error(
251
256
  fail(
252
257
  `the hotfix plan was refused: ${msg}`,
253
- planRes.status === 401 || planRes.status === 403
254
- ? "check the operator secret and that it's authorised for this tenant"
255
- : "check --tenant / --url / --pr, then re-run",
258
+ missing.length > 0 ? refusalHint(planRes.status, missing) : "check the operator secret and that it's authorised for this tenant",
256
259
  ),
257
260
  );
258
261
  return 1;
259
262
  }
263
+ if (!planRes.ok && planData?.ok !== false) {
264
+ // A transport-level refusal the endpoint didn't explain in its own shape
265
+ // (tenant-resolution/network) — surface it honestly.
266
+ const msg = planData?.error || `HTTP ${planRes.status}`;
267
+ console.error(fail(`the hotfix plan was refused: ${msg}`, "check --tenant / --url / --pr, then re-run"));
268
+ return 1;
269
+ }
260
270
  const plan = normalizeHotfixPlan(planData);
261
271
  if (!plan.ok) {
262
272
  console.error(fail(plan.message, hotfixNextStep(plan.reason)));
@@ -41,6 +41,7 @@ import { repoNameFromRemote, currentBranch } from "./submit.mjs";
41
41
  // `tot pr list` reuses `tot ship --pr`'s operator-secret transport helpers verbatim
42
42
  // (same env precedence, same queue normalisation) so the two speak one wire, not two.
43
43
  import { resolveOperatorSecret, normalizeChangesQueue } from "./ship.mjs";
44
+ import { missingGrantsOf, refusalHint } from "../admission-hint.mjs";
44
45
  import {
45
46
  defaultCandidateStatePath,
46
47
  readActiveChangeId,
@@ -280,11 +281,14 @@ export async function runPrListOperator({ tenant, secret, storefrontUrl = null }
280
281
  }
281
282
  if (!res.ok) {
282
283
  const msg = data?.error || `HTTP ${res.status}`;
284
+ const missing = missingGrantsOf(data);
283
285
  console.error(
284
286
  fail(
285
287
  `the candidate queue refused the request: ${msg}`,
286
288
  res.status === 401 || res.status === 403
287
- ? "check the operator secret and that it's authorised for this tenant"
289
+ ? missing.length > 0
290
+ ? refusalHint(res.status, missing)
291
+ : "check the operator secret and that it's authorised for this tenant"
288
292
  : "check --tenant / --url, then re-run",
289
293
  ),
290
294
  );
@@ -14,6 +14,7 @@
14
14
  * Treat the token like a password: anyone holding it sees the unpublished site.
15
15
  */
16
16
  import { resolveViewerTransport } from "../viewer-session.mjs";
17
+ import { missingGrantsOf, refusalHint } from "../admission-hint.mjs";
17
18
 
18
19
  const MAX_TTL_SECONDS = 24 * 3600;
19
20
 
@@ -107,19 +108,19 @@ export async function run(argv, ctx, deps = {}) {
107
108
  return 1;
108
109
  }
109
110
 
111
+ let data = null;
112
+ try {
113
+ data = await res.json();
114
+ } catch {
115
+ /* non-JSON error body — this endpoint answers 401/403 as plain text */
116
+ }
110
117
  if (res.status === 401 || res.status === 403) {
111
118
  err(
112
119
  `✗ your account can't view ${tenant}'s preview.\n\n` +
113
- " → next: ask the store owner for developer access (or check `tot whoami`), then re-run.",
120
+ ` → next: ${refusalHint(res.status, missingGrantsOf(data))}.`,
114
121
  );
115
122
  return 1;
116
123
  }
117
- let data = null;
118
- try {
119
- data = await res.json();
120
- } catch {
121
- /* non-JSON error body */
122
- }
123
124
  if (!res.ok || typeof data?.token !== "string") {
124
125
  err(`✗ the store didn't mint a token (HTTP ${res.status}).\n\n → next: retry shortly; if it persists, report it with \`tot feedback\`.`);
125
126
  return 1;