@tokenoftrust/cli 2.4.15 → 2.4.17

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.15",
3
+ "version": "2.4.17",
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",
@@ -222,6 +222,9 @@ export function readStoreState(statePath) {
222
222
  lifecycle: state?.lifecycle ?? "hot-standby",
223
223
  commerceSync: state?.commerceSync ?? {},
224
224
  goLive: state?.goLive ?? {},
225
+ // Absent/empty reads as "no open requests recorded", never as "nothing needed" — see
226
+ // newMigrationState() in agency-kit/tools/lib/migration-state-template.mjs.
227
+ merchantRequests: Array.isArray(state?.merchantRequests) ? state.merchantRequests : [],
225
228
  };
226
229
  }
227
230
 
@@ -295,7 +298,7 @@ export function run(argv, ctx, io = {}) {
295
298
  }
296
299
 
297
300
  const baseline = readJson(args.baseline ? resolve(args.baseline) : join(store, ".tot", "lighthouse-source-baseline.json"));
298
- const { addOns, lifecycle, commerceSync, goLive } = readStoreState(
301
+ const { addOns, lifecycle, commerceSync, goLive, merchantRequests } = readStoreState(
299
302
  args.state ? resolve(args.state) : join(store, ".tot", "migration-state.json"),
300
303
  );
301
304
  const generatedAt = evidence.generatedAt ?? now();
@@ -313,6 +316,7 @@ export function run(argv, ctx, io = {}) {
313
316
  baselineCapturedAt: baseline?.capturedAt ?? null,
314
317
  commerceSync,
315
318
  goLive,
319
+ merchantRequests,
316
320
  });
317
321
  const outPath = resolve(args.out ?? join(pagesDir, "go-live-report.md"));
318
322
  mkdirSync(dirname(outPath), { recursive: true });
@@ -31,7 +31,7 @@ export const FEATURE_KEYS = [
31
31
  export const SITE_TYPES = ["commerce", "marketing"];
32
32
 
33
33
  /** Every `seo` key (mirrors packages/public-runtime/src/declared-tenant-config.mjs). */
34
- export const SEO_KEYS = Object.freeze(["trailingSlash", "verbatimCatalogTitles", "structuredData"]);
34
+ export const SEO_KEYS = Object.freeze(["trailingSlash", "verbatimSeoTitles", "structuredData"]);
35
35
 
36
36
  /** Every `seo.structuredData` key. */
37
37
  export const STRUCTURED_DATA_KEYS = Object.freeze(["graphs", "names", "reviews", "onlyOn"]);
@@ -86,8 +86,8 @@ export function seoDeclarationProblems(seo) {
86
86
  if (source.trailingSlash !== undefined && typeof source.trailingSlash !== "boolean") {
87
87
  problems.push("`seo.trailingSlash` must be true or false");
88
88
  }
89
- if (source.verbatimCatalogTitles !== undefined && typeof source.verbatimCatalogTitles !== "boolean") {
90
- problems.push("`seo.verbatimCatalogTitles` must be true or false");
89
+ if (source.verbatimSeoTitles !== undefined && typeof source.verbatimSeoTitles !== "boolean") {
90
+ problems.push("`seo.verbatimSeoTitles` must be true or false");
91
91
  }
92
92
  const sd = source.structuredData;
93
93
  if (sd === undefined) return problems;
@@ -511,6 +511,20 @@ export function summariseByProfile(pages) {
511
511
  }).filter((p) => p.dimensions.length > 0);
512
512
  }
513
513
 
514
+ /**
515
+ * Open merchant requests, split into the ones that block go-live and everything else. A request
516
+ * is "open" while its status is `open` or `requested` — `received`/`declined`/`not-needed` are
517
+ * settled and drop out of both lists. Pure, so the report's go/no-go language stays testable.
518
+ * @param {{ id: string, ask: string, why: string, blocks?: string[], sensitive?: boolean, channel?: string, status: string }[]} requests
519
+ */
520
+ export function openMerchantRequests(requests = []) {
521
+ const open = requests.filter((r) => r.status === "open" || r.status === "requested");
522
+ return {
523
+ open,
524
+ blockingGoLive: open.filter((r) => (r.blocks ?? []).includes("go-live")),
525
+ };
526
+ }
527
+
514
528
  /** The whole report. Pure: every input is data, so the output is testable byte-for-byte. */
515
529
  export function buildGoLiveReport(input) {
516
530
  const {
@@ -524,7 +538,12 @@ export function buildGoLiveReport(input) {
524
538
  commerceSync = {},
525
539
  // Recorded go-live checklist confirmations, keyed by GO_LIVE_CHECKLIST.key.
526
540
  goLive = {},
541
+ // Things we are waiting on the MERCHANT for (a credential, a decision, a fix to their own
542
+ // content) — the other direction from `checks`, which is what WE have open. Absent/empty
543
+ // reads as "no open requests recorded", never as "nothing needed".
544
+ merchantRequests = [],
527
545
  } = input;
546
+ const { open: openMerchant, blockingGoLive } = openMerchantRequests(merchantRequests);
528
547
 
529
548
  const parity = summariseParity(pages);
530
549
  const { confirmed, risks, hotStandby } = classifyChecks(checks, { lifecycle });
@@ -828,7 +847,17 @@ export function buildGoLiveReport(input) {
828
847
 
829
848
  out.push("### Blocks Storefront go-live");
830
849
  out.push("");
831
- if (goLiveRisks.length === 0) {
850
+ // An open merchant request that names "go-live" in its `blocks` list is a go-live blocker by
851
+ // the same rule as any other open item here — recording it anywhere else (only in "Waiting on
852
+ // the merchant") would let the verdict read READY while something is still outstanding.
853
+ const merchantGoLiveRows = blockingGoLive.map((r) => [
854
+ r.ask,
855
+ r.why,
856
+ r.sensitive
857
+ ? `Needs you — have the merchant send it via ${r.channel ?? "(no channel recorded)"}.`
858
+ : "Needs you — see **Waiting on the merchant** below.",
859
+ ]);
860
+ if (goLiveRisks.length === 0 && merchantGoLiveRows.length === 0) {
832
861
  out.push("**Nothing.** Every go-live check passed.");
833
862
  } else {
834
863
  out.push(hotStandby
@@ -838,12 +867,35 @@ export function buildGoLiveReport(input) {
838
867
  : "_These must clear before the domain moves._");
839
868
  out.push("");
840
869
  out.push(table(
841
- goLiveRisks.map((c) => [c.area, whyItMatters(c), whoCloses(c)]),
870
+ [...goLiveRisks.map((c) => [c.area, whyItMatters(c), whoCloses(c)]), ...merchantGoLiveRows],
842
871
  ["Area", "Why it matters", "What closes it"],
843
872
  ));
844
873
  }
845
874
  out.push("");
846
875
 
876
+ // ---- What we are waiting on the MERCHANT for — the other direction from everything above,
877
+ // which is work WE have open. Printed even when empty-but-for-blockers would read clean, since
878
+ // a non-blocking open ask (an analytics id, a review-access email) is still something the
879
+ // merchant needs to see asked for in plain language, not buried in an internal tracker.
880
+ if (openMerchant.length > 0) {
881
+ out.push("## Waiting on the merchant");
882
+ out.push("");
883
+ out.push("_These need something from you, not from us — a credential, a decision, or a fix "
884
+ + "to your own content. Nothing below is stored as a secret: a sensitive item records only "
885
+ + "that we asked and that we received it._");
886
+ out.push("");
887
+ out.push(table(
888
+ openMerchant.map((r) => [
889
+ r.ask,
890
+ r.why,
891
+ r.sensitive ? `Send via ${r.channel ?? "(no channel recorded)"}` : (r.channel ? `Via ${r.channel}` : "Let us know"),
892
+ (r.blocks ?? []).includes("go-live") ? "**go-live**" : ((r.blocks ?? []).join(", ") || "—"),
893
+ ]),
894
+ ["What we need", "Why", "How to send it", "Blocks"],
895
+ ));
896
+ out.push("");
897
+ }
898
+
847
899
  // COMMERCE READINESS IS PART OF GO-LIVE, not a section of its own. It was a separate heading
848
900
  // listing the same kind of thing — work that must be closed before the store can open — so a
849
901
  // reader had to hold two lists and de-duplicate them mentally. One list, one place.