create-ailk 0.27.0 → 0.28.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 (210) hide show
  1. package/README.md +84 -7
  2. package/dist/add-module-core.js +2 -1
  3. package/dist/add-module.d.ts +2 -1
  4. package/dist/add-module.js +9 -4
  5. package/dist/add-route.js +46 -19
  6. package/dist/body-heading.d.ts +56 -0
  7. package/dist/body-heading.js +394 -0
  8. package/dist/cli.js +28 -1
  9. package/dist/content-payloads.js +2 -2
  10. package/dist/derive-architecture.js +8 -2
  11. package/dist/index.d.ts +2 -1
  12. package/dist/index.js +4 -1
  13. package/dist/lib/auth-secret-file.d.ts +6 -2
  14. package/dist/lib/auth-secret-file.js +46 -20
  15. package/dist/lib/env-catalog.js +20 -0
  16. package/dist/lib/site-modules.d.ts +8 -0
  17. package/dist/lib/site-modules.js +34 -0
  18. package/dist/module-architecture.js +23 -14
  19. package/dist/paid-sections.d.ts +17 -0
  20. package/dist/paid-sections.js +39 -0
  21. package/dist/programmatic.d.ts +16 -0
  22. package/dist/programmatic.js +73 -4
  23. package/dist/remove-module.d.ts +98 -0
  24. package/dist/remove-module.js +620 -0
  25. package/dist/seed-flows.d.ts +2 -2
  26. package/dist/seed-flows.js +16 -9
  27. package/dist/site-roster.js +4 -1
  28. package/dist/starter-grid.d.ts +26 -0
  29. package/dist/starter-grid.js +34 -0
  30. package/dist/surfaces.d.ts +26 -0
  31. package/dist/surfaces.js +105 -9
  32. package/dist/sync-routes.d.ts +7 -0
  33. package/dist/sync-routes.js +53 -6
  34. package/dist/template-pages.d.ts +36 -0
  35. package/dist/template-pages.js +129 -0
  36. package/dist/topology.d.ts +2 -1
  37. package/dist/topology.js +4 -2
  38. package/dist/update.d.ts +3 -1
  39. package/dist/update.js +38 -4
  40. package/package.json +3 -3
  41. package/release-scope.json +2 -1
  42. package/site-templates/aliases/hub.TEMPLATE-MANIFEST.json +115 -0
  43. package/templates/.env.example +5 -2
  44. package/templates/apps/api/.env.example +9 -5
  45. package/templates/apps/api/CLAUDE.md +14 -9
  46. package/templates/apps/api/src/__tests__/vercel-handler.test.ts +37 -0
  47. package/templates/apps/api/src/vercel-handler.ts +8 -1
  48. package/templates/apps/mcp/CLAUDE.md +18 -10
  49. package/templates/apps/web/.env.example +25 -11
  50. package/templates/apps/web/CLAUDE.md +45 -4
  51. package/templates/apps/web/__tests__/setup.ts +7 -0
  52. package/templates/apps/web/app/.well-known/mcp.json/route.ts +7 -7
  53. package/templates/apps/web/app/[locale]/(authed)/account/artifacts/[pieceId]/versions/[version]/file/route.ts +61 -2
  54. package/templates/apps/web/app/[locale]/(authed)/account/artifacts/__tests__/file-route.test.ts +81 -0
  55. package/templates/apps/web/app/[locale]/(authed)/account/connect/__tests__/page.test.tsx +197 -0
  56. package/templates/apps/web/app/[locale]/(authed)/account/connect/page.tsx +152 -0
  57. package/templates/apps/web/app/[locale]/about/__tests__/about-routes.test.tsx +2 -2
  58. package/templates/apps/web/app/[locale]/careers/[slug]/[action]/page.tsx +76 -21
  59. package/templates/apps/web/app/[locale]/careers/[slug]/apply.actions.ts +46 -16
  60. package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +20 -3
  61. package/templates/apps/web/app/[locale]/careers/__tests__/apply.actions.test.ts +180 -2
  62. package/templates/apps/web/app/[locale]/careers/__tests__/careers-set.test.tsx +6 -2
  63. package/templates/apps/web/app/[locale]/careers/__tests__/job-apply-page.test.tsx +122 -0
  64. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +1 -1
  65. package/templates/apps/web/app/[locale]/case-studies/[slug]/page.tsx +20 -3
  66. package/templates/apps/web/app/[locale]/case-studies/__tests__/hub-children.test.tsx +65 -0
  67. package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +6 -2
  68. package/templates/apps/web/app/[locale]/case-studies/page.tsx +23 -11
  69. package/templates/apps/web/app/[locale]/checkout/__tests__/thank-you-account.test.tsx +11 -1
  70. package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +1 -1
  71. package/templates/apps/web/app/[locale]/consent.actions.ts +44 -6
  72. package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +4 -4
  73. package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +269 -42
  74. package/templates/apps/web/app/[locale]/contact/contact.actions.ts +88 -30
  75. package/templates/apps/web/app/[locale]/courses/[slug]/page.tsx +20 -3
  76. package/templates/apps/web/app/[locale]/courses/__tests__/hub-children.test.tsx +32 -0
  77. package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +6 -2
  78. package/templates/apps/web/app/[locale]/courses/page.tsx +22 -6
  79. package/templates/apps/web/app/[locale]/events/[slug]/page.tsx +15 -2
  80. package/templates/apps/web/app/[locale]/events/__tests__/hub-children.test.tsx +32 -0
  81. package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +8 -2
  82. package/templates/apps/web/app/[locale]/events/page.tsx +23 -7
  83. package/templates/apps/web/app/[locale]/features/__tests__/hub-children.test.tsx +32 -0
  84. package/templates/apps/web/app/[locale]/features/page.tsx +23 -25
  85. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/flow.actions.test.ts +95 -0
  86. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/save.actions.test.ts +193 -0
  87. package/templates/apps/web/app/[locale]/flows/[slug]/email-moment.ts +92 -0
  88. package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +10 -0
  89. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx +513 -0
  90. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsEmailStep.tsx +122 -0
  91. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/ResultsClient.no-commerce.test.tsx +195 -0
  92. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/landing.test.tsx +293 -0
  93. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/page.test.tsx +397 -0
  94. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/checkout-seam.ts +74 -0
  95. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx +264 -0
  96. package/templates/apps/web/app/[locale]/flows/[slug]/save.actions.ts +75 -0
  97. package/templates/apps/web/app/[locale]/for/__tests__/hub-children.test.tsx +32 -0
  98. package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +5 -1
  99. package/templates/apps/web/app/[locale]/for/page.tsx +23 -25
  100. package/templates/apps/web/app/[locale]/how-to/[slug]/page.tsx +20 -3
  101. package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
  102. package/templates/apps/web/app/[locale]/integrations/[slug]/page.tsx +25 -4
  103. package/templates/apps/web/app/[locale]/integrations/__tests__/hub-children.test.tsx +38 -0
  104. package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +8 -2
  105. package/templates/apps/web/app/[locale]/integrations/page.tsx +23 -11
  106. package/templates/apps/web/app/[locale]/layout.tsx +157 -139
  107. package/templates/apps/web/app/[locale]/page.tsx +51 -3
  108. package/templates/apps/web/app/[locale]/partners/[slug]/page.tsx +16 -3
  109. package/templates/apps/web/app/[locale]/partners/__tests__/hub-children.test.tsx +32 -0
  110. package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +6 -2
  111. package/templates/apps/web/app/[locale]/partners/page.tsx +22 -6
  112. package/templates/apps/web/app/[locale]/portfolio/__tests__/hub-children.test.tsx +38 -0
  113. package/templates/apps/web/app/[locale]/portfolio/page.tsx +25 -24
  114. package/templates/apps/web/app/[locale]/pricing/[slug]/__tests__/page.test.tsx +321 -0
  115. package/templates/apps/web/app/[locale]/pricing/[slug]/page.tsx +87 -0
  116. package/templates/apps/web/app/[locale]/pricing/__tests__/offerings.test.tsx +3 -0
  117. package/templates/apps/web/app/[locale]/pricing/__tests__/pricing-pages.test.tsx +215 -0
  118. package/templates/apps/web/app/[locale]/pricing/page.tsx +56 -341
  119. package/templates/apps/web/app/[locale]/products/[slug]/page.tsx +21 -4
  120. package/templates/apps/web/app/[locale]/products/__tests__/hub-children.test.tsx +32 -0
  121. package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +8 -2
  122. package/templates/apps/web/app/[locale]/products/page.tsx +22 -6
  123. package/templates/apps/web/app/[locale]/projects/__tests__/hub-children.test.tsx +32 -0
  124. package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +4 -0
  125. package/templates/apps/web/app/[locale]/projects/page.tsx +18 -11
  126. package/templates/apps/web/app/[locale]/resources/[slug]/page.tsx +25 -4
  127. package/templates/apps/web/app/[locale]/resources/__tests__/hub-children.test.tsx +38 -0
  128. package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +6 -2
  129. package/templates/apps/web/app/[locale]/resources/page.tsx +20 -10
  130. package/templates/apps/web/app/[locale]/services/[slug]/[child]/page.tsx +68 -0
  131. package/templates/apps/web/app/[locale]/services/[slug]/page.tsx +64 -9
  132. package/templates/apps/web/app/[locale]/services/__tests__/hub-children.test.tsx +158 -0
  133. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +12 -2
  134. package/templates/apps/web/app/[locale]/services/__tests__/service-detail-sections.test.tsx +223 -0
  135. package/templates/apps/web/app/[locale]/services/__tests__/service-families.test.tsx +368 -0
  136. package/templates/apps/web/app/[locale]/services/page.tsx +45 -19
  137. package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
  138. package/templates/apps/web/app/[locale]/team/__tests__/hub-children.test.tsx +32 -0
  139. package/templates/apps/web/app/[locale]/team/page.tsx +25 -24
  140. package/templates/apps/web/app/[locale]/tech-articles/[slug]/page.tsx +24 -3
  141. package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
  142. package/templates/apps/web/app/[locale]/templates/[slug]/__tests__/page.test.tsx +203 -0
  143. package/templates/apps/web/app/[locale]/templates/[slug]/page.tsx +81 -0
  144. package/templates/apps/web/app/[locale]/templates/__tests__/page.test.tsx +236 -0
  145. package/templates/apps/web/app/[locale]/templates/page.tsx +108 -0
  146. package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +1 -1
  147. package/templates/apps/web/app/[locale]/whitepapers/[slug]/page.tsx +24 -3
  148. package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
  149. package/templates/apps/web/app/api/auth/hub/authorize/__tests__/route.test.ts +204 -2
  150. package/templates/apps/web/app/api/auth/hub/authorize/route.ts +151 -33
  151. package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/__tests__/route.test.ts +233 -7
  152. package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +92 -41
  153. package/templates/apps/web/app/api/offerings/checkout/__tests__/route.test.ts +77 -39
  154. package/templates/apps/web/app/llms.txt/route.ts +41 -3
  155. package/templates/apps/web/app/robots.txt/route.ts +5 -1
  156. package/templates/apps/web/app/sitemap.xml/route.ts +45 -3
  157. package/templates/apps/web/auth.ts +102 -50
  158. package/templates/apps/web/components/signed-in-nav.tsx +8 -1
  159. package/templates/apps/web/lib/__tests__/bot-check.test.ts +254 -0
  160. package/templates/apps/web/lib/__tests__/consent-visitor.test.ts +212 -13
  161. package/templates/apps/web/lib/__tests__/deck-page.test.ts +13 -1
  162. package/templates/apps/web/lib/__tests__/docs-index.test.ts +93 -0
  163. package/templates/apps/web/lib/__tests__/docs-path.test.ts +168 -0
  164. package/templates/apps/web/lib/__tests__/hub-children.fixtures.ts +278 -0
  165. package/templates/apps/web/lib/__tests__/nav-label-keys.test.ts +138 -0
  166. package/templates/apps/web/lib/__tests__/newsletter.actions.test.ts +178 -2
  167. package/templates/apps/web/lib/__tests__/offerings-pricing.test.ts +183 -0
  168. package/templates/apps/web/lib/__tests__/page-chrome.test.ts +412 -0
  169. package/templates/apps/web/lib/__tests__/pricing-page.test.ts +142 -0
  170. package/templates/apps/web/lib/__tests__/relay.test.ts +202 -10
  171. package/templates/apps/web/lib/__tests__/route-family.test.ts +57 -0
  172. package/templates/apps/web/lib/__tests__/service-listing.test.ts +111 -0
  173. package/templates/apps/web/lib/__tests__/services-path.test.ts +96 -0
  174. package/templates/apps/web/lib/__tests__/site-header.test.ts +151 -0
  175. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +148 -6
  176. package/templates/apps/web/lib/__tests__/telemetry-scrub.test.ts +84 -0
  177. package/templates/apps/web/lib/__tests__/template-listing.test.ts +158 -0
  178. package/templates/apps/web/lib/auth-secret.ts +57 -0
  179. package/templates/apps/web/lib/bot-check.ts +138 -0
  180. package/templates/apps/web/lib/consent-visitor.ts +81 -7
  181. package/templates/apps/web/lib/deck-page.ts +6 -3
  182. package/templates/apps/web/lib/docs-index.ts +100 -0
  183. package/templates/apps/web/lib/docs-path.ts +94 -0
  184. package/templates/apps/web/lib/hub-children.tsx +318 -0
  185. package/templates/apps/web/lib/modules.generated.ts +9 -0
  186. package/templates/apps/web/lib/nav-label-check.ts +61 -0
  187. package/templates/apps/web/lib/nav-label-keys.ts +78 -0
  188. package/templates/apps/web/lib/newsletter.actions.ts +31 -6
  189. package/templates/apps/web/lib/offerings-pricing.ts +135 -6
  190. package/templates/apps/web/lib/page-chrome.ts +367 -0
  191. package/templates/apps/web/lib/posting-title.ts +26 -4
  192. package/templates/apps/web/lib/pricing-page.ts +430 -0
  193. package/templates/apps/web/lib/relay.ts +31 -106
  194. package/templates/apps/web/lib/route-family.ts +69 -0
  195. package/templates/apps/web/lib/service-listing.ts +89 -0
  196. package/templates/apps/web/lib/services-path.ts +97 -0
  197. package/templates/apps/web/lib/site-header.ts +94 -0
  198. package/templates/apps/web/lib/telemetry-scrub.ts +68 -3
  199. package/templates/apps/web/lib/template-listing.ts +169 -0
  200. package/templates/apps/web/messages/en.json +86 -30
  201. package/templates/apps/web/middleware.ts +171 -11
  202. package/templates/apps/web/next.config.mjs +211 -2
  203. package/templates/apps/web/package.json +1 -1
  204. package/templates/apps/web/scripts/assert-content-bundled.ts +28 -6
  205. package/templates/apps/web/scripts/check-nav-labels.ts +39 -0
  206. package/templates/apps/web/sentry.server.config.ts +30 -2
  207. package/templates/apps/web/src/lib/__tests__/rate-limit.test.ts +67 -9
  208. package/templates/apps/web/src/lib/rate-limit.ts +68 -27
  209. package/templates/apps/web/src/lib/routes.ts +15 -1
  210. package/versions.json +48 -48
@@ -0,0 +1,129 @@
1
+ /**
2
+ * The templates route family's generated seeds (#10051 D10) — the /templates
3
+ * hub seed plus one placeholder TemplatePage per AILK template, derived from
4
+ * `TEMPLATE_MANIFESTS` so a new template never needs a hand-written seed.
5
+ * `add-route templates` writes them (`GENERATED_SEEDS` in `add-route.ts`);
6
+ * the site then edits or deletes them like any content.
7
+ *
8
+ * A seed never invents a fact: its copy is placeholder (`placeholder: true`,
9
+ * renderPage's replace-before-launch label), it has no thumbnail, and its Get
10
+ * it action is `/contact`. Only a template whose `templateDelivery` is
11
+ * `"free"` gets `price: 0` — a paid template's price is the site's to set.
12
+ *
13
+ * Pure (no fs). create-ailk has no YAML dependency by design, so every
14
+ * frontmatter value is written as `JSON.stringify(value)` — a JSON scalar,
15
+ * array or object is valid YAML flow syntax (the `derive-architecture.ts`
16
+ * precedent).
17
+ */
18
+ import { RECOGNIZED_MODULES } from "./parse-args.js";
19
+ import { TEMPLATE_MANIFESTS, templateDelivery, } from "./template-manifest.js";
20
+ /** What every template carries whatever its modules: the base site. */
21
+ export const BASE_INCLUDE = {
22
+ title: "The base site",
23
+ description: "Your pages with structured data on every one, plus an API and an MCP server agents can read.",
24
+ };
25
+ /** One row per module, typed exhaustively so a new module fails type-check. */
26
+ export const MODULE_INCLUDES = {
27
+ marketing: {
28
+ title: "Marketing pages",
29
+ description: "The pages a marketing site needs, with lead capture.",
30
+ },
31
+ "docs-blog": {
32
+ title: "Docs and blog",
33
+ description: "A documentation section and a blog, written as content files.",
34
+ },
35
+ "multi-tenant": {
36
+ title: "Organizations and workspaces",
37
+ description: "Many customers on one deployment, each with their own members.",
38
+ },
39
+ workspace: {
40
+ title: "Admin",
41
+ description: "Your own signed-in area to edit pages, manage assets and review submissions.",
42
+ },
43
+ scheduling: {
44
+ title: "Scheduling",
45
+ description: "Booking pages visitors use to schedule time with you.",
46
+ },
47
+ "signal-collectors": {
48
+ title: "Quizzes and reports",
49
+ description: "Assessments and downloadable reports that capture interested visitors.",
50
+ },
51
+ commerce: {
52
+ title: "Checkout",
53
+ description: "Take payment through your own Stripe account.",
54
+ },
55
+ };
56
+ const HUB_SEED_PATH = "content/en/TemplatesList/templates.mdx";
57
+ /** Words a naive capitalisation would mis-case in a template name. */
58
+ const WORD_CASING = { saas: "SaaS" };
59
+ /** `marketing-site-free` → `Marketing Site Free`; `saas-product` → `SaaS Product`. */
60
+ function titleCase(name) {
61
+ return name
62
+ .split("-")
63
+ .map((word) => WORD_CASING[word] ?? word.charAt(0).toUpperCase() + word.slice(1))
64
+ .join(" ");
65
+ }
66
+ /** A seed file: a JSON-valued YAML frontmatter block and a placeholder body. */
67
+ function mdx(frontmatter, body) {
68
+ const lines = Object.entries(frontmatter).map(([key, value]) => `${key}: ${JSON.stringify(value)}`);
69
+ return `---\n${lines.join("\n")}\n---\n\n${body}\n`;
70
+ }
71
+ function templateSeed(name, manifest) {
72
+ const title = titleCase(name);
73
+ const includes = [
74
+ BASE_INCLUDE,
75
+ ...RECOGNIZED_MODULES.filter((module) => manifest.requires.includes(module)).map((module) => MODULE_INCLUDES[module]),
76
+ ];
77
+ const free = templateDelivery(manifest) === "free";
78
+ return {
79
+ path: `content/en/TemplatePage/${name}.mdx`,
80
+ text: mdx({
81
+ type: "TemplatePage",
82
+ slug: name,
83
+ title,
84
+ description: `Replace with a one-sentence description of the ${title} template.`,
85
+ summary: `Replace with what a site built on ${title} gives a customer.`,
86
+ placeholder: true,
87
+ template: name,
88
+ categories: [...manifest.categories],
89
+ includes,
90
+ demoUrl: `https://${name}.salessmyth.com`,
91
+ getItUrl: "/contact",
92
+ ...(free && { price: 0, priceCurrency: "USD" }),
93
+ }, `{/* Placeholder: /templates/${name} renders its hero and "What's included" from the frontmatter above. Replace the copy, add a thumbnail and set getItUrl to your buy link before launch. */}`),
94
+ };
95
+ }
96
+ /**
97
+ * The hub seed, then one TemplatePage seed per manifest, sorted by name.
98
+ * `manifests` defaults to the frozen registry; a test may pass its own.
99
+ */
100
+ export function templatePageSeeds(manifests = TEMPLATE_MANIFESTS) {
101
+ const description = "Replace with a one-sentence description of the templates you sell.";
102
+ const hub = {
103
+ path: HUB_SEED_PATH,
104
+ text: mdx({
105
+ type: "TemplatesList",
106
+ slug: "templates",
107
+ title: "Templates",
108
+ description,
109
+ placeholder: true,
110
+ // A tight hero keeps the first cards — the hub's actions — inside
111
+ // the first screen (design review round 1, B1).
112
+ sections: [
113
+ {
114
+ id: "templates-hero",
115
+ type: "hero",
116
+ data: {
117
+ headline: "Templates",
118
+ subheadline: description,
119
+ gapBelow: "tight",
120
+ },
121
+ },
122
+ ],
123
+ }, "{/* Placeholder: /templates lists every TemplatePage automatically. Replace the description before launch. */}"),
124
+ };
125
+ const pages = Object.keys(manifests)
126
+ .sort()
127
+ .map((name) => templateSeed(name, manifests[name]));
128
+ return [hub, ...pages];
129
+ }
@@ -32,7 +32,8 @@ export declare const SPOKE_EXCLUDED_MODULE_PATHS: readonly string[];
32
32
  * The `(authed)` seeds of the sign-in-through-the-hub mode (#7894 D9): the
33
33
  * whole `(authed)` group goes in the default mode; in the hub mode its account
34
34
  * area, layout and gate seam stay, and only these go — the operator surfaces,
35
- * the Better Auth gate and the suites that mock `auth.ts` by path.
35
+ * the Better Auth gate, the hub's own consent page (#9966 D2, a 404 on a
36
+ * spoke) and the suites that mock `auth.ts` by path.
36
37
  */
37
38
  export declare const HUB_MODE_AUTHED_SEEDS: readonly string[];
38
39
  /** The generated seam files (D5; #7874 D12). A seam is rewritten, never removed. */
package/dist/topology.js CHANGED
@@ -86,11 +86,13 @@ export const SPOKE_EXCLUDED_MODULE_PATHS = [
86
86
  * The `(authed)` seeds of the sign-in-through-the-hub mode (#7894 D9): the
87
87
  * whole `(authed)` group goes in the default mode; in the hub mode its account
88
88
  * area, layout and gate seam stay, and only these go — the operator surfaces,
89
- * the Better Auth gate and the suites that mock `auth.ts` by path.
89
+ * the Better Auth gate, the hub's own consent page (#9966 D2, a 404 on a
90
+ * spoke) and the suites that mock `auth.ts` by path.
90
91
  */
91
92
  export const HUB_MODE_AUTHED_SEEDS = [
92
93
  "apps/web/app/[locale]/(authed)/__tests__",
93
94
  "apps/web/app/[locale]/(authed)/account/__tests__",
95
+ "apps/web/app/[locale]/(authed)/account/connect",
94
96
  "apps/web/app/[locale]/(authed)/admin",
95
97
  "apps/web/app/[locale]/(authed)/appointments",
96
98
  "apps/web/app/[locale]/(authed)/edit",
@@ -154,7 +156,7 @@ export function spokeRemovedRoots(opts) {
154
156
  }
155
157
  /**
156
158
  * The seeds that apply — an mcp seed is moot when `apps/mcp` itself goes; in
157
- * the hub sign-in mode the `(authed)` seed is replaced by eight (#7894 D9).
159
+ * the hub sign-in mode the `(authed)` seed is replaced by nine (#7894 D9).
158
160
  */
159
161
  function spokeSeeds(opts) {
160
162
  return modeSeeds(opts.signIn).filter((seed) => opts.mcp || !underPrefix(seed, SPOKE_MCP_ROOT));
package/dist/update.d.ts CHANGED
@@ -45,6 +45,8 @@ export interface UpdateResult {
45
45
  file: string;
46
46
  tests: string[];
47
47
  }>;
48
+ /** The mdx-body pages under `content/` given a `# ` heading this run (#7495). */
49
+ headingsAdded: string[];
48
50
  backfilled?: {
49
51
  version: string;
50
52
  files: number;
@@ -57,6 +59,6 @@ export interface UpdateArgs {
57
59
  target: string | undefined;
58
60
  help: boolean;
59
61
  }
60
- export declare const UPDATE_HELP = "create-ailk update \u2014 bring template fixes into a site's copied app code.\n\nUsage:\n create-ailk update [<target-dir>]\n\nArguments:\n <target-dir> The existing site. Defaults to the current dir.\n\nOptions:\n --help, -h Print this message.\n\nReads .ailk-template-files.json (written beside .ailk-site-manifest.json by\nevery template scaffold) to tell an untouched copy from an edit. An\nuntouched apps/web, apps/api or apps/mcp file, or a root tools/jest file the\napp jest configs require, whose template copy changed is overwritten. An\nedited file whose template copy also changed is three-way merged with\n`git` (git must be on PATH); a conflict leaves the file alone and is\nlisted under \"Needs review\". Every template test that imports a file\nleft under \"Needs review\" or \"Skipped\" (by a relative import) is held at the\nsite's version with it, its record entry is not advanced, and it is listed\nunder \"Held back with <file>\" \u2014 reconcile the file, then run update again to\nbring its tests forward. Every other file in the run still updates.\nA site with no record gets one on its first run, from the version its\n.ailk-site-manifest.json already records. A site scaffolded before\n@working-theory/mcp-server has its copied apps/mcp moved to the boot set:\nthe server and each module's paid MCP pack join apps/mcp/package.json, the\ncopied server files go, and apps/mcp/src/lib/paid-tools.generated.ts is\nrewritten on every run. A site scaffolded before @working-theory/api has its\ncopied apps/api moved to the boot file: the app becomes @working-theory/app-api,\nthe package and the six @working-theory/api-<module> packages join\napps/api/package.json, its bins and build:vercel delegate to the package, and\nthe copied server files go. Run `pnpm install` after either move, since the\napp's package.json gained dependencies. Commit before running, and review the\ndiff after.\n";
62
+ export declare const UPDATE_HELP = "create-ailk update \u2014 bring template fixes into a site's copied app code.\n\nUsage:\n create-ailk update [<target-dir>]\n\nArguments:\n <target-dir> The existing site. Defaults to the current dir.\n\nOptions:\n --help, -h Print this message.\n\nReads .ailk-template-files.json (written beside .ailk-site-manifest.json by\nevery template scaffold) to tell an untouched copy from an edit. An\nuntouched apps/web, apps/api or apps/mcp file, or a root tools/jest file the\napp jest configs require, whose template copy changed is overwritten. An\nedited file whose template copy also changed is three-way merged with\n`git` (git must be on PATH); a conflict leaves the file alone and is\nlisted under \"Needs review\". Every template test that imports a file\nleft under \"Needs review\" or \"Skipped\" (by a relative import) is held at the\nsite's version with it, its record entry is not advanced, and it is listed\nunder \"Held back with <file>\" \u2014 reconcile the file, then run update again to\nbring its tests forward. Every other file in the run still updates.\nA site with no record gets one on its first run, from the version its\n.ailk-site-manifest.json already records. A site scaffolded before\n@working-theory/mcp-server has its copied apps/mcp moved to the boot set:\nthe server and each module's paid MCP pack join apps/mcp/package.json, the\ncopied server files go, and apps/mcp/src/lib/paid-tools.generated.ts is\nrewritten on every run. A site scaffolded before @working-theory/api has its\ncopied apps/api moved to the boot file: the app becomes @working-theory/app-api,\nthe package and the six @working-theory/api-<module> packages join\napps/api/package.json, its bins and build:vercel delegate to the package, and\nthe copied server files go. Run `pnpm install` after either move, since the\napp's package.json gained dependencies. Each mdx-body page in content/ with no\n'# ' heading gets its title and description written into its body as its\nheading and intro, listed under \"Page heading added\"; a page with one heading\nis left as it is, and one with several, or one whose frontmatter it cannot\nread, is listed under \"Needs review\". Commit before running, and review the\ndiff after.\n";
61
63
  /** Parse the argv AFTER the `update` word. Strict `node:util` parseArgs, like `parseAdoptArgs`. */
62
64
  export declare function parseUpdateArgs(argv: string[]): UpdateArgs;
package/dist/update.js CHANGED
@@ -27,24 +27,29 @@
27
27
  * (#9566): the record retires the copied server files and lands the boot
28
28
  * files; the app's rename, its dependencies and delegating scripts, and the
29
29
  * bundle-delivered module files are planned here. Every refusal throws
30
- * before the first write.
30
+ * before the first write. Last, every mdx-body page in the site's `content/`
31
+ * with no `# ` heading gets its title and description written into its body
32
+ * (#7495, `body-heading.ts`); a page with one or more headings is never
33
+ * touched, and `content/` is never recorded.
31
34
  *
32
35
  * Pure module: no `import.meta.url` — the CLI supplies `templatesDir`, the
33
36
  * `adopt.ts` / `add-route.ts` split.
34
37
  */
35
38
  import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, rmdirSync, writeFileSync, } from "node:fs";
36
- import { dirname, join, resolve, sep } from "node:path";
39
+ import { dirname, join, relative, resolve, sep } from "node:path";
37
40
  import { parseArgs } from "node:util";
38
41
  import { AddModuleRefusedError, readArchitectureHeader } from "./add-module-core.js";
42
+ import { headingViolations, mdxBodyRule, migrateBodyHeadings } from "./body-heading.js";
39
43
  import { buildScaffoldMarkers, isExcluded, isTextFile, renderTemplateFile, templateDestination } from "./copy.js";
40
44
  import { fetchTemplateSnapshot } from "./lib/fetch-template-snapshot.js";
41
45
  import { mergeThreeWay } from "./lib/merge-three-way.js";
46
+ import { publicTreeRemoved } from "./lib/site-modules.js";
42
47
  import { MODULE_SLICES } from "./module-architecture.js";
43
48
  import { MODULE_FLAGS_PATH, PAID_MCP_TOOLS_PATH, writeModuleFlags, writePaidMcpTools, } from "./paid-sections.js";
44
49
  import { ParseError } from "./parse-args.js";
45
50
  import { SITE_ROSTER_PATH } from "./site-roster.js";
46
51
  import { SPOKE_CHECKOUT_TEMPLATE_PATHS } from "./spoke-checkout.js";
47
- import { excludedRoutePaths } from "./surfaces.js";
52
+ import { excludedRoutePaths, isRemovedPublicRoutePath } from "./surfaces.js";
48
53
  import { DATABASE_BOOT_FILES, ROOT_TOOL_FILES, TEMPLATE_RECORD_PATH, isRecordable, parseTemplateRecord, serializeTemplateRecord, sha256Hex, } from "./template-record.js";
49
54
  import { MCP_SEAM_PATH, MCP_SERVER_HUB, TopologyError, isSpokeExcluded, readSpokeShape, spokeExclusions, } from "./topology.js";
50
55
  /** A refusal the CLI exits 2 on, thrown before anything is written. */
@@ -1091,10 +1096,17 @@ export async function updateSite(opts) {
1091
1096
  // module object at a boot-set path goes before the writes below, and the
1092
1097
  // template's re-export lands there as an added file.
1093
1098
  const retired = new Set(apiMove?.deletes ?? []);
1099
+ // #10040 D10 — a site that dropped `marketing` never gains a public route
1100
+ // file from a newer template, tombstone or not.
1101
+ const marketingRemoved = publicTreeRemoved(targetDir);
1094
1102
  const knownPaths = new Set([...Object.keys(record.files), ...(knownSnapshotPaths ?? [])]);
1095
1103
  for (const path of [...newMap.keys()].sort()) {
1096
1104
  if (Object.prototype.hasOwnProperty.call(record.files, path))
1097
1105
  continue;
1106
+ if (marketingRemoved && isRemovedPublicRoutePath(path)) {
1107
+ skipped.push({ path, reason: "marketing removed" });
1108
+ continue;
1109
+ }
1098
1110
  // The guard runs before the disk check, so a full-copy site's boot path
1099
1111
  // reads as guarded, never as "exists, not in the record" (#9569 DD8).
1100
1112
  if (bootFileGuarded(path)) {
@@ -1250,6 +1262,21 @@ export async function updateSite(opts) {
1250
1262
  writeFileSync(join(targetDir, ...WEB_PACKAGE_JSON.split("/")), webPackageJson);
1251
1263
  moved.updated.push(WEB_PACKAGE_JSON);
1252
1264
  }
1265
+ // #7495 D6 — the page body owns its H1: give every heading-less mdx-body
1266
+ // page its title and description, read the rule after the writes so a
1267
+ // route this run added counts. Its rows join `review` after `heldFiles`
1268
+ // was computed, so a content path never holds a template test back.
1269
+ const contentDir = join(targetDir, "content");
1270
+ const rule = mdxBodyRule(targetDir);
1271
+ const sitePath = (abs) => relative(targetDir, abs).split(sep).join("/");
1272
+ const headings = migrateBodyHeadings(contentDir, rule);
1273
+ for (const { path, count } of headingViolations(contentDir, rule)) {
1274
+ if (count > 1)
1275
+ review.push({ path: sitePath(path), reason: `${count} '# ' headings; keep one` });
1276
+ }
1277
+ for (const { path, reason } of headings.refused) {
1278
+ review.push({ path: sitePath(path), reason: `no '# ' heading added: ${reason}` });
1279
+ }
1253
1280
  return {
1254
1281
  targetDir,
1255
1282
  version,
@@ -1257,6 +1284,7 @@ export async function updateSite(opts) {
1257
1284
  review,
1258
1285
  skipped,
1259
1286
  heldBack,
1287
+ headingsAdded: headings.written.map(sitePath),
1260
1288
  ...(backfilled !== undefined ? { backfilled } : {}),
1261
1289
  };
1262
1290
  }
@@ -1272,6 +1300,7 @@ export function formatUpdateReport(result) {
1272
1300
  ["Added", result.added],
1273
1301
  ["Removed", result.removed],
1274
1302
  ["Already current", result.current],
1303
+ ["Page heading added", result.headingsAdded],
1275
1304
  ];
1276
1305
  for (const [label, paths] of sections) {
1277
1306
  if (paths.length === 0)
@@ -1297,6 +1326,7 @@ export function formatUpdateReport(result) {
1297
1326
  result.added.length === 0 &&
1298
1327
  result.removed.length === 0 &&
1299
1328
  result.current.length === 0 &&
1329
+ result.headingsAdded.length === 0 &&
1300
1330
  result.review.length === 0 &&
1301
1331
  result.skipped.length === 0;
1302
1332
  if (nothingHappened) {
@@ -1344,7 +1374,11 @@ copied apps/api moved to the boot file: the app becomes @working-theory/app-api,
1344
1374
  the package and the six @working-theory/api-<module> packages join
1345
1375
  apps/api/package.json, its bins and build:vercel delegate to the package, and
1346
1376
  the copied server files go. Run \`pnpm install\` after either move, since the
1347
- app's package.json gained dependencies. Commit before running, and review the
1377
+ app's package.json gained dependencies. Each mdx-body page in content/ with no
1378
+ '# ' heading gets its title and description written into its body as its
1379
+ heading and intro, listed under "Page heading added"; a page with one heading
1380
+ is left as it is, and one with several, or one whose frontmatter it cannot
1381
+ read, is listed under "Needs review". Commit before running, and review the
1348
1382
  diff after.
1349
1383
  `;
1350
1384
  /** Parse the argv AFTER the `update` word. Strict `node:util` parseArgs, like `parseAdoptArgs`. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ailk",
3
- "version": "0.27.0",
3
+ "version": "0.28.0",
4
4
  "description": "Scaffolder for AI Launch Kit — `pnpm create ailk@latest <dir>` produces a starter repo consuming @working-theory/* from npm (per ADR 0007).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -28,8 +28,8 @@
28
28
  "tar": "^7.5.21"
29
29
  },
30
30
  "devDependencies": {
31
- "@working-theory/schema": "0.49.0",
32
- "@working-theory/templates": "0.49.0"
31
+ "@working-theory/schema": "0.50.0",
32
+ "@working-theory/templates": "0.50.0"
33
33
  },
34
34
  "repository": {
35
35
  "type": "git",
@@ -2,7 +2,8 @@
2
2
  "templates": {
3
3
  "marketing-site-free": "released",
4
4
  "marketing-site": "released",
5
- "saas-product": "gated"
5
+ "saas-product": "gated",
6
+ "hub": "released"
6
7
  },
7
8
  "shapes": {
8
9
  "organization-product": "released",
@@ -0,0 +1,115 @@
1
+ {
2
+ "template": "hub",
3
+ "requires": [],
4
+ "categories": ["accounting", "legal", "real-estate"],
5
+ "shape": "organization-service",
6
+ "design": "standard",
7
+ "generatedHash": "7386e7f6ebcd8d291b75c265a428dec50612d25ae522cae1be611c4089a217a1",
8
+ "layout": {
9
+ "nav": "full",
10
+ "footer": "columns",
11
+ "announcementBanner": false,
12
+ "home": "default"
13
+ },
14
+ "theme": {
15
+ "corners": "square",
16
+ "eyebrowCase": "sentence",
17
+ "placeholders": "fill",
18
+ "checkmarks": "plain",
19
+ "colors": {
20
+ "light": {
21
+ "bg": "#ffffff",
22
+ "bgMuted": "#ffffff",
23
+ "surface": "#ffffff",
24
+ "border": "#000000",
25
+ "text": "#000000",
26
+ "textMuted": "#333333",
27
+ "primary": "#000000",
28
+ "primaryFg": "#ffffff",
29
+ "accent": "#000000"
30
+ },
31
+ "dark": {
32
+ "bg": "#000000",
33
+ "bgMuted": "#000000",
34
+ "surface": "#000000",
35
+ "border": "#ffffff",
36
+ "text": "#ffffff",
37
+ "textMuted": "#cccccc",
38
+ "primary": "#ffffff",
39
+ "primaryFg": "#000000",
40
+ "accent": "#ffffff"
41
+ }
42
+ }
43
+ },
44
+ "rows": [
45
+ {
46
+ "route": "/sign-in",
47
+ "kind": "route",
48
+ "pageType": null,
49
+ "schema": null,
50
+ "shell": "MarketingShell",
51
+ "chrome": "on",
52
+ "sections": []
53
+ },
54
+ {
55
+ "route": "/sign-up",
56
+ "kind": "route",
57
+ "pageType": null,
58
+ "schema": null,
59
+ "shell": "MarketingShell",
60
+ "chrome": "on",
61
+ "sections": []
62
+ },
63
+ {
64
+ "route": "/privacy",
65
+ "kind": "page",
66
+ "pageType": "PrivacyPage",
67
+ "schema": "PrivacyPage",
68
+ "shell": "MarketingShell",
69
+ "chrome": "on",
70
+ "sections": [
71
+ {
72
+ "type": "document-chassis",
73
+ "component": "DocumentChassis",
74
+ "options": {
75
+ "layout": "article"
76
+ }
77
+ }
78
+ ]
79
+ },
80
+ {
81
+ "route": "/terms",
82
+ "kind": "page",
83
+ "pageType": "TermsPage",
84
+ "schema": "TermsPage",
85
+ "shell": "MarketingShell",
86
+ "chrome": "on",
87
+ "sections": [
88
+ {
89
+ "type": "document-chassis",
90
+ "component": "DocumentChassis",
91
+ "options": {
92
+ "layout": "article"
93
+ }
94
+ }
95
+ ]
96
+ },
97
+ {
98
+ "route": "/working-agreement",
99
+ "kind": "page",
100
+ "pageType": "WorkingAgreementPage",
101
+ "schema": "WorkingAgreementPage",
102
+ "shell": "MarketingShell",
103
+ "chrome": "on",
104
+ "sections": [
105
+ {
106
+ "type": "document-chassis",
107
+ "component": "DocumentChassis",
108
+ "options": {
109
+ "layout": "article"
110
+ }
111
+ }
112
+ ]
113
+ }
114
+ ]
115
+ }
@@ -462,8 +462,11 @@ CONTENT_ADAPTER=mdx
462
462
  # SLACK_CONNECTION_KEY=
463
463
  # SLACK_CONNECTION_KEY_PREVIOUS=
464
464
  # The Organization that owns STRIPE_SECRET_KEY (#9285): with the multi-tenant
465
- # module only the Organization whose id is named here sells through it; unset
466
- # means nobody. Without the module the single Organization owns it.
465
+ # module selected in AILK_ENABLED_MODULES, only the Organization whose id is
466
+ # named here sells through it; unset means nobody. That holds whether or not
467
+ # the licence claims the module (#9968), so a downgraded deployment that still
468
+ # selects it must set this too. Without the module the single Organization
469
+ # owns it.
467
470
  # STRIPE_ENV_ORGANIZATION_ID=
468
471
  # HMAC-SHA256 key POST /v1/webhooks/calcom verifies the x-cal-signature-256
469
472
  # header against (#6085). Unset makes the receiver answer 503 and accept
@@ -146,11 +146,15 @@ STRIPE_WEBHOOK_SECRET=whsec_xxx
146
146
  # SLACK_CONNECTION_KEY=
147
147
  # SLACK_CONNECTION_KEY_PREVIOUS=
148
148
  #
149
- # The Organization that owns STRIPE_SECRET_KEY (#9285). On a deployment with
150
- # the multi-tenant module, only the Organization whose id is named here sees
151
- # that key as its Stripe connection and sells its Offerings through it; unset,
152
- # blank or naming no Organization, nobody owns it. Without the multi-tenant
153
- # module the deployment's single Organization owns it and this is not read.
149
+ # The Organization that owns STRIPE_SECRET_KEY (#9285). On a deployment whose
150
+ # AILK_ENABLED_MODULES selects the multi-tenant module, only the Organization
151
+ # whose id is named here sees that key as its Stripe connection and sells its
152
+ # Offerings through it; unset, blank or naming no Organization, nobody owns it.
153
+ # The selection decides, not the licence (#9968): a deployment that selects
154
+ # multi-tenant must set this for any Organization to own the key, including a
155
+ # downgraded one whose licence no longer claims the module. Without the
156
+ # multi-tenant module selected the deployment's single Organization owns it
157
+ # and this is not read.
154
158
  # STRIPE_ENV_ORGANIZATION_ID=
155
159
  #
156
160
  # The booking webhook receiver (#6085) — a SEPARATE credential from the