create-ailk 0.22.0 → 0.24.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 (287) hide show
  1. package/README.md +27 -14
  2. package/component-catalog.json +149 -5
  3. package/component-catalog.md +7 -6
  4. package/dist/add-module.js +17 -10
  5. package/dist/add-route.d.ts +55 -1
  6. package/dist/add-route.js +301 -13
  7. package/dist/cli.js +27 -10
  8. package/dist/derive-architecture.d.ts +7 -5
  9. package/dist/derive-architecture.js +26 -32
  10. package/dist/lib/design-checks.js +4 -2
  11. package/dist/lib/env-catalog.js +0 -4
  12. package/dist/lib/npm-auth.d.ts +9 -0
  13. package/dist/lib/npm-auth.js +48 -0
  14. package/dist/lib/scaffold-render-sweep.d.ts +36 -2
  15. package/dist/lib/scaffold-render-sweep.js +50 -8
  16. package/dist/module-architecture.js +115 -10
  17. package/dist/paid-sections.d.ts +21 -2
  18. package/dist/paid-sections.js +69 -7
  19. package/dist/seed-flows.d.ts +4 -1
  20. package/dist/seed-flows.js +50 -4
  21. package/dist/spoke-checkout.d.ts +38 -35
  22. package/dist/spoke-checkout.js +66 -229
  23. package/dist/starter-grid.d.ts +0 -3
  24. package/dist/starter-grid.js +24 -14
  25. package/dist/surfaces.js +33 -12
  26. package/dist/sync-routes.d.ts +68 -5
  27. package/dist/sync-routes.js +455 -29
  28. package/dist/template-record.d.ts +1 -1
  29. package/dist/template-record.js +3 -2
  30. package/dist/update.js +10 -0
  31. package/package.json +3 -3
  32. package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +4 -49
  33. package/site-templates/_base/content/en/Pricing/pricing.mdx +3 -21
  34. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +23 -20
  35. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +28 -14
  36. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +15 -39
  37. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +38 -34
  38. package/site-templates/marketing-site-free/content/en/NewsletterSignup/newsletter.mdx +6 -43
  39. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +14 -31
  40. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +26 -25
  41. package/site-templates/shapes/local-business-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
  42. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +14 -31
  43. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +26 -25
  44. package/site-templates/shapes/organization-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
  45. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +14 -31
  46. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +26 -25
  47. package/site-templates/shapes/person-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
  48. package/templates/.env.example +13 -7
  49. package/templates/.github/workflows/ci.yml +13 -0
  50. package/templates/CLAUDE.md +4 -2
  51. package/templates/README.md +1 -0
  52. package/templates/_gitignore +4 -0
  53. package/templates/apps/api/.env.example +34 -0
  54. package/templates/apps/api/CLAUDE.md +94 -19
  55. package/templates/apps/api/package.json +1 -0
  56. package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +2 -0
  57. package/templates/apps/api/src/bin/stripe-connections-rekey.ts +45 -0
  58. package/templates/apps/api/src/config/__tests__/stripe-connection-key.test.ts +185 -0
  59. package/templates/apps/api/src/config/index.ts +109 -0
  60. package/templates/apps/api/src/lib/__mocks__/prisma.ts +7 -1
  61. package/templates/apps/api/src/lib/__tests__/flow-rename-race.db.test.ts +116 -0
  62. package/templates/apps/api/src/lib/__tests__/site.test.ts +16 -0
  63. package/templates/apps/api/src/lib/error-serializer.ts +74 -7
  64. package/templates/apps/api/src/lib/site.ts +4 -1
  65. package/templates/apps/api/src/lib/stripe.ts +77 -4
  66. package/templates/apps/api/src/lib/webhook-idempotency.ts +66 -2
  67. package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +218 -0
  68. package/templates/apps/api/src/middleware/__tests__/write-budget.test.ts +174 -0
  69. package/templates/apps/api/src/middleware/rate-limit.ts +226 -4
  70. package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +127 -0
  71. package/templates/apps/api/src/openapi/spec.ts +343 -7
  72. package/templates/apps/api/src/routes/billing/plan-checkout.ts +332 -0
  73. package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +11 -0
  74. package/templates/apps/api/src/routes/checkout/__tests__/offerings-authorization.test.ts +1426 -0
  75. package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +11 -0
  76. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +49 -0
  77. package/templates/apps/api/src/routes/checkout/session.ts +57 -25
  78. package/templates/apps/api/src/routes/checkout/sessions.ts +232 -82
  79. package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +5 -5
  80. package/templates/apps/api/src/routes/consent/__tests__/sign-up-rate-limit.test.ts +160 -0
  81. package/templates/apps/api/src/routes/consent/account.ts +4 -2
  82. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +2 -2
  83. package/templates/apps/api/src/routes/content/__tests__/offerings.test.ts +731 -0
  84. package/templates/apps/api/src/routes/content/__tests__/pricing.test.ts +753 -0
  85. package/templates/apps/api/src/routes/content/__tests__/shown-offerings.test.ts +762 -0
  86. package/templates/apps/api/src/routes/content/get-page.ts +34 -119
  87. package/templates/apps/api/src/routes/content/index.ts +16 -2
  88. package/templates/apps/api/src/routes/content/list-pages.ts +15 -36
  89. package/templates/apps/api/src/routes/content/offerings.ts +184 -0
  90. package/templates/apps/api/src/routes/content/pricing.ts +176 -0
  91. package/templates/apps/api/src/routes/content/search.ts +16 -45
  92. package/templates/apps/api/src/routes/content/shown-offerings.ts +229 -0
  93. package/templates/apps/api/src/routes/leads/index.ts +377 -308
  94. package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +20 -9
  95. package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +17 -6
  96. package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +1 -1
  97. package/templates/apps/api/src/routes/webhooks/README.md +66 -0
  98. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +1 -0
  99. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-offerings.test.ts +814 -0
  100. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +80 -68
  101. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +6 -5
  102. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +8 -2
  103. package/templates/apps/api/src/routes/webhooks/index.ts +23 -0
  104. package/templates/apps/api/src/routes/webhooks/stripe-offerings.ts +384 -0
  105. package/templates/apps/api/src/routes/webhooks/stripe.ts +93 -17
  106. package/templates/apps/api/src/server.ts +165 -51
  107. package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +121 -4
  108. package/templates/apps/api/src/services/__tests__/checkout-plan.test.ts +313 -0
  109. package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +61 -2
  110. package/templates/apps/api/src/services/__tests__/keyless-workspace-predicate.test.ts +234 -0
  111. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +374 -44
  112. package/templates/apps/api/src/services/__tests__/person.test.ts +260 -1
  113. package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +450 -0
  114. package/templates/apps/api/src/services/__tests__/project-listings.counts.test.ts +202 -0
  115. package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +15 -18
  116. package/templates/apps/api/src/services/abandon-email.ts +28 -5
  117. package/templates/apps/api/src/services/deliverable-fulfillment.ts +15 -12
  118. package/templates/apps/api/src/services/entitlements.ts +157 -1
  119. package/templates/apps/api/src/services/lead-routing.ts +113 -58
  120. package/templates/apps/api/src/services/offerings/__tests__/catalog.test.ts +348 -0
  121. package/templates/apps/api/src/services/offerings/__tests__/checkout-session-record.test.ts +268 -0
  122. package/templates/apps/api/src/services/offerings/__tests__/connection.test.ts +975 -0
  123. package/templates/apps/api/src/services/offerings/__tests__/fixtures.ts +416 -0
  124. package/templates/apps/api/src/services/offerings/__tests__/org-products.test.ts +400 -0
  125. package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +685 -0
  126. package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +231 -0
  127. package/templates/apps/api/src/services/offerings/catalog.ts +318 -0
  128. package/templates/apps/api/src/services/offerings/checkout-authorization.ts +403 -0
  129. package/templates/apps/api/src/services/offerings/checkout-session-record.ts +192 -0
  130. package/templates/apps/api/src/services/offerings/connection.ts +1083 -0
  131. package/templates/apps/api/src/services/offerings/index.ts +614 -0
  132. package/templates/apps/api/src/services/offerings/source.ts +140 -0
  133. package/templates/apps/api/src/services/offerings/store.ts +150 -0
  134. package/templates/apps/api/src/services/person.ts +147 -9
  135. package/templates/apps/api/src/services/plan-offerings.ts +96 -30
  136. package/templates/apps/api/src/services/project-listings.ts +43 -31
  137. package/templates/apps/api/src/services/public-site-resolver.ts +19 -14
  138. package/templates/apps/mcp/CLAUDE.md +32 -18
  139. package/templates/apps/mcp/__tests__/app-entry.test.ts +2 -2
  140. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +9 -4
  141. package/templates/apps/mcp/__tests__/get_offerings.test.ts +358 -0
  142. package/templates/apps/mcp/__tests__/get_pricing.test.ts +364 -0
  143. package/templates/apps/mcp/__tests__/http-transport.test.ts +95 -14
  144. package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +8 -3
  145. package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +0 -25
  146. package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +22 -7
  147. package/templates/apps/mcp/__tests__/list_pages.test.ts +84 -44
  148. package/templates/apps/mcp/__tests__/locale.test.ts +41 -56
  149. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +2 -2
  150. package/templates/apps/mcp/__tests__/rate-limit-write.test.ts +189 -0
  151. package/templates/apps/mcp/__tests__/read_content.test.ts +115 -78
  152. package/templates/apps/mcp/__tests__/request_callback.test.ts +63 -128
  153. package/templates/apps/mcp/__tests__/search_content.test.ts +76 -61
  154. package/templates/apps/mcp/__tests__/server.test.ts +13 -11
  155. package/templates/apps/mcp/__tests__/start_purchase.test.ts +208 -12
  156. package/templates/apps/mcp/__tests__/submit_lead.test.ts +82 -167
  157. package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +59 -130
  158. package/templates/apps/mcp/__tests__/tools/purchase.test.ts +11 -11
  159. package/templates/apps/mcp/package.json +1 -0
  160. package/templates/apps/mcp/src/abuse/index.ts +18 -11
  161. package/templates/apps/mcp/src/http-transport.ts +47 -9
  162. package/templates/apps/mcp/src/lib/content-adapter.ts +0 -26
  163. package/templates/apps/mcp/src/lib/empty-content-adapter.ts +5 -4
  164. package/templates/apps/mcp/src/lib/shown-offerings.ts +105 -0
  165. package/templates/apps/mcp/src/lib/site-config.ts +53 -0
  166. package/templates/apps/mcp/src/server.ts +5 -0
  167. package/templates/apps/mcp/src/tools/capture_lead.ts +30 -28
  168. package/templates/apps/mcp/src/tools/get_offerings.ts +47 -30
  169. package/templates/apps/mcp/src/tools/get_pricing.ts +40 -40
  170. package/templates/apps/mcp/src/tools/index.ts +22 -21
  171. package/templates/apps/mcp/src/tools/list_pages.ts +27 -33
  172. package/templates/apps/mcp/src/tools/purchase.ts +30 -2
  173. package/templates/apps/mcp/src/tools/read_content.ts +46 -40
  174. package/templates/apps/mcp/src/tools/request_callback.ts +21 -28
  175. package/templates/apps/mcp/src/tools/search_content.ts +20 -24
  176. package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +23 -26
  177. package/templates/apps/mcp/src/workspace-server.ts +15 -0
  178. package/templates/apps/web/.env.example +10 -31
  179. package/templates/apps/web/CLAUDE.md +324 -42
  180. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/agreements-prompt.test.tsx +3 -3
  181. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout.test.tsx +3 -3
  182. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/privacy.test.tsx +3 -3
  183. package/templates/apps/web/app/[locale]/careers/[slug]/[action]/page.tsx +189 -0
  184. package/templates/apps/web/app/[locale]/careers/[slug]/apply.actions.ts +4 -2
  185. package/templates/apps/web/app/[locale]/careers/[slug]/job-application-form.ts +8 -3
  186. package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +22 -144
  187. package/templates/apps/web/app/[locale]/careers/__tests__/apply.actions.test.ts +9 -0
  188. package/templates/apps/web/app/[locale]/careers/__tests__/careers-hub.test.tsx +172 -0
  189. package/templates/apps/web/app/[locale]/careers/__tests__/careers-set.test.tsx +149 -0
  190. package/templates/apps/web/app/[locale]/careers/__tests__/job-application-form.test.ts +22 -24
  191. package/templates/apps/web/app/[locale]/careers/__tests__/job-apply-page.test.tsx +140 -66
  192. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +57 -22
  193. package/templates/apps/web/app/[locale]/careers/page.tsx +129 -5
  194. package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +1 -1
  195. package/templates/apps/web/app/[locale]/contact/__tests__/contact-magnet-page.test.tsx +119 -11
  196. package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +28 -5
  197. package/templates/apps/web/app/[locale]/contact/contact.actions.ts +20 -6
  198. package/templates/apps/web/app/[locale]/contact/intake-script.ts +1 -1
  199. package/templates/apps/web/app/[locale]/contact/page.tsx +99 -92
  200. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +237 -5
  201. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.count.test.tsx +43 -0
  202. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.deliverable.test.tsx +200 -0
  203. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.ending.test.tsx +197 -0
  204. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/magnet-page.test.tsx +430 -0
  205. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +21 -9
  206. package/templates/apps/web/app/[locale]/flows/[slug]/magnet-page.tsx +369 -0
  207. package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +12 -73
  208. package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +10 -31
  209. package/templates/apps/web/app/[locale]/layout.tsx +55 -4
  210. package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +174 -104
  211. package/templates/apps/web/app/[locale]/newsletter/page.tsx +95 -58
  212. package/templates/apps/web/app/[locale]/pricing/__tests__/__snapshots__/unconnected.test.tsx.snap +7 -0
  213. package/templates/apps/web/app/[locale]/pricing/__tests__/offerings.test.tsx +599 -0
  214. package/templates/apps/web/app/[locale]/pricing/__tests__/page.test.tsx +216 -0
  215. package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +236 -0
  216. package/templates/apps/web/app/[locale]/pricing/page.tsx +367 -6
  217. package/templates/apps/web/app/[locale]/team/__tests__/team-routes.test.tsx +4 -1
  218. package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe-rsc-boundary.test.tsx +17 -0
  219. package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe.test.tsx +62 -3
  220. package/templates/apps/web/app/[locale]/unsubscribe/page.tsx +70 -18
  221. package/templates/apps/web/app/[locale]/unsubscribe/unsubscribe-button.tsx +30 -9
  222. package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +316 -0
  223. package/templates/apps/web/app/api/offerings/checkout/__tests__/route.test.ts +810 -0
  224. package/templates/apps/web/lib/__tests__/auth-routes.test.ts +27 -1
  225. package/templates/apps/web/lib/__tests__/deck-page.test.ts +2 -1
  226. package/templates/apps/web/lib/__tests__/in-page-magnet.test.ts +265 -0
  227. package/templates/apps/web/lib/__tests__/magnet-routes.test.ts +64 -0
  228. package/templates/apps/web/lib/__tests__/newsletter.actions.test.ts +293 -0
  229. package/templates/apps/web/lib/__tests__/offerings-pricing.test.ts +601 -0
  230. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +67 -8
  231. package/templates/apps/web/lib/auth-routes.ts +36 -2
  232. package/templates/apps/web/lib/checkout-config.ts +9 -20
  233. package/templates/apps/web/lib/deck-page.ts +1 -0
  234. package/templates/apps/web/lib/deliverable-grant.ts +18 -0
  235. package/templates/apps/web/lib/in-page-magnet.ts +416 -0
  236. package/templates/apps/web/lib/magnet-routes.ts +44 -0
  237. package/templates/apps/web/lib/modules.generated.ts +9 -0
  238. package/templates/apps/web/lib/newsletter.actions.ts +205 -0
  239. package/templates/apps/web/lib/offerings-pricing.ts +326 -0
  240. package/templates/apps/web/lib/posting-title.ts +17 -0
  241. package/templates/apps/web/lib/sign-up-agreements.ts +4 -2
  242. package/templates/apps/web/messages/en.json +49 -15
  243. package/templates/apps/web/middleware.ts +12 -2
  244. package/templates/apps/web/security-headers.config.ts +17 -0
  245. package/templates/content/en/BlogList/blog.mdx +4 -71
  246. package/templates/content/en/BookCall/book-a-call.mdx +1 -41
  247. package/templates/content/en/BookCall/onsite-estimate.mdx +1 -17
  248. package/templates/content/en/BookCall/onsite-service.mdx +1 -17
  249. package/templates/content/en/BookCall/start-project.mdx +1 -17
  250. package/templates/content/en/ContactPage/contact.mdx +6 -63
  251. package/templates/content/en/HomePage/home.mdx +3 -21
  252. package/templates/database/CHANGELOG.md +51 -0
  253. package/templates/database/__tests__/in-page-magnets-migration.test.ts +161 -0
  254. package/templates/database/__tests__/offerings-checkout-session.db.test.ts +270 -0
  255. package/templates/database/__tests__/offerings-migration.test.ts +197 -0
  256. package/templates/database/__tests__/offerings-shown-price-ids-migration.test.ts +86 -0
  257. package/templates/database/__tests__/offerings.db.test.ts +303 -0
  258. package/templates/database/__tests__/seed.test.ts +63 -1
  259. package/templates/database/__tests__/trial-magnet-migration.test.ts +186 -0
  260. package/templates/database/auth/schema.prisma +4 -0
  261. package/templates/database/config/schema.prisma +91 -0
  262. package/templates/database/migrations/20261020120000_offerings_data_model/migration.sql +146 -0
  263. package/templates/database/migrations/20261021120000_offerings_checkout_session/migration.sql +77 -0
  264. package/templates/database/migrations/20261022120000_in_page_magnets/migration.sql +45 -0
  265. package/templates/database/migrations/20261023120000_offerings_shown_price_ids/migration.sql +47 -0
  266. package/templates/database/migrations/20261024120000_organization_plan_checkout_session/migration.sql +16 -0
  267. package/templates/database/migrations/20261025120000_trial_magnet/migration.sql +28 -0
  268. package/templates/database/migrations/20261026120000_trial_magnet_proof_line/migration.sql +23 -0
  269. package/templates/database/package.json +1 -1
  270. package/templates/database/seed.ts +57 -2
  271. package/templates/tools/lint-architecture.ts +205 -1
  272. package/versions.json +35 -35
  273. package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +0 -199
  274. package/templates/apps/api/src/routes/content/slug.ts +0 -9
  275. package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +0 -93
  276. package/templates/apps/web/app/[locale]/downloads/page.tsx +0 -37
  277. package/templates/apps/web/app/[locale]/newsletter/__tests__/newsletter.actions.test.ts +0 -139
  278. package/templates/apps/web/app/[locale]/newsletter/newsletter.actions.ts +0 -93
  279. package/templates/apps/web/app/api/checkout/[offering]/route.ts +0 -48
  280. package/templates/apps/web/app/api/checkout/__tests__/route.test.ts +0 -120
  281. package/templates/apps/web/app/api/checkout/__tests__/success-path.test.ts +0 -86
  282. package/templates/apps/web/app/api/webhooks/stripe/__tests__/route.test.ts +0 -92
  283. package/templates/apps/web/app/api/webhooks/stripe/route.ts +0 -31
  284. package/templates/apps/web/lib/__tests__/checkout.test.ts +0 -243
  285. package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +0 -120
  286. package/templates/apps/web/lib/checkout.ts +0 -93
  287. package/templates/apps/web/lib/meeting-slugs.ts +0 -63
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Hands `NPM_TOKEN` to the scaffold's own `pnpm install` through a user-level
3
+ * npmrc (#9092).
4
+ *
5
+ * The published `@working-theory/*` scope is private, and pnpm sends an
6
+ * authorization header only when an npmrc maps the registry to a token.
7
+ * pnpm >= 11 ignores registry credentials in a PROJECT `.npmrc`, so the
8
+ * scaffold commits none; the token has to arrive at user level instead.
9
+ *
10
+ * When the caller exported `NPM_TOKEN` and has no user npmrc of its own (the
11
+ * credential-less environment: no `~/.npmrc`, no `npm login`), the install
12
+ * runs with `NPM_CONFIG_USERCONFIG` pointing at a temporary, owner-only file
13
+ * carrying only the `${NPM_TOKEN}` reference — never the value — removed when
14
+ * the install returns. A caller with its own user npmrc owns its auth, and is
15
+ * left untouched; a userconfig override naming a missing file is replaced.
16
+ *
17
+ * Pure module: no `import.meta.url`, so jest imports it directly.
18
+ */
19
+ import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
20
+ import { tmpdir } from "node:os";
21
+ import { join } from "node:path";
22
+ /** The one user-level line: the registry mapped to the env var, no value. */
23
+ export const NPM_TOKEN_AUTH_LINE = "//registry.npmjs.org/:_authToken=${NPM_TOKEN}";
24
+ /** The user npmrc npm and pnpm read for `env` — the override, else `~/.npmrc`. */
25
+ function userNpmrcPath(env, home) {
26
+ return env["NPM_CONFIG_USERCONFIG"] || env["npm_config_userconfig"] || join(home, ".npmrc");
27
+ }
28
+ /**
29
+ * Calls `run` with the env the install should use: `env` itself, or — when
30
+ * `NPM_TOKEN` is set and no user npmrc exists — `env` plus
31
+ * `NPM_CONFIG_USERCONFIG` naming a temporary reference-only npmrc, which is
32
+ * removed once `run` returns or throws.
33
+ */
34
+ export function withNpmTokenUserConfig(env, home, run) {
35
+ if (!env["NPM_TOKEN"] || existsSync(userNpmrcPath(env, home)))
36
+ return run(env);
37
+ const dir = mkdtempSync(join(tmpdir(), "create-ailk-npmrc-"));
38
+ try {
39
+ const path = join(dir, ".npmrc");
40
+ writeFileSync(path, `${NPM_TOKEN_AUTH_LINE}\n`, { mode: 0o600 });
41
+ // Both spellings: npm and pnpm read either, and a stale lower-case one
42
+ // left beside ours would make the winner depend on key order.
43
+ return run({ ...env, NPM_CONFIG_USERCONFIG: path, npm_config_userconfig: path });
44
+ }
45
+ finally {
46
+ rmSync(dir, { recursive: true, force: true });
47
+ }
48
+ }
@@ -21,6 +21,39 @@ export declare const MAX_REDIRECTS = 5;
21
21
  * so each entry must answer exactly 404. A 200 means the gate stopped gating.
22
22
  */
23
23
  export declare const CLAIM_GATED_ROUTES: Readonly<Record<string, string>>;
24
+ /**
25
+ * Routes whose answer depends on whether the site has a hub to ask (#9423),
26
+ * and the flow each one sells. With no API origin configured the flow read
27
+ * is `not-configured` and the route must answer exactly 404 — the no-hub
28
+ * 404 where the module that owns it is installed, Next's own 404 where it
29
+ * is not. With one configured it must render its own content.
30
+ */
31
+ export declare const HUB_GATED_ROUTES: Readonly<Record<string, string>>;
32
+ /**
33
+ * The API origin names `apps/web` reads, in step with `apiOriginConfigured()`
34
+ * in `apps/web/app/[locale]/flows/[slug]/magnet-page.tsx`.
35
+ */
36
+ export declare const HUB_ORIGIN_ENV_NAMES: readonly ["AILK_API_URL", "NEXT_PUBLIC_AILK_API_URL", "NEXT_PUBLIC_API_URL"];
37
+ /**
38
+ * Whether a served `apps/web` has an API origin: a non-empty value for one
39
+ * of `HUB_ORIGIN_ENV_NAMES` in the serve environment or assigned in one of
40
+ * the env files Next loads (`envFileTexts`).
41
+ */
42
+ export declare function hubOriginConfigured(env: Readonly<Record<string, string | undefined>>, envFileTexts: string[]): boolean;
43
+ /**
44
+ * A `HUB_GATED_ROUTES` entry. Hub configured: the page must render its own
45
+ * content (`assessPageRender`). No hub: exactly 404 with no redirect — a 200
46
+ * means the no-hub branch stopped running.
47
+ */
48
+ export declare function assessHubGatedRoute(input: {
49
+ route: string;
50
+ hubConfigured: boolean;
51
+ hops: RedirectHop[];
52
+ status: number | null;
53
+ contentType: string | null;
54
+ html: string;
55
+ locale: string;
56
+ }): SweepVerdict;
24
57
  /**
25
58
  * A route whose live page needs an env var the driver never sets (#6146).
26
59
  * While it is unset the route serves its Free placeholder seed instead
@@ -33,8 +66,9 @@ export declare const CLAIM_GATED_ROUTES: Readonly<Record<string, string>>;
33
66
  export declare const CONFIG_GATED_ROUTES: Readonly<Record<string, string>>;
34
67
  /**
35
68
  * The placeholder heading each `CONFIG_GATED_ROUTES` entry renders while its
36
- * env var is unset — for `/meet`, the hero headline of the overlay's
37
- * `content/en/LandingPage/meet.mdx` seed. A fresh scaffold never edits it.
69
+ * env var is unset. Empty since #8843 D14: `/meet` reads the workspace's
70
+ * meetings and answers 200 with its own H1 unconfigured, so no route is
71
+ * config-gated today.
38
72
  */
39
73
  export declare const CONFIG_GATED_PLACEHOLDERS: Readonly<Record<string, string>>;
40
74
  /** Reachability only — for routes that are not pages (`/og`, `/llms.txt`, …). */
@@ -37,6 +37,51 @@ export const CLAIM_GATED_ROUTES = {
37
37
  "/purchase/success": "commerce",
38
38
  "/purchase/cancel": "commerce",
39
39
  };
40
+ /**
41
+ * Routes whose answer depends on whether the site has a hub to ask (#9423),
42
+ * and the flow each one sells. With no API origin configured the flow read
43
+ * is `not-configured` and the route must answer exactly 404 — the no-hub
44
+ * 404 where the module that owns it is installed, Next's own 404 where it
45
+ * is not. With one configured it must render its own content.
46
+ */
47
+ export const HUB_GATED_ROUTES = {
48
+ "/trial": "trial",
49
+ };
50
+ /**
51
+ * The API origin names `apps/web` reads, in step with `apiOriginConfigured()`
52
+ * in `apps/web/app/[locale]/flows/[slug]/magnet-page.tsx`.
53
+ */
54
+ export const HUB_ORIGIN_ENV_NAMES = ["AILK_API_URL", "NEXT_PUBLIC_AILK_API_URL", "NEXT_PUBLIC_API_URL"];
55
+ /**
56
+ * Whether a served `apps/web` has an API origin: a non-empty value for one
57
+ * of `HUB_ORIGIN_ENV_NAMES` in the serve environment or assigned in one of
58
+ * the env files Next loads (`envFileTexts`).
59
+ */
60
+ export function hubOriginConfigured(env, envFileTexts) {
61
+ if (HUB_ORIGIN_ENV_NAMES.some((name) => Boolean(env[name])))
62
+ return true;
63
+ return envFileTexts.some((text) => HUB_ORIGIN_ENV_NAMES.some((name) => new RegExp(`^\\s*(?:export\\s+)?${name}\\s*=\\s*["']?[^\\s"'#]`, "m").test(text)));
64
+ }
65
+ /**
66
+ * A `HUB_GATED_ROUTES` entry. Hub configured: the page must render its own
67
+ * content (`assessPageRender`). No hub: exactly 404 with no redirect — a 200
68
+ * means the no-hub branch stopped running.
69
+ */
70
+ export function assessHubGatedRoute(input) {
71
+ const { route, hubConfigured, hops, status, contentType, html, locale } = input;
72
+ const flow = HUB_GATED_ROUTES[route];
73
+ if (hubConfigured) {
74
+ const rendered = assessPageRender({ route, hops, status, contentType, html, locale });
75
+ return { ok: rendered.ok, reason: `${rendered.reason} (hub-gated: ${flow}, hub configured)` };
76
+ }
77
+ if (hops.length > 0 || status !== 404) {
78
+ const first = hops[0];
79
+ const seen = first ? `${first.status} redirect` : `status ${status}`;
80
+ const why = status === 200 ? "rendered with no hub to ask" : `expected 404, got ${seen}`;
81
+ return { ok: false, reason: `${route} -> ${why} (hub-gated: ${flow}, no hub)` };
82
+ }
83
+ return { ok: true, reason: `404 (hub-gated: ${flow}, no hub)` };
84
+ }
40
85
  /**
41
86
  * A route whose live page needs an env var the driver never sets (#6146).
42
87
  * While it is unset the route serves its Free placeholder seed instead
@@ -46,17 +91,14 @@ export const CLAIM_GATED_ROUTES = {
46
91
  * `CLAIM_GATED_ROUTES`, a route absent from the route table is not stale: the
47
92
  * module that owns it may simply not be installed in this leg.
48
93
  */
49
- export const CONFIG_GATED_ROUTES = {
50
- "/meet": "SCHEDULING_CAL_LINK",
51
- };
94
+ export const CONFIG_GATED_ROUTES = {};
52
95
  /**
53
96
  * The placeholder heading each `CONFIG_GATED_ROUTES` entry renders while its
54
- * env var is unset — for `/meet`, the hero headline of the overlay's
55
- * `content/en/LandingPage/meet.mdx` seed. A fresh scaffold never edits it.
97
+ * env var is unset. Empty since #8843 D14: `/meet` reads the workspace's
98
+ * meetings and answers 200 with its own H1 unconfigured, so no route is
99
+ * config-gated today.
56
100
  */
57
- export const CONFIG_GATED_PLACEHOLDERS = {
58
- "/meet": "{Book a time with us}",
59
- };
101
+ export const CONFIG_GATED_PLACEHOLDERS = {};
60
102
  const LOOPBACK_BASE = "http://localhost";
61
103
  function pathnameOf(location, relativeTo) {
62
104
  return new URL(location, new URL(relativeTo, LOOPBACK_BASE)).pathname;
@@ -793,6 +793,9 @@ export const MODULE_SLICES = {
793
793
  "apps/api/src/routes/workspaces/index.ts",
794
794
  "apps/api/src/routes/workspaces/sessions.ts",
795
795
  "apps/api/src/routes/workspaces/config.ts",
796
+ // #8926 D1 — the workspace Offerings routes, registered in the
797
+ // multi-tenant tenant scope beside the billing routes.
798
+ "apps/api/src/routes/workspaces/offerings.ts",
796
799
  // #8438 D8 — the Admin owner's consents to Working Theory: registered
797
800
  // under `isModuleEnabled("multi-tenant")` alone, the module that
798
801
  // contributes the Admin app.
@@ -805,6 +808,8 @@ export const MODULE_SLICES = {
805
808
  "apps/api/src/routes/billing/index.ts",
806
809
  "apps/api/src/routes/billing/portal.ts",
807
810
  "apps/api/src/routes/billing/read.ts",
811
+ "apps/api/src/routes/billing/stripe-connection.ts",
812
+ "apps/api/src/routes/billing/stripe-products.ts",
808
813
  "apps/api/src/routes/billing/usage.ts",
809
814
  "apps/api/src/routes/waitlist-experiments/index.ts",
810
815
  "apps/api/src/routes/waitlist-experiments/list.ts",
@@ -854,6 +859,17 @@ export const MODULE_SLICES = {
854
859
  "apps/api/src/routes/workspaces/submissions.ts",
855
860
  "apps/api/src/routes/workspaces/subscribers.ts",
856
861
  "apps/api/src/services/inbox-submissions.ts",
862
+ // #9170 — the Inbox orders read: the same gate and the same
863
+ // tenant-db.ts import, co-owned with `workspace` for the same reason.
864
+ "apps/api/src/routes/workspaces/orders.ts",
865
+ "apps/api/src/services/inbox-orders.ts",
866
+ // #9254 DD5 — the Inbox callbacks read: the same gate and the same
867
+ // tenant-db.ts import, co-owned with `workspace` for the same reason.
868
+ "apps/api/src/routes/workspaces/callbacks.ts",
869
+ // #9287 DD2 — Start, Get and Extend trial: the same gate and the same
870
+ // tenant-db.ts import, co-owned with `workspace` for the same reason.
871
+ "apps/api/src/routes/workspaces/trial.ts",
872
+ "apps/api/src/services/trial.ts",
857
873
  // #852 D8 — the People reads: the same workspace||multi-tenant gate and
858
874
  // the same tenant-db.ts import, co-owned with `workspace` for the same
859
875
  // reason.
@@ -1023,6 +1039,14 @@ export const MODULE_SLICES = {
1023
1039
  "apps/api/src/routes/workspaces/submissions.ts",
1024
1040
  "apps/api/src/routes/workspaces/subscribers.ts",
1025
1041
  "apps/api/src/services/inbox-submissions.ts",
1042
+ // Co-owned with `multi-tenant` (#9170), as the reads above.
1043
+ "apps/api/src/routes/workspaces/orders.ts",
1044
+ "apps/api/src/services/inbox-orders.ts",
1045
+ // Co-owned with `multi-tenant` (#9254 DD5), as the reads above.
1046
+ "apps/api/src/routes/workspaces/callbacks.ts",
1047
+ // Co-owned with `multi-tenant` (#9287 DD2), as the reads above.
1048
+ "apps/api/src/routes/workspaces/trial.ts",
1049
+ "apps/api/src/services/trial.ts",
1026
1050
  // Co-owned with `multi-tenant` (#852 D8), as the reads above.
1027
1051
  "apps/api/src/routes/workspaces/people.ts",
1028
1052
  // Co-owned with `multi-tenant` (#7877 D9, D11) — see that slice's entry.
@@ -1134,8 +1158,16 @@ export const MODULE_SLICES = {
1134
1158
  pattern: CAL_DOMAIN_PATTERN,
1135
1159
  // The two server-side apps: apps/api holds the provider client, and
1136
1160
  // apps/mcp's tools name booking links in their fixtures. Everything
1137
- // else — apps/web above all — is browser-reachable.
1138
- paths_excluded: ["apps/api/**", "apps/mcp/**"],
1161
+ // else — apps/web above all — is browser-reachable. The two web files
1162
+ // are the booking pages' CSP (#8843 D12): the policy file names the
1163
+ // embed's origin only to admit it on those pages, and the middleware
1164
+ // applies it. Same carve-out as the source monorepo's architecture.yaml.
1165
+ paths_excluded: [
1166
+ "apps/api/**",
1167
+ "apps/mcp/**",
1168
+ "apps/web/security-headers.config.ts",
1169
+ "apps/web/middleware.ts",
1170
+ ],
1139
1171
  reason: "Only this site's server tier addresses the provider; every browser call goes to this site's own /v1/schedule/* routes instead",
1140
1172
  },
1141
1173
  {
@@ -1224,10 +1256,22 @@ export const MODULE_SLICES = {
1224
1256
  "apps/api/src/services/scheduling/__tests__/cal-provider.test.ts",
1225
1257
  "apps/mcp/src/tools/list_availability.ts",
1226
1258
  "apps/mcp/src/tools/get_event_meta.ts",
1259
+ // #8843 D5 — the twin of GET /v1/schedule/meetings.
1260
+ "apps/mcp/src/tools/list_meetings.ts",
1227
1261
  "apps/mcp/src/tools/create_booking.ts",
1228
1262
  "apps/web/app/[locale]/meet/page.tsx",
1229
1263
  "apps/web/app/[locale]/meet/[slug]/page.tsx",
1230
1264
  "apps/web/app/[locale]/meet/confirmed/page.tsx",
1265
+ // #8843 D6–D8 — the booking pages' one builder, the index frame
1266
+ // `/meet` and `/private` share, the private index, and their suites,
1267
+ // individually (§ D12, property 1: a glob ships no test).
1268
+ "apps/web/lib/meetings.tsx",
1269
+ "apps/web/lib/__tests__/meetings.test.tsx",
1270
+ "apps/web/lib/__tests__/meetings.fixtures.ts",
1271
+ "apps/web/app/[locale]/meet/meeting-index.tsx",
1272
+ "apps/web/app/[locale]/private/page.tsx",
1273
+ "apps/web/app/[locale]/private/__tests__/page.test.tsx",
1274
+ "apps/web/app/[locale]/private/__tests__/sitemap.test.ts",
1231
1275
  // #6085 (brief H7) — the six files the webhook receiver + the
1232
1276
  // tenant-guarded appointments read add. Individually, never as a
1233
1277
  // glob (§ D12 / this card's own comment above, property 1: a glob
@@ -1269,6 +1313,9 @@ export const MODULE_SLICES = {
1269
1313
  "apps/api/src/lib/public-meetings-config.ts",
1270
1314
  "apps/api/src/routes/schedule/meeting-types.ts",
1271
1315
  "apps/api/src/routes/schedule/__tests__/meeting-types.test.ts",
1316
+ // #8843 D1 — the meetings read's own suite; the route is in
1317
+ // `routes/schedule/index.ts`, listed above.
1318
+ "apps/api/src/routes/schedule/__tests__/meetings.test.ts",
1272
1319
  // #7532 — a hub spoke's meeting types and booking, through the real
1273
1320
  // scheduling routes; it ships and strips with them.
1274
1321
  "apps/api/src/routes/schedule/__tests__/spoke.test.ts",
@@ -1301,6 +1348,10 @@ export const MODULE_SLICES = {
1301
1348
  "apps/api/src/services/scheduling/resolve.ts",
1302
1349
  "apps/api/src/services/scheduling/__tests__/connections.test.ts",
1303
1350
  "apps/api/src/services/scheduling/__tests__/provider-resolution.test.ts",
1351
+ // #9129 — the meetings-list assembly the meetings route and the member
1352
+ // meeting types share, plus its suite.
1353
+ "apps/api/src/services/scheduling/meetings.ts",
1354
+ "apps/api/src/services/scheduling/__tests__/meetings.test.ts",
1304
1355
  "apps/api/src/routes/workspaces/__tests__/meetings-connection.test.ts",
1305
1356
  "apps/api/src/routes/schedule/__tests__/public-connection.test.ts",
1306
1357
  "apps/api/src/routes/webhooks/__tests__/calcom-connection.test.ts",
@@ -1323,14 +1374,16 @@ export const MODULE_SLICES = {
1323
1374
  "apps/api/src/__tests__/schedule-forwarded-rate-limit.test.ts",
1324
1375
  "apps/api/src/__tests__/openapi-keyless-hub.test.ts",
1325
1376
  ],
1326
- // pageContributions (spec 5478; parent § D6 shape 1) — the booker, the
1327
- // per-booking-type page and the confirmation page, whose route files
1328
- // `ownedFiles` above already carries. No row names seed content: all
1329
- // three render from the /v1/schedule/* routes, not from an MDX file.
1377
+ // pageContributions (spec 5478; parent § D6 shape 1) — the meeting
1378
+ // index, the meeting page, the confirmation page and the private index
1379
+ // (#8843 D8), whose route files `ownedFiles` above already carries. No
1380
+ // row names seed content: all four render from the /v1/schedule/*
1381
+ // routes, not from an MDX file.
1330
1382
  pageContributions: [
1331
1383
  { route: "/meet" },
1332
1384
  { route: "/meet/[slug]" },
1333
1385
  { route: "/meet/confirmed" },
1386
+ { route: "/private" },
1334
1387
  // #6086 — the authed appointments page. Bracket (roster) grammar and no
1335
1388
  // `content` key, for the same reason the three /meet rows carry none:
1336
1389
  // it renders from `/v1/schedule/appointments`, not from an MDX file.
@@ -1668,6 +1721,21 @@ export const MODULE_SLICES = {
1668
1721
  // `NESTED_FOREIGN_MODULE_ENTRIES` for the companion fix that keeps its
1669
1722
  // presence from falsely reading as "projects carried".
1670
1723
  "apps/web/app/[locale]/projects/apply/ListingApplicationClient.tsx",
1724
+ // #8840 D11/D13 — the Job application magnet's two walks. Same kind as
1725
+ // `projects/apply` above: they sit under the UNRELATED "careers"
1726
+ // surface's URL segment, which `excludedRoutePaths` does not gate on
1727
+ // signal-collectors, so `ownedFiles` keeps them out of a keyless
1728
+ // scaffold, and `NESTED_FOREIGN_MODULE_ENTRIES` keeps their presence
1729
+ // from reading as "careers carried". `flows/[slug]/magnet-page.tsx`,
1730
+ // the renderer they mount, rides the `flows` surface's directory with
1731
+ // its siblings (the note above).
1732
+ "apps/web/app/[locale]/careers/apply/page.tsx",
1733
+ "apps/web/app/[locale]/careers/[slug]/apply/page.tsx",
1734
+ // #9287 DD10 — the trial magnet pair. Its own `trial` route segment,
1735
+ // which no surface gates, so `ownedFiles` keeps both pages out of a
1736
+ // keyless scaffold, beside the careers walks.
1737
+ "apps/web/app/[locale]/trial/page.tsx",
1738
+ "apps/web/app/[locale]/trial/request/page.tsx",
1671
1739
  // #5581 U4 — the eleven signal-collectors components + the slice's own
1672
1740
  // re-export entry point moved to packages-paid/ui-signal-collectors;
1673
1741
  // the package is delivered to a keyed scaffold via `patches` below,
@@ -1683,6 +1751,17 @@ export const MODULE_SLICES = {
1683
1751
  // spec 6892 D2 — the /reports hub, alongside its reader above: also a
1684
1752
  // URL segment MODULE_SURFACES["signal-collectors"] does not gate.
1685
1753
  "apps/web/app/[locale]/reports/page.tsx",
1754
+ // #8848 D6, D7 — the hub deliverable at `/reports/<slug>`: its renderer,
1755
+ // the gated walk and the ungated file's byte proxy. Same segment, same
1756
+ // reason as the reader above.
1757
+ "apps/web/app/[locale]/reports/deliverable-page.tsx",
1758
+ "apps/web/app/[locale]/reports/[slug]/start/page.tsx",
1759
+ "apps/web/app/[locale]/reports/[slug]/file/route.ts",
1760
+ // #8848 D12 — `/downloads` redirects into `/reports`, so a scaffold
1761
+ // without this module ships no `/downloads` at all (the redirect target
1762
+ // is this module's) rather than the old broken list.
1763
+ "apps/web/app/[locale]/downloads/page.tsx",
1764
+ "apps/web/app/[locale]/downloads/[slug]/page.tsx",
1686
1765
  //
1687
1766
  // The two sections that route composes — `report-header` and
1688
1767
  // `reports-list` — are deliberately NOT owned here. They are
@@ -1721,6 +1800,9 @@ export const MODULE_SLICES = {
1721
1800
  // the module gets them — `moduleRosterRows` derives the code-route form
1722
1801
  // for this row rather than requiring a template to ship one (D9).
1723
1802
  { route: "/reports/[slug]", contentDir: "content/en/ReportPage" },
1803
+ // #8848 D7's walk (`/reports/[slug]/start`) is NOT a contribution: a
1804
+ // contribution needs a shipped template manifest to carry its row, and
1805
+ // none does yet. Its code ships through `ownedFiles` above.
1724
1806
  ],
1725
1807
  // installMap (#5618 D6.3) — the module's sample files, moved here out
1726
1808
  // of `site-templates/marketing-site/` so the module OWNS its samples
@@ -1750,6 +1832,12 @@ export const MODULE_SLICES = {
1750
1832
  // code of its own for either route.
1751
1833
  // `generate-sample-report.py` keeps its root-level destination
1752
1834
  // (finding 1 — AC7 forbids changing the Pro file set).
1835
+ // `diagnostic` and `join-waitlist` are the starter's two placeholder
1836
+ // flow seeds (#8850 D5): every visible string a `{brace}`, every key one
1837
+ // a spoke may seed, so `create-ailk seed-flows` sends them to a hub with
1838
+ // no edits once `spokeDestination` lands them at `flows/<slug>.json`. A
1839
+ // hub gets them as inert `database/flows/` files, as it gets
1840
+ // `fit-check` and `referral`.
1753
1841
  installMap: {
1754
1842
  "packages/create-ailk/module-templates/signal-collectors/content/en/ProjectsList/projects.mdx": "content/en/ProjectsList/projects.mdx",
1755
1843
  "packages/create-ailk/module-templates/signal-collectors/content/en/Project/fit-check-funnel.mdx": "content/en/Project/fit-check-funnel.mdx",
@@ -1765,6 +1853,8 @@ export const MODULE_SLICES = {
1765
1853
  "packages/create-ailk/module-templates/signal-collectors/content/en/LandingPage/referral.mdx": "content/en/LandingPage/referral.mdx",
1766
1854
  "packages/create-ailk/module-templates/signal-collectors/flows/fit-check.flow.json": "database/flows/fit-check.flow.json",
1767
1855
  "packages/create-ailk/module-templates/signal-collectors/flows/referral.flow.json": "database/flows/referral.flow.json",
1856
+ "packages/create-ailk/module-templates/signal-collectors/flows/diagnostic.flow.json": "database/flows/diagnostic.flow.json",
1857
+ "packages/create-ailk/module-templates/signal-collectors/flows/join-waitlist.flow.json": "database/flows/join-waitlist.flow.json",
1768
1858
  "packages/create-ailk/module-templates/signal-collectors/waitlist/launch-waitlist.experiment.json": "database/waitlist/launch-waitlist.experiment.json",
1769
1859
  "packages/create-ailk/module-templates/signal-collectors/reports/sample-report.pdf": "apps/web/public/reports/sample-report.pdf",
1770
1860
  "packages/create-ailk/module-templates/signal-collectors/reports/generate-sample-report.py": "reports/generate-sample-report.py",
@@ -1812,10 +1902,10 @@ export const MODULE_SLICES = {
1812
1902
  // checkout (`SPOKE_HUB_CHECKOUT_SEEDS`: flows/[slug]/checkout.actions.ts,
1813
1903
  // the flow results pages, dev/purchase — spoke-intake Rule 3). What lands
1814
1904
  // is the scaffold-commerce skill, the commerce-boundary rule and the
1815
- // @working-theory/ui-commerce dependency (`patches` below); the spoke's own
1816
- // checkout — the standard spoke checkout, `apps/web` template source
1817
- // (two routes, lib/checkout.ts, checkout-config.ts, two return pages and
1818
- // their tests) — is copied by `writeSpokeCheckoutSlice`
1905
+ // @working-theory/ui-commerce dependency (`patches` below). A spoke sells
1906
+ // through its hub's Offerings buy route (#9142); its remaining checkout
1907
+ // files — `apps/web` template source (checkout-config.ts, two return
1908
+ // pages and their tests) — are copied by `writeSpokeCheckoutSlice`
1819
1909
  // (`spoke-checkout.ts`, #8748), not carried here.
1820
1910
  spoke: true,
1821
1911
  paidSectionsPackage: "@working-theory/ui-commerce",
@@ -2138,6 +2228,18 @@ export function coOwnedModuleDestinations() {
2138
2228
  // red the scaffold's database jest run. Matched exactly, not by basename
2139
2229
  // in `EXCLUDE_FILES`, which would drop a `build.test.ts` anywhere in the
2140
2230
  // snapshot; every other database suite still ships.
2231
+ // - the /pricing suite `monorepo-only.test.tsx` and its snapshot (#9179) —
2232
+ // it snapshots the monorepo's own `content/en/Pricing/pricing.mdx`, which
2233
+ // a scaffold replaces with its seed, and reads
2234
+ // `packages/api-client/src/offerings.ts`, which no scaffold carries. Only
2235
+ // those two assertions moved there; `unconnected.test.tsx` and
2236
+ // `offerings.test.tsx` keep every scaffold-meaningful case and ship.
2237
+ // - the Offerings `delegate-confinement.test.ts` (#9179) — it runs
2238
+ // `git grep -- apps apps-paid packages` from the repo root to prove the
2239
+ // `stripeConnection` and `offeringsCheckoutSession` delegates are each
2240
+ // named by one file. A scaffold is not a git repository. Only those two
2241
+ // cases moved there; `connection.test.ts` and
2242
+ // `checkout-session-record.test.ts` keep the stores' coverage and ship.
2141
2243
  export const EXCLUDE_TEMPLATE_DIRS = [
2142
2244
  "apps/web/__tests__",
2143
2245
  "apps/web/app/__tests__",
@@ -2163,6 +2265,9 @@ export const EXCLUDE_TEMPLATE_DIRS = [
2163
2265
  "apps/mcp/__tests__/parity.test.ts",
2164
2266
  "apps/mcp/__tests__/audit.test.ts",
2165
2267
  "database/__tests__/build.test.ts",
2268
+ "apps/web/app/[locale]/pricing/__tests__/monorepo-only.test.tsx",
2269
+ "apps/web/app/[locale]/pricing/__tests__/__snapshots__/monorepo-only.test.tsx.snap",
2270
+ "apps/api/src/services/offerings/__tests__/delegate-confinement.test.ts",
2166
2271
  ];
2167
2272
  export function paidModuleTemplateExclusions(snapshotRoots, repoRoot) {
2168
2273
  const roots = new Set(snapshotRoots);
@@ -1,5 +1,7 @@
1
1
  /** Scaffold-relative path of the generated registry. */
2
2
  export declare const PAID_SECTIONS_PATH = "apps/web/lib/paid-sections.generated.ts";
3
+ /** Scaffold-relative path of the generated module-presence flags (#8843 D10). */
4
+ export declare const MODULE_FLAGS_PATH = "apps/web/lib/modules.generated.ts";
3
5
  export interface PaidSectionRow {
4
6
  module: string;
5
7
  package: string;
@@ -15,9 +17,26 @@ export declare function paidSectionRows(): PaidSectionRow[];
15
17
  * public strip drops the file (\`check-oss-strip.sh\` condition (e)).
16
18
  */
17
19
  export declare function renderPaidSections(present: ReadonlySet<string>): string;
20
+ /**
21
+ * Render the module-presence flags. Line 1 is `// oss: false` exactly when a
22
+ * flag is on, the registry's own rule: an `on` flag names pages the public
23
+ * tree does not carry.
24
+ */
25
+ export declare function renderModuleFlags(flags: {
26
+ schedulingPages: boolean;
27
+ }): string;
28
+ /**
29
+ * Write the module-presence flags into a scaffold from its own
30
+ * `apps/web/package.json` (#8843 D10) — never copied from a template, so a
31
+ * site without the scheduling module can never carry `true`. Answers whether
32
+ * the file was `added`, `updated` (changed bytes) or `unchanged`, or
33
+ * `undefined` (writing nothing) for a scaffold with no web app.
34
+ */
35
+ export declare function writeModuleFlags(targetDir: string): "added" | "updated" | "unchanged" | undefined;
18
36
  /**
19
37
  * Write the registry into a scaffold, deriving presence from the scaffold's
20
- * own `apps/web/package.json`. Returns `false` (writing nothing) when that
21
- * file or `apps/web/lib/` is absent — a scaffold with no web app.
38
+ * own `apps/web/package.json`, and the module-presence flags beside it.
39
+ * Returns `false` (writing nothing) when that file or `apps/web/lib/` is
40
+ * absent — a scaffold with no web app.
22
41
  */
23
42
  export declare function writePaidSections(targetDir: string): boolean;
@@ -15,6 +15,13 @@
15
15
  * every layer (snapshot transform, module `patches`, `add-module`) already
16
16
  * keeps true. The monorepo's committed copy is the render with every row
17
17
  * present and is pinned byte-equal by `__tests__/paid-sections.test.ts`.
18
+ *
19
+ * Its sibling, `apps/web/lib/modules.generated.ts` (#8843 D10), is the
20
+ * module-presence flag: `SCHEDULING_PAGES` is true exactly when the site
21
+ * carries the scheduling slice's UI package, so the locale layout drops the
22
+ * marketing chrome on the booking pages only where they exist. A separate
23
+ * file, importing nothing, because the registry's `import()`s would pull UI
24
+ * packages into any edge code that reads the flag.
18
25
  */
19
26
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
20
27
  import { join } from "node:path";
@@ -22,6 +29,10 @@ import { MODULE_SLICES } from "./module-architecture.js";
22
29
  import { RECOGNIZED_MODULES } from "./parse-args.js";
23
30
  /** Scaffold-relative path of the generated registry. */
24
31
  export const PAID_SECTIONS_PATH = "apps/web/lib/paid-sections.generated.ts";
32
+ /** Scaffold-relative path of the generated module-presence flags (#8843 D10). */
33
+ export const MODULE_FLAGS_PATH = "apps/web/lib/modules.generated.ts";
34
+ /** The dependency whose presence means the scheduling module's pages exist. */
35
+ const SCHEDULING_PAGES_PACKAGE = "@working-theory/ui-scheduling";
25
36
  function pascalCase(shortName) {
26
37
  return shortName
27
38
  .split("-")
@@ -93,27 +104,78 @@ export function renderPaidSections(present) {
93
104
  }
94
105
  return `${lines.join("\n")}\n`;
95
106
  }
107
+ const MODULE_FLAGS_HEADER = `// modules.generated.ts — which module pages this site carries.
108
+ //
109
+ // GENERATED by create-ailk from this app's package.json dependencies (#8843).
110
+ // Do not edit: every scaffold, \`create-ailk add-module\` and
111
+ // \`create-ailk update\` rewrites it. It imports nothing, so edge code may
112
+ // read it as well as the locale layout.
113
+ `;
96
114
  /**
97
- * Write the registry into a scaffold, deriving presence from the scaffold's
98
- * own `apps/web/package.json`. Returns `false` (writing nothing) when that
99
- * file or `apps/web/lib/` is absent — a scaffold with no web app.
115
+ * Render the module-presence flags. Line 1 is `// oss: false` exactly when a
116
+ * flag is on, the registry's own rule: an `on` flag names pages the public
117
+ * tree does not carry.
100
118
  */
101
- export function writePaidSections(targetDir) {
119
+ export function renderModuleFlags(flags) {
120
+ const lines = [];
121
+ if (flags.schedulingPages)
122
+ lines.push("// oss: false");
123
+ lines.push(MODULE_FLAGS_HEADER.trimEnd());
124
+ lines.push("");
125
+ lines.push("/** The scheduling module's booking pages (/meet, /private) exist. */");
126
+ lines.push(`export const SCHEDULING_PAGES: boolean = ${String(flags.schedulingPages)};`);
127
+ return `${lines.join("\n")}\n`;
128
+ }
129
+ /** The web app's `dependencies`, or `undefined` for a scaffold with no web app. */
130
+ function webDependencies(targetDir, writing) {
102
131
  const pkgPath = join(targetDir, "apps/web/package.json");
103
132
  if (!existsSync(pkgPath) || !existsSync(join(targetDir, "apps/web/lib"))) {
104
- return false;
133
+ return undefined;
105
134
  }
106
135
  let pkg;
107
136
  try {
108
137
  pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
109
138
  }
110
139
  catch (err) {
111
- throw new Error(`cannot read ${pkgPath} to write the paid-sections registry: ${err.message}`, { cause: err });
140
+ throw new Error(`cannot read ${pkgPath} to write the ${writing}: ${err.message}`, { cause: err });
112
141
  }
113
- const deps = pkg.dependencies ?? {};
142
+ return pkg.dependencies ?? {};
143
+ }
144
+ /**
145
+ * Write the module-presence flags into a scaffold from its own
146
+ * `apps/web/package.json` (#8843 D10) — never copied from a template, so a
147
+ * site without the scheduling module can never carry `true`. Answers whether
148
+ * the file was `added`, `updated` (changed bytes) or `unchanged`, or
149
+ * `undefined` (writing nothing) for a scaffold with no web app.
150
+ */
151
+ export function writeModuleFlags(targetDir) {
152
+ const deps = webDependencies(targetDir, "module flags");
153
+ if (deps === undefined)
154
+ return undefined;
155
+ const path = join(targetDir, MODULE_FLAGS_PATH);
156
+ const before = existsSync(path) ? readFileSync(path, "utf8") : undefined;
157
+ const next = renderModuleFlags({
158
+ schedulingPages: Object.hasOwn(deps, SCHEDULING_PAGES_PACKAGE),
159
+ });
160
+ if (before === next)
161
+ return "unchanged";
162
+ writeFileSync(path, next);
163
+ return before === undefined ? "added" : "updated";
164
+ }
165
+ /**
166
+ * Write the registry into a scaffold, deriving presence from the scaffold's
167
+ * own `apps/web/package.json`, and the module-presence flags beside it.
168
+ * Returns `false` (writing nothing) when that file or `apps/web/lib/` is
169
+ * absent — a scaffold with no web app.
170
+ */
171
+ export function writePaidSections(targetDir) {
172
+ const deps = webDependencies(targetDir, "paid-sections registry");
173
+ if (deps === undefined)
174
+ return false;
114
175
  const present = new Set(paidSectionRows()
115
176
  .map((row) => row.package)
116
177
  .filter((name) => Object.hasOwn(deps, name)));
117
178
  writeFileSync(join(targetDir, PAID_SECTIONS_PATH), renderPaidSections(present));
179
+ writeModuleFlags(targetDir);
118
180
  return true;
119
181
  }
@@ -26,6 +26,8 @@ export type SeedFlowOutcome = {
26
26
  ok: true;
27
27
  result: string;
28
28
  status: string;
29
+ /** #8850 D6 — the flow's copy is the starter's `{brace}` placeholder. */
30
+ placeholder?: true;
29
31
  } | {
30
32
  slug: string;
31
33
  ok: false;
@@ -33,6 +35,7 @@ export type SeedFlowOutcome = {
33
35
  } | {
34
36
  slug: string;
35
37
  ok: "dry-run";
38
+ placeholder?: true;
36
39
  };
37
40
  export interface SeedFlowsResult {
38
41
  /** The hub origin, when one resolved (a dry run may have none). */
@@ -49,7 +52,7 @@ export interface SeedFlowsResult {
49
52
  /** The whole report, one line per row — what `seedFlows` printed as it went. */
50
53
  export declare function formatSeedFlowsReport(result: SeedFlowsResult): string;
51
54
  export declare function seedFlows(opts: SeedFlowsOptions): Promise<SeedFlowsResult>;
52
- export declare const SPOKE_FLOWS_README = "# flows/\n\nEach file here is one **declared flow**: `flows/<slug>.json` holds one flow\nconfig, exactly as the hub stores it. This repo owns these configs \u2014 the hub\nruns them for this site.\n\n## What a spoke may declare\n\nA spoke key may seed only what this site's own flow pages show:\n`slug`, `defaultLocale`, `steps`, `thankYou` and `flags`. Anything the\nhub acts on outside these pages \u2014 emails, deliverables, pricing, registers,\nlistings \u2014 is refused (`field_not_seedable`). A flow that needs one of those\nis authored in the hub's Admin instead.\n\nThe file name must be the config's `slug` plus `.json`.\n\n## Seeding them into the hub\n\nRun this as part of each rollout, from the site's root, with the spoke key\nyour hub issued set as `AILK_SPOKE_KEY` in the environment (a shell or CI\nsecret \u2014 never a file in this repo):\n\n npx create-ailk seed-flows .\n\nThe hub origin comes from `--hub-url`, else `AILK_API_URL`, else the\n`AILK_API_URL=` line of `apps/web/.env.local`. The key is read from the\nenvironment only. Each flow answers `created`, `updated` or `unchanged`,\nso running it again is safe. `--dry-run` checks the files and sends nothing.\n\nThe hub's operator must first turn seeding on (`AILK_SPOKE_FLOW_SEED=true`\non the hub). Until it is on and this command has run, links to these flows\n(`/flows/<slug>`) reach no flow.\n\n## Who owns what\n\nThe next seed that differs overwrites the hub's copy of a flow's config, so\nedit flows here, not in the Admin. A flow's status (live or draft) is the\nhub's: switching a seeded flow to draft in the Admin keeps it draft through\nevery later seed. Removing a file here does not delete the hub's flow. If\nthe Admin adds a block only it can author (an email, a deliverable, an\noffer), the hub refuses the next seed of that flow (`slug_owned`) rather\nthan delete the block.\n";
55
+ export declare const SPOKE_FLOWS_README = "# flows/\n\nEach file here is one **declared flow**: `flows/<slug>.json` holds one flow\nconfig, exactly as the hub stores it. This repo owns these configs \u2014 the hub\nruns them for this site.\n\n## What a spoke may declare\n\nA spoke key may seed only what this site's own flow pages show:\n`slug`, `defaultLocale`, `steps`, `thankYou` and `flags`. Anything the\nhub acts on outside these pages \u2014 emails, deliverables, pricing, registers,\nlistings \u2014 is refused (`field_not_seedable`). A flow that needs one of those\nis authored in the hub's Admin instead.\n\nThe file name must be the config's `slug` plus `.json`.\n\n## Seeding them into the hub\n\nRun this as part of each rollout, from the site's root, with the spoke key\nyour hub issued set as `AILK_SPOKE_KEY` in the environment (a shell or CI\nsecret \u2014 never a file in this repo):\n\n npx create-ailk seed-flows .\n\nThe hub origin comes from `--hub-url`, else `AILK_API_URL`, else the\n`AILK_API_URL=` line of `apps/web/.env.local`. The key is read from the\nenvironment only. Each flow answers `created`, `updated` or `unchanged`,\nso running it again is safe. `--dry-run` checks the files and sends nothing.\n\nThe hub's operator must first turn seeding on (`AILK_SPOKE_FLOW_SEED=true`\non the hub). Until it is on and this command has run, links to these flows\n(`/flows/<slug>`) reach no flow.\n\n## The starter's placeholder flows\n\nThe `signal-collectors` module ships two declared flows here:\n`flows/diagnostic.json` (an email step, then two questions) and\n`flows/join-waitlist.json` (an email and an optional name). Every visible\nstring in both is a `{brace}` placeholder: replace each one with your own\ncopy. Both go live on the hub with the first seed, so each line this command\nprints for them says they still carry placeholder copy.\n\nA flow is reached at `/flows/<slug>`. To give one its own page pair, run:\n\n npx create-ailk add-route magnet --path /diagnostic --flow diagnostic\n npx create-ailk add-route magnet --path /waitlist --flow join-waitlist\n\nA job application comes with the `careers` surface. Contact, newsletter and\njob application are the hub's own standard magnets: the hub seeds them in\nevery workspace and refuses a spoke's copy of each, so they need no file\nhere. If the hub's Admin already owns a magnet under one of these two slugs,\nthe hub refuses the seed (`slug_owned`); rename the file and its `slug`.\n\n## Who owns what\n\nThe next seed that differs overwrites the hub's copy of a flow's config, so\nedit flows here, not in the Admin. A flow's status (live or draft) is the\nhub's: switching a seeded flow to draft in the Admin keeps it draft through\nevery later seed. Removing a file here does not delete the hub's flow. If\nthe Admin adds a block only it can author (an email, a deliverable, an\noffer), the hub refuses the next seed of that flow (`slug_owned`) rather\nthan delete the block.\n";
53
56
  /**
54
57
  * Write `SPOKE_FLOWS_README` to `<targetDir>/flows/README.md` unless that
55
58
  * file exists. Returns whether it wrote.