create-ailk 0.18.0 → 0.19.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 (167) hide show
  1. package/LICENSE +6 -5
  2. package/NOTICE +2 -0
  3. package/dist/add-module.js +4 -1
  4. package/dist/category-profile.d.ts +10 -0
  5. package/dist/category-profile.js +21 -0
  6. package/dist/derive-architecture.d.ts +5 -1
  7. package/dist/derive-architecture.js +8 -3
  8. package/dist/lib/license-key-files.d.ts +6 -3
  9. package/dist/lib/license-key-files.js +8 -2
  10. package/dist/lib/scaffold-render-sweep.d.ts +9 -0
  11. package/dist/lib/scaffold-render-sweep.js +24 -3
  12. package/dist/license-set.d.ts +1 -1
  13. package/dist/license-set.js +13 -8
  14. package/dist/module-architecture.js +119 -0
  15. package/dist/programmatic.js +3 -0
  16. package/dist/spoke-checkout.d.ts +11 -3
  17. package/dist/spoke-checkout.js +62 -12
  18. package/dist/starter-grid.d.ts +1 -1
  19. package/dist/surfaces.js +140 -1
  20. package/dist/template-manifest.d.ts +4 -3
  21. package/dist/topology.d.ts +7 -2
  22. package/dist/topology.js +60 -3
  23. package/package.json +5 -4
  24. package/templates/.env.example +3 -3
  25. package/templates/.github/workflows/ci.yml +5 -0
  26. package/templates/apps/api/CLAUDE.md +25 -7
  27. package/templates/apps/api/LICENSE +6 -5
  28. package/templates/apps/api/NOTICE +2 -0
  29. package/templates/apps/api/jest.config.cjs +4 -29
  30. package/templates/apps/api/package.json +1 -0
  31. package/templates/apps/api/src/lib/__mocks__/prisma.ts +9 -0
  32. package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +38 -0
  33. package/templates/apps/api/src/lib/deliverable-asset-store.ts +5 -4
  34. package/templates/apps/api/src/lib/stripe.ts +81 -0
  35. package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +266 -39
  36. package/templates/apps/api/src/middleware/auth.ts +206 -46
  37. package/templates/apps/api/src/routes/leads/index.ts +4 -0
  38. package/templates/apps/api/src/server.ts +107 -0
  39. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +58 -0
  40. package/templates/apps/api/src/services/lead-routing.ts +30 -2
  41. package/templates/apps/api/vercel.json +2 -2
  42. package/templates/apps/mcp/.env.example +10 -0
  43. package/templates/apps/mcp/CLAUDE.md +9 -4
  44. package/templates/apps/mcp/LICENSE +6 -5
  45. package/templates/apps/mcp/NOTICE +2 -0
  46. package/templates/apps/mcp/__tests__/abuse/guard.test.ts +198 -0
  47. package/templates/apps/mcp/__tests__/audit-single-page.test.ts +15 -3
  48. package/templates/apps/mcp/__tests__/boot.test.ts +78 -1
  49. package/templates/apps/mcp/__tests__/http-transport.test.ts +291 -2
  50. package/templates/apps/mcp/__tests__/request_callback.test.ts +69 -7
  51. package/templates/apps/mcp/__tests__/submit_lead.test.ts +69 -8
  52. package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +69 -7
  53. package/templates/apps/mcp/jest.config.cjs +3 -44
  54. package/templates/apps/mcp/src/abuse/index.ts +117 -21
  55. package/templates/apps/mcp/src/boot.ts +5 -23
  56. package/templates/apps/mcp/src/http-transport.ts +93 -73
  57. package/templates/apps/mcp/src/index.ts +2 -0
  58. package/templates/apps/mcp/src/request-context.ts +52 -0
  59. package/templates/apps/mcp/src/tools/capture_lead.ts +9 -1
  60. package/templates/apps/mcp/src/tools/request_callback.ts +9 -1
  61. package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +9 -1
  62. package/templates/apps/web/LICENSE +6 -5
  63. package/templates/apps/web/NOTICE +2 -0
  64. package/templates/apps/web/app/.well-known/ai-plugin.json/route.ts +3 -3
  65. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/billing.test.tsx +288 -0
  66. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/general.test.tsx +568 -0
  67. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/hub-mode.test.tsx +272 -42
  68. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/item.test.tsx +11 -13
  69. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout.test.tsx +115 -16
  70. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/new-menu.test.tsx +305 -0
  71. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/new-popups.test.tsx +82 -0
  72. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/notifications.test.tsx +126 -0
  73. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +163 -72
  74. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/popups.test.tsx +451 -0
  75. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/security.test.tsx +139 -0
  76. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/settings.test.tsx +20 -390
  77. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/support.test.tsx +16 -11
  78. package/templates/apps/web/app/[locale]/(authed)/account/account-data.ts +1 -1
  79. package/templates/apps/web/app/[locale]/(authed)/account/account-frame.tsx +114 -50
  80. package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +24 -45
  81. package/templates/apps/web/app/[locale]/(authed)/account/artifacts/[pieceId]/page.tsx +253 -0
  82. package/templates/apps/web/app/[locale]/(authed)/account/artifacts/[pieceId]/review-form.tsx +145 -0
  83. package/templates/apps/web/app/[locale]/(authed)/account/artifacts/[pieceId]/versions/[version]/file/route.ts +100 -0
  84. package/templates/apps/web/app/[locale]/(authed)/account/artifacts/__tests__/file-route.test.ts +179 -0
  85. package/templates/apps/web/app/[locale]/(authed)/account/artifacts/__tests__/page.test.tsx +514 -0
  86. package/templates/apps/web/app/[locale]/(authed)/account/artifacts/__tests__/review.test.tsx +288 -0
  87. package/templates/apps/web/app/[locale]/(authed)/account/artifacts/artifact-actions.ts +123 -0
  88. package/templates/apps/web/app/[locale]/(authed)/account/artifacts/artifact-data.ts +82 -0
  89. package/templates/apps/web/app/[locale]/(authed)/account/artifacts/page.tsx +177 -0
  90. package/templates/apps/web/app/[locale]/(authed)/account/billing/page.tsx +172 -0
  91. package/templates/apps/web/app/[locale]/(authed)/account/billing-source.ts +147 -0
  92. package/templates/apps/web/app/[locale]/(authed)/account/components/request-quote.tsx +229 -0
  93. package/templates/apps/web/app/[locale]/(authed)/account/components/schedule-service.tsx +276 -0
  94. package/templates/apps/web/app/[locale]/(authed)/account/components/send-message.tsx +128 -0
  95. package/templates/apps/web/app/[locale]/(authed)/account/general/billing-anchor.tsx +27 -0
  96. package/templates/apps/web/app/[locale]/(authed)/account/general/deletion-request.tsx +132 -0
  97. package/templates/apps/web/app/[locale]/(authed)/account/general/page.tsx +131 -0
  98. package/templates/apps/web/app/[locale]/(authed)/account/general/profile-form.tsx +104 -0
  99. package/templates/apps/web/app/[locale]/(authed)/account/general/profile-source.ts +73 -0
  100. package/templates/apps/web/app/[locale]/(authed)/account/inbox/message/[id]/file/__tests__/route.test.ts +18 -8
  101. package/templates/apps/web/app/[locale]/(authed)/account/inbox/message/[id]/file/route.ts +4 -6
  102. package/templates/apps/web/app/[locale]/(authed)/account/layout.tsx +4 -1
  103. package/templates/apps/web/app/[locale]/(authed)/account/new-actions.ts +219 -0
  104. package/templates/apps/web/app/[locale]/(authed)/account/new-menu.tsx +340 -0
  105. package/templates/apps/web/app/[locale]/(authed)/account/new-popups.tsx +107 -0
  106. package/templates/apps/web/app/[locale]/(authed)/account/notifications/page.tsx +57 -0
  107. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +85 -26
  108. package/templates/apps/web/app/[locale]/(authed)/account/profile-actions.ts +116 -0
  109. package/templates/apps/web/app/[locale]/(authed)/account/security/page.tsx +80 -0
  110. package/templates/apps/web/app/[locale]/(authed)/account/settings/page.tsx +13 -354
  111. package/templates/apps/web/app/[locale]/(authed)/account/settings-frame.tsx +108 -0
  112. package/templates/apps/web/app/[locale]/(authed)/account/sign-in-method.ts +37 -0
  113. package/templates/apps/web/app/[locale]/(authed)/account/support-actions.ts +9 -10
  114. package/templates/apps/web/app/[locale]/layout.tsx +23 -4
  115. package/templates/apps/web/app/api/auth/hub/__tests__/continuation.test.ts +143 -0
  116. package/templates/apps/web/app/api/auth/hub/authorize/__tests__/route.test.ts +62 -10
  117. package/templates/apps/web/app/api/auth/hub/authorize/route.ts +22 -7
  118. package/templates/apps/web/app/api/auth/hub/continuation.ts +73 -0
  119. package/templates/apps/web/app/api/auth/hub/continue/[ref]/__tests__/route.test.ts +188 -0
  120. package/templates/apps/web/app/api/auth/hub/continue/[ref]/route.ts +53 -0
  121. package/templates/apps/web/app/robots.txt/route.ts +10 -4
  122. package/templates/apps/web/auth.ts +5 -1
  123. package/templates/apps/web/lib/__tests__/account-config.test.ts +258 -46
  124. package/templates/apps/web/lib/account-config.ts +188 -52
  125. package/templates/apps/web/lib/hub-session/__tests__/hub-session.test.ts +143 -0
  126. package/templates/apps/web/lib/hub-session/index.ts +1 -0
  127. package/templates/apps/web/lib/hub-session/reseal.ts +90 -0
  128. package/templates/apps/web/lib/hub-session/seal.ts +13 -9
  129. package/templates/apps/web/lib/topology.generated.ts +24 -0
  130. package/templates/apps/web/messages/en.json +156 -35
  131. package/templates/database/CHANGELOG.md +21 -0
  132. package/templates/database/LICENSE +6 -5
  133. package/templates/database/NOTICE +2 -0
  134. package/templates/database/README.md +1 -1
  135. package/templates/database/__tests__/member-area.db.test.ts +15 -3
  136. package/templates/database/__tests__/member-contract-migration.test.ts +85 -0
  137. package/templates/database/__tests__/member-contract.db.test.ts +413 -0
  138. package/templates/database/__tests__/seed.test.ts +54 -2
  139. package/templates/database/inbox/schema.prisma +90 -15
  140. package/templates/database/migrations/20261012120000_member_contract/migration.sql +192 -0
  141. package/templates/database/migrations/20261013120000_lead_spoke_checkout/migration.sql +20 -0
  142. package/templates/database/package.json +3 -2
  143. package/templates/database/seed.ts +34 -2
  144. package/versions.json +36 -36
  145. package/templates/apps/api/src/__tests__/content-write-production-boot.test.ts +0 -104
  146. package/templates/apps/api/src/__tests__/cors.test.ts +0 -276
  147. package/templates/apps/api/src/__tests__/module-exclusion.test.ts +0 -279
  148. package/templates/apps/api/src/__tests__/openapi-keyless-hub.test.ts +0 -122
  149. package/templates/apps/api/src/__tests__/review-surface-registration.test.ts +0 -167
  150. package/templates/apps/api/src/__tests__/root-modules.test.ts +0 -175
  151. package/templates/apps/api/src/__tests__/schedule-forwarded-rate-limit.test.ts +0 -117
  152. package/templates/apps/api/src/__tests__/server.test.ts +0 -591
  153. package/templates/apps/api/src/__tests__/workspace-module-registration.test.ts +0 -175
  154. package/templates/apps/api/src/openapi/__tests__/surface-drift.test.ts +0 -293
  155. package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +0 -1620
  156. package/templates/apps/api/src/routes/project-listings/__tests__/directory.test.ts +0 -528
  157. package/templates/apps/api/src/routes/workspaces/__tests__/inbox-wire-schemas.test.ts +0 -198
  158. package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +0 -652
  159. package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +0 -408
  160. package/templates/apps/api/src/services/__tests__/leads-inbox.test.ts +0 -476
  161. package/templates/apps/mcp/__tests__/audit.test.ts +0 -346
  162. package/templates/apps/mcp/__tests__/parity.test.ts +0 -325
  163. package/templates/apps/mcp/__tests__/workspace-surface.test.ts +0 -364
  164. package/templates/apps/web/app/[locale]/(authed)/account/settings/color-mode.tsx +0 -68
  165. package/templates/apps/web/app/[locale]/(authed)/account/settings/deletion-request.tsx +0 -55
  166. package/templates/apps/web/app/[locale]/(authed)/account/settings/name-form.tsx +0 -118
  167. package/templates/database/__tests__/build.test.ts +0 -131
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * The spoke checkout slice (#7838) — what a spoke carrying `commerce` gets in
3
- * `apps/web`: the buy route and the Stripe webhook route (the only two files
4
- * allowed to import the checkout package, #7873), the site's own
5
- * `lib/checkout-config.ts`, and the thank-you and unavailable return pages.
3
+ * `apps/web`: the buy route and the Stripe webhook route (allowed to import
4
+ * the checkout package, #7873), the site's own `lib/checkout-config.ts`, and
5
+ * the thank-you and unavailable return pages. A spoke that signs in through
6
+ * its hub also gets the billing action, the third file allowed to import the
7
+ * package, and the web seam render that reaches it (#8217 D6–D8).
6
8
  *
7
9
  * Every guarantee lives in `@working-theory/spoke-checkout` and arrives by a
8
10
  * package bump; these files are thin and, apart from the config's one stamp
@@ -23,7 +25,7 @@ import { SPOKE_CHECKOUT_ROUTE_FILES } from "./derive-architecture.js";
23
25
  import { ENV_CATALOG } from "./lib/env-catalog.js";
24
26
  import { diffTwoStrings, } from "./lib/extract-module-bundle.js";
25
27
  import { substituteWorkspaceVersions, } from "./substitute.js";
26
- import { TopologyError } from "./topology.js";
28
+ import { TopologyError, WEB_SEAM_PATH, renderWebSeam, } from "./topology.js";
27
29
  /** The checkout package a spoke's two route files import. */
28
30
  export const SPOKE_CHECKOUT_PACKAGE = "@working-theory/spoke-checkout";
29
31
  const API_CLIENT_PACKAGE = "@working-theory/api-client";
@@ -131,6 +133,33 @@ const WEBHOOK_ROUTE = renderRoute({
131
133
  waiver: "the endpoint URL registered in Stripe, not a versioned API contract",
132
134
  exportLine: "export const { POST } = createSpokeCheckout(checkoutConfig).webhook;",
133
135
  });
136
+ /**
137
+ * The spoke billing action (#8217 D6): wiring only, byte-identical across
138
+ * sites, so every guarantee arrives by a package bump. A plain server module
139
+ * (C2): its two exports take no argument, and the hub's read is its only
140
+ * source of customer ids.
141
+ */
142
+ const BILLING_ACTION = [
143
+ "// The spoke billing action — GENERATED by create-ailk (#8217). Do not edit:",
144
+ "// every guarantee lives in the spoke-checkout package and arrives by a",
145
+ "// package bump. A plain server module, not a Server Action file: neither",
146
+ "// export takes an argument, and the hub's GET /v1/member/billing is the only",
147
+ "// source of customer ids. billing-source.ts reaches it through",
148
+ "// lib/topology.generated.ts and never imports the package.",
149
+ `import { member } from "${API_CLIENT_PACKAGE}";`,
150
+ `import { createSpokeCheckout } from "${SPOKE_CHECKOUT_PACKAGE}";`,
151
+ "",
152
+ `import { checkoutConfig } from "../../../../../lib/checkout-config";`,
153
+ `import { forwardedCookieInit } from "../../../../../lib/server-api";`,
154
+ "",
155
+ "const billing = createSpokeCheckout(checkoutConfig).billing.forMember(",
156
+ " async () => member.billing(await forwardedCookieInit()),",
157
+ ");",
158
+ "",
159
+ "export const readSpokeBilling = billing.read;",
160
+ "export const spokeBillingPortalUrl = billing.portalUrl;",
161
+ "",
162
+ ].join("\n");
134
163
  /** The site's checkout data (D5). Only the `const SITE` line varies by site. */
135
164
  function renderConfig(site) {
136
165
  return [
@@ -138,9 +167,10 @@ function renderConfig(site) {
138
167
  "//",
139
168
  "// Written once by create-ailk and yours from then on: create-ailk never",
140
169
  "// rewrites it. The buy route and the Stripe webhook route build their",
141
- "// handlers from it. Those two are the only files allowed to import the",
142
- "// checkout package (architecture.yaml), so this file never imports it,",
143
- "// stripe-client or the stripe SDK. Keep it to data and the fulfil callback.",
170
+ "// handlers from it, and so does the billing action on a spoke that signs",
171
+ "// in through its hub. Only those files may import the checkout package",
172
+ "// (architecture.yaml), so this file never imports it, stripe-client or the",
173
+ "// stripe SDK. Keep it to data and the fulfil callback.",
144
174
  "//",
145
175
  "// site Stamped on every Checkout Session as metadata.site. This site's",
146
176
  "// webhook acts only on its own stamp, and the hub ignores any stamp",
@@ -462,15 +492,20 @@ jobs:
462
492
  /**
463
493
  * The slice for `site`, in order: the two route files, the config, the two
464
494
  * pages (D3), then the checkout e2e workflow (#7839 D8), which does not
465
- * depend on `site`. Throws `TopologyError` for an invalid stamp (SEC-7838-10).
495
+ * depend on `site`. With `signIn: "hub"` the billing action is the third
496
+ * pair, so the first three paths are `SPOKE_CHECKOUT_ROUTE_FILES` (#8217 D8).
497
+ * Throws `TopologyError` for an invalid stamp (SEC-7838-10).
466
498
  */
467
- export function renderSpokeCheckoutFiles(site) {
499
+ export function renderSpokeCheckoutFiles(site, opts = {}) {
468
500
  assertSite(site);
469
- const [buyPath, webhookPath] = SPOKE_CHECKOUT_ROUTE_FILES;
501
+ const [buyPath, webhookPath, billingActionPath] = SPOKE_CHECKOUT_ROUTE_FILES;
470
502
  const [thankYouPath, unavailablePath] = SPOKE_CHECKOUT_PAGE_FILES;
471
503
  return [
472
504
  [buyPath, BUY_ROUTE],
473
505
  [webhookPath, WEBHOOK_ROUTE],
506
+ ...(opts.signIn === "hub"
507
+ ? [[billingActionPath, BILLING_ACTION]]
508
+ : []),
474
509
  [SPOKE_CHECKOUT_CONFIG_PATH, renderConfig(site)],
475
510
  [thankYouPath, THANK_YOU_PAGE],
476
511
  [unavailablePath, UNAVAILABLE_PAGE],
@@ -516,7 +551,9 @@ function isOlderPin(current, wanted) {
516
551
  /**
517
552
  * Write the slice into `targetDir` (D3). A file that already exists is left
518
553
  * byte-for-byte and reported `skipped` — the config is the site's once
519
- * written. Adds the package to `apps/web/package.json` when absent, and
554
+ * written. With `signIn: "hub"` (#8217 D8) the slice carries the billing
555
+ * action, and the web seam — generated, so always rewritten — becomes the
556
+ * billing render: reported `patched` when it changed, not at all when equal. Adds the package to `apps/web/package.json` when absent, and
520
557
  * raises a plain pin older than the one it writes (#7839): the generated
521
558
  * checkout e2e workflow runs a command a `^0.2.0` pin never reaches. Any
522
559
  * other range — newer, `workspace:`, a tag — is the site's and is kept.
@@ -524,7 +561,7 @@ function isOlderPin(current, wanted) {
524
561
  * missing or malformed `apps/web/package.json`) throws before the first write.
525
562
  */
526
563
  export function writeSpokeCheckoutSlice(targetDir, opts) {
527
- const files = renderSpokeCheckoutFiles(opts.site);
564
+ const files = renderSpokeCheckoutFiles(opts.site, { signIn: opts.signIn });
528
565
  const range = JSON.parse(substituteWorkspaceVersions(`{"${SPOKE_CHECKOUT_PACKAGE}": "workspace:*"}`, opts.versions));
529
566
  const version = range[SPOKE_CHECKOUT_PACKAGE];
530
567
  const pkgPath = join(targetDir, WEB_PACKAGE_JSON);
@@ -541,6 +578,19 @@ export function writeSpokeCheckoutSlice(targetDir, opts) {
541
578
  writeFileSync(abs, text);
542
579
  rows.push({ path, action: "written" });
543
580
  }
581
+ if (opts.signIn === "hub") {
582
+ const seamPath = join(targetDir, WEB_SEAM_PATH);
583
+ const seamBefore = existsSync(seamPath) ? readFileSync(seamPath, "utf8") : "";
584
+ const seam = renderWebSeam("spoke", "hub", { spokeBilling: true });
585
+ if (seamBefore !== seam) {
586
+ writeFileSync(seamPath, seam);
587
+ rows.push({
588
+ path: WEB_SEAM_PATH,
589
+ action: "patched",
590
+ change: diffTwoStrings(WEB_SEAM_PATH, seamBefore, seam, true),
591
+ });
592
+ }
593
+ }
544
594
  const deps = pkg.dependencies ?? {};
545
595
  const current = Object.prototype.hasOwnProperty.call(deps, SPOKE_CHECKOUT_PACKAGE)
546
596
  ? deps[SPOKE_CHECKOUT_PACKAGE]
@@ -14,7 +14,7 @@
14
14
  * lives in `template-manifest.ts`, whose `resolveTemplate` is the one reader
15
15
  * that yields a template's roster. Nothing but those two files imports this one.
16
16
  */
17
- import type { CategorySlug } from "@working-theory/schema/categories";
17
+ import type { CategorySlug } from "./category-profile.js";
18
18
  import type { ModuleName } from "./parse-args.js";
19
19
  import type { TemplateLayout } from "./site-chrome.js";
20
20
  import type { StarterDesign, StarterShape, TemplateRosterRow } from "./template-manifest.js";
package/dist/surfaces.js CHANGED
@@ -630,6 +630,23 @@ export const ROUTE_SINCE = {
630
630
  // new, so it attaches at today's fixed-group version, the same rule #7430
631
631
  // applied above.
632
632
  "(authed)/account/inbox/message/[id]/file": "0.37.0",
633
+ // #8232 — User settings' two pages, General and Billing (the old
634
+ // `(authed)/account/settings` now redirects to General). Genuinely new, so
635
+ // they attach at today's fixed-group version, the same rule #7430 applied.
636
+ "(authed)/account/general": "0.38.0",
637
+ "(authed)/account/billing": "0.38.0",
638
+ // #8339 — User settings' two new tabs, Security and Notifications
639
+ // (General and Billing now render through the shared account-settings
640
+ // composition). Genuinely new, so they attach at today's fixed-group
641
+ // version, the same rule #7430/#8232 applied.
642
+ "(authed)/account/security": "0.38.0",
643
+ "(authed)/account/notifications": "0.38.0",
644
+ // #8234 — the member Artifacts pages and a version's file route. Genuinely
645
+ // new, so they attach at today's fixed-group version, the same rule #7430
646
+ // applied.
647
+ "(authed)/account/artifacts": "0.38.0",
648
+ "(authed)/account/artifacts/[pieceId]": "0.38.0",
649
+ "(authed)/account/artifacts/[pieceId]/versions/[version]/file": "0.38.0",
633
650
  // The leads inbox (#5678). The ROUTE ships in every scaffold, Free
634
651
  // included — lead capture is a never-gate surface, so reading a lead back
635
652
  // is one too. Whether it shows a ROW is a separate question with two
@@ -759,24 +776,139 @@ export const ROUTE_DEPENDENCIES = {
759
776
  `${ROUTE_ROOT}/(authed)/account/account-data.ts`,
760
777
  `${ROUTE_ROOT}/(authed)/account/error-row.tsx`,
761
778
  `${ROUTE_ROOT}/(authed)/account/retry-button.tsx`,
779
+ // #8233 C9 — the New menu's files beside account-config.ts: new-popups.tsx
780
+ // binds the config's actions to the popups, which import the menu and its
781
+ // server actions. (account-config.ts itself imports none of them — a
782
+ // spoke without sign-in drops the `(authed)` group and keeps the config.)
783
+ `${ROUTE_ROOT}/(authed)/account/new-menu.tsx`,
784
+ `${ROUTE_ROOT}/(authed)/account/new-actions.ts`,
785
+ `${ROUTE_ROOT}/(authed)/account/new-popups.tsx`,
786
+ `${ROUTE_ROOT}/(authed)/account/components/schedule-service.tsx`,
787
+ `${ROUTE_ROOT}/(authed)/account/components/request-quote.tsx`,
788
+ `${ROUTE_ROOT}/(authed)/account/components/send-message.tsx`,
762
789
  ],
763
790
  // #7920 — the member file read re-serves the hub's bytes with the
764
791
  // credential `forwardedCookieInit` returns.
765
792
  "(authed)/account/inbox/message/[id]/file": ["apps/web/lib/server-api.ts"],
793
+ // #8232 D13 — the settings route is a redirect now; what it still imports
794
+ // from outside itself is `theme-modes.ts`'s theme reader and the brand file
795
+ // that reader imports.
766
796
  "(authed)/account/settings": [
797
+ "apps/web/lib/site-brand.tsx",
798
+ "apps/web/lib/site-theme.ts",
799
+ ],
800
+ // #8232 D13 — General and Billing import files in their parent's directory
801
+ // too. Each listed parent file is new in #8232 or unchanged by it, so a
802
+ // scaffold that already has one keeps a copy that still compiles; the
803
+ // settings components General changed moved into `general/` instead (C3).
804
+ // `profile-actions.ts` imports `lib/hub-session/reseal.ts` by its own path,
805
+ // since a scaffold's existing `lib/hub-session/index.ts` is never updated.
806
+ //
807
+ // #8339 D11 — `settings/color-mode.tsx` drops: General's own color-mode
808
+ // control is now the shared `AccountSettings` composition's
809
+ // (@working-theory/templates/account). `sign-in-method.ts` stays: General no
810
+ // longer renders the Sign-in section itself (moved to Security), but it
811
+ // still imports `sign-in-method.ts` transitively through
812
+ // `profile-source.ts`'s `via` derivation.
813
+ "(authed)/account/general": [
767
814
  "apps/web/lib/account-config.ts",
768
815
  "apps/web/lib/auth-env.ts",
769
816
  "apps/web/lib/server-api.ts",
770
817
  "apps/web/lib/site-brand.tsx",
771
818
  "apps/web/lib/site-theme.ts",
819
+ "apps/web/lib/hub-session/reseal.ts",
772
820
  `${ROUTE_ROOT}/(authed)/account/account-data.ts`,
773
- `${ROUTE_ROOT}/(authed)/account/support-actions.ts`,
821
+ `${ROUTE_ROOT}/(authed)/account/settings-frame.tsx`,
822
+ `${ROUTE_ROOT}/(authed)/account/sign-in-method.ts`,
823
+ `${ROUTE_ROOT}/(authed)/account/profile-actions.ts`,
824
+ `${ROUTE_ROOT}/(authed)/account/settings/theme-modes.ts`,
825
+ // #8233 C9 — the New menu's files beside account-config.ts: new-popups.tsx
826
+ // binds the config's actions to the popups, which import the menu and its
827
+ // server actions. (account-config.ts itself imports none of them — a
828
+ // spoke without sign-in drops the `(authed)` group and keeps the config.)
829
+ `${ROUTE_ROOT}/(authed)/account/new-menu.tsx`,
830
+ `${ROUTE_ROOT}/(authed)/account/new-actions.ts`,
831
+ `${ROUTE_ROOT}/(authed)/account/new-popups.tsx`,
832
+ `${ROUTE_ROOT}/(authed)/account/components/schedule-service.tsx`,
833
+ `${ROUTE_ROOT}/(authed)/account/components/request-quote.tsx`,
834
+ `${ROUTE_ROOT}/(authed)/account/components/send-message.tsx`,
835
+ ],
836
+ // #8339 D11 — Security holds the former Sign-in section, moved verbatim
837
+ // from General: `sign-in-method.ts` and what it imports.
838
+ "(authed)/account/security": [
839
+ "apps/web/lib/account-config.ts",
840
+ "apps/web/lib/auth-env.ts",
841
+ "apps/web/lib/server-api.ts",
842
+ "apps/web/lib/hub-session/config.ts",
843
+ `${ROUTE_ROOT}/(authed)/account/account-data.ts`,
844
+ `${ROUTE_ROOT}/(authed)/account/settings-frame.tsx`,
845
+ `${ROUTE_ROOT}/(authed)/account/sign-in-method.ts`,
846
+ ],
847
+ // #8339 D11 — Notifications holds the former Privacy section's
848
+ // cookie-settings link, moved verbatim from General.
849
+ "(authed)/account/notifications": [
850
+ "apps/web/lib/account-config.ts",
851
+ "apps/web/lib/server-api.ts",
852
+ `${ROUTE_ROOT}/(authed)/account/account-data.ts`,
853
+ `${ROUTE_ROOT}/(authed)/account/settings-frame.tsx`,
854
+ ],
855
+ "(authed)/account/billing": [
856
+ "apps/web/lib/account-config.ts",
857
+ "apps/web/lib/server-api.ts",
858
+ `${ROUTE_ROOT}/(authed)/account/account-data.ts`,
859
+ `${ROUTE_ROOT}/(authed)/account/actions.ts`,
860
+ `${ROUTE_ROOT}/(authed)/account/error-row.tsx`,
861
+ `${ROUTE_ROOT}/(authed)/account/retry-button.tsx`,
862
+ `${ROUTE_ROOT}/(authed)/account/settings-frame.tsx`,
863
+ `${ROUTE_ROOT}/(authed)/account/billing-source.ts`,
864
+ // #8233 C9 — the New menu's files beside account-config.ts: new-popups.tsx
865
+ // binds the config's actions to the popups, which import the menu and its
866
+ // server actions. (account-config.ts itself imports none of them — a
867
+ // spoke without sign-in drops the `(authed)` group and keeps the config.)
868
+ `${ROUTE_ROOT}/(authed)/account/new-menu.tsx`,
869
+ `${ROUTE_ROOT}/(authed)/account/new-actions.ts`,
870
+ `${ROUTE_ROOT}/(authed)/account/new-popups.tsx`,
871
+ `${ROUTE_ROOT}/(authed)/account/components/schedule-service.tsx`,
872
+ `${ROUTE_ROOT}/(authed)/account/components/request-quote.tsx`,
873
+ `${ROUTE_ROOT}/(authed)/account/components/send-message.tsx`,
874
+ ],
875
+ // #8234 — the Artifacts pages import files in their parent's directory, as
876
+ // #7430's children do; the artifact page also imports its own parent's
877
+ // `artifact-data.ts` and `artifact-actions.ts`, which an older synced
878
+ // `artifacts/` might lack. The file route re-serves the hub's bytes with
879
+ // the credential `forwardedCookieInit` returns.
880
+ "(authed)/account/artifacts": [
881
+ "apps/web/lib/account-config.ts",
882
+ "apps/web/lib/server-api.ts",
883
+ `${ROUTE_ROOT}/(authed)/account/account-data.ts`,
884
+ `${ROUTE_ROOT}/(authed)/account/error-row.tsx`,
885
+ `${ROUTE_ROOT}/(authed)/account/retry-button.tsx`,
886
+ ],
887
+ "(authed)/account/artifacts/[pieceId]": [
888
+ "apps/web/lib/account-config.ts",
889
+ "apps/web/lib/server-api.ts",
890
+ `${ROUTE_ROOT}/(authed)/account/account-data.ts`,
891
+ `${ROUTE_ROOT}/(authed)/account/error-row.tsx`,
892
+ `${ROUTE_ROOT}/(authed)/account/retry-button.tsx`,
893
+ `${ROUTE_ROOT}/(authed)/account/artifacts/artifact-data.ts`,
894
+ `${ROUTE_ROOT}/(authed)/account/artifacts/artifact-actions.ts`,
774
895
  ],
896
+ "(authed)/account/artifacts/[pieceId]/versions/[version]/file": ["apps/web/lib/server-api.ts"],
775
897
  "(authed)/account/support": [
776
898
  "apps/web/lib/account-config.ts",
777
899
  "apps/web/lib/server-api.ts",
778
900
  `${ROUTE_ROOT}/(authed)/account/account-data.ts`,
779
901
  `${ROUTE_ROOT}/(authed)/account/support-actions.ts`,
902
+ // #8233 C9 — the New menu's files beside account-config.ts: new-popups.tsx
903
+ // binds the config's actions to the popups, which import the menu and its
904
+ // server actions. (account-config.ts itself imports none of them — a
905
+ // spoke without sign-in drops the `(authed)` group and keeps the config.)
906
+ `${ROUTE_ROOT}/(authed)/account/new-menu.tsx`,
907
+ `${ROUTE_ROOT}/(authed)/account/new-actions.ts`,
908
+ `${ROUTE_ROOT}/(authed)/account/new-popups.tsx`,
909
+ `${ROUTE_ROOT}/(authed)/account/components/schedule-service.tsx`,
910
+ `${ROUTE_ROOT}/(authed)/account/components/request-quote.tsx`,
911
+ `${ROUTE_ROOT}/(authed)/account/components/send-message.tsx`,
780
912
  ],
781
913
  "(authed)/admin": [
782
914
  "apps/web/lib/server-api.ts",
@@ -832,6 +964,13 @@ export const ROUTE_MESSAGE_NAMESPACES = {
832
964
  "(authed)/account/inbox/[kind]/[id]": ["account"],
833
965
  "(authed)/account/settings": ["account"],
834
966
  "(authed)/account/support": ["account"],
967
+ "(authed)/account/general": ["account"],
968
+ "(authed)/account/security": ["account"],
969
+ "(authed)/account/notifications": ["account"],
970
+ "(authed)/account/billing": ["account"],
971
+ // #8234 — the two Artifacts pages; the file route renders no string.
972
+ "(authed)/account/artifacts": ["account"],
973
+ "(authed)/account/artifacts/[pieceId]": ["account"],
835
974
  "(authed)/admin": ["admin"],
836
975
  "(authed)/leads": ["leadsInbox"],
837
976
  // #5977 — every string on both editor routes comes from the `edit`
@@ -14,7 +14,7 @@
14
14
  * package already twice records that a fixed template FILE is the drift a
15
15
  * registry removes.
16
16
  */
17
- import type { CategorySlug } from "@working-theory/schema/categories";
17
+ import type { CategorySlug } from "./category-profile.js";
18
18
  import { type PageRosterRow } from "./module-architecture.js";
19
19
  import type { ModuleName } from "./parse-args.js";
20
20
  import type { TemplateLayout } from "./site-chrome.js";
@@ -66,8 +66,9 @@ export interface TemplateManifest {
66
66
  * The business categories this template is FOR — one or more LEAF slugs from
67
67
  * the site-category registry (`@working-theory/schema/categories`, #5830),
68
68
  * shared with content's frontmatter `categories`. Required, and typed against
69
- * `CategorySlug`, for the same reason `requires` is typed against
70
- * `ModuleName`: an unrecognized name fails type-check rather than a runtime
69
+ * `CategorySlug` — create-ailk's drift-tested copy of the registry's leaves
70
+ * (`category-profile.ts`, #8412) — for the same reason `requires` is typed
71
+ * against `ModuleName`: an unrecognized name fails type-check rather than a runtime
71
72
  * validation, and every template must be findable under at least one
72
73
  * category on the hub's "Browse by business" band.
73
74
  *
@@ -134,9 +134,14 @@ export declare const SPOKE_UNSERVED_MCP_TOOLS: readonly string[];
134
134
  * `apps/web/lib/topology.generated.ts` for `topology` (D5) and, on a spoke,
135
135
  * its sign-in mode (#7894 D8). The hub render loads `auth.ts` only when a
136
136
  * session is read, so a module that imports this seam for a constant never
137
- * loads Better Auth.
137
+ * loads Better Auth. `opts.spokeBilling` (#8217 D7) makes `loadSpokeBilling`
138
+ * reach the spoke billing action — only on a spoke that signs in through its
139
+ * hub, the one render whose account area can call it.
140
+ * @throws TopologyError for `spokeBilling` on a hub or a default-mode spoke.
138
141
  */
139
- export declare function renderWebSeam(topology: Topology, signIn?: SignInMode): string;
142
+ export declare function renderWebSeam(topology: Topology, signIn?: SignInMode, opts?: {
143
+ spokeBilling?: boolean;
144
+ }): string;
140
145
  /**
141
146
  * `apps/web/app/[locale]/(authed)/signed-out-gate.generated.tsx` (#7894 D8).
142
147
  * The hub render is the `(authed)` layout's no-session branch, moved; the
package/dist/topology.js CHANGED
@@ -271,7 +271,9 @@ const WEB_SEAM_HEADER = `// topology.generated.ts — how this site reads a sess
271
271
  // has no sign-in, so its render reads no session and imports nothing; with
272
272
  // \`--hub-sign-in\` (#7894) it reads the sealed hub-session cookie.
273
273
  // UNSERVED_MCP_TOOLS names the MCP tools /.well-known/mcp.json leaves out
274
- // on this topology (#8070).`;
274
+ // on this topology (#8070).
275
+ // loadSpokeBilling reaches the spoke billing action only on a spoke that
276
+ // signs in through its hub and carries commerce (#8217); elsewhere, null.`;
275
277
  const MCP_SEAM_HEADER = `// topology.generated.ts — how this MCP server resolves its workspace store.
276
278
  //
277
279
  // GENERATED by create-ailk for this site's topology (#7426). Do not edit:
@@ -310,13 +312,62 @@ export const SIGN_IN_PATH: string | null = ${literal(values.signInPath)};
310
312
  export const SIGN_UP_PATH: string | null = ${literal(values.signUpPath)};
311
313
  export const SIGN_OUT_ENDPOINT: string | null = ${literal(values.signOutEndpoint)};`;
312
314
  }
315
+ /** The spoke billing action (#8217 D6), relative to the web seam. */
316
+ const SPOKE_BILLING_ACTION_IMPORT = "../app/[locale]/(authed)/account/billing/spoke-billing.action";
317
+ /** The billing types every web seam render carries (#8217 D7). */
318
+ const SPOKE_BILLING_TYPES = `export type SpokeCard = { brand: string; last4: string; expMonth: number; expYear: number };
319
+
320
+ export type SpokeBillingRead =
321
+ | {
322
+ kind: "subscription";
323
+ status: "trialing" | "active" | "past_due" | "canceled";
324
+ planName: string | null;
325
+ paymentMethod: SpokeCard | null;
326
+ unreadable: boolean;
327
+ }
328
+ | { kind: "none"; unreadable: boolean }
329
+ | { kind: "failed" };
330
+
331
+ export type SpokeBilling = {
332
+ read(): Promise<SpokeBillingRead>;
333
+ portalUrl(): Promise<string | null>;
334
+ };`;
335
+ /**
336
+ * `loadSpokeBilling` (#8217 D7): the billing render imports the action, by a
337
+ * dynamic import, so a module that reads this seam for a constant never
338
+ * loads the checkout package; every other render resolves `null`.
339
+ */
340
+ function spokeBillingLoader(spokeBilling) {
341
+ if (!spokeBilling) {
342
+ return `${SPOKE_BILLING_TYPES}
343
+
344
+ export function loadSpokeBilling(): Promise<SpokeBilling | null> {
345
+ return Promise.resolve(null);
346
+ }`;
347
+ }
348
+ return `${SPOKE_BILLING_TYPES}
349
+
350
+ export async function loadSpokeBilling(): Promise<SpokeBilling | null> {
351
+ const { readSpokeBilling, spokeBillingPortalUrl } = await import(
352
+ "${SPOKE_BILLING_ACTION_IMPORT}"
353
+ );
354
+ return { read: readSpokeBilling, portalUrl: spokeBillingPortalUrl };
355
+ }`;
356
+ }
313
357
  /**
314
358
  * `apps/web/lib/topology.generated.ts` for `topology` (D5) and, on a spoke,
315
359
  * its sign-in mode (#7894 D8). The hub render loads `auth.ts` only when a
316
360
  * session is read, so a module that imports this seam for a constant never
317
- * loads Better Auth.
361
+ * loads Better Auth. `opts.spokeBilling` (#8217 D7) makes `loadSpokeBilling`
362
+ * reach the spoke billing action — only on a spoke that signs in through its
363
+ * hub, the one render whose account area can call it.
364
+ * @throws TopologyError for `spokeBilling` on a hub or a default-mode spoke.
318
365
  */
319
- export function renderWebSeam(topology, signIn = "off") {
366
+ export function renderWebSeam(topology, signIn = "off", opts = {}) {
367
+ const spokeBilling = opts.spokeBilling === true;
368
+ if (spokeBilling && (topology !== "spoke" || signIn !== "hub")) {
369
+ throw new TopologyError("the spoke billing loader renders only on a spoke that signs in through its hub (--hub-sign-in)");
370
+ }
320
371
  if (topology === "hub") {
321
372
  return `${WEB_SEAM_HEADER}
322
373
 
@@ -335,6 +386,8 @@ export async function registerSiteStores(): Promise<void> {
335
386
  const { registerLicenseKeyStore } = await import("./license-key-store");
336
387
  await registerLicenseKeyStore();
337
388
  }
389
+
390
+ ${spokeBillingLoader(false)}
338
391
  `;
339
392
  }
340
393
  if (signIn === "hub") {
@@ -354,6 +407,8 @@ export function readSession(headers: Headers): Promise<SiteSession> {
354
407
  export function registerSiteStores(): Promise<void> {
355
408
  return Promise.resolve();
356
409
  }
410
+
411
+ ${spokeBillingLoader(spokeBilling)}
357
412
  `;
358
413
  }
359
414
  return `${WEB_SEAM_HEADER}
@@ -372,6 +427,8 @@ export function readSession(headers: Headers): Promise<SiteSession> {
372
427
  export function registerSiteStores(): Promise<void> {
373
428
  return Promise.resolve();
374
429
  }
430
+
431
+ ${spokeBillingLoader(false)}
375
432
  `;
376
433
  }
377
434
  const AUTHED_GATE_SEAM_HEADER = `// signed-out-gate.generated.tsx — what the (authed) group shows a signed-out visitor.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ailk",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "Scaffolder for AI Launch Kit — `pnpm create ailk@latest <dir>` produces a starter repo consuming @working-theory/* from npm (per ADR 0007).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -18,7 +18,8 @@
18
18
  "component-catalog.json",
19
19
  "component-catalog.md",
20
20
  "README.md",
21
- "LICENSE"
21
+ "LICENSE",
22
+ "NOTICE"
22
23
  ],
23
24
  "publishConfig": {
24
25
  "access": "public"
@@ -27,8 +28,8 @@
27
28
  "tar": "^7.5.21"
28
29
  },
29
30
  "devDependencies": {
30
- "@working-theory/templates": "0.38.0",
31
- "@working-theory/schema": "0.38.0"
31
+ "@working-theory/schema": "0.39.0",
32
+ "@working-theory/templates": "0.39.0"
32
33
  },
33
34
  "repository": {
34
35
  "type": "git",
@@ -9,9 +9,9 @@ AILK_PUBLIC_DOMAIN=ailaunchkit.ai
9
9
  # STEALTH defaults to "true" for new installs so a freshly-deployed instance
10
10
  # isn't indexed by crawlers or unfurled by Slack/Twitter/LinkedIn before the
11
11
  # operator is ready. Three layers gate on this single flag:
12
- # 1. apps-paid/marketing-web/public/robots.txt (always-blocking static file)
13
- # 2. X-Robots-Tag: noindex, nofollow, noarchive HTTP header (next.config.ts)
14
- # 3. <meta name="robots" content="noindex,nofollow"> tag (app/layout.tsx)
12
+ # 1. robots.txt answers "Disallow: /" for every crawler (apps/web/app/robots.txt/route.ts)
13
+ # 2. X-Robots-Tag: noindex, nofollow, noarchive HTTP header (apps/web/next.config.mjs)
14
+ # 3. <meta name="robots" content="noindex,nofollow"> tag (apps/web/app/[locale]/layout.tsx)
15
15
  # At launch, set STEALTH=false on the hosting platform's env vars and redeploy.
16
16
  # See README.md § "Going live" for the full flip checklist.
17
17
  STEALTH=true
@@ -54,4 +54,9 @@ jobs:
54
54
 
55
55
  - run: pnpm test
56
56
 
57
+ # apps/web's `next build` with the docs module outgrows Node's default
58
+ # ~2 GB heap and aborts with "JavaScript heap out of memory" (#8069).
59
+ # The hosted runner has ~7 GB; give the build room.
57
60
  - run: pnpm build
61
+ env:
62
+ NODE_OPTIONS: --max-old-space-size=6144