create-ailk 0.22.0 → 0.23.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 (238) hide show
  1. package/component-catalog.json +149 -5
  2. package/component-catalog.md +7 -6
  3. package/dist/add-module.js +11 -9
  4. package/dist/add-route.d.ts +55 -1
  5. package/dist/add-route.js +294 -12
  6. package/dist/cli.js +27 -10
  7. package/dist/derive-architecture.d.ts +7 -5
  8. package/dist/derive-architecture.js +9 -32
  9. package/dist/lib/design-checks.js +4 -2
  10. package/dist/lib/env-catalog.js +0 -4
  11. package/dist/lib/npm-auth.d.ts +9 -0
  12. package/dist/lib/npm-auth.js +48 -0
  13. package/dist/lib/scaffold-render-sweep.d.ts +3 -2
  14. package/dist/lib/scaffold-render-sweep.js +5 -8
  15. package/dist/module-architecture.js +98 -10
  16. package/dist/paid-sections.d.ts +21 -2
  17. package/dist/paid-sections.js +69 -7
  18. package/dist/seed-flows.d.ts +4 -1
  19. package/dist/seed-flows.js +50 -4
  20. package/dist/spoke-checkout.d.ts +38 -35
  21. package/dist/spoke-checkout.js +66 -229
  22. package/dist/starter-grid.d.ts +0 -3
  23. package/dist/starter-grid.js +24 -14
  24. package/dist/surfaces.js +33 -12
  25. package/dist/sync-routes.d.ts +38 -1
  26. package/dist/sync-routes.js +184 -11
  27. package/dist/template-record.d.ts +1 -1
  28. package/dist/template-record.js +3 -2
  29. package/dist/update.js +10 -0
  30. package/package.json +3 -3
  31. package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +4 -49
  32. package/site-templates/_base/content/en/Pricing/pricing.mdx +3 -21
  33. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +23 -20
  34. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +28 -14
  35. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +15 -39
  36. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +38 -34
  37. package/site-templates/marketing-site-free/content/en/NewsletterSignup/newsletter.mdx +6 -43
  38. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +14 -31
  39. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +26 -25
  40. package/site-templates/shapes/local-business-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
  41. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +14 -31
  42. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +26 -25
  43. package/site-templates/shapes/organization-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
  44. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +14 -31
  45. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +26 -25
  46. package/site-templates/shapes/person-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
  47. package/templates/.env.example +9 -7
  48. package/templates/.github/workflows/ci.yml +13 -0
  49. package/templates/README.md +1 -0
  50. package/templates/_gitignore +4 -0
  51. package/templates/apps/api/.env.example +27 -0
  52. package/templates/apps/api/CLAUDE.md +77 -16
  53. package/templates/apps/api/package.json +1 -0
  54. package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +2 -0
  55. package/templates/apps/api/src/bin/stripe-connections-rekey.ts +45 -0
  56. package/templates/apps/api/src/config/__tests__/stripe-connection-key.test.ts +185 -0
  57. package/templates/apps/api/src/config/index.ts +109 -0
  58. package/templates/apps/api/src/lib/__mocks__/prisma.ts +3 -0
  59. package/templates/apps/api/src/lib/__tests__/flow-rename-race.db.test.ts +116 -0
  60. package/templates/apps/api/src/lib/error-serializer.ts +74 -7
  61. package/templates/apps/api/src/lib/stripe.ts +74 -4
  62. package/templates/apps/api/src/lib/webhook-idempotency.ts +66 -2
  63. package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +218 -0
  64. package/templates/apps/api/src/middleware/__tests__/write-budget.test.ts +174 -0
  65. package/templates/apps/api/src/middleware/rate-limit.ts +226 -4
  66. package/templates/apps/api/src/openapi/spec.ts +10 -7
  67. package/templates/apps/api/src/routes/billing/plan-checkout.ts +325 -0
  68. package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +11 -0
  69. package/templates/apps/api/src/routes/checkout/__tests__/offerings-authorization.test.ts +1300 -0
  70. package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +11 -0
  71. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +49 -0
  72. package/templates/apps/api/src/routes/checkout/session.ts +57 -25
  73. package/templates/apps/api/src/routes/checkout/sessions.ts +232 -82
  74. package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +5 -5
  75. package/templates/apps/api/src/routes/consent/__tests__/sign-up-rate-limit.test.ts +160 -0
  76. package/templates/apps/api/src/routes/consent/account.ts +4 -2
  77. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +2 -2
  78. package/templates/apps/api/src/routes/content/__tests__/offerings.test.ts +731 -0
  79. package/templates/apps/api/src/routes/content/__tests__/pricing.test.ts +753 -0
  80. package/templates/apps/api/src/routes/content/__tests__/shown-offerings.test.ts +762 -0
  81. package/templates/apps/api/src/routes/content/index.ts +16 -2
  82. package/templates/apps/api/src/routes/content/offerings.ts +184 -0
  83. package/templates/apps/api/src/routes/content/pricing.ts +176 -0
  84. package/templates/apps/api/src/routes/content/shown-offerings.ts +229 -0
  85. package/templates/apps/api/src/routes/leads/index.ts +17 -14
  86. package/templates/apps/api/src/routes/webhooks/README.md +66 -0
  87. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-offerings.test.ts +814 -0
  88. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +80 -68
  89. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +5 -5
  90. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +8 -2
  91. package/templates/apps/api/src/routes/webhooks/index.ts +23 -0
  92. package/templates/apps/api/src/routes/webhooks/stripe-offerings.ts +384 -0
  93. package/templates/apps/api/src/routes/webhooks/stripe.ts +91 -17
  94. package/templates/apps/api/src/server.ts +140 -51
  95. package/templates/apps/api/src/services/__tests__/checkout-plan.test.ts +313 -0
  96. package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +51 -2
  97. package/templates/apps/api/src/services/__tests__/keyless-workspace-predicate.test.ts +234 -0
  98. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +374 -44
  99. package/templates/apps/api/src/services/__tests__/person.test.ts +232 -1
  100. package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +444 -0
  101. package/templates/apps/api/src/services/deliverable-fulfillment.ts +13 -5
  102. package/templates/apps/api/src/services/entitlements.ts +157 -1
  103. package/templates/apps/api/src/services/lead-routing.ts +113 -58
  104. package/templates/apps/api/src/services/offerings/__tests__/catalog.test.ts +295 -0
  105. package/templates/apps/api/src/services/offerings/__tests__/checkout-session-record.test.ts +268 -0
  106. package/templates/apps/api/src/services/offerings/__tests__/connection.test.ts +868 -0
  107. package/templates/apps/api/src/services/offerings/__tests__/fixtures.ts +416 -0
  108. package/templates/apps/api/src/services/offerings/__tests__/org-products.test.ts +396 -0
  109. package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +681 -0
  110. package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +231 -0
  111. package/templates/apps/api/src/services/offerings/catalog.ts +308 -0
  112. package/templates/apps/api/src/services/offerings/checkout-authorization.ts +385 -0
  113. package/templates/apps/api/src/services/offerings/checkout-session-record.ts +192 -0
  114. package/templates/apps/api/src/services/offerings/connection.ts +1003 -0
  115. package/templates/apps/api/src/services/offerings/index.ts +614 -0
  116. package/templates/apps/api/src/services/offerings/source.ts +140 -0
  117. package/templates/apps/api/src/services/offerings/store.ts +150 -0
  118. package/templates/apps/api/src/services/person.ts +124 -9
  119. package/templates/apps/api/src/services/plan-offerings.ts +96 -30
  120. package/templates/apps/api/src/services/public-site-resolver.ts +19 -14
  121. package/templates/apps/mcp/CLAUDE.md +22 -14
  122. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +9 -4
  123. package/templates/apps/mcp/__tests__/get_offerings.test.ts +358 -0
  124. package/templates/apps/mcp/__tests__/get_pricing.test.ts +364 -0
  125. package/templates/apps/mcp/__tests__/http-transport.test.ts +4 -4
  126. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +2 -2
  127. package/templates/apps/mcp/__tests__/server.test.ts +13 -11
  128. package/templates/apps/mcp/__tests__/start_purchase.test.ts +208 -12
  129. package/templates/apps/mcp/__tests__/tools/purchase.test.ts +11 -11
  130. package/templates/apps/mcp/src/lib/shown-offerings.ts +105 -0
  131. package/templates/apps/mcp/src/server.ts +5 -0
  132. package/templates/apps/mcp/src/tools/get_offerings.ts +47 -30
  133. package/templates/apps/mcp/src/tools/get_pricing.ts +40 -40
  134. package/templates/apps/mcp/src/tools/purchase.ts +30 -2
  135. package/templates/apps/mcp/src/workspace-server.ts +15 -0
  136. package/templates/apps/web/.env.example +10 -31
  137. package/templates/apps/web/CLAUDE.md +315 -42
  138. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/agreements-prompt.test.tsx +3 -3
  139. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout.test.tsx +3 -3
  140. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/privacy.test.tsx +3 -3
  141. package/templates/apps/web/app/[locale]/careers/[slug]/[action]/page.tsx +189 -0
  142. package/templates/apps/web/app/[locale]/careers/[slug]/apply.actions.ts +4 -2
  143. package/templates/apps/web/app/[locale]/careers/[slug]/job-application-form.ts +8 -3
  144. package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +22 -144
  145. package/templates/apps/web/app/[locale]/careers/__tests__/apply.actions.test.ts +9 -0
  146. package/templates/apps/web/app/[locale]/careers/__tests__/careers-hub.test.tsx +172 -0
  147. package/templates/apps/web/app/[locale]/careers/__tests__/careers-set.test.tsx +149 -0
  148. package/templates/apps/web/app/[locale]/careers/__tests__/job-application-form.test.ts +22 -24
  149. package/templates/apps/web/app/[locale]/careers/__tests__/job-apply-page.test.tsx +140 -66
  150. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +57 -22
  151. package/templates/apps/web/app/[locale]/careers/page.tsx +129 -5
  152. package/templates/apps/web/app/[locale]/contact/__tests__/contact-magnet-page.test.tsx +119 -11
  153. package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +28 -5
  154. package/templates/apps/web/app/[locale]/contact/contact.actions.ts +6 -2
  155. package/templates/apps/web/app/[locale]/contact/page.tsx +99 -92
  156. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +237 -5
  157. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.count.test.tsx +43 -0
  158. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.deliverable.test.tsx +200 -0
  159. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.ending.test.tsx +197 -0
  160. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/magnet-page.test.tsx +430 -0
  161. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +21 -9
  162. package/templates/apps/web/app/[locale]/flows/[slug]/magnet-page.tsx +352 -0
  163. package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +12 -73
  164. package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +10 -31
  165. package/templates/apps/web/app/[locale]/layout.tsx +55 -4
  166. package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +174 -104
  167. package/templates/apps/web/app/[locale]/newsletter/page.tsx +95 -58
  168. package/templates/apps/web/app/[locale]/pricing/__tests__/__snapshots__/unconnected.test.tsx.snap +7 -0
  169. package/templates/apps/web/app/[locale]/pricing/__tests__/offerings.test.tsx +599 -0
  170. package/templates/apps/web/app/[locale]/pricing/__tests__/page.test.tsx +216 -0
  171. package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +236 -0
  172. package/templates/apps/web/app/[locale]/pricing/page.tsx +367 -6
  173. package/templates/apps/web/app/[locale]/team/__tests__/team-routes.test.tsx +4 -1
  174. package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe-rsc-boundary.test.tsx +17 -0
  175. package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe.test.tsx +62 -3
  176. package/templates/apps/web/app/[locale]/unsubscribe/page.tsx +70 -18
  177. package/templates/apps/web/app/[locale]/unsubscribe/unsubscribe-button.tsx +30 -9
  178. package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +316 -0
  179. package/templates/apps/web/app/api/offerings/checkout/__tests__/route.test.ts +810 -0
  180. package/templates/apps/web/lib/__tests__/auth-routes.test.ts +27 -1
  181. package/templates/apps/web/lib/__tests__/deck-page.test.ts +2 -1
  182. package/templates/apps/web/lib/__tests__/in-page-magnet.test.ts +265 -0
  183. package/templates/apps/web/lib/__tests__/magnet-routes.test.ts +57 -0
  184. package/templates/apps/web/lib/__tests__/newsletter.actions.test.ts +293 -0
  185. package/templates/apps/web/lib/__tests__/offerings-pricing.test.ts +601 -0
  186. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +67 -8
  187. package/templates/apps/web/lib/auth-routes.ts +36 -2
  188. package/templates/apps/web/lib/checkout-config.ts +9 -20
  189. package/templates/apps/web/lib/deck-page.ts +1 -0
  190. package/templates/apps/web/lib/deliverable-grant.ts +18 -0
  191. package/templates/apps/web/lib/in-page-magnet.ts +416 -0
  192. package/templates/apps/web/lib/magnet-routes.ts +42 -0
  193. package/templates/apps/web/lib/modules.generated.ts +9 -0
  194. package/templates/apps/web/lib/newsletter.actions.ts +202 -0
  195. package/templates/apps/web/lib/offerings-pricing.ts +326 -0
  196. package/templates/apps/web/lib/posting-title.ts +17 -0
  197. package/templates/apps/web/lib/sign-up-agreements.ts +4 -2
  198. package/templates/apps/web/messages/en.json +49 -15
  199. package/templates/apps/web/middleware.ts +12 -2
  200. package/templates/apps/web/security-headers.config.ts +17 -0
  201. package/templates/content/en/BlogList/blog.mdx +4 -71
  202. package/templates/content/en/BookCall/book-a-call.mdx +1 -41
  203. package/templates/content/en/BookCall/onsite-estimate.mdx +1 -17
  204. package/templates/content/en/BookCall/onsite-service.mdx +1 -17
  205. package/templates/content/en/BookCall/start-project.mdx +1 -17
  206. package/templates/content/en/ContactPage/contact.mdx +6 -63
  207. package/templates/content/en/HomePage/home.mdx +3 -21
  208. package/templates/database/CHANGELOG.md +31 -0
  209. package/templates/database/__tests__/in-page-magnets-migration.test.ts +161 -0
  210. package/templates/database/__tests__/offerings-checkout-session.db.test.ts +270 -0
  211. package/templates/database/__tests__/offerings-migration.test.ts +197 -0
  212. package/templates/database/__tests__/offerings-shown-price-ids-migration.test.ts +86 -0
  213. package/templates/database/__tests__/offerings.db.test.ts +303 -0
  214. package/templates/database/__tests__/seed.test.ts +31 -1
  215. package/templates/database/auth/schema.prisma +4 -0
  216. package/templates/database/config/schema.prisma +91 -0
  217. package/templates/database/migrations/20261020120000_offerings_data_model/migration.sql +146 -0
  218. package/templates/database/migrations/20261021120000_offerings_checkout_session/migration.sql +77 -0
  219. package/templates/database/migrations/20261022120000_in_page_magnets/migration.sql +45 -0
  220. package/templates/database/migrations/20261023120000_offerings_shown_price_ids/migration.sql +47 -0
  221. package/templates/database/migrations/20261024120000_organization_plan_checkout_session/migration.sql +16 -0
  222. package/templates/database/package.json +1 -1
  223. package/templates/database/seed.ts +30 -2
  224. package/versions.json +35 -35
  225. package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +0 -199
  226. package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +0 -93
  227. package/templates/apps/web/app/[locale]/downloads/page.tsx +0 -37
  228. package/templates/apps/web/app/[locale]/newsletter/__tests__/newsletter.actions.test.ts +0 -139
  229. package/templates/apps/web/app/[locale]/newsletter/newsletter.actions.ts +0 -93
  230. package/templates/apps/web/app/api/checkout/[offering]/route.ts +0 -48
  231. package/templates/apps/web/app/api/checkout/__tests__/route.test.ts +0 -120
  232. package/templates/apps/web/app/api/checkout/__tests__/success-path.test.ts +0 -86
  233. package/templates/apps/web/app/api/webhooks/stripe/__tests__/route.test.ts +0 -92
  234. package/templates/apps/web/app/api/webhooks/stripe/route.ts +0 -31
  235. package/templates/apps/web/lib/__tests__/checkout.test.ts +0 -243
  236. package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +0 -120
  237. package/templates/apps/web/lib/checkout.ts +0 -93
  238. package/templates/apps/web/lib/meeting-slugs.ts +0 -63
@@ -2,26 +2,26 @@ import type { EnvCatalogEntry, PlanEnvRowsOptions } from "./lib/declared-env.js"
2
2
  import { type InstallReportRow } from "./lib/extract-module-bundle.js";
3
3
  import { type WorkspaceVersionMap } from "./substitute.js";
4
4
  import { type SignInMode } from "./topology.js";
5
- /** The checkout package a spoke's two route files import. */
5
+ /** The checkout package a spoke's billing action and thank-you page import. */
6
6
  export declare const SPOKE_CHECKOUT_PACKAGE = "@working-theory/spoke-checkout";
7
- /** The site's checkout data — written once, the site's from then on (#8748 D2). */
7
+ /** The site's checkout stamp — written once, the site's from then on (#8748 D2). */
8
8
  export declare const SPOKE_CHECKOUT_CONFIG_PATH = "apps/web/lib/checkout-config.ts";
9
- /** The two return pages: Stripe's success URL and every refused buy's target. */
9
+ /** The two return pages a commerce spoke still carries (#8748 D5). */
10
10
  export declare const SPOKE_CHECKOUT_PAGE_FILES: readonly string[];
11
11
  /**
12
- * The standard spoke checkout's template paths, scaffold-relative (#8748 D3).
13
- * A directory entry covers every file under it, so the tests that sit beside
14
- * the routes and pages travel with them. `copyTemplate` skips all of them for
15
- * every scaffold; `writeSpokeCheckoutSlice` copies them into a commerce spoke.
12
+ * The spoke checkout's template paths, scaffold-relative (#8748 D3). A
13
+ * directory entry covers every file under it, so the tests that sit beside
14
+ * the pages travel with them. `copyTemplate` skips all of them for every
15
+ * scaffold; `writeSpokeCheckoutSlice` copies them into a commerce spoke.
16
16
  */
17
17
  export declare const SPOKE_CHECKOUT_TEMPLATE_PATHS: readonly string[];
18
- /** The variables a commerce spoke depends on (D9). The package reads three of them. */
18
+ /** The variables a commerce spoke depends on (D9): the package reads both. */
19
19
  export declare const SPOKE_CHECKOUT_ENV_CATALOG: Readonly<Record<string, EnvCatalogEntry>>;
20
20
  /** The names a commerce spoke declares although only the package reads them (D9). */
21
21
  export declare const SPOKE_CHECKOUT_DECLARED_ENV: readonly string[];
22
22
  /**
23
23
  * The catalog (and declared names) a spoke's `appendEnvRows` call passes: a
24
- * commerce spoke adds the checkout's four rows (D9). `ENV_CATALOG` itself is
24
+ * commerce spoke adds the package's two rows (D9). `ENV_CATALOG` itself is
25
25
  * unchanged — its test fails on an entry the monorepo's apps never read.
26
26
  */
27
27
  export declare function spokeEnvRowOptions(commerce: boolean): Pick<PlanEnvRowsOptions, "catalog" | "declare">;
@@ -32,33 +32,22 @@ export declare function spokeEnvRowOptions(commerce: boolean): Pick<PlanEnvRowsO
32
32
  * `site-` and the first 8 hex characters of its SHA-256. Never `AILK_SITE_ID`.
33
33
  */
34
34
  export declare function spokeCheckoutSite(projectName: string): string;
35
- /** Where a commerce spoke's end-to-end checkout workflow lives. */
36
- export declare const SPOKE_CHECKOUT_E2E_WORKFLOW_PATH = ".github/workflows/checkout-e2e.yml";
35
+ /** The billing action's first line, which marks the file as create-ailk's to rewrite. */
36
+ export declare const BILLING_ACTION_MARKER = "// The spoke billing action \u2014 GENERATED by create-ailk (#8217). Do not edit:";
37
37
  /**
38
- * The Stripe CLI release the workflow installs, and the SHA-256 of its
39
- * `stripe_<v>_linux_x86_64.tar.gz` from that release's own
40
- * `stripe-linux-checksums.txt`. Never bump one without re-reading the other.
38
+ * The spoke billing action (#8217 D6): wiring only, byte-identical across
39
+ * sites, so every guarantee arrives by a package bump. A plain server module
40
+ * (C2): its two exports take no argument, and the hub's read is its only
41
+ * source of customer ids. It builds its config from the site's stamp and the
42
+ * site's own origin (#9142: no longer from `lib/checkout.ts`).
41
43
  */
42
- export declare const STRIPE_CLI_VERSION = "1.51.1";
43
- export declare const STRIPE_CLI_LINUX_X64_SHA256 = "38c8de73a8cab810a4aab8a4015cd4e3398b7b87aa42b6944d09d89ed524ac53";
44
- /** The Playwright the workflow installs — the monorepo's own pinned version. */
45
- export declare const E2E_PLAYWRIGHT_VERSION = "1.61.1";
46
- /**
47
- * Every action the workflow uses, pinned to the commit its `v4` tag named
48
- * when this was written (SEC-7839-2): `gh api repos/<action>/commits/v4`.
49
- */
50
- export declare const E2E_ACTION_PINS: Readonly<Record<string, string>>;
51
- /**
52
- * The workflow text (D8). Identical for every spoke at one create-ailk
53
- * version. Every `${{ … }}` sits in an env-style value, never in a script.
54
- */
55
- export declare const CHECKOUT_E2E_WORKFLOW: string;
44
+ export declare const BILLING_ACTION: string;
56
45
  /**
57
46
  * The rendered part of the slice for `site` (#8748 D3): with `signIn: "hub"`
58
- * the billing action (#8217 D8), then the checkout e2e workflow (#7839 D8).
59
- * Neither depends on `site`. The rest of the slice is template source,
60
- * copied by `writeSpokeCheckoutSlice`. Throws `TopologyError` for an invalid
61
- * stamp (SEC-7838-10).
47
+ * the billing action (#8217 D8), else nothing. It does not depend on `site`.
48
+ * The rest of the slice is template source, copied by
49
+ * `writeSpokeCheckoutSlice`. Throws `TopologyError` for an invalid stamp
50
+ * (SEC-7838-10).
62
51
  */
63
52
  export declare function renderSpokeCheckoutFiles(site: string, opts?: {
64
53
  signIn?: SignInMode | undefined;
@@ -76,9 +65,10 @@ export declare function renderSpokeCheckoutFiles(site: string, opts?: {
76
65
  * action, and the web seam — generated, so always rewritten — becomes the
77
66
  * billing render: reported `patched` when it changed, not at all when equal.
78
67
  * Adds the package to `apps/web/package.json` when absent, and raises a plain
79
- * pin older than the one it writes (#7839): the generated checkout e2e
80
- * workflow runs a command a `^0.2.0` pin never reaches. Any other range —
81
- * newer, `workspace:`, a tag — is the site's and is kept.
68
+ * pin older than the one it writes (#7839): the rendered billing action
69
+ * calls the package's current `createSpokeCheckout` shape, which an older
70
+ * pin does not take (#9142). Any other range — newer, `workspace:`, a tag —
71
+ * is the site's and is kept.
82
72
  * Every refusal (an invalid stamp, a version map without the package, a
83
73
  * missing or malformed `apps/web/package.json`, a template missing any
84
74
  * `SPOKE_CHECKOUT_TEMPLATE_PATHS` entry, a template config without exactly
@@ -101,3 +91,16 @@ export declare function writeSpokeCheckoutSlice(targetDir: string, opts: {
101
91
  * dependency, or without `apps/web/package.json`, is left as it is.
102
92
  */
103
93
  export declare function dropSpokeCheckoutDependency(targetDir: string): void;
94
+ /**
95
+ * What the legacy env-key checkout left in a spoke, scaffold-relative: the
96
+ * buy route and the Stripe webhook route (a directory entry covers the tests
97
+ * beside each), `lib/checkout.ts` (the fulfilment) and its test. Each was a
98
+ * template copy create-ailk recorded in `.ailk-template-files.json`;
99
+ * `create-ailk sync-routes --apply` removes the ones still byte-identical to
100
+ * that record.
101
+ */
102
+ export declare const RETIRED_SPOKE_CHECKOUT_PATHS: readonly string[];
103
+ /** The retired checkout e2e workflow — rendered, never recorded (#7839 D8). */
104
+ export declare const RETIRED_CHECKOUT_E2E_WORKFLOW_PATH = ".github/workflows/checkout-e2e.yml";
105
+ /** The workflow's first line, which marks it as create-ailk's to remove. */
106
+ export declare const RETIRED_CHECKOUT_E2E_MARKER = "# Checkout e2e \u2014 GENERATED by create-ailk (#7839). Do not edit: it changes";
@@ -1,21 +1,24 @@
1
1
  /**
2
2
  * The spoke checkout slice (#7838, #8748) — what a spoke carrying `commerce`
3
- * gets in `apps/web`: the standard spoke checkout, copied from the template
4
- * tree. That is the buy route and the Stripe webhook route (allowed to import
5
- * the checkout package, #7873), `lib/checkout.ts` (the standard fulfilment),
6
- * the site's own `lib/checkout-config.ts`, the thank-you and unavailable
7
- * return pages and their tests (`SPOKE_CHECKOUT_TEMPLATE_PATHS`). A spoke that
8
- * signs in through its hub also gets the billing action, the third file
9
- * allowed to import the package, and the web seam render that reaches it
10
- * (#8217 D6–D8); that action and the checkout e2e workflow are still rendered
11
- * here.
3
+ * gets in `apps/web`, copied from the template tree: the site's own
4
+ * `lib/checkout-config.ts` (its stamp) and the thank-you and unavailable
5
+ * return pages with their tests (`SPOKE_CHECKOUT_TEMPLATE_PATHS`). A spoke
6
+ * that signs in through its hub also gets the billing action, the one file
7
+ * allowed to import the package besides the thank-you page, and the web seam
8
+ * render that reaches it (#8217 D6–D8); that action is rendered here.
9
+ *
10
+ * A spoke sells through its hub's Offerings buy route
11
+ * (`apps/web/app/api/offerings/checkout/[lookupKey]/route.ts`), never its own
12
+ * key. The legacy env-key buy route, its Stripe webhook route, `lib/checkout.ts`
13
+ * (the fulfilment) and the checkout e2e workflow are retired (#9142):
14
+ * `create-ailk sync-routes --apply` removes them from an existing spoke
15
+ * (`RETIRED_SPOKE_CHECKOUT_PATHS`, sync-routes.ts).
12
16
  *
13
17
  * Every guarantee lives in `@working-theory/spoke-checkout` and arrives by a
14
18
  * package bump; the copied files are `apps/web` source, tested in the
15
19
  * monorepo and kept current by `create-ailk update`, and apart from the
16
20
  * config's one stamp line they are byte-identical across spokes at one
17
- * create-ailk version (roadmap D3, AC-SPOKE-02). A hub never gets them: its
18
- * checkout is `apps/api`'s.
21
+ * create-ailk version (roadmap D3, AC-SPOKE-02). A hub never gets them.
19
22
  *
20
23
  * Spec: docs/specs/7838-spoke-web-checkout-slice-spec.md (D3–D7, D9);
21
24
  * docs/specs/8748-a-standard-spoke-checkout-route-spec.md (D2, D3).
@@ -30,62 +33,50 @@ import { createHash } from "node:crypto";
30
33
  import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, writeFileSync, } from "node:fs";
31
34
  import { dirname, join, resolve, sep } from "node:path";
32
35
  import { buildScaffoldMarkers, renderTemplateFile, templateDestination } from "./copy.js";
33
- import { SPOKE_CHECKOUT_ROUTE_FILES } from "./derive-architecture.js";
36
+ import { SPOKE_BILLING_ACTION_PATH } from "./derive-architecture.js";
34
37
  import { ENV_CATALOG } from "./lib/env-catalog.js";
35
38
  import { diffTwoStrings, } from "./lib/extract-module-bundle.js";
36
39
  import { substituteWorkspaceVersions, } from "./substitute.js";
37
40
  import { TopologyError, WEB_SEAM_PATH, renderWebSeam, } from "./topology.js";
38
- /** The checkout package a spoke's two route files import. */
41
+ /** The checkout package a spoke's billing action and thank-you page import. */
39
42
  export const SPOKE_CHECKOUT_PACKAGE = "@working-theory/spoke-checkout";
40
43
  const API_CLIENT_PACKAGE = "@working-theory/api-client";
41
- /** The site's checkout data — written once, the site's from then on (#8748 D2). */
44
+ /** The site's checkout stamp — written once, the site's from then on (#8748 D2). */
42
45
  export const SPOKE_CHECKOUT_CONFIG_PATH = "apps/web/lib/checkout-config.ts";
43
- /** The two return pages: Stripe's success URL and every refused buy's target. */
46
+ /** The two return pages a commerce spoke still carries (#8748 D5). */
44
47
  export const SPOKE_CHECKOUT_PAGE_FILES = Object.freeze([
45
48
  "apps/web/app/[locale]/checkout/thank-you/page.tsx",
46
49
  "apps/web/app/[locale]/checkout/unavailable/page.tsx",
47
50
  ]);
48
51
  /**
49
- * The standard spoke checkout's template paths, scaffold-relative (#8748 D3).
50
- * A directory entry covers every file under it, so the tests that sit beside
51
- * the routes and pages travel with them. `copyTemplate` skips all of them for
52
- * every scaffold; `writeSpokeCheckoutSlice` copies them into a commerce spoke.
52
+ * The spoke checkout's template paths, scaffold-relative (#8748 D3). A
53
+ * directory entry covers every file under it, so the tests that sit beside
54
+ * the pages travel with them. `copyTemplate` skips all of them for every
55
+ * scaffold; `writeSpokeCheckoutSlice` copies them into a commerce spoke.
53
56
  */
54
57
  export const SPOKE_CHECKOUT_TEMPLATE_PATHS = Object.freeze([
55
- "apps/web/app/api/checkout",
56
- "apps/web/app/api/webhooks/stripe",
57
58
  "apps/web/app/[locale]/checkout",
58
- "apps/web/lib/checkout.ts",
59
59
  SPOKE_CHECKOUT_CONFIG_PATH,
60
- "apps/web/lib/__tests__/checkout.test.ts",
61
60
  ]);
62
- /** The variables a commerce spoke depends on (D9). The package reads three of them. */
61
+ /** The variables a commerce spoke depends on (D9): the package reads both. */
63
62
  export const SPOKE_CHECKOUT_ENV_CATALOG = Object.freeze({
64
63
  STRIPE_SECRET_KEY: {
65
- description: `Read by ${SPOKE_CHECKOUT_PACKAGE}: a test key (sk_test_ or rk_test_), or a restricted live key the operator sets with STRIPE_MODE=live.`,
66
- secret: true,
67
- },
68
- STRIPE_WEBHOOK_SECRET: {
69
- description: `Read by ${SPOKE_CHECKOUT_PACKAGE}: the signing secret of this site's Stripe webhook endpoint.`,
64
+ description: `Read by ${SPOKE_CHECKOUT_PACKAGE} for the thank-you page's paid-session read and the billing reads, never to sell: a test key (sk_test_ or rk_test_), or a restricted live key the operator sets with STRIPE_MODE=live.`,
70
65
  secret: true,
71
66
  },
72
67
  STRIPE_MODE: {
73
68
  description: `Read by ${SPOKE_CHECKOUT_PACKAGE}: test (the default) or live. Live is the operator's switch, per site.`,
74
69
  secret: false,
75
70
  },
76
- // Read by the standard spoke checkout's fulfilment, so the base catalog
77
- // carries it too (#8748).
78
- AILK_SPOKE_KEY: ENV_CATALOG["AILK_SPOKE_KEY"],
79
71
  });
80
72
  /** The names a commerce spoke declares although only the package reads them (D9). */
81
73
  export const SPOKE_CHECKOUT_DECLARED_ENV = Object.freeze([
82
74
  "STRIPE_SECRET_KEY",
83
- "STRIPE_WEBHOOK_SECRET",
84
75
  "STRIPE_MODE",
85
76
  ]);
86
77
  /**
87
78
  * The catalog (and declared names) a spoke's `appendEnvRows` call passes: a
88
- * commerce spoke adds the checkout's four rows (D9). `ENV_CATALOG` itself is
79
+ * commerce spoke adds the package's two rows (D9). `ENV_CATALOG` itself is
89
80
  * unchanged — its test fails on an entry the monorepo's apps never read.
90
81
  */
91
82
  export function spokeEnvRowOptions(commerce) {
@@ -96,7 +87,7 @@ export function spokeEnvRowOptions(commerce) {
96
87
  }
97
88
  : { catalog: ENV_CATALOG };
98
89
  }
99
- /** `metadata.site` / an offering slug — the package's own pattern. */
90
+ /** `metadata.site` — the package's own pattern. */
100
91
  const SITE_PATTERN = /^[a-z0-9][a-z0-9-]{0,39}$/;
101
92
  /** A stamp a renderer may interpolate: the package's pattern, never `default` (D6). */
102
93
  function isValidSite(site) {
@@ -125,14 +116,17 @@ export function spokeCheckoutSite(projectName) {
125
116
  const digest = createHash("sha256").update(projectName).digest("hex");
126
117
  return `site-${digest.slice(0, 8)}`;
127
118
  }
119
+ /** The billing action's first line, which marks the file as create-ailk's to rewrite. */
120
+ export const BILLING_ACTION_MARKER = "// The spoke billing action — GENERATED by create-ailk (#8217). Do not edit:";
128
121
  /**
129
122
  * The spoke billing action (#8217 D6): wiring only, byte-identical across
130
123
  * sites, so every guarantee arrives by a package bump. A plain server module
131
124
  * (C2): its two exports take no argument, and the hub's read is its only
132
- * source of customer ids.
125
+ * source of customer ids. It builds its config from the site's stamp and the
126
+ * site's own origin (#9142: no longer from `lib/checkout.ts`).
133
127
  */
134
- const BILLING_ACTION = [
135
- "// The spoke billing action — GENERATED by create-ailk (#8217). Do not edit:",
128
+ export const BILLING_ACTION = [
129
+ BILLING_ACTION_MARKER,
136
130
  "// every guarantee lives in the spoke-checkout package and arrives by a",
137
131
  "// package bump. A plain server module, not a Server Action file: neither",
138
132
  "// export takes an argument, and the hub's GET /v1/member/billing is the only",
@@ -141,206 +135,29 @@ const BILLING_ACTION = [
141
135
  `import { member } from "${API_CLIENT_PACKAGE}";`,
142
136
  `import { createSpokeCheckout } from "${SPOKE_CHECKOUT_PACKAGE}";`,
143
137
  "",
144
- `import { spokeCheckoutConfig } from "../../../../../lib/checkout";`,
138
+ `import { checkoutConfig } from "../../../../../lib/checkout-config";`,
145
139
  `import { forwardedCookieInit } from "../../../../../lib/server-api";`,
140
+ `import { resolveBaseUrl } from "../../../../../src/lib/routes";`,
146
141
  "",
147
- "const billing = createSpokeCheckout(spokeCheckoutConfig).billing.forMember(",
148
- " async () => member.billing(await forwardedCookieInit()),",
149
- ");",
142
+ "const billing = createSpokeCheckout({",
143
+ " site: checkoutConfig.site,",
144
+ " siteBaseUrl: resolveBaseUrl(),",
145
+ "}).billing.forMember(async () => member.billing(await forwardedCookieInit()));",
150
146
  "",
151
147
  "export const readSpokeBilling = billing.read;",
152
148
  "export const spokeBillingPortalUrl = billing.portalUrl;",
153
149
  "",
154
150
  ].join("\n");
155
- // ── #7839 D8 — the checkout e2e workflow ─────────────────────────────────────
156
- /** Where a commerce spoke's end-to-end checkout workflow lives. */
157
- export const SPOKE_CHECKOUT_E2E_WORKFLOW_PATH = ".github/workflows/checkout-e2e.yml";
158
- /**
159
- * The Stripe CLI release the workflow installs, and the SHA-256 of its
160
- * `stripe_<v>_linux_x86_64.tar.gz` from that release's own
161
- * `stripe-linux-checksums.txt`. Never bump one without re-reading the other.
162
- */
163
- export const STRIPE_CLI_VERSION = "1.51.1";
164
- export const STRIPE_CLI_LINUX_X64_SHA256 = "38c8de73a8cab810a4aab8a4015cd4e3398b7b87aa42b6944d09d89ed524ac53";
165
- /** The Playwright the workflow installs — the monorepo's own pinned version. */
166
- export const E2E_PLAYWRIGHT_VERSION = "1.61.1";
167
- /**
168
- * Every action the workflow uses, pinned to the commit its `v4` tag named
169
- * when this was written (SEC-7839-2): `gh api repos/<action>/commits/v4`.
170
- */
171
- export const E2E_ACTION_PINS = Object.freeze({
172
- "actions/checkout": "11d5960a326750d5838078e36cf38b85af677262",
173
- "pnpm/action-setup": "b906affcce14559ad1aafd4ab0e942779e9f58b1",
174
- "actions/setup-node": "49933ea5288caeca8642d1e84afbd3f7d6820020",
175
- "actions/upload-artifact": "ea165f8d65b6e75b540449e92b4886f43607fa02",
176
- });
177
- function uses(action) {
178
- return `uses: ${action}@${E2E_ACTION_PINS[action]} # v4`;
179
- }
180
- /**
181
- * The workflow text (D8). Identical for every spoke at one create-ailk
182
- * version. Every `${{ … }}` sits in an env-style value, never in a script.
183
- */
184
- export const CHECKOUT_E2E_WORKFLOW = `# Checkout e2e — GENERATED by create-ailk (#7839). Do not edit: it changes
185
- # with the ${SPOKE_CHECKOUT_PACKAGE} command it runs.
186
- #
187
- # Pays each named offering once through Stripe's hosted Checkout in TEST
188
- # mode, then checks Stripe's webhook was answered 2xx and this site's
189
- # fulfilment ran. Start it by hand (Actions > Checkout e2e > Run workflow)
190
- # after a checkout change and before any live flip. It never runs on a push
191
- # or a pull request: it needs the network, the Stripe CLI and test-mode state.
192
- #
193
- # Setup, once: create the GitHub Environment "checkout-e2e", limit its
194
- # deployment branches to the default branch, and add to it the secrets
195
- # STRIPE_TEST_SECRET_KEY (an sk_test_ or rk_test_ key) and AILK_SPOKE_KEY,
196
- # and the variable AILK_API_URL (https). Each paid session posts one
197
- # test-mode lead to the hub. Test subscriptions it creates are cancelled.
198
- name: Checkout e2e
199
-
200
- on:
201
- workflow_dispatch:
202
- inputs:
203
- offerings:
204
- description: Comma-separated fixed offering slugs from apps/web/lib/checkout-config.ts
205
- required: true
206
- type: string
207
-
208
- permissions:
209
- contents: read
210
-
211
- concurrency:
212
- group: checkout-e2e
213
- cancel-in-progress: false
214
-
215
- jobs:
216
- checkout-e2e:
217
- runs-on: ubuntu-latest
218
- timeout-minutes: 30
219
- environment: checkout-e2e
220
- env:
221
- NEXT_PUBLIC_BASE_URL: http://localhost:3000
222
- STRIPE_MODE: test
223
- steps:
224
- - name: Refuse a live key or a missing setting
225
- env:
226
- STRIPE_SECRET_KEY: \${{ secrets.STRIPE_TEST_SECRET_KEY }}
227
- AILK_SPOKE_KEY: \${{ secrets.AILK_SPOKE_KEY }}
228
- AILK_API_URL: \${{ vars.AILK_API_URL }}
229
- run: |
230
- case "$STRIPE_SECRET_KEY" in
231
- sk_test_*|rk_test_*) ;;
232
- *) echo "::error::STRIPE_TEST_SECRET_KEY must be an sk_test_ or rk_test_ key"; exit 2 ;;
233
- esac
234
- if [ -z "$AILK_SPOKE_KEY" ]; then
235
- echo "::error::set the AILK_SPOKE_KEY secret in the checkout-e2e environment"; exit 2
236
- fi
237
- case "$AILK_API_URL" in
238
- https://*) ;;
239
- *) echo "::error::AILK_API_URL must be the hub's https origin"; exit 2 ;;
240
- esac
241
-
242
- - ${uses("actions/checkout")}
243
-
244
- - ${uses("pnpm/action-setup")}
245
-
246
- - run: test -f pnpm-lock.yaml || touch pnpm-lock.yaml
247
-
248
- - ${uses("actions/setup-node")}
249
- with:
250
- node-version: 20
251
- cache: pnpm
252
- cache-dependency-path: pnpm-lock.yaml
253
-
254
- - name: Install
255
- run: |
256
- if [ -s pnpm-lock.yaml ]; then pnpm install --frozen-lockfile; else pnpm install; fi
257
-
258
- - name: Install the Stripe CLI (pinned, checksum-checked)
259
- run: |
260
- curl -fsSL -o "$RUNNER_TEMP/stripe.tar.gz" \\
261
- https://github.com/stripe/stripe-cli/releases/download/v${STRIPE_CLI_VERSION}/stripe_${STRIPE_CLI_VERSION}_linux_x86_64.tar.gz
262
- echo "${STRIPE_CLI_LINUX_X64_SHA256} $RUNNER_TEMP/stripe.tar.gz" | sha256sum -c -
263
- mkdir -p "$RUNNER_TEMP/stripe-cli"
264
- tar -xzf "$RUNNER_TEMP/stripe.tar.gz" -C "$RUNNER_TEMP/stripe-cli" stripe
265
-
266
- - name: Install Playwright (pinned) and Chromium
267
- run: |
268
- npm install --no-save --ignore-scripts --prefix "$RUNNER_TEMP/e2e-deps" playwright@${E2E_PLAYWRIGHT_VERSION}
269
- "$RUNNER_TEMP/e2e-deps/node_modules/.bin/playwright" install --with-deps chromium
270
-
271
- - name: Fetch the Stripe CLI's signing secret
272
- env:
273
- STRIPE_API_KEY: \${{ secrets.STRIPE_TEST_SECRET_KEY }}
274
- run: |
275
- umask 077
276
- secret="$("$RUNNER_TEMP/stripe-cli/stripe" listen --print-secret)"
277
- echo "::add-mask::$secret"
278
- printf '%s' "$secret" > "$RUNNER_TEMP/stripe-whsec"
279
-
280
- - name: Build the site
281
- run: pnpm --filter ./apps/web build
282
-
283
- - name: Start the site
284
- env:
285
- STRIPE_SECRET_KEY: \${{ secrets.STRIPE_TEST_SECRET_KEY }}
286
- AILK_SPOKE_KEY: \${{ secrets.AILK_SPOKE_KEY }}
287
- AILK_API_URL: \${{ vars.AILK_API_URL }}
288
- run: |
289
- STRIPE_WEBHOOK_SECRET="$(cat "$RUNNER_TEMP/stripe-whsec")"
290
- export STRIPE_WEBHOOK_SECRET
291
- cd apps/web
292
- nohup pnpm exec next start -p 3000 -H localhost > "$RUNNER_TEMP/next.log" 2>&1 &
293
- echo $! > "$RUNNER_TEMP/next.pid"
294
- for _ in $(seq 1 60); do
295
- curl -fsS -o /dev/null http://localhost:3000/checkout/thank-you && exit 0
296
- sleep 2
297
- done
298
- echo "::error::the site did not start on localhost:3000; run it locally with the same settings to see why"
299
- exit 1
300
-
301
- - name: Pay each offering
302
- working-directory: apps/web
303
- env:
304
- STRIPE_SECRET_KEY: \${{ secrets.STRIPE_TEST_SECRET_KEY }}
305
- OFFERINGS: \${{ inputs.offerings }}
306
- SPOKE_CHECKOUT_E2E_DEPS: \${{ runner.temp }}/e2e-deps
307
- STRIPE_CLI_PATH: \${{ runner.temp }}/stripe-cli/stripe
308
- run: |
309
- STRIPE_WEBHOOK_SECRET="$(cat "$RUNNER_TEMP/stripe-whsec")"
310
- export STRIPE_WEBHOOK_SECRET
311
- pnpm exec spoke-checkout-e2e --base-url http://localhost:3000 --offerings "$OFFERINGS" --out "$RUNNER_TEMP/checkout-e2e"
312
-
313
- - name: Stop the site
314
- if: always()
315
- run: |
316
- if [ -f "$RUNNER_TEMP/next.pid" ]; then pkill -P "$(cat "$RUNNER_TEMP/next.pid")" || true; kill "$(cat "$RUNNER_TEMP/next.pid")" || true; fi
317
- rm -f "$RUNNER_TEMP/stripe-whsec"
318
-
319
- - name: Keep the report
320
- if: always()
321
- ${uses("actions/upload-artifact")}
322
- with:
323
- name: checkout-e2e
324
- path: \${{ runner.temp }}/checkout-e2e
325
- if-no-files-found: ignore
326
- retention-days: 7
327
- `;
328
151
  /**
329
152
  * The rendered part of the slice for `site` (#8748 D3): with `signIn: "hub"`
330
- * the billing action (#8217 D8), then the checkout e2e workflow (#7839 D8).
331
- * Neither depends on `site`. The rest of the slice is template source,
332
- * copied by `writeSpokeCheckoutSlice`. Throws `TopologyError` for an invalid
333
- * stamp (SEC-7838-10).
153
+ * the billing action (#8217 D8), else nothing. It does not depend on `site`.
154
+ * The rest of the slice is template source, copied by
155
+ * `writeSpokeCheckoutSlice`. Throws `TopologyError` for an invalid stamp
156
+ * (SEC-7838-10).
334
157
  */
335
158
  export function renderSpokeCheckoutFiles(site, opts = {}) {
336
159
  assertSite(site);
337
- const billingActionPath = SPOKE_CHECKOUT_ROUTE_FILES[2];
338
- return [
339
- ...(opts.signIn === "hub"
340
- ? [[billingActionPath, BILLING_ACTION]]
341
- : []),
342
- [SPOKE_CHECKOUT_E2E_WORKFLOW_PATH, CHECKOUT_E2E_WORKFLOW],
343
- ];
160
+ return opts.signIn === "hub" ? [[SPOKE_BILLING_ACTION_PATH, BILLING_ACTION]] : [];
344
161
  }
345
162
  /** The one line of the template's config the writer stamps (#8748 D2). */
346
163
  const SITE_LINE = /^const SITE = "[a-z0-9-]+";$/gm;
@@ -438,9 +255,10 @@ function isOlderPin(current, wanted) {
438
255
  * action, and the web seam — generated, so always rewritten — becomes the
439
256
  * billing render: reported `patched` when it changed, not at all when equal.
440
257
  * Adds the package to `apps/web/package.json` when absent, and raises a plain
441
- * pin older than the one it writes (#7839): the generated checkout e2e
442
- * workflow runs a command a `^0.2.0` pin never reaches. Any other range —
443
- * newer, `workspace:`, a tag — is the site's and is kept.
258
+ * pin older than the one it writes (#7839): the rendered billing action
259
+ * calls the package's current `createSpokeCheckout` shape, which an older
260
+ * pin does not take (#9142). Any other range — newer, `workspace:`, a tag —
261
+ * is the site's and is kept.
444
262
  * Every refusal (an invalid stamp, a version map without the package, a
445
263
  * missing or malformed `apps/web/package.json`, a template missing any
446
264
  * `SPOKE_CHECKOUT_TEMPLATE_PATHS` entry, a template config without exactly
@@ -558,3 +376,22 @@ export function dropSpokeCheckoutDependency(targetDir) {
558
376
  delete deps[SPOKE_CHECKOUT_PACKAGE];
559
377
  writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}${before.endsWith("\n") ? "\n" : ""}`);
560
378
  }
379
+ // ── #9142 — the retired legacy env-key checkout ─────────────────────────────
380
+ /**
381
+ * What the legacy env-key checkout left in a spoke, scaffold-relative: the
382
+ * buy route and the Stripe webhook route (a directory entry covers the tests
383
+ * beside each), `lib/checkout.ts` (the fulfilment) and its test. Each was a
384
+ * template copy create-ailk recorded in `.ailk-template-files.json`;
385
+ * `create-ailk sync-routes --apply` removes the ones still byte-identical to
386
+ * that record.
387
+ */
388
+ export const RETIRED_SPOKE_CHECKOUT_PATHS = Object.freeze([
389
+ "apps/web/app/api/checkout",
390
+ "apps/web/app/api/webhooks/stripe",
391
+ "apps/web/lib/checkout.ts",
392
+ "apps/web/lib/__tests__/checkout.test.ts",
393
+ ]);
394
+ /** The retired checkout e2e workflow — rendered, never recorded (#7839 D8). */
395
+ export const RETIRED_CHECKOUT_E2E_WORKFLOW_PATH = ".github/workflows/checkout-e2e.yml";
396
+ /** The workflow's first line, which marks it as create-ailk's to remove. */
397
+ export const RETIRED_CHECKOUT_E2E_MARKER = "# Checkout e2e — GENERATED by create-ailk (#7839). Do not edit: it changes";
@@ -109,9 +109,6 @@ export declare const ALIASES: Readonly<{
109
109
  readonly design: "standard";
110
110
  readonly modules: readonly ["marketing"];
111
111
  readonly categories: readonly ["accounting", "legal", "technology"];
112
- readonly layout: {
113
- readonly announcementBanner: true;
114
- };
115
112
  };
116
113
  /**
117
114
  * The Pro flagship. Its module list is OFFERINGS § 4 offering 3's tiers line
@@ -1,13 +1,17 @@
1
1
  /**
2
- * The two pages every starter carries whatever its shape (#6658 D5,
2
+ * The pages every starter carries whatever its shape (#6658 D5,
3
3
  * `docs/decisions/starter-roster-page-set.md` D4/D7): a glossary and a
4
- * newsletter signup. Each is a module-less `GATEABLE_SURFACES` entry the
5
- * roster enables, with its seed in `_base`.
4
+ * newsletter signup — each a module-less `GATEABLE_SURFACES` entry the
5
+ * roster enables, with its seed in `_base` — and the `/unsubscribe` landing
6
+ * every signed link calls (#8850 D1). That one is a code route with no
7
+ * `content`, like `/sign-in`: a `CORE_ROUTE_DIRS` entry every scaffold
8
+ * already serves, never gated (#8438 D17), declared here so the roster of
9
+ * record lists it beside the newsletter.
6
10
  *
7
- * The third — `/book-a-call` — was retired by #6897 D10: every scaffold's
8
- * former `/book-a-call` link now points at `/meet/book-a-call`, served by
9
- * `FREE_MEET_PLACEHOLDER_ROWS` below (a Free placeholder) or by the
10
- * scheduling module's own live `/meet/[slug]` page.
11
+ * A former third page — `/book-a-call` — was retired by #6897 D10: every
12
+ * scaffold's former `/book-a-call` link now points at `/meet/book-a-call`,
13
+ * served by `FREE_MEET_PLACEHOLDER_ROWS` below (a Free placeholder) or by
14
+ * the scheduling module's own live `/meet/[slug]` page.
11
15
  */
12
16
  const SHAPE_INDEPENDENT_ROWS = [
13
17
  { route: "/glossary", content: ["content/en/GlossaryList/glossary.mdx"] },
@@ -15,6 +19,7 @@ const SHAPE_INDEPENDENT_ROWS = [
15
19
  route: "/newsletter",
16
20
  content: ["content/en/NewsletterSignup/newsletter.mdx"],
17
21
  },
22
+ { route: "/unsubscribe" },
18
23
  ];
19
24
  /**
20
25
  * The Free `/meet` placeholder pages (#6897 D5) — a meeting-types hub
@@ -30,10 +35,12 @@ const SHAPE_INDEPENDENT_ROWS = [
30
35
  * Every alias that takes the `scheduling` module drops `"/meet"` and
31
36
  * `"/meet/[slug]"` (D5) — otherwise `mergeRoster` refuses the duplicate
32
37
  * route the module's own rows also contribute. The ROWS go; the SEEDS stay
33
- * (#6942, operator 2026-09-28, partly reversing D5): until
34
- * `SCHEDULING_CAL_LINK` is configured the module's pages hand off to the Free
35
- * placeholder routes, which serve these seeds. `withMeetPlaceholderSeeds`
36
- * (`site-roster.ts`) folds them onto the module's rows.
38
+ * (#6942, operator 2026-09-28, partly reversing D5): the module's meeting
39
+ * page reads a `BookCall/<slug>` seed for its URL, its JSON-LD and its
40
+ * fallback title, never its sections (#8843 C5). `withMeetPlaceholderSeeds`
41
+ * (`site-roster.ts`) folds them onto the module's rows. The hub-driven
42
+ * `/private` index has no row here: it reaches those aliases through the
43
+ * module's own `pageContributions` (#8843 D8, #8850 D2).
37
44
  */
38
45
  const FREE_MEET_PLACEHOLDER_ROWS = [
39
46
  { route: "/meet", content: ["content/en/LandingPage/meet.mdx"] },
@@ -391,9 +398,12 @@ export const ALIASES = Object.freeze({
391
398
  design: "standard",
392
399
  modules: ["marketing"],
393
400
  categories: ["accounting", "legal", "technology"],
394
- // The demo site carries the "Get started with this template" banner
395
- // (operator ruling 2026-09-23); every other layout knob keeps its default.
396
- layout: { announcementBanner: true },
401
+ // No `layout`: every knob keeps its default. The "Get started for free or
402
+ // upgrade to Pro" banner this alias carried for the demo site (operator
403
+ // ruling 2026-09-23) reached every keyless `organization-product`
404
+ // scaffold too, so Epic #8845's "no starter page ships our own brands or
405
+ // upsells" retires it (#8850, spec C6); a demo deployment that wants it
406
+ // sets it in its own config.
397
407
  },
398
408
  /**
399
409
  * The Pro flagship. Its module list is OFFERINGS § 4 offering 3's tiers line