create-ailk 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/README.md +71 -3
  2. package/component-catalog.json +279 -45
  3. package/dist/add-module.d.ts +20 -0
  4. package/dist/add-module.js +43 -7
  5. package/dist/add-route.d.ts +56 -0
  6. package/dist/add-route.js +286 -0
  7. package/dist/apply-design-md.d.ts +7 -0
  8. package/dist/apply-design-md.js +50 -0
  9. package/dist/apply-design.d.ts +64 -0
  10. package/dist/apply-design.js +273 -0
  11. package/dist/claude-md.d.ts +111 -0
  12. package/dist/claude-md.js +334 -0
  13. package/dist/cli.js +104 -5
  14. package/dist/derive-architecture.js +41 -36
  15. package/dist/design-compile.d.ts +71 -0
  16. package/dist/design-compile.js +98 -0
  17. package/dist/design-package.d.ts +83 -0
  18. package/dist/design-package.js +468 -0
  19. package/dist/design-presets.d.ts +86 -0
  20. package/dist/design-presets.js +642 -0
  21. package/dist/design-scaffold.d.ts +76 -0
  22. package/dist/design-scaffold.js +220 -0
  23. package/dist/index.d.ts +2 -1
  24. package/dist/index.js +4 -1
  25. package/dist/legacy-nav-renderers.d.ts +45 -0
  26. package/dist/legacy-nav-renderers.js +545 -0
  27. package/dist/lib/auth-secret-file.d.ts +19 -0
  28. package/dist/lib/auth-secret-file.js +112 -0
  29. package/dist/lib/design-checks.d.ts +78 -0
  30. package/dist/lib/design-checks.js +174 -0
  31. package/dist/lib/license-key-files.d.ts +9 -0
  32. package/dist/lib/license-key-files.js +2 -2
  33. package/dist/lib/module-license-gate.d.ts +48 -32
  34. package/dist/lib/module-license-gate.js +117 -8
  35. package/dist/lib/render-verdict.d.ts +52 -0
  36. package/dist/lib/render-verdict.js +77 -0
  37. package/dist/lib/scaffold-render-sweep.d.ts +105 -0
  38. package/dist/lib/scaffold-render-sweep.js +236 -0
  39. package/dist/module-architecture.d.ts +19 -16
  40. package/dist/module-architecture.js +67 -23
  41. package/dist/parse-args.d.ts +12 -2
  42. package/dist/parse-args.js +74 -13
  43. package/dist/programmatic.d.ts +30 -6
  44. package/dist/programmatic.js +216 -19
  45. package/dist/site-chrome.d.ts +19 -9
  46. package/dist/site-chrome.js +23 -13
  47. package/dist/site-manifest.d.ts +25 -0
  48. package/dist/site-manifest.js +254 -0
  49. package/dist/site-roster.d.ts +41 -0
  50. package/dist/site-roster.js +141 -8
  51. package/dist/starter-grid.d.ts +16 -63
  52. package/dist/starter-grid.js +102 -84
  53. package/dist/surfaces.d.ts +41 -9
  54. package/dist/surfaces.js +200 -83
  55. package/dist/template-manifest.d.ts +30 -13
  56. package/dist/template-manifest.js +53 -28
  57. package/package.json +6 -3
  58. package/release-scope.json +25 -0
  59. package/site-templates/_base/content/en/UseCase/persona-1.mdx +7 -6
  60. package/site-templates/_base/content/en/UseCase/persona-2.mdx +7 -6
  61. package/site-templates/_base/content/en/UseCase/persona-3.mdx +7 -6
  62. package/site-templates/designs/minimalism/DESIGN.md +98 -6
  63. package/site-templates/designs/minimalism/sample/README.md +48 -0
  64. package/site-templates/{minimalism-site → designs/minimalism/sample}/content/en/HomePage/home.mdx +1 -1
  65. package/site-templates/designs/standard/DESIGN.md +98 -6
  66. package/site-templates/marketing-site/DESIGN.md +98 -6
  67. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +60 -27
  68. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +50 -30
  69. package/site-templates/marketing-site-free/DESIGN.md +98 -6
  70. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +60 -24
  71. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +44 -27
  72. package/site-templates/marketing-site-free/ailk-template.json +1 -1
  73. package/site-templates/saas-product/DESIGN.md +98 -6
  74. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +127 -18
  75. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +74 -20
  76. package/site-templates/shapes/local-business-service/ailk-template.json +1 -1
  77. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +127 -18
  78. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +74 -20
  79. package/site-templates/shapes/organization-service/ailk-template.json +1 -1
  80. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +127 -18
  81. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +75 -21
  82. package/site-templates/shapes/person-service/ailk-template.json +1 -1
  83. package/site-templates/shapes/person-service/content/en/AboutPage/about.mdx +1 -1
  84. package/site-templates/shapes/person-service/content/en/HomePage/home.mdx +2 -2
  85. package/site-templates/shapes/person-service/content/en/PortfolioList/portfolio.mdx +4 -2
  86. package/site-templates/shapes/person-service/content/en/ServicesList/services.mdx +1 -1
  87. package/templates/.claude/agents/web.md +10 -8
  88. package/templates/.claude/rules/architecture.md +17 -7
  89. package/templates/.claude/skills/README.md +3 -2
  90. package/templates/.claude/skills/add-auth/SKILL.md +142 -90
  91. package/templates/.claude/skills/add-auth/references/env-vars.md +32 -17
  92. package/templates/.claude/skills/add-auth/references/oauth-app-setup.md +17 -8
  93. package/templates/.claude/skills/add-page/SKILL.md +241 -238
  94. package/templates/.claude/skills/add-page/references/page-types.md +94 -86
  95. package/templates/.claude/skills/add-page/scripts/site-pages.mjs +87 -0
  96. package/templates/.claude/skills/add-page/templates/comparison-page.mdx.tmpl +99 -0
  97. package/templates/.claude/skills/add-page/templates/content.mdx.tmpl +8 -18
  98. package/templates/.claude/skills/add-page/templates/flow-landing-page.mdx.tmpl +43 -0
  99. package/templates/.claude/skills/add-page/templates/report-page.mdx.tmpl +52 -0
  100. package/templates/.claude/skills/add-page/templates/reports-list.mdx.tmpl +38 -0
  101. package/templates/.claude/skills/check-site/SKILL.md +51 -18
  102. package/templates/.claude/skills/check-site/references/common-errors.md +12 -9
  103. package/templates/.claude/skills/check-site/references/findings-glossary.md +4 -4
  104. package/templates/.claude/skills/check-site/references/severity-levels.md +3 -3
  105. package/templates/.claude/skills/fill-page/SKILL.md +26 -7
  106. package/templates/.claude/skills/fill-page/profiles/plumbing.yaml +1 -1
  107. package/templates/.claude/skills/setup-deploy/SKILL.md +75 -38
  108. package/templates/.claude/skills/update-page/SKILL.md +117 -0
  109. package/templates/.claude/skills/update-page/scripts/update-page.ts +640 -0
  110. package/templates/.claude/skills/update-page/templates/redirects.mjs.tmpl +31 -0
  111. package/templates/.claude/skills/upgrade-plan/SKILL.md +12 -8
  112. package/templates/.env.example +23 -2
  113. package/templates/.github/workflows/ci.yml +4 -0
  114. package/templates/.prettierignore +1 -0
  115. package/templates/CLAUDE.md +146 -103
  116. package/templates/DESIGN.md +98 -6
  117. package/templates/README.md +50 -38
  118. package/templates/apps/api/.env.example +2 -1
  119. package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +2 -0
  120. package/templates/apps/api/src/__tests__/server.test.ts +116 -0
  121. package/templates/apps/api/src/lib/stripe.ts +9 -0
  122. package/templates/apps/api/src/middleware/__tests__/auth.optional-cookie.test.ts +140 -0
  123. package/templates/apps/api/src/middleware/auth.ts +54 -0
  124. package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +5 -2
  125. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +628 -0
  126. package/templates/apps/api/src/routes/checkout/sessions.ts +222 -24
  127. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +1 -1
  128. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +5 -1
  129. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +521 -20
  130. package/templates/apps/api/src/routes/webhooks/stripe.ts +124 -32
  131. package/templates/apps/api/src/server.ts +28 -0
  132. package/templates/apps/api/src/services/__tests__/revalidation.test.ts +90 -15
  133. package/templates/apps/api/src/services/checkout-buyer.ts +72 -0
  134. package/templates/apps/api/src/services/revalidation.ts +60 -15
  135. package/templates/apps/mcp/__tests__/tools/create_project.test.ts +30 -24
  136. package/templates/apps/mcp/src/tools/purchase.ts +4 -2
  137. package/templates/apps/web/.env.example +13 -0
  138. package/templates/apps/web/CLAUDE.md +22 -28
  139. package/templates/apps/web/app/[locale]/(auth)/layout.tsx +1 -1
  140. package/templates/apps/web/app/[locale]/(authed)/leads/__tests__/page.test.tsx +24 -0
  141. package/templates/apps/web/app/[locale]/(authed)/leads/page.tsx +17 -6
  142. package/templates/apps/web/app/[locale]/[slug]/[detail]/__tests__/page.test.tsx +110 -0
  143. package/templates/apps/web/app/[locale]/[slug]/[detail]/page.tsx +60 -0
  144. package/templates/apps/web/app/[locale]/[slug]/page.tsx +1 -1
  145. package/templates/apps/web/app/[locale]/alternatives/__tests__/page.test.tsx +161 -0
  146. package/templates/apps/web/app/[locale]/alternatives/page.tsx +104 -0
  147. package/templates/apps/web/app/[locale]/{use-cases → for}/[slug]/page.tsx +4 -3
  148. package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +73 -0
  149. package/templates/apps/web/app/[locale]/{use-cases → for}/page.tsx +8 -7
  150. package/templates/apps/web/app/[locale]/glossary/page.tsx +1 -1
  151. package/templates/apps/web/app/[locale]/layout.tsx +35 -19
  152. package/templates/apps/web/app/[locale]/newsletter/page.tsx +1 -1
  153. package/templates/apps/web/app/[locale]/portfolio/[slug]/page.tsx +1 -1
  154. package/templates/apps/web/app/[locale]/portfolio/page.tsx +1 -1
  155. package/templates/apps/web/app/[locale]/vs/[slug]/__tests__/page.test.tsx +112 -0
  156. package/templates/apps/web/app/[locale]/vs/[slug]/page.tsx +70 -0
  157. package/templates/apps/web/app/api/revalidate/__tests__/route.test.ts +236 -44
  158. package/templates/apps/web/app/api/revalidate/route.ts +78 -36
  159. package/templates/apps/web/lib/__tests__/site-brand.test.ts +79 -0
  160. package/templates/apps/web/lib/__tests__/site-theme.test.ts +104 -11
  161. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +21 -4
  162. package/templates/apps/web/lib/locale-guard.ts +18 -0
  163. package/templates/apps/web/lib/meet-placeholder.ts +5 -0
  164. package/templates/apps/web/lib/nav-config.ts +14 -13
  165. package/templates/apps/web/lib/site-brand.tsx +13 -9
  166. package/templates/apps/web/lib/site-logo.tsx +36 -0
  167. package/templates/apps/web/lib/site-theme.ts +46 -26
  168. package/templates/apps/web/messages/en.json +23 -9
  169. package/templates/apps/web/middleware.ts +28 -4
  170. package/templates/content/en/BookCall/book-a-call.mdx +80 -0
  171. package/templates/content/en/BookCall/onsite-estimate.mdx +56 -0
  172. package/templates/content/en/BookCall/onsite-service.mdx +56 -0
  173. package/templates/content/en/BookCall/start-project.mdx +56 -0
  174. package/templates/content/en/ContactPage/contact.mdx +40 -1
  175. package/templates/content/en/HomePage/home.mdx +2 -6
  176. package/templates/content/en/LandingPage/meet.mdx +59 -0
  177. package/templates/content/en/UseCasesList/{use-cases.mdx → for.mdx} +19 -18
  178. package/templates/database/CHANGELOG.md +7 -0
  179. package/templates/database/package.json +1 -1
  180. package/templates/package.json +4 -1
  181. package/templates/tools/check-claude-md.ts +128 -0
  182. package/versions.json +33 -33
  183. package/dist/modules/commerce/0.6.0.tar.gz +0 -0
  184. package/dist/modules/commerce/0.6.1.tar.gz +0 -0
  185. package/dist/modules/docs-blog/0.6.0.tar.gz +0 -0
  186. package/dist/modules/docs-blog/0.6.1.tar.gz +0 -0
  187. package/dist/modules/marketing-site/0.6.1.tar.gz +0 -0
  188. package/dist/modules/minimalism-site/0.6.1.tar.gz +0 -0
  189. package/dist/modules/multi-tenant/0.6.0.tar.gz +0 -0
  190. package/dist/modules/multi-tenant/0.6.1.tar.gz +0 -0
  191. package/dist/modules/saas-product/0.6.1.tar.gz +0 -0
  192. package/dist/modules/scheduling/0.6.0.tar.gz +0 -0
  193. package/dist/modules/scheduling/0.6.1.tar.gz +0 -0
  194. package/dist/modules/signal-collectors/0.6.0.tar.gz +0 -0
  195. package/dist/modules/signal-collectors/0.6.1.tar.gz +0 -0
  196. package/dist/modules/simple-service/0.6.1.tar.gz +0 -0
  197. package/dist/modules/workspace/0.6.0.tar.gz +0 -0
  198. package/dist/modules/workspace/0.6.1.tar.gz +0 -0
  199. package/site-templates/_base/content/en/BookCall/book-call.mdx +0 -52
  200. package/site-templates/minimalism-site/DESIGN.md +0 -162
  201. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +0 -506
  202. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +0 -458
  203. package/site-templates/minimalism-site/ailk-template.json +0 -23
  204. package/site-templates/minimalism-site/content/en/AboutPage/about.mdx +0 -110
  205. package/site-templates/minimalism-site/content/en/BookCall/book-call.mdx +0 -52
  206. package/site-templates/minimalism-site/content/en/ContactPage/contact.mdx +0 -34
  207. package/site-templates/minimalism-site/content/en/FAQPage/faq.mdx +0 -57
  208. package/site-templates/minimalism-site/content/en/LandingPage/referral.mdx +0 -77
  209. package/site-templates/minimalism-site/content/en/LandingPage/reports.mdx +0 -73
  210. package/site-templates/minimalism-site/content/en/PurchasePolicyPage/purchase-policy.mdx +0 -123
  211. package/site-templates/minimalism-site/content/en/ReportPage/annual-letter-2024.mdx +0 -52
  212. package/site-templates/minimalism-site/content/en/ReportPage/field-notes-2023.mdx +0 -52
  213. package/site-templates/minimalism-site/content/en/ReportPage/platform-shift-2025.mdx +0 -52
  214. package/site-templates/minimalism-site/content/en/ReportPage/state-of-the-market-2026.mdx +0 -52
  215. package/site-templates/minimalism-site/content/en/Service/first-engagement.mdx +0 -38
  216. package/site-templates/minimalism-site/content/en/Service/second-engagement.mdx +0 -38
  217. package/site-templates/minimalism-site/content/en/Service/third-engagement.mdx +0 -38
  218. package/site-templates/minimalism-site/content/en/ServicesList/services.mdx +0 -73
  219. package/site-templates/minimalism-site/flows/referral.flow.json +0 -71
  220. package/site-templates/minimalism-site/provenance.json +0 -4
  221. package/templates/.claude/skills/add-page/templates/route.tsx.tmpl +0 -28
  222. package/templates/.claude/skills/setup-deploy/references/env-vars-template.md +0 -175
  223. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-31.552Z-fallback-1789916251590-037e9005.json +0 -8
  224. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.840Z-fallback-1789916253842-b52659e9.json +0 -12
  225. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.844Z-fallback-1789916253845-6b266f8c.json +0 -12
  226. package/templates/apps/web/app/[locale]/book-a-call/page.tsx +0 -50
  227. package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +0 -72
  228. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-1.svg +0 -0
  229. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-2.svg +0 -0
  230. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-3.svg +0 -0
  231. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-4.svg +0 -0
  232. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-5.svg +0 -0
  233. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-6.svg +0 -0
  234. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/hero-backdrop.svg +0 -0
  235. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/logo-placeholder-1.svg +0 -0
  236. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/logo-placeholder-2.svg +0 -0
  237. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/logo-placeholder-3.svg +0 -0
  238. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/wordmark-placeholder.svg +0 -0
@@ -71,6 +71,26 @@ export interface AddModuleResult {
71
71
  * everything before it copies).
72
72
  */
73
73
  export declare function applyModuleToScaffold(opts: ApplyModuleOptions): Promise<AddModuleResult>;
74
+ /**
75
+ * Whether an existing nav-config.ts is still unedited (#6908 D3), so
76
+ * `syncNavConfig` may safely overwrite it with the new surface set's render.
77
+ *
78
+ * A valid self-hashing stamp (`readNavConfigStamp`, surfaces.ts D1) proves
79
+ * the file unedited whatever generator wrote it — F2. A stamp that fails its
80
+ * hash is edited and never also tried against the legacy renders (F4). With
81
+ * no stamp at all, the file is unedited only when it byte-equals (after CRLF
82
+ * folding) one of the closed set of published pre-stamp generators'
83
+ * `before`-surface renders (F3) — the only sites this branch can ever apply
84
+ * to, since every nav a stamped generator writes carries a stamp.
85
+ */
86
+ export declare function isUneditedNav(existing: string, before: string[]): boolean;
87
+ /**
88
+ * Regenerate the site nav (`scaffoldSync` writes it from the surface set) for
89
+ * the new surfaces — but only when the site's copy is still unedited
90
+ * (`isUneditedNav`). An edited nav is the customer's file: left as-is and
91
+ * reported `skipped` with the change, like any other.
92
+ */
93
+ export declare function syncNavConfig(targetAbs: string, before: string[], after: string[]): InstallReportRow | undefined;
74
94
  /** The report `add-module` prints. */
75
95
  export declare function formatAddModuleReport(result: AddModuleResult, moduleName: string): string;
76
96
  export interface AddModuleArgs {
@@ -30,11 +30,13 @@ import { basename, dirname, join, resolve } from "node:path";
30
30
  import { parseArgs } from "node:util";
31
31
  import { AddModuleRefusedError, readArchitectureHeader, } from "./add-module-core.js";
32
32
  import { deriveArchitecture } from "./derive-architecture.js";
33
+ import { LEGACY_NAV_RENDERERS } from "./legacy-nav-renderers.js";
34
+ import { writeAuthSecretFile } from "./lib/auth-secret-file.js";
33
35
  import { diffTwoStrings, extractModuleBundle, } from "./lib/extract-module-bundle.js";
34
36
  import { normalizeModules } from "./module-architecture.js";
35
37
  import { ParseError, parseModules } from "./parse-args.js";
36
38
  import { SITE_ROSTER_PATH, moduleRosterRows, planRosterAppend, readSiteRoster, siteRosterProblems, } from "./site-roster.js";
37
- import { MODULE_SURFACES, NAV_CONFIG_PATH, renderNavConfig, resolveSurfaces, } from "./surfaces.js";
39
+ import { MODULE_SURFACES, NAV_CONFIG_PATH, readNavConfigStamp, renderNavConfig, resolveSurfaces, } from "./surfaces.js";
38
40
  import { syncRoutes } from "./sync-routes.js";
39
41
  export { AddModuleRefusedError, readArchitectureHeader };
40
42
  /** `package.json` paths a `json` patch may write under the plus-config policy. */
@@ -117,6 +119,10 @@ export async function applyModuleToScaffold(opts) {
117
119
  apply: true,
118
120
  forceSurfaces: MODULE_SURFACES[opts.moduleName] ?? [],
119
121
  });
122
+ // #6107 D8 — a module that brings `apps/admin` brings its auth hard-fail;
123
+ // the Admin app gets the web app's effective secret, and a secret already
124
+ // held in any Next-loaded env file is never shadowed.
125
+ writeAuthSecretFile(targetAbs);
120
126
  // The same composition `syncArchitectureSync` writes — a pure function of
121
127
  // the module set, so an unchanged set rewrites identical bytes.
122
128
  writeFileSync(archPath, deriveArchitecture(modules, surfaces));
@@ -174,21 +180,51 @@ export async function applyModuleToScaffold(opts) {
174
180
  }
175
181
  return { targetDir: targetAbs, modules, report: rows, routes, roster };
176
182
  }
183
+ /**
184
+ * Whether an existing nav-config.ts is still unedited (#6908 D3), so
185
+ * `syncNavConfig` may safely overwrite it with the new surface set's render.
186
+ *
187
+ * A valid self-hashing stamp (`readNavConfigStamp`, surfaces.ts D1) proves
188
+ * the file unedited whatever generator wrote it — F2. A stamp that fails its
189
+ * hash is edited and never also tried against the legacy renders (F4). With
190
+ * no stamp at all, the file is unedited only when it byte-equals (after CRLF
191
+ * folding) one of the closed set of published pre-stamp generators'
192
+ * `before`-surface renders (F3) — the only sites this branch can ever apply
193
+ * to, since every nav a stamped generator writes carries a stamp.
194
+ */
195
+ export function isUneditedNav(existing, before) {
196
+ const stamp = readNavConfigStamp(existing);
197
+ if (stamp === "valid")
198
+ return true;
199
+ if (stamp === "invalid")
200
+ return false;
201
+ const folded = existing.replace(/\r\n/g, "\n");
202
+ return LEGACY_NAV_RENDERERS.some((r) => folded === r.render(before));
203
+ }
177
204
  /**
178
205
  * Regenerate the site nav (`scaffoldSync` writes it from the surface set) for
179
- * the new surfaces — but only when the site's copy is still exactly what the
180
- * OLD surface set generates. An edited nav is the customer's file: left as-is
181
- * and reported `skipped` with the change, like any other.
206
+ * the new surfaces — but only when the site's copy is still unedited
207
+ * (`isUneditedNav`). An edited nav is the customer's file: left as-is and
208
+ * reported `skipped` with the change, like any other.
182
209
  */
183
- function syncNavConfig(targetAbs, before, after) {
210
+ export function syncNavConfig(targetAbs, before, after) {
184
211
  const navPath = join(targetAbs, NAV_CONFIG_PATH);
185
- const wanted = renderNavConfig(after);
212
+ // #6878 D6 — a finished site already has `content/en/Service` (it exists
213
+ // on ANY site that ever enabled the `services` surface), so read the
214
+ // SAME service slugs for the `wanted` render.
215
+ const serviceContentDir = join(targetAbs, "content", "en", "Service");
216
+ const serviceSlugs = existsSync(serviceContentDir)
217
+ ? readdirSync(serviceContentDir)
218
+ .filter((f) => f.endsWith(".mdx"))
219
+ .map((f) => f.slice(0, -".mdx".length))
220
+ : [];
221
+ const wanted = renderNavConfig(after, { serviceSlugs });
186
222
  const existing = existsSync(navPath)
187
223
  ? readFileSync(navPath, "utf8")
188
224
  : undefined;
189
225
  if (existing === wanted)
190
226
  return undefined;
191
- if (existing === undefined || existing === renderNavConfig(before)) {
227
+ if (existing === undefined || isUneditedNav(existing, before)) {
192
228
  writeFileSync(navPath, wanted);
193
229
  return { path: NAV_CONFIG_PATH, action: "written" };
194
230
  }
@@ -0,0 +1,56 @@
1
+ import { type InstallReportRow } from "./lib/extract-module-bundle.js";
2
+ import type { MarkerMap, WorkspaceVersionMap } from "./substitute.js";
3
+ import { type SyncRoutesResult } from "./sync-routes.js";
4
+ /** A refusal the CLI exits 2 on, thrown before anything is written. */
5
+ export declare class AddRouteRefusedError extends Error {
6
+ readonly exitCode = 2;
7
+ constructor(message: string);
8
+ }
9
+ export interface AddRouteOptions {
10
+ /** The already-scaffolded site. */
11
+ targetDir: string;
12
+ /** The route family to add — one `--surfaces` id, e.g. `pricing`. */
13
+ route: string;
14
+ /** Workspace version map, for the route copy. */
15
+ versions: WorkspaceVersionMap;
16
+ /** The scaffold's marker map (`buildScaffoldMarkers`). */
17
+ markers: MarkerMap;
18
+ /** The package's `templates/` dir — the route source and the overlay's seeds. */
19
+ templatesDir: string;
20
+ /** The package's `site-templates/` dir — the `_base` layer's seeds. */
21
+ siteTemplatesDir: string;
22
+ }
23
+ export interface AddRouteResult {
24
+ /** Resolved absolute path to the site. */
25
+ targetDir: string;
26
+ /** The canonical route id added. */
27
+ route: string;
28
+ /** The surface set `architecture.yaml` now declares. */
29
+ surfaces: string[];
30
+ /** One row per seed or nav written or skipped — empty when nothing changed. */
31
+ report: InstallReportRow[];
32
+ /** The route sync run over the surface — absent when the site already declares it. */
33
+ routes?: SyncRoutesResult;
34
+ }
35
+ /**
36
+ * Add one gateable route surface to an existing site. Every refusal — an
37
+ * unknown route, a route another module delivers, an unreadable header —
38
+ * throws before the first write.
39
+ */
40
+ export declare function addRouteToScaffold(opts: AddRouteOptions): AddRouteResult;
41
+ /** The report `add-route` prints. */
42
+ export declare function formatAddRouteReport(result: AddRouteResult): string;
43
+ export interface AddRouteArgs {
44
+ /** The route to add; undefined only with `help`. */
45
+ route: string | undefined;
46
+ /** Positional target directory; undefined = the caller's cwd. */
47
+ target: string | undefined;
48
+ help: boolean;
49
+ }
50
+ export declare const ADD_ROUTE_HELP = "create-ailk add-route \u2014 add a route family to an existing site.\n\nUsage:\n create-ailk add-route <route> [<target-dir>]\n\nArguments:\n <route> One route family, named as --surfaces names it,\n e.g. pricing.\n <target-dir> An already-scaffolded repo (contains\n architecture.yaml). Defaults to the current dir.\n\nOptions:\n --help, -h Print this message.\n\nCopies the route's pages, writes its seed content, declares it in\narchitecture.yaml and adds its link to the generated nav \u2014 the site a fresh\nscaffold with --surfaces <route> would have been. A file the site already has\nis never overwritten: one whose content differs, a hand-edited nav included,\nis left as-is and listed for manual review with the change the route wanted.\nLike add-module, it also restores any other page route the site carries but\nlacks. A route a module delivers (blog and docs come with docs-blog) is\nrefused; add that module with create-ailk add-module. Running it again changes\nnothing.\n";
51
+ /**
52
+ * Parse the argv AFTER the `add-route` word. Strict `node:util` parseArgs,
53
+ * like `parseAddModuleArgs`; the route itself is validated by
54
+ * `addRouteToScaffold`, which refuses an unknown one naming the recognized.
55
+ */
56
+ export declare function parseAddRouteArgs(argv: string[]): AddRouteArgs;
@@ -0,0 +1,286 @@
1
+ /**
2
+ * `create-ailk add-route <route> [<target-dir>]` — add a route family the
3
+ * site was scaffolded without, in place (#6947).
4
+ *
5
+ * A site gets only the route surfaces its shape and level declared at
6
+ * scaffold time: `sync-routes` skips a surface gated off then, and
7
+ * `add-module` adds only a paid module's surfaces. So nothing else can give
8
+ * a person-service site `/pricing`. This is `add-module`'s machinery minus the
9
+ * licence and the bundle:
10
+ *
11
+ * - `architecture.yaml`'s header is read, and the new surface resolved
12
+ * against the site's module set — a route another module delivers is
13
+ * refused, naming the module and `add-module`, before any write;
14
+ * - the surface's route dirs are synced in (`syncRoutes` with it forced
15
+ * carried), `architecture.yaml` is regenerated with it declared, and the
16
+ * nav follows unless the site has edited it (`syncNavConfig`);
17
+ * - the surface's seed content is written from the two shared layers a
18
+ * fresh scaffold keeps it from (the `templates/` overlay, then `_base`),
19
+ * never over a file the site already has.
20
+ *
21
+ * The result is the site a fresh scaffold with `--surfaces <route>` would
22
+ * have been. That scaffold adds no row to `.ailk-site-manifest.json` for a
23
+ * surface (the roster names only the template's own pages), so neither does
24
+ * this. A route the site already declares is a no-op: a second run changes
25
+ * nothing, even where the site has since deleted a seed.
26
+ *
27
+ * Pure module: no `import.meta.url`, so jest can import it directly — the
28
+ * package-rooted inputs are supplied by the CLI, the `add-module.ts` split.
29
+ */
30
+ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync, } from "node:fs";
31
+ import { dirname, join, resolve } from "node:path";
32
+ import { parseArgs } from "node:util";
33
+ import { AddModuleRefusedError, readArchitectureHeader, syncNavConfig, } from "./add-module.js";
34
+ import { isTextFile } from "./copy.js";
35
+ import { deriveArchitecture } from "./derive-architecture.js";
36
+ import { diffTwoStrings, } from "./lib/extract-module-bundle.js";
37
+ import { ParseError } from "./parse-args.js";
38
+ import { substituteMarkers } from "./substitute.js";
39
+ import { GATEABLE_SURFACES, MODULE_SURFACES, parseSurfaces, resolveSurfaces, } from "./surfaces.js";
40
+ import { syncRoutes } from "./sync-routes.js";
41
+ import { BASE_LAYER_DIR } from "./template-manifest.js";
42
+ /** A refusal the CLI exits 2 on, thrown before anything is written. */
43
+ export class AddRouteRefusedError extends Error {
44
+ exitCode = 2;
45
+ constructor(message) {
46
+ super(message);
47
+ this.name = "AddRouteRefusedError";
48
+ }
49
+ }
50
+ /**
51
+ * Add one gateable route surface to an existing site. Every refusal — an
52
+ * unknown route, a route another module delivers, an unreadable header —
53
+ * throws before the first write.
54
+ */
55
+ export function addRouteToScaffold(opts) {
56
+ const targetAbs = resolve(opts.targetDir);
57
+ if (!existsSync(targetAbs)) {
58
+ throw new Error(`add-route target does not exist: ${targetAbs}. Point add-route at an already-scaffolded repo.`);
59
+ }
60
+ if (!statSync(targetAbs).isDirectory()) {
61
+ throw new Error(`add-route target is not a directory: ${targetAbs}`);
62
+ }
63
+ const archPath = join(targetAbs, "architecture.yaml");
64
+ if (!existsSync(archPath)) {
65
+ throw new Error(`not a scaffolded repo: no architecture.yaml at ${targetAbs}. Run \`create-ailk <target-dir>\` to scaffold first.`);
66
+ }
67
+ const route = canonicalRoute(opts.route);
68
+ const owner = Object.entries(MODULE_SURFACES).find(([, surfaces]) => surfaces.includes(route))?.[0];
69
+ let current;
70
+ try {
71
+ current = readArchitectureHeader(readFileSync(archPath, "utf8"));
72
+ }
73
+ catch (err) {
74
+ if (err instanceof AddModuleRefusedError) {
75
+ throw new AddRouteRefusedError(err.message);
76
+ }
77
+ throw err;
78
+ }
79
+ if (owner !== undefined &&
80
+ !current.modules.includes(owner)) {
81
+ throw new AddRouteRefusedError(`route "${route}" is delivered by the "${owner}" module, which this site does not carry — add it with \`create-ailk add-module ${owner}\``);
82
+ }
83
+ let declared;
84
+ let surfaces;
85
+ try {
86
+ declared = resolveSurfaces(current.modules, current.surfaces);
87
+ surfaces = resolveSurfaces(current.modules, [...current.surfaces, route]);
88
+ }
89
+ catch (err) {
90
+ throw new AddRouteRefusedError(`architecture.yaml's \`# Surfaces:\` header cannot be carried forward: ${err.message}`);
91
+ }
92
+ if (declared.includes(route)) {
93
+ return { targetDir: targetAbs, route, surfaces, report: [] };
94
+ }
95
+ // add-module's sync: every in-scope route dir the site lacks is copied,
96
+ // not only this surface's — a fresh site lacks none but this one.
97
+ const routes = syncRoutes({
98
+ targetDir: targetAbs,
99
+ templatesDir: opts.templatesDir,
100
+ versions: opts.versions,
101
+ apply: true,
102
+ forceSurfaces: [route],
103
+ });
104
+ const report = seedSurfaceContent(targetAbs, route, opts);
105
+ const navRow = syncNavConfig(targetAbs, current.surfaces, surfaces);
106
+ if (navRow !== undefined)
107
+ report.push(navRow);
108
+ // Last: the declared header is what makes a rerun a no-op, so it is
109
+ // written only once every other step has landed.
110
+ writeFileSync(archPath, deriveArchitecture(current.modules, surfaces));
111
+ return { targetDir: targetAbs, route, surfaces, report, routes };
112
+ }
113
+ /** The canonical id for one route argument, refusing an unknown one. */
114
+ function canonicalRoute(raw) {
115
+ let parsed;
116
+ try {
117
+ parsed = parseSurfaces(raw);
118
+ }
119
+ catch (err) {
120
+ throw new AddRouteRefusedError(err.message.replace(/^unknown surface/, "unknown route"));
121
+ }
122
+ const [route] = parsed;
123
+ if (route === undefined) {
124
+ throw new AddRouteRefusedError(`"${raw}" is not a route add-route can add`);
125
+ }
126
+ return route;
127
+ }
128
+ /**
129
+ * Write the surface's seed content the way a fresh `--surfaces <route>`
130
+ * scaffold keeps it: every file under the surface's content dirs in the
131
+ * `templates/` overlay, then in the `_base` layer (a later layer wins, as in
132
+ * `scaffold()`), with the scaffold's markers. A file the site already has is
133
+ * never overwritten — when its bytes differ it is reported `skipped` with the
134
+ * change the seed wanted.
135
+ */
136
+ function seedSurfaceContent(targetAbs, route, opts) {
137
+ const dirs = (GATEABLE_SURFACES.find((s) => s.id === route)?.content ?? []).map((dir) => dir.replace("<locale>", "en"));
138
+ const sources = new Map();
139
+ for (const root of [
140
+ opts.templatesDir,
141
+ join(opts.siteTemplatesDir, BASE_LAYER_DIR),
142
+ ]) {
143
+ for (const dir of dirs)
144
+ collectFiles(root, dir, sources);
145
+ }
146
+ const report = [];
147
+ for (const [path, src] of [...sources].sort(([a], [b]) => a.localeCompare(b))) {
148
+ const isText = isTextFile(path);
149
+ const wanted = isText
150
+ ? Buffer.from(substituteMarkers(readFileSync(src, "utf8"), opts.markers))
151
+ : readFileSync(src);
152
+ const dest = join(targetAbs, path);
153
+ if (existsSync(dest)) {
154
+ const existing = readFileSync(dest);
155
+ if (existing.equals(wanted))
156
+ continue;
157
+ const encoding = isText ? "utf8" : "latin1";
158
+ report.push({
159
+ path,
160
+ action: "skipped",
161
+ wanted: diffTwoStrings(path, existing.toString(encoding), wanted.toString(encoding), isText),
162
+ });
163
+ continue;
164
+ }
165
+ mkdirSync(dirname(dest), { recursive: true });
166
+ writeFileSync(dest, wanted);
167
+ report.push({ path, action: "written" });
168
+ }
169
+ return report;
170
+ }
171
+ /** Every file under `root/dir`, recorded site-relative → absolute source. */
172
+ function collectFiles(root, dir, out) {
173
+ const abs = join(root, dir);
174
+ if (!existsSync(abs))
175
+ return;
176
+ for (const entry of readdirSync(abs, { withFileTypes: true })) {
177
+ const rel = `${dir}/${entry.name}`;
178
+ if (entry.isDirectory())
179
+ collectFiles(root, rel, out);
180
+ else if (entry.isFile() && entry.name !== ".gitkeep") {
181
+ out.set(rel, join(root, rel));
182
+ }
183
+ }
184
+ }
185
+ /** Indent a multi-line diff under its report row. */
186
+ function indent(text) {
187
+ return text.split("\n").map((line) => ` ${line}`);
188
+ }
189
+ /** The report `add-route` prints. */
190
+ export function formatAddRouteReport(result) {
191
+ const written = result.report.filter((row) => row.action === "written");
192
+ const skipped = result.report.filter((row) => row.action === "skipped");
193
+ const routes = result.routes ?? {
194
+ applied: [],
195
+ appliedDependencies: [],
196
+ missingMessageNamespaces: [],
197
+ };
198
+ const lines = [
199
+ `[create-ailk] add-route ${result.route} → ${result.targetDir}`,
200
+ ];
201
+ if (result.report.length === 0 && routes.applied.length === 0) {
202
+ lines.push(`Nothing to add — the site already carries the ${result.route} route.`);
203
+ }
204
+ if (routes.applied.length > 0) {
205
+ lines.push(`Routes copied (${routes.applied.length}):`);
206
+ lines.push(...routes.applied.map((route) => ` ${route}`));
207
+ }
208
+ if (routes.appliedDependencies.length > 0) {
209
+ lines.push(`Also copied ${routes.appliedDependencies.length} file(s) those routes import:`);
210
+ lines.push(...routes.appliedDependencies.map((dep) => ` ${dep}`));
211
+ }
212
+ if (written.length > 0) {
213
+ lines.push(`Written (${written.length}):`);
214
+ lines.push(...written.map((row) => ` ${row.path}`));
215
+ }
216
+ if (skipped.length > 0) {
217
+ lines.push(`Needs manual review (${skipped.length}) — the site's copy differs from the one this route brings and was left untouched. Apply the change by hand if you want it:`);
218
+ for (const row of skipped) {
219
+ lines.push(` ${row.path}`);
220
+ if (row.action === "skipped")
221
+ lines.push(...indent(row.wanted));
222
+ }
223
+ }
224
+ if (routes.missingMessageNamespaces.length > 0) {
225
+ lines.push("Also required — message namespace(s) not found in apps/web/messages/en.json (add manually, not auto-merged):");
226
+ lines.push(...routes.missingMessageNamespaces.map((ns) => ` ${ns}`));
227
+ }
228
+ lines.push(`architecture.yaml surfaces: ${result.surfaces.length > 0 ? result.surfaces.join(", ") : "core only"}`);
229
+ if (written.length > 0 || routes.applied.length > 0) {
230
+ lines.push("Next: pnpm build.");
231
+ }
232
+ return lines.join("\n") + "\n";
233
+ }
234
+ export const ADD_ROUTE_HELP = `create-ailk add-route — add a route family to an existing site.
235
+
236
+ Usage:
237
+ create-ailk add-route <route> [<target-dir>]
238
+
239
+ Arguments:
240
+ <route> One route family, named as --surfaces names it,
241
+ e.g. pricing.
242
+ <target-dir> An already-scaffolded repo (contains
243
+ architecture.yaml). Defaults to the current dir.
244
+
245
+ Options:
246
+ --help, -h Print this message.
247
+
248
+ Copies the route's pages, writes its seed content, declares it in
249
+ architecture.yaml and adds its link to the generated nav — the site a fresh
250
+ scaffold with --surfaces <route> would have been. A file the site already has
251
+ is never overwritten: one whose content differs, a hand-edited nav included,
252
+ is left as-is and listed for manual review with the change the route wanted.
253
+ Like add-module, it also restores any other page route the site carries but
254
+ lacks. A route a module delivers (blog and docs come with docs-blog) is
255
+ refused; add that module with create-ailk add-module. Running it again changes
256
+ nothing.
257
+ `;
258
+ /**
259
+ * Parse the argv AFTER the `add-route` word. Strict `node:util` parseArgs,
260
+ * like `parseAddModuleArgs`; the route itself is validated by
261
+ * `addRouteToScaffold`, which refuses an unknown one naming the recognized.
262
+ */
263
+ export function parseAddRouteArgs(argv) {
264
+ const parsed = parseArgs({
265
+ args: argv,
266
+ strict: true,
267
+ allowPositionals: true,
268
+ options: {
269
+ help: { type: "boolean", default: false, short: "h" },
270
+ },
271
+ });
272
+ const help = parsed.values.help === true;
273
+ const [route, target, ...rest] = parsed.positionals;
274
+ if (rest.length > 0) {
275
+ throw new ParseError(`expected a route and at most one target directory, got ${parsed.positionals.length} arguments`);
276
+ }
277
+ if (route === undefined) {
278
+ if (help)
279
+ return { route: undefined, target: undefined, help };
280
+ throw new ParseError("add-route needs a route name, e.g. `create-ailk add-route pricing`");
281
+ }
282
+ if (route.includes(",")) {
283
+ throw new ParseError(`add-route takes exactly one route, got "${route}" — run it once per route`);
284
+ }
285
+ return { route, target, help };
286
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The text `<siteDir>/DESIGN.md` should hold to wear the design in
3
+ * `designDir`: the design's DESIGN.md under the site's own header lines. A
4
+ * header line the site carries and the design does not goes in a comment
5
+ * block after the frontmatter.
6
+ */
7
+ export declare function planDesignMd(designDir: string, siteDir: string): string;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * A site's root `DESIGN.md` is the design it wears (#6939). `scaffold()` and
3
+ * `apply-design` both replace it with the resolved design's own DESIGN.md.
4
+ *
5
+ * Every shipped copy of a design's DESIGN.md differs only in its "Source
6
+ * template" line (and its `{{CREATE_AILK_VERSION}}` stamp), so the site keeps
7
+ * its own copy of those two header lines: upgrade-plan's `readSite` reads the
8
+ * site's template from "Source template", and "Version" records the
9
+ * create-ailk that generated the site. The design's own values never stand in
10
+ * for the site's: a "Source template" line the site lacks is dropped (the
11
+ * design's `designs/<name>` is not a template), and a "Version" line is
12
+ * stamped with this create-ailk.
13
+ */
14
+ import { existsSync, readFileSync } from "node:fs";
15
+ import { join } from "node:path";
16
+ import pkg from "../package.json" with { type: "json" };
17
+ /** The two header lines a site's DESIGN.md keeps across a design change. */
18
+ const SOURCE_TEMPLATE_LINE = /^[ \t]*Source template:.*$/m;
19
+ const VERSION_LINE = /^[ \t]*Version:.*$/m;
20
+ /**
21
+ * The text `<siteDir>/DESIGN.md` should hold to wear the design in
22
+ * `designDir`: the design's DESIGN.md under the site's own header lines. A
23
+ * header line the site carries and the design does not goes in a comment
24
+ * block after the frontmatter.
25
+ */
26
+ export function planDesignMd(designDir, siteDir) {
27
+ const sitePath = join(siteDir, "DESIGN.md");
28
+ const current = existsSync(sitePath) ? readFileSync(sitePath, "utf8") : "";
29
+ let next = readFileSync(join(designDir, "DESIGN.md"), "utf8").replaceAll("{{CREATE_AILK_VERSION}}", pkg.version);
30
+ const missing = [];
31
+ for (const line of [SOURCE_TEMPLATE_LINE, VERSION_LINE]) {
32
+ const kept = line.exec(current)?.[0];
33
+ if (kept === undefined) {
34
+ if (line === SOURCE_TEMPLATE_LINE)
35
+ next = next.replace(/^[ \t]*Source template:.*\n/m, "");
36
+ }
37
+ else if (line.test(next)) {
38
+ next = next.replace(line, () => kept);
39
+ }
40
+ else {
41
+ missing.push(kept);
42
+ }
43
+ }
44
+ if (missing.length === 0)
45
+ return next;
46
+ const close = next.startsWith("---\n") ? next.indexOf("\n---\n", 4) : -1;
47
+ const at = close === -1 ? 0 : close + "\n---\n".length;
48
+ const block = `\n<!--\n${missing.join("\n")}\n-->\n`;
49
+ return `${next.slice(0, at)}${block}${next.slice(at)}`;
50
+ }
@@ -0,0 +1,64 @@
1
+ export interface ApplyDesignOptions {
2
+ /** The design: a shipped name, or a directory holding a DESIGN.md. */
3
+ design: string;
4
+ /** The existing site (absolute or relative). */
5
+ targetDir: string;
6
+ /** Package-rooted inputs, supplied by the caller (cli.ts from its PKG_ROOT). */
7
+ designsDir: string;
8
+ siteTemplatesDir: string;
9
+ catalogPath: string;
10
+ /** Where a relative design path resolves from. Default process.cwd(). */
11
+ cwd?: string;
12
+ /** Read for AILK_LICENSE_KEY only (D6). Default process.env. */
13
+ env?: NodeJS.ProcessEnv;
14
+ /** Forwarded to deriveLevel. Tests inject; the CLI leaves them unset. */
15
+ registryUrl?: string;
16
+ fetchImpl?: typeof fetch;
17
+ }
18
+ export type ApplyDesignChange = {
19
+ path: string;
20
+ kind: "theme";
21
+ } | {
22
+ path: string;
23
+ kind: "layout";
24
+ } | {
25
+ path: string;
26
+ kind: "design";
27
+ } | {
28
+ path: string;
29
+ kind: "preset";
30
+ fields: string[];
31
+ } | {
32
+ path: string;
33
+ kind: "manifest";
34
+ };
35
+ export interface ApplyDesignResult {
36
+ targetDir: string;
37
+ design: string;
38
+ display: string;
39
+ /** In write order (D4); [] when nothing changed. */
40
+ changes: ApplyDesignChange[];
41
+ manifest: "recorded" | "unchanged" | "no-roster" | "legacy-roster";
42
+ /** The site-relative roster file, when there is one. */
43
+ rosterPath?: string;
44
+ }
45
+ export interface ApplyDesignArgs {
46
+ design: string | undefined;
47
+ target: string | undefined;
48
+ help: boolean;
49
+ }
50
+ export declare const APPLY_DESIGN_HELP = "create-ailk apply-design \u2014 make an existing site wear a design.\n\nUsage:\n create-ailk apply-design <name> [--target <site-dir>]\n create-ailk apply-design <path> [--target <site-dir>]\n\nArguments:\n <name|path> A shipped design (standard, minimalism), or a\n directory holding a DESIGN.md. A relative path\n resolves from the current directory.\n\nOptions:\n --target <site-dir> An already-scaffolded repo (contains\n architecture.yaml). Defaults to the current dir.\n --help, -h Print this message.\n\nWrites only the look: DESIGN.md (replaced whole by the design's, keeping the\nsite's Source template and Version lines), the theme: block of\ncontent/_site.mdx, the generated apps/web/lib/site-chrome.ts, the look fields\nof sections the site's pages already place, and the design/layout/theme record\nin .ailk-site-manifest.json.\nNever adds, removes or renames a route or page, never touches a content field\nor the brand: block, and leaves every page's structured data unchanged.\n\nA design other than standard needs a Pro or Pro Plus key: the site's own\nailk.key (put one there with create-ailk license set <key>), else\nAILK_LICENSE_KEY. A malformed package, a preset the validator refuses or a\nmissing key is refused before any file is written.\n\nA preset sets the look fields it names and never unsets one: a field it does\nnot name keeps whatever value the page has. Running it again changes nothing.\n";
51
+ /**
52
+ * Parse the argv AFTER the `apply-design` word (D1). Strict `node:util`
53
+ * parseArgs, like `parseSyncRoutesArgs`: exactly one positional, the design
54
+ * reference, and an optional `--target`.
55
+ */
56
+ export declare function parseApplyDesignArgs(argv: string[]): ApplyDesignArgs;
57
+ /**
58
+ * Apply a design to an existing site (D2–D7). Refuses before any write
59
+ * (C1–C6); otherwise writes only the files whose text changes — page files,
60
+ * `site-chrome.ts`, `DESIGN.md`, `_site.mdx`, then the manifest last.
61
+ */
62
+ export declare function applyDesign(opts: ApplyDesignOptions): Promise<ApplyDesignResult>;
63
+ /** The changed-file report (D9). */
64
+ export declare function formatApplyDesignReport(result: ApplyDesignResult): string;