create-ailk 0.30.0 → 0.32.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 (95) hide show
  1. package/README.md +108 -4
  2. package/component-catalog.json +524 -8
  3. package/component-catalog.md +8 -5
  4. package/dist/body-heading.d.ts +13 -0
  5. package/dist/body-heading.js +19 -5
  6. package/dist/cli.js +99 -0
  7. package/dist/cutover.d.ts +114 -0
  8. package/dist/cutover.js +511 -0
  9. package/dist/design-presets.js +20 -4
  10. package/dist/export-content.d.ts +55 -0
  11. package/dist/export-content.js +303 -0
  12. package/dist/inventory.d.ts +216 -0
  13. package/dist/inventory.js +833 -0
  14. package/dist/language-import.d.ts +127 -0
  15. package/dist/language-import.js +451 -0
  16. package/dist/lib/env-catalog.js +1 -1
  17. package/dist/lib/import-mdx.d.ts +38 -0
  18. package/dist/lib/import-mdx.js +445 -65
  19. package/dist/lib/render-comparison.d.ts +251 -0
  20. package/dist/lib/render-comparison.js +573 -0
  21. package/dist/mdx-braces.d.ts +73 -0
  22. package/dist/mdx-braces.js +285 -0
  23. package/dist/migrate-content.d.ts +6 -2
  24. package/dist/migrate-content.js +62 -14
  25. package/dist/programmatic.js +2 -8
  26. package/dist/seed-workspace.d.ts +3 -1
  27. package/dist/seed-workspace.js +45 -16
  28. package/dist/site-manifest.js +6 -38
  29. package/dist/site-roster.d.ts +46 -3
  30. package/dist/site-roster.js +124 -4
  31. package/dist/surfaces.d.ts +8 -0
  32. package/dist/surfaces.js +16 -0
  33. package/dist/topology.d.ts +12 -0
  34. package/dist/topology.js +24 -10
  35. package/dist/update.d.ts +21 -1
  36. package/dist/update.js +142 -11
  37. package/package.json +3 -3
  38. package/templates/.claude/cloudparity-repo-hooks.json +5 -0
  39. package/templates/.claude/hooks/content-source-guard.sh +75 -0
  40. package/templates/.claude/settings.json +9 -0
  41. package/templates/.github/workflows/ci.yml +4 -0
  42. package/templates/CLAUDE.md +5 -0
  43. package/templates/apps/mcp/.env.example +7 -1
  44. package/templates/apps/mcp/CLAUDE.md +8 -4
  45. package/templates/apps/web/.env.example +4 -2
  46. package/templates/apps/web/CLAUDE.md +16 -5
  47. package/templates/apps/web/__tests__/setup.ts +9 -0
  48. package/templates/apps/web/app/[locale]/[slug]/[detail]/__tests__/page.test.tsx +156 -4
  49. package/templates/apps/web/app/[locale]/[slug]/[detail]/page.tsx +45 -2
  50. package/templates/apps/web/app/[locale]/[slug]/__tests__/page.test.tsx +3 -0
  51. package/templates/apps/web/app/[locale]/about/__tests__/about-routes.test.tsx +3 -0
  52. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +3 -0
  53. package/templates/apps/web/app/[locale]/contact/__tests__/site-contact-lines.test.ts +49 -0
  54. package/templates/apps/web/app/[locale]/contact/site-contact-lines.ts +6 -4
  55. package/templates/apps/web/app/[locale]/events/[slug]/page.tsx +13 -1
  56. package/templates/apps/web/app/[locale]/events/__tests__/program-event.test.tsx +120 -0
  57. package/templates/apps/web/app/[locale]/events/page.tsx +11 -0
  58. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +44 -5
  59. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.thanks-by-tier.test.tsx +306 -0
  60. package/templates/apps/web/app/[locale]/layout.tsx +122 -27
  61. package/templates/apps/web/app/[locale]/pricing/[slug]/__tests__/page.test.tsx +3 -0
  62. package/templates/apps/web/app/[locale]/pricing/__tests__/offerings.test.tsx +3 -0
  63. package/templates/apps/web/app/[locale]/pricing/__tests__/page.test.tsx +3 -0
  64. package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +3 -0
  65. package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +3 -0
  66. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +3 -0
  67. package/templates/apps/web/app/[locale]/team/__tests__/team-routes.test.tsx +3 -0
  68. package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +21 -2
  69. package/templates/apps/web/app/[locale]/thanks/[slug]/page.tsx +1 -0
  70. package/templates/apps/web/app/llms.txt/route.ts +16 -9
  71. package/templates/apps/web/app/sitemap.xml/route.ts +3 -0
  72. package/templates/apps/web/jest.config.cjs +46 -1
  73. package/templates/apps/web/lib/__tests__/bot-check.test.ts +247 -4
  74. package/templates/apps/web/lib/__tests__/in-page-magnet-self-request.test.ts +92 -0
  75. package/templates/apps/web/lib/__tests__/page-chrome.test.ts +93 -0
  76. package/templates/apps/web/lib/__tests__/relay.test.ts +33 -8
  77. package/templates/apps/web/lib/__tests__/site-identity-feature-list.test.ts +196 -0
  78. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +145 -11
  79. package/templates/apps/web/lib/bot-check.ts +113 -13
  80. package/templates/apps/web/lib/in-page-magnet.ts +4 -1
  81. package/templates/apps/web/lib/page-chrome.ts +65 -5
  82. package/templates/apps/web/lib/relay.ts +22 -3
  83. package/templates/apps/web/lib/site-identity.ts +75 -24
  84. package/templates/apps/web/messages/en.json +5 -1
  85. package/templates/apps/web/next.config.mjs +15 -1
  86. package/templates/apps/web/package.json +1 -1
  87. package/templates/apps/web/scripts/assert-content-bundled.ts +36 -8
  88. package/templates/apps/web/scripts/content-snapshot.ts +33 -0
  89. package/templates/apps/web/src/lib/routes.ts +5 -3
  90. package/templates/package.json +3 -1
  91. package/templates/tools/body-heading.ts +440 -0
  92. package/templates/tools/check-content-source.ts +108 -0
  93. package/templates/tools/check-mdx-body.ts +123 -0
  94. package/templates/tools/mdx-braces.ts +303 -0
  95. package/versions.json +48 -48
@@ -0,0 +1,251 @@
1
+ /** The two viewports every page is compared at. */
2
+ export declare const VIEWPORTS: readonly [{
3
+ readonly name: "desktop";
4
+ readonly width: 1440;
5
+ readonly height: 900;
6
+ }, {
7
+ readonly name: "mobile";
8
+ readonly width: 390;
9
+ readonly height: 844;
10
+ }];
11
+ /** Requests in flight at once, for a sweep or a crawl. */
12
+ export declare const IN_FLIGHT = 4;
13
+ /** The page's words: `main`'s rendered text, else `body`'s. */
14
+ export declare const TEXT_EXPRESSION = "(() => { const root = document.querySelector(\"main\") ?? document.body; return root ? root.innerText : \"\"; })()";
15
+ /** Every JSON-LD script's text, in document order. */
16
+ export declare const JSONLD_EXPRESSION = "Array.from(document.querySelectorAll('script[type=\"application/ld+json\"]'), (s) => s.textContent ?? \"\")";
17
+ /**
18
+ * A Language item #10734's kind pull lists on /faq or /glossary after the pins,
19
+ * which step 1 reported as a top-up: removed from the after side only.
20
+ */
21
+ export interface ExcludedItem {
22
+ kind: "faq" | "term";
23
+ slug: string;
24
+ /** The question or term the read renders it with. */
25
+ name: string;
26
+ }
27
+ /** `removed` names every excluded key; `refused` says why nothing was excluded. */
28
+ export type Exclusion = {
29
+ removed: string[];
30
+ } | {
31
+ refused: string;
32
+ };
33
+ /**
34
+ * Plain JS run in the after page, so no transform reaches it. Under the kind
35
+ * pull's position rule (#10734 D3) it removes exactly the reported items, or
36
+ * removes nothing and answers why: FAQs are the tail of the last `faq`
37
+ * section's items, matched by question; terms are the tail of their letter
38
+ * group in the last `glossary-index` section, matched by slug, and a letter
39
+ * group they leave empty goes with its nav link. A reason never carries an
40
+ * item's words.
41
+ */
42
+ export declare const EXCLUDE_SOURCE = "function (document, exclude) {\n var norm = function (s) { return String(s).replace(/\\s+/g, \" \").trim(); };\n var firstLine = function (el) {\n var lines = String(el.innerText || \"\").split(\"\\n\");\n for (var i = 0; i < lines.length; i++) if (norm(lines[i]) !== \"\") return norm(lines[i]);\n return \"\";\n };\n var last = function (selector) {\n var all = document.querySelectorAll(selector);\n return all.length === 0 ? null : all[all.length - 1];\n };\n var byTestId = function (root, id) {\n return Array.from(root.querySelectorAll(\"[data-testid]\")).filter(function (el) { return el.getAttribute(\"data-testid\") === id; });\n };\n var faqs = exclude.filter(function (t) { return t.kind === \"faq\"; });\n var terms = exclude.filter(function (t) { return t.kind === \"term\"; });\n var items = [];\n if (faqs.length > 0) {\n var faq = last('[data-section=\"faq\"]');\n if (faq === null) return { refused: \"no faq section\" };\n var all = Array.from(faq.querySelectorAll('[data-testid=\"faq-item-expanded\"], [data-testid=\"faq-item-accordion\"], [data-testid=\"faq-item-grid\"]'));\n var left = faqs.slice();\n for (var i = Math.max(0, all.length - faqs.length); i < all.length; i++) {\n var line = firstLine(all[i]);\n var at = left.findIndex(function (t) { return norm(t.name) === line; });\n if (at < 0) return { refused: \"item \" + (i + 1) + \" of \" + all.length + \" in the last faq section is not a reported top-up\" };\n left.splice(at, 1);\n items.push(all[i]);\n }\n if (left.length > 0) return { refused: \"the last faq section has \" + all.length + \" item(s), fewer than its \" + faqs.length + \" top-up(s)\" };\n }\n var glossary = null;\n var termItems = [];\n if (terms.length > 0) {\n glossary = last('[data-section=\"glossary-index\"]');\n if (glossary === null) return { refused: \"no glossary-index section\" };\n for (var j = 0; j < terms.length; j++) {\n var links = byTestId(glossary, \"glossary-index-term-\" + terms[j].slug);\n var li = links.length === 1 ? links[0].closest(\"li\") : null;\n if (li === null) return { refused: \"term \" + terms[j].slug + \" is not shown once in the last glossary-index section\" };\n termItems.push(li);\n }\n for (var k = 0; k < terms.length; k++) {\n var siblings = Array.from(termItems[k].parentElement.children);\n var later = siblings.slice(siblings.indexOf(termItems[k]) + 1);\n if (later.some(function (el) { return termItems.indexOf(el) < 0; })) {\n return { refused: \"term \" + terms[k].slug + \" is not at the tail of its letter group\" };\n }\n }\n }\n items.forEach(function (el) { el.remove(); });\n var lists = [];\n termItems.forEach(function (el) {\n if (lists.indexOf(el.parentElement) < 0) lists.push(el.parentElement);\n el.remove();\n });\n lists.forEach(function (ul) {\n if (ul.children.length > 0) return;\n var group = ul.closest(\"section[data-letter]\");\n if (group === null) return;\n var link = byTestId(glossary, \"glossary-index-letter-\" + group.getAttribute(\"data-letter\"))[0];\n group.remove();\n if (link === undefined) return;\n var nav = link.closest(\"nav\");\n link.remove();\n if (nav !== null && nav.children.length === 0) nav.remove();\n });\n return { removed: exclude.map(function (t) { return t.kind + \"/\" + t.slug; }) };\n}";
43
+ /** The expression the after page evaluates: `EXCLUDE_SOURCE` called on a JSON literal. */
44
+ export declare function exclusionExpression(exclude: readonly ExcludedItem[]): string;
45
+ /**
46
+ * The after side's JSON-LD without the top-ups' nodes: per kind, the one array
47
+ * holding a matching node (a `Question` by `name`, a `DefinedTerm` by
48
+ * `termCode`) must end in exactly those nodes and keep one other. The script
49
+ * that changed is re-serialized with `serializeJsonLd`'s rule
50
+ * (`packages/schema/src/json-ld.ts`, restated: no runtime import); every other
51
+ * keeps its string. Anything else is `refused`, with the scripts untouched.
52
+ */
53
+ export declare function excludeJsonLd(scripts: readonly string[], exclude: readonly ExcludedItem[]): {
54
+ scripts: string[];
55
+ } | {
56
+ refused: string;
57
+ };
58
+ /** An image as 8-bit RGBA, row-major. */
59
+ export interface RgbaImage {
60
+ width: number;
61
+ height: number;
62
+ data: Uint8Array;
63
+ }
64
+ export type DecodedPng = {
65
+ ok: true;
66
+ image: RgbaImage;
67
+ } | {
68
+ ok: false;
69
+ reason: "unsupported-png";
70
+ };
71
+ /**
72
+ * An 8-bit RGB or RGBA, non-interlaced PNG as RGBA, every scanline filter
73
+ * undone. Anything else — another depth or colour type, interlacing, a
74
+ * truncated or corrupt stream — is `unsupported-png`.
75
+ */
76
+ export declare function decodePng(png: Buffer): DecodedPng;
77
+ /** How many pixels differ in any channel, or `size-differs`. */
78
+ export declare function diffPixels(a: RgbaImage, b: RgbaImage): number | "size-differs";
79
+ export type TextVerdict = {
80
+ verdict: "same";
81
+ } | {
82
+ verdict: "differs";
83
+ line: number;
84
+ before: string;
85
+ after: string;
86
+ } | {
87
+ verdict: "not-compared";
88
+ };
89
+ export type JsonLdVerdict = {
90
+ verdict: "same";
91
+ } | {
92
+ verdict: "differs";
93
+ index: number;
94
+ before: string | null;
95
+ after: string | null;
96
+ } | {
97
+ verdict: "not-compared";
98
+ };
99
+ export type PixelVerdict = {
100
+ verdict: "same" | "within-tolerance" | "differs";
101
+ differing: number;
102
+ total: number;
103
+ } | {
104
+ verdict: "size-differs" | "unsupported-png" | "not-compared";
105
+ };
106
+ /** Whitespace runs collapsed, then line for line; `differs` names the first differing line (1-based). */
107
+ export declare function compareText(before: string, after: string): TextVerdict;
108
+ /** `same` only when the two arrays are equal string for string — never after a parse. */
109
+ export declare function compareJsonLd(before: readonly string[], after: readonly string[]): JsonLdVerdict;
110
+ /** The slice of a Playwright `Page` the comparison uses. */
111
+ export interface ComparisonPage {
112
+ setViewportSize(size: {
113
+ width: number;
114
+ height: number;
115
+ }): Promise<void>;
116
+ goto(url: string, options: {
117
+ waitUntil: "load";
118
+ timeout: number;
119
+ }): Promise<{
120
+ status(): number;
121
+ } | null>;
122
+ url(): string;
123
+ evaluate(expression: string): Promise<unknown>;
124
+ screenshot(options: {
125
+ fullPage: true;
126
+ animations: "disabled";
127
+ }): Promise<Buffer>;
128
+ }
129
+ export interface ViewportComparison {
130
+ viewport: string;
131
+ /** `ok`, or why that side did not render. */
132
+ render: {
133
+ before: string;
134
+ after: string;
135
+ };
136
+ text: TextVerdict;
137
+ jsonLd: JsonLdVerdict;
138
+ /** Absent when the comparison takes no screenshots (the verify step). */
139
+ pixels?: PixelVerdict;
140
+ /** `afterFull`: the after page before its exclusion, for the eye read. */
141
+ shots?: {
142
+ before: string;
143
+ after: string;
144
+ afterFull?: string;
145
+ };
146
+ /** Present only on a page with top-ups whose after side rendered (#10753). */
147
+ exclusion?: Exclusion;
148
+ }
149
+ export interface PageComparison {
150
+ path: string;
151
+ n: number;
152
+ pass: boolean;
153
+ viewports: ViewportComparison[];
154
+ /** The excluded top-ups' keys, when every viewport removed them (#10753). */
155
+ excluded?: string[];
156
+ }
157
+ interface Side {
158
+ origin: string;
159
+ page: ComparisonPage;
160
+ }
161
+ export interface ComparePageOptions {
162
+ path: string;
163
+ /** The page's number in the run, naming its screenshots. */
164
+ n: number;
165
+ before: Side;
166
+ after: Side;
167
+ /** The share of pixels that may differ; `0` is exact. */
168
+ tolerance: number;
169
+ screenshots: boolean;
170
+ /** Stores one screenshot under `shots/<name>`. */
171
+ writeShot: (name: string, png: Buffer) => void;
172
+ /** The top-ups removed from the after side before it is read (#10753); `[]` is none. */
173
+ exclude?: readonly ExcludedItem[];
174
+ }
175
+ /** One frozen URL at every viewport, before against after. */
176
+ export declare function comparePage(o: ComparePageOptions): Promise<PageComparison>;
177
+ /** `fn` over `items`, at most `limit` at once; results in input order. */
178
+ export declare function mapLimit<T, R>(items: readonly T[], limit: number, fn: (item: T) => Promise<R>): Promise<R[]>;
179
+ export interface SweepResult {
180
+ pass: boolean;
181
+ /** One row per path, in the order given; `status` 0 for a network failure. */
182
+ results: {
183
+ path: string;
184
+ status: number;
185
+ }[];
186
+ }
187
+ /** Every path on `origin` must answer 200 directly; a redirect is not a 200. */
188
+ export declare function sweep(o: {
189
+ origin: string;
190
+ paths: readonly string[];
191
+ fetch: typeof globalThis.fetch;
192
+ headers?: Record<string, string>;
193
+ }): Promise<SweepResult>;
194
+ export interface ComparisonReport {
195
+ before: string;
196
+ after: string;
197
+ pages: PageComparison[];
198
+ sweep: SweepResult;
199
+ }
200
+ /** The eye-read table: one row per URL and viewport, linking its screenshot pair. */
201
+ export declare function comparisonMarkdown(report: ComparisonReport): string;
202
+ /** `comparison.json` and `comparison.md` in `dir`; the screenshots are already in `dir/shots`. */
203
+ export declare function writeComparisonEvidence(dir: string, report: ComparisonReport): void;
204
+ /** The slice of a Playwright route the header scoping uses. */
205
+ interface RouteLike {
206
+ request(): {
207
+ url(): string;
208
+ headers(): Record<string, string>;
209
+ };
210
+ continue(options?: {
211
+ headers?: Record<string, string>;
212
+ }): Promise<void>;
213
+ }
214
+ /** The slice of Playwright's `chromium` the comparison launches. */
215
+ export interface ChromiumLike {
216
+ launch(): Promise<{
217
+ newContext(): Promise<{
218
+ newPage(): Promise<ComparisonPage & {
219
+ route(url: string, handler: (route: RouteLike) => Promise<void>): Promise<void>;
220
+ }>;
221
+ }>;
222
+ close(): Promise<void>;
223
+ }>;
224
+ }
225
+ export declare const PLAYWRIGHT_INSTALL_HINT = "pnpm add -D @playwright/test && pnpm exec playwright install chromium";
226
+ /** `chromium` from `playwright` or `@playwright/test`, resolved from each dir in turn. */
227
+ export declare function loadChromium(dirs: readonly string[]): ChromiumLike | undefined;
228
+ export interface RunComparisonOptions {
229
+ chromium: ChromiumLike;
230
+ before: string;
231
+ after: string;
232
+ /** Headers the `after` origin's requests carry (a preview's protection bypass). */
233
+ afterHeaders?: Record<string, string>;
234
+ /** The frozen page paths compared in the browser. */
235
+ pages: readonly string[];
236
+ /** Every frozen path swept on `after`. */
237
+ sweepPaths: readonly string[];
238
+ tolerance: number;
239
+ screenshots: boolean;
240
+ /** Where `comparison.json`, `comparison.md` and `shots/` go. */
241
+ dir: string;
242
+ fetch: typeof globalThis.fetch;
243
+ onLine?: (line: string) => void;
244
+ /** Per frozen page path, the top-ups its after side leaves out (#10753). */
245
+ exclude?: Readonly<Record<string, readonly ExcludedItem[]>>;
246
+ }
247
+ /** Every page compared, the sweep run, the evidence written; `pass` gates the step. */
248
+ export declare function runComparison(o: RunComparisonOptions): Promise<ComparisonReport & {
249
+ pass: boolean;
250
+ }>;
251
+ export {};