@tokenoftrust/cli 2.0.28 → 2.1.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.
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Readiness checks for a store, derived from per-page gate results and the style-concept audit.
3
+ *
4
+ * One page or every page: the same derivation serves `tot report go-live` for a single store and
5
+ * the Agency Kit's site-wide validation, so the two never describe a gate differently.
6
+ *
7
+ * A page result is `{ gates: { <gate>: true | false | null } }`: true passed, false failed, null
8
+ * not measured in this run.
9
+ */
10
+ export function countGate(results, gateName) {
11
+ return results.reduce(
12
+ (counts, page) => {
13
+ const value = page.gates?.[gateName];
14
+ if (value === true) counts.pass += 1;
15
+ else if (value === false) counts.fail += 1;
16
+ else counts.unknown += 1;
17
+ return counts;
18
+ },
19
+ { pass: 0, fail: 0, unknown: 0, total: results.length },
20
+ );
21
+ }
22
+
23
+ export function combineStatus(counts, { unknownIsWarn = false } = {}) {
24
+ if (counts.fail > 0) return "FAIL";
25
+ if (counts.unknown > 0) return unknownIsWarn ? "WARN" : "UNKNOWN";
26
+ return "PASS";
27
+ }
28
+
29
+ export function combineCounts(...countsList) {
30
+ return countsList.reduce(
31
+ (out, counts) => ({
32
+ pass: out.pass + counts.pass,
33
+ fail: out.fail + counts.fail,
34
+ unknown: out.unknown + counts.unknown,
35
+ total: out.total + counts.total,
36
+ }),
37
+ { pass: 0, fail: 0, unknown: 0, total: 0 },
38
+ );
39
+ }
40
+
41
+ export function gateProblem(label, counts) {
42
+ if (counts.total === 0) return "No pages were discovered for validation.";
43
+ if (counts.fail > 0) return `${counts.fail}/${counts.total} ${label} gate checks failed.`;
44
+ if (counts.unknown > 0) return `${counts.unknown}/${counts.total} ${label} gate checks were not run in this pass.`;
45
+ return `All ${counts.total} page checks passed.`;
46
+ }
47
+
48
+ export function styleGuideCheck(styleJson, manifestPath) {
49
+ const gate = styleJson?.gates?.styleGuide;
50
+ const manifest = styleJson?.manifest;
51
+ const manifestPresent = manifest?.present === true;
52
+ if (!gate) {
53
+ return {
54
+ area: "Style guide",
55
+ status: "UNKNOWN",
56
+ why: "Business owners need one approved visual reference before the look is rolled across the sitemap.",
57
+ problem: "The style-guide gate was not produced by this validation run.",
58
+ fix: "Run the style-concept audit with the store manifest so the guide index and per-theme guide records can be checked.",
59
+ };
60
+ }
61
+ if (!manifestPresent) {
62
+ return {
63
+ area: "Style guide",
64
+ status: "FAIL",
65
+ why: "The style guide is the approved visual contract for brand, accessibility states, responsive behavior, and reusable page patterns.",
66
+ problem: `No migration manifest was found at ${manifestPath}, so style-guide readiness cannot be certified.`,
67
+ fix: "Create the migration manifest with styleGuideIndex and styleGuides records, then validate the preview guide pages.",
68
+ };
69
+ }
70
+ if (!gate.present) {
71
+ return {
72
+ area: "Style guide",
73
+ status: "FAIL",
74
+ why: "Without a guide, page workers can create inconsistent layouts and business reviewers have no single source of truth.",
75
+ problem: "No preview-only style-guide index and standalone theme guide records are present.",
76
+ fix: "Create a preview-only guide index plus one standalone guide per theme; mark them noindex and sitemap-excluded.",
77
+ };
78
+ }
79
+ if (!gate.pass) {
80
+ const missingGuides = gate.themesMissingStandaloneGuide?.length ?? 0;
81
+ const missingLinks = gate.indexMissingThemeLinks?.length ?? 0;
82
+ const guideProblems = gate.problems?.length ?? 0;
83
+ return {
84
+ area: "Style guide",
85
+ status: "FAIL",
86
+ why: "The approved guide prevents brand drift and gives compliance/business reviewers a concrete preview before broad propagation.",
87
+ problem: `Index ${gate.indexRoute ?? "missing"} has ${gate.approvedCurrentCount ?? 0} approved current guide(s), ${missingGuides} theme(s) without standalone guides, ${missingLinks} missing index link(s), and ${guideProblems} protection/status problem(s).`,
88
+ fix: "Link every current/candidate/legacy theme from the index, keep all guide pages preview-only/noindex/out of sitemap, validate them, then approve the current guide.",
89
+ };
90
+ }
91
+ return {
92
+ area: "Style guide",
93
+ status: "PASS",
94
+ why: "The approved guide gives business, design, and engineering reviewers the same reference before remaining pages are restyled.",
95
+ problem: `Index ${gate.indexRoute} links ${gate.indexLinksToThemeIds?.length ?? 0} theme guide(s); ${gate.approvedCurrentCount ?? 0} current guide is approved.`,
96
+ fix: "Keep the guide updated when tokens, components, or approved page patterns change.",
97
+ };
98
+ }
99
+
100
+ export function styleQualityCheck(styleJson) {
101
+ const gates = styleJson?.gates;
102
+ if (!gates) {
103
+ return {
104
+ area: "Theme/CSS quality",
105
+ status: "UNKNOWN",
106
+ why: "Readable shared CSS makes future page work cheaper and reduces visual drift.",
107
+ problem: "The style audit did not produce gate output.",
108
+ fix: "Run the style-concept audit again and inspect its gate output.",
109
+ };
110
+ }
111
+ const failed = [
112
+ "schema",
113
+ "storyboard",
114
+ "theme",
115
+ "css",
116
+ "readability",
117
+ "manifestConventions",
118
+ "acceptedRisks",
119
+ "styleConcept",
120
+ "tokenContrast",
121
+ "guideCoverage",
122
+ ].filter((name) => gates[name] && gates[name].pass !== true);
123
+ return {
124
+ area: "Theme/CSS quality",
125
+ status: failed.length > 0 ? "FAIL" : "PASS",
126
+ why: "A shared, readable tenant theme lets the site be maintained without copying large CSS blobs page by page.",
127
+ problem:
128
+ failed.length > 0
129
+ ? `Failing style gates: ${failed.join(", ")}.`
130
+ : "Theme record, CSS structure, readability, and style-concept drift gates passed.",
131
+ fix:
132
+ failed.length > 0
133
+ ? "Record storyboard/inspiration policy, extract shared tenant CSS, reduce page-local inline CSS, format source for human review, fix text-token contrast/guide coverage, and update manifest hashes/conformance."
134
+ : "Re-run after any theme or page restyle change.",
135
+ };
136
+ }
137
+
138
+ export function ecommerceStyleGuideCheck(styleJson) {
139
+ const gate = styleJson?.gates?.ecommerceStyleGuide;
140
+ if (!gate) {
141
+ return {
142
+ area: "Ecommerce style-guide bar",
143
+ status: "UNKNOWN",
144
+ why: "High-converting ecommerce themes need mobile ergonomics, accessible states, trust patterns, clear CTAs, and speed budgets before broad propagation.",
145
+ problem: "The ecommerce style-guide gate was not produced by this validation run.",
146
+ fix: "Run the style-concept audit with the store manifest.",
147
+ };
148
+ }
149
+ // Components that exist in the guide only as marked placeholders. Named here because they need
150
+ // MERCHANT evidence, not a code change, and the difference decides who the row is addressed to.
151
+ // Without naming them the row reads as a theme-CSS task and the real blocker stays invisible
152
+ // until somebody opens the rendered guide — which is how it was found the first time.
153
+ const unsourced = gate.unsourcedComponents ?? [];
154
+ if (!gate.pass) {
155
+ return {
156
+ area: "Ecommerce style-guide bar",
157
+ status: "FAIL",
158
+ why: "This is the v1.1 hardening checkpoint for accessibility-as-conversion, mobile tap targets, trust/social-proof components, semantic tokens, accessible forms, CTA hierarchy, and performance.",
159
+ problem:
160
+ `Required ${gate.requiredVersion ?? "v1.1"} status is ${gate.status ?? "missing"} with ` +
161
+ `${gate.problemCount ?? 0} theme quality problem(s).` +
162
+ (unsourced.length > 0
163
+ ? ` ${unsourced.length} component(s) appear in the style guide ONLY as marked placeholders` +
164
+ ` and need merchant-supplied evidence: ${unsourced.join(", ")}.`
165
+ : ""),
166
+ fix:
167
+ unsourced.length > 0
168
+ ? "Source the placeholder components from the merchant (real ratings, badges, guarantees, testimonials, logos) before approving. The claimsPolicy forbids fabricating them, so this is a content-gathering task, not a theme-CSS one. Then update theme CSS/descriptor for any remaining quality problems and mark the bar approved."
169
+ : "App/content lane should update the theme CSS, descriptor, and style-guide examples, then mark the ecommerce quality bar approved after validation. Do not fabricate ratings, badges, or guarantees.",
170
+ };
171
+ }
172
+ return {
173
+ area: "Ecommerce style-guide bar",
174
+ status: "PASS",
175
+ why: "The theme/style guide has the conversion-critical patterns needed before applying the look across the sitemap.",
176
+ problem: `${gate.requiredVersion ?? "v1.1"} is approved and current theme quality checks passed.`,
177
+ fix: "Keep this gate current when CTA, form, trust, token, or motion patterns change.",
178
+ };
179
+ }
180
+
181
+ export function buildReadinessChecks({ aggregate, styleJson, args }) {
182
+ const results = aggregate.pages;
183
+ const render = countGate(results, "render");
184
+ const boundary = countGate(results, "boundary");
185
+ const seo = combineCounts(countGate(results, "seo"), countGate(results, "sitemap"));
186
+ const secrets = countGate(results, "secrets");
187
+ const csp = countGate(results, "csp");
188
+ const mobileUx = combineCounts(countGate(results, "mobile"), countGate(results, "ux"));
189
+ const a11y = countGate(results, "a11y");
190
+ const perf = countGate(results, "perf");
191
+ const lowRisk = aggregate.totals.issues.lowRiskOpen ?? 0;
192
+
193
+ return [
194
+ styleGuideCheck(styleJson, args.manifest),
195
+ ecommerceStyleGuideCheck(styleJson),
196
+ styleQualityCheck(styleJson),
197
+ {
198
+ area: "Product boundary",
199
+ status: combineStatus(boundary),
200
+ why: "The marketing site must not accidentally expose catalog, cart, checkout, saved, search, price, inventory, or offer behavior.",
201
+ problem: gateProblem("boundary", boundary),
202
+ fix: "Keep commerce routes reserved/404 for marketing tenants and remove or reclassify product-offer leaks.",
203
+ },
204
+ {
205
+ area: "Route availability",
206
+ status: combineStatus(render),
207
+ why: "Business reviewers and crawlers need each launch page to resolve reliably.",
208
+ problem: gateProblem("render", render),
209
+ fix: "Fix routing/content-provider errors, then rerun validation for failed slugs.",
210
+ },
211
+ {
212
+ area: "SEO and sitemap",
213
+ status: combineStatus(seo),
214
+ why: "Search engines and social previews depend on canonical URLs, descriptions, structured data, and a clean sitemap.",
215
+ problem: gateProblem("SEO/sitemap", seo),
216
+ fix: "Restore required head tags, canonical handling, JSON-LD, and sitemap entries while keeping commerce URLs out.",
217
+ },
218
+ {
219
+ area: "Security and CSP",
220
+ status: combineStatus(combineCounts(secrets, csp), { unknownIsWarn: true }),
221
+ why: "A clean CSP and zero secrets reduce launch risk and protect the tenant brand.",
222
+ problem: `${gateProblem("secret", secrets)} ${gateProblem("CSP", csp)}`,
223
+ fix: "Remove private tokens and fix violating scripts/styles without weakening CSP with unsafe allowances.",
224
+ },
225
+ {
226
+ area: "Accessibility",
227
+ status: combineStatus(a11y),
228
+ why: "Accessible pages are easier to use, reduce legal risk, and matter especially for trust/compliance-oriented buyers.",
229
+ problem: gateProblem("a11y", a11y),
230
+ fix: "Run browser validation, then fix contrast, semantics, keyboard flow, and reduced-motion issues.",
231
+ },
232
+ {
233
+ area: "Performance",
234
+ status: combineStatus(perf),
235
+ why: "Mobile performance affects conversion, perceived trust, and search quality.",
236
+ problem: gateProblem("performance", perf),
237
+ fix: "Run browser validation, then reduce main-thread JS, reserve layout space, and optimize blocking resources.",
238
+ },
239
+ {
240
+ area: "Mobile and UX",
241
+ status: combineStatus(mobileUx),
242
+ why: "Most reviewers and customers will scan the site on smaller screens before trusting it.",
243
+ problem: gateProblem("mobile/UX", mobileUx),
244
+ fix: "Fix viewport, overflow, tap-target, image-alt, landmark, form-label, and mixed-content issues.",
245
+ },
246
+ {
247
+ area: "Low-risk fix loop",
248
+ status: lowRisk > 0 ? "WARN" : "PASS",
249
+ why: "Small safe fixes should be drained before asking owners to accept bigger design or performance tradeoffs.",
250
+ problem: lowRisk > 0 ? `${lowRisk} open low-risk issue(s) remain.` : "No open low-risk issues were reported.",
251
+ fix: "Assign bounded low-risk issues, append fix-log rows, rerun validation, and repeat until zero remain.",
252
+ },
253
+ ];
254
+ }