create-ailk 0.6.0 → 0.7.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 (248) hide show
  1. package/README.md +44 -5
  2. package/component-catalog.json +146 -21
  3. package/component-catalog.md +9 -6
  4. package/dist/add-module-core.d.ts +22 -0
  5. package/dist/add-module-core.js +50 -0
  6. package/dist/add-module.d.ts +89 -0
  7. package/dist/add-module.js +334 -0
  8. package/dist/category-profile.d.ts +126 -0
  9. package/dist/category-profile.js +262 -0
  10. package/dist/cli.js +124 -13
  11. package/dist/copy.d.ts +14 -0
  12. package/dist/copy.js +38 -1
  13. package/dist/index.d.ts +4 -2
  14. package/dist/index.js +6 -1
  15. package/dist/lib/extract-module-bundle.d.ts +76 -2
  16. package/dist/lib/extract-module-bundle.js +219 -8
  17. package/dist/lib/license-key-files.d.ts +15 -0
  18. package/dist/lib/license-key-files.js +102 -0
  19. package/dist/lib/module-license-gate.d.ts +6 -1
  20. package/dist/lib/module-license-gate.js +6 -1
  21. package/dist/lib/pro-callout.d.ts +16 -10
  22. package/dist/lib/pro-callout.js +27 -4
  23. package/dist/license-set.d.ts +34 -0
  24. package/dist/license-set.js +194 -0
  25. package/dist/module-architecture.d.ts +20 -0
  26. package/dist/module-architecture.js +77 -19
  27. package/dist/modules/commerce/0.6.0.tar.gz +0 -0
  28. package/dist/modules/commerce/0.6.1.tar.gz +0 -0
  29. package/dist/modules/docs-blog/0.6.0.tar.gz +0 -0
  30. package/dist/modules/docs-blog/0.6.1.tar.gz +0 -0
  31. package/dist/modules/marketing-site/0.6.1.tar.gz +0 -0
  32. package/dist/modules/minimalism-site/0.6.1.tar.gz +0 -0
  33. package/dist/modules/multi-tenant/0.6.0.tar.gz +0 -0
  34. package/dist/modules/multi-tenant/0.6.1.tar.gz +0 -0
  35. package/dist/modules/saas-product/0.6.1.tar.gz +0 -0
  36. package/dist/modules/scheduling/0.6.0.tar.gz +0 -0
  37. package/dist/modules/scheduling/0.6.1.tar.gz +0 -0
  38. package/dist/modules/signal-collectors/0.6.0.tar.gz +0 -0
  39. package/dist/modules/signal-collectors/0.6.1.tar.gz +0 -0
  40. package/dist/modules/simple-service/0.6.1.tar.gz +0 -0
  41. package/dist/modules/workspace/0.6.0.tar.gz +0 -0
  42. package/dist/modules/workspace/0.6.1.tar.gz +0 -0
  43. package/dist/parse-args.d.ts +7 -0
  44. package/dist/parse-args.js +23 -1
  45. package/dist/programmatic.d.ts +46 -0
  46. package/dist/programmatic.js +144 -19
  47. package/dist/site-chrome.d.ts +18 -7
  48. package/dist/site-chrome.js +32 -14
  49. package/dist/site-roster.d.ts +127 -0
  50. package/dist/site-roster.js +494 -0
  51. package/dist/surfaces.d.ts +20 -7
  52. package/dist/surfaces.js +149 -28
  53. package/dist/sync-routes.d.ts +9 -0
  54. package/dist/sync-routes.js +2 -2
  55. package/dist/template-manifest.d.ts +58 -2
  56. package/dist/template-manifest.js +196 -69
  57. package/package.json +6 -4
  58. package/site-templates/_base/content/en/BookCall/book-call.mdx +53 -0
  59. package/site-templates/_base/content/en/GlossaryList/glossary.mdx +66 -0
  60. package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +68 -0
  61. package/site-templates/_base/content/en/Pricing/pricing.mdx +71 -62
  62. package/site-templates/_base/content/en/PrivacyPage/privacy.mdx +2 -3
  63. package/site-templates/_base/content/en/TermsPage/terms.mdx +2 -3
  64. package/site-templates/_base/content/en/UseCase/first-use-case.mdx +23 -0
  65. package/site-templates/_base/content/en/UseCase/second-use-case.mdx +23 -0
  66. package/site-templates/_base/content/en/UseCase/third-use-case.mdx +23 -0
  67. package/site-templates/designs/minimalism/DESIGN.md +162 -0
  68. package/site-templates/designs/standard/DESIGN.md +162 -0
  69. package/site-templates/marketing-site/DESIGN.md +162 -0
  70. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +312 -57
  71. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +209 -90
  72. package/site-templates/marketing-site/ailk-template.json +1 -0
  73. package/site-templates/marketing-site-free/DESIGN.md +162 -0
  74. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +260 -66
  75. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +294 -114
  76. package/site-templates/marketing-site-free/ailk-template.json +3 -2
  77. package/site-templates/minimalism-site/DESIGN.md +162 -0
  78. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +246 -19
  79. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +185 -51
  80. package/site-templates/minimalism-site/ailk-template.json +3 -2
  81. package/site-templates/minimalism-site/content/en/BookCall/book-call.mdx +53 -0
  82. package/site-templates/minimalism-site/content/en/ContactPage/contact.mdx +3 -37
  83. package/site-templates/minimalism-site/content/en/PurchasePolicyPage/purchase-policy.mdx +124 -0
  84. package/site-templates/saas-product/DESIGN.md +162 -0
  85. package/site-templates/simple-service/DESIGN.md +162 -0
  86. package/site-templates/simple-service/TEMPLATE-MANIFEST.json +135 -26
  87. package/site-templates/simple-service/TEMPLATE-MANIFEST.md +114 -59
  88. package/site-templates/simple-service/ailk-template.json +4 -3
  89. package/site-templates/simple-service/content/en/BookCall/book-call.mdx +53 -0
  90. package/site-templates/simple-service/content/en/ContactPage/contact.mdx +1 -36
  91. package/site-templates/simple-service/content/en/PurchasePolicyPage/purchase-policy.mdx +124 -0
  92. package/templates/.claude/settings.json +15 -0
  93. package/templates/.claude/skills/README.md +8 -8
  94. package/templates/.claude/skills/{scaffold-auth → add-auth}/SKILL.md +7 -7
  95. package/templates/.claude/skills/{scaffold-auth → add-auth}/references/env-vars.md +2 -2
  96. package/templates/.claude/skills/{scaffold-auth → add-auth}/references/oauth-app-setup.md +3 -3
  97. package/templates/.claude/skills/{scaffold-page → add-page}/SKILL.md +76 -62
  98. package/templates/.claude/skills/add-page/references/page-types.md +111 -0
  99. package/templates/.claude/skills/{scaffold-page → add-page}/templates/content.mdx.tmpl +4 -4
  100. package/templates/.claude/skills/{scaffold-page → add-page}/templates/route.tsx.tmpl +1 -1
  101. package/templates/.claude/skills/{audit-site → check-site}/SKILL.md +16 -17
  102. package/templates/.claude/skills/{audit-site → check-site}/references/common-errors.md +3 -3
  103. package/templates/.claude/skills/{audit-site → check-site}/references/findings-glossary.md +16 -14
  104. package/templates/.claude/skills/{audit-site → check-site}/references/severity-levels.md +1 -1
  105. package/templates/.claude/skills/fill-page/SKILL.md +180 -0
  106. package/templates/.claude/skills/fill-page/profiles/plumbing.yaml +80 -0
  107. package/templates/.claude/skills/fill-page/references/section-sources.md +41 -0
  108. package/templates/.claude/skills/{provision-config → setup-deploy}/SKILL.md +8 -8
  109. package/templates/.claude/skills/{provision-config → setup-deploy}/references/env-vars-template.md +2 -2
  110. package/templates/.claude/skills/{provision-config → setup-deploy}/references/runbook-template.md +8 -8
  111. package/templates/.claude/skills/{provision-config → setup-deploy}/references/vercel-json-template.md +1 -1
  112. package/templates/.claude/skills/upgrade-plan/SKILL.md +117 -0
  113. package/templates/.claude/skills/upgrade-plan/references/options.md +24 -0
  114. package/templates/.claude/skills/upgrade-plan/scripts/upgrade-plan.ts +527 -0
  115. package/templates/.github/workflows/ci.yml +5 -0
  116. package/templates/CLAUDE.md +10 -5
  117. package/templates/DESIGN.md +162 -0
  118. package/templates/README.md +134 -38
  119. package/templates/apps/api/.env.example +2 -1
  120. package/templates/apps/api/package.json +1 -1
  121. package/templates/apps/api/src/config/__tests__/index.test.ts +37 -2
  122. package/templates/apps/api/src/config/index.ts +19 -6
  123. package/templates/apps/api/src/lib/license-key-store.ts +45 -0
  124. package/templates/apps/api/src/lib/site.ts +5 -3
  125. package/templates/apps/api/src/middleware/__tests__/auth.database-not-configured.test.ts +97 -0
  126. package/templates/apps/api/src/middleware/auth.ts +9 -0
  127. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +127 -5
  128. package/templates/apps/api/src/routes/content/get-page.ts +86 -2
  129. package/templates/apps/api/src/server.ts +6 -0
  130. package/templates/apps/mcp/__tests__/audit-single-page.test.ts +75 -1
  131. package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +57 -9
  132. package/templates/apps/mcp/package.json +2 -1
  133. package/templates/apps/mcp/src/index.ts +7 -0
  134. package/templates/apps/mcp/src/lib/audit-single-page.ts +36 -10
  135. package/templates/apps/mcp/src/lib/license-key-store.ts +45 -0
  136. package/templates/apps/web/PRODUCT.md +86 -0
  137. package/templates/apps/web/__tests__/setup.ts +18 -0
  138. package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +10 -2
  139. package/templates/apps/web/app/[locale]/(auth)/layout.tsx +2 -2
  140. package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +10 -4
  141. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +22 -14
  142. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +26 -18
  143. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +12 -4
  144. package/templates/apps/web/app/[locale]/[slug]/__tests__/page.test.tsx +35 -4
  145. package/templates/apps/web/app/[locale]/[slug]/page.tsx +49 -9
  146. package/templates/apps/web/app/[locale]/book-a-call/page.tsx +50 -0
  147. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +3 -3
  148. package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +3 -3
  149. package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +3 -4
  150. package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +94 -33
  151. package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +53 -23
  152. package/templates/apps/web/app/[locale]/contact/__tests__/ContactForm.test.tsx +96 -0
  153. package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +84 -2
  154. package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +5 -6
  155. package/templates/apps/web/app/[locale]/contact/page.tsx +25 -7
  156. package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +3 -3
  157. package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +3 -4
  158. package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +4 -5
  159. package/templates/apps/web/app/[locale]/glossary/page.tsx +50 -0
  160. package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
  161. package/templates/apps/web/app/[locale]/how-to/page.tsx +25 -15
  162. package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +3 -3
  163. package/templates/apps/web/app/[locale]/layout.tsx +113 -51
  164. package/templates/apps/web/app/[locale]/newsletter/page.tsx +50 -0
  165. package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +3 -3
  166. package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +3 -3
  167. package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +4 -5
  168. package/templates/apps/web/app/[locale]/{shipping → purchase-policy}/page.tsx +15 -10
  169. package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +4 -5
  170. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +3 -3
  171. package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +14 -1
  172. package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
  173. package/templates/apps/web/app/[locale]/software/page.tsx +25 -15
  174. package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
  175. package/templates/apps/web/app/[locale]/tech-articles/page.tsx +25 -15
  176. package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +3 -4
  177. package/templates/apps/web/app/[locale]/use-cases/[slug]/page.tsx +48 -0
  178. package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +3 -4
  179. package/templates/apps/web/app/[locale]/use-cases/page.tsx +15 -2
  180. package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
  181. package/templates/apps/web/app/[locale]/whitepapers/page.tsx +25 -15
  182. package/templates/apps/web/app/error-fallback.tsx +17 -45
  183. package/templates/apps/web/app/not-found.tsx +14 -38
  184. package/templates/apps/web/app/og/route.tsx +6 -0
  185. package/templates/apps/web/eslint.config.mjs +3 -0
  186. package/templates/apps/web/instrumentation.ts +14 -0
  187. package/templates/apps/web/lib/__tests__/site-theme.test.ts +135 -20
  188. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +38 -0
  189. package/templates/apps/web/lib/license-key-store.ts +52 -0
  190. package/templates/apps/web/lib/nav-config.ts +64 -12
  191. package/templates/apps/web/lib/prisma-for-auth.ts +9 -6
  192. package/templates/apps/web/lib/site-brand.tsx +4 -8
  193. package/templates/apps/web/lib/site-chrome.ts +3 -3
  194. package/templates/apps/web/lib/site-identity.ts +134 -0
  195. package/templates/apps/web/lib/site-theme.ts +11 -0
  196. package/templates/apps/web/messages/en.json +52 -37
  197. package/templates/apps/web/middleware.ts +1 -1
  198. package/templates/apps/web/public/android-chrome-192x192.png +0 -0
  199. package/templates/apps/web/public/android-chrome-512x512.png +0 -0
  200. package/templates/apps/web/public/apple-touch-icon.png +0 -0
  201. package/templates/apps/web/public/favicon-16x16.png +0 -0
  202. package/templates/apps/web/public/favicon-32x32.png +0 -0
  203. package/templates/apps/web/public/favicon.ico +0 -0
  204. package/templates/apps/web/public/favicon.svg +3 -3
  205. package/templates/apps/web/public/logomark.svg +6 -3
  206. package/templates/apps/web/public/logos/ai-launch-kit-light.svg +4 -0
  207. package/templates/apps/web/public/logos/ai-launch-kit.svg +4 -0
  208. package/templates/apps/web/public/logos/atomic-agent-light.svg +4 -0
  209. package/templates/apps/web/public/logos/atomic-agent.svg +4 -0
  210. package/templates/apps/web/public/logos/salessmyth-light.svg +4 -0
  211. package/templates/apps/web/public/logos/salessmyth.svg +4 -0
  212. package/templates/apps/web/public/site.webmanifest +4 -4
  213. package/templates/apps/web/public/wordmark-light.svg +4 -0
  214. package/templates/apps/web/public/wordmark.svg +3 -3
  215. package/templates/apps/web/security-headers.config.ts +5 -1
  216. package/templates/apps/web/src/lib/commerce-sections.ts +35 -3
  217. package/templates/content/_site.mdx +41 -3
  218. package/templates/content/en/AboutPage/about.mdx +150 -144
  219. package/templates/content/en/BlogList/blog.mdx +105 -0
  220. package/templates/content/en/ContactPage/contact.mdx +44 -85
  221. package/templates/content/en/FAQPage/faq.mdx +86 -57
  222. package/templates/content/en/HomePage/home.mdx +276 -217
  223. package/templates/content/en/PurchasePolicyPage/purchase-policy.mdx +182 -0
  224. package/templates/content/en/SoftwareProduct/product.mdx +175 -0
  225. package/templates/content/en/UseCasesList/use-cases.mdx +170 -0
  226. package/templates/database/CHANGELOG.md +14 -0
  227. package/templates/database/package.json +3 -2
  228. package/templates/package.json +3 -1
  229. package/templates/tools/lint-architecture.ts +315 -0
  230. package/templates/tools/tsconfig.json +14 -0
  231. package/versions.json +33 -33
  232. package/templates/.claude/skills/add-schema/SKILL.md +0 -277
  233. package/templates/.claude/skills/add-schema/references/conflict-handling.md +0 -200
  234. package/templates/.claude/skills/add-schema/references/file-finding.md +0 -123
  235. package/templates/.claude/skills/add-schema/references/schema-types.md +0 -234
  236. package/templates/.claude/skills/scaffold-page/references/page-types.md +0 -169
  237. package/templates/.claude/skills/suggest-site-pages/SKILL.md +0 -242
  238. package/templates/.claude/skills/suggest-site-pages/references/categories.md +0 -287
  239. package/templates/apps/web/app/[locale]/cancellation/page.tsx +0 -44
  240. package/templates/apps/web/app/[locale]/returns/page.tsx +0 -44
  241. package/templates/apps/web/public/logo-dark.svg +0 -21
  242. package/templates/apps/web/public/logo-light.svg +0 -21
  243. package/templates/content/en/CancellationPolicyPage/cancellation.mdx +0 -108
  244. package/templates/content/en/LandingPage/blog.mdx +0 -83
  245. package/templates/content/en/LandingPage/features.mdx +0 -103
  246. package/templates/content/en/LandingPage/how-it-works.mdx +0 -143
  247. package/templates/content/en/ReturnsPolicyPage/returns.mdx +0 -108
  248. package/templates/content/en/ShippingPolicyPage/shipping.mdx +0 -108
@@ -15,21 +15,23 @@
15
15
  * import { scaffoldSync } from './programmatic.js';
16
16
  * const result = scaffoldSync({ targetDir: '/tmp/my-site', projectName: 'my-site' });
17
17
  */
18
- import { existsSync, readdirSync, readFileSync, statSync, writeFileSync, } from "node:fs";
19
- import { basename, dirname, join, resolve } from "node:path";
18
+ import { existsSync, readdirSync, readFileSync, rmdirSync, rmSync, statSync, writeFileSync, } from "node:fs";
19
+ import { basename, dirname, join, relative, resolve, sep } from "node:path";
20
20
  import { fileURLToPath } from "node:url";
21
+ import { AddModuleRefusedError, applyModuleToScaffold, } from "./add-module.js";
21
22
  import { CATALOG_TARGET_PATH, writeComponentCatalog, } from "./component-catalog.js";
22
- import { copyTemplate } from "./copy.js";
23
+ import { buildScaffoldMarkers, copyTemplate } from "./copy.js";
23
24
  import { deriveArchitecture } from "./derive-architecture.js";
24
25
  import { extractModuleBundle, installBundleTree, } from "./lib/extract-module-bundle.js";
25
26
  import { fetchTemplate } from "./lib/fetch-template.js";
27
+ import { assertLicenseKeyShape, writeLicenseKeyFiles, } from "./lib/license-key-files.js";
26
28
  import { resolvePaidModules } from "./lib/module-license-gate.js";
27
29
  import { classifyModules } from "./lib/module-tier.js";
28
30
  import { normalizeModules } from "./module-architecture.js";
29
31
  import { SITE_CHROME_PATH, renderSiteChrome, resolveSiteChrome, } from "./site-chrome.js";
30
32
  import { GATEABLE_SURFACES, MODULE_SURFACES, NAV_CONFIG_PATH, excludedRoutePaths, renderNavConfig, resolveSurfaces, } from "./surfaces.js";
31
33
  import { syncRoutes } from "./sync-routes.js";
32
- import { BASE_LAYER_DIR, mergeRoster, resolveTemplate, resolveTemplateLineage, templateDelivery, } from "./template-manifest.js";
34
+ import { BASE_LAYER_DIR, STARTER_DESIGNS, TemplateError, mergeRoster, resolveTemplate, resolveTemplateLineage, rosterDeclares, templateDelivery, } from "./template-manifest.js";
33
35
  import { validateTarget } from "./validate.js";
34
36
  const PKG_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
35
37
  const TEMPLATES_DIR = resolve(PKG_ROOT, "templates");
@@ -92,15 +94,7 @@ export function scaffoldSync(opts) {
92
94
  targetDir: targetAbs,
93
95
  versions,
94
96
  excludePaths: excludedRoutePaths(enabledSurfaces),
95
- markers: {
96
- PROJECT_NAME: projectName,
97
- COMPANY_NAME: "Your Company",
98
- COMPANY_DOMAIN: "example.com",
99
- // The social-profile handle the Free chrome's footer links carry
100
- // (`https://x.com/{{COMPANY_HANDLE}}`, #5469 D5) — a placeholder the
101
- // site owner replaces, never the kit vendor's own handle.
102
- COMPANY_HANDLE: "yourcompany",
103
- },
97
+ markers: buildScaffoldMarkers(projectName),
104
98
  });
105
99
  // Derive + write the module-set-driven architecture.yaml (spec #3787). This
106
100
  // is the single source of the scaffolded site's code-boundary config and
@@ -118,7 +112,7 @@ export function scaffoldSync(opts) {
118
112
  // template's `layout` block; a bare `scaffoldSync` call (every existing
119
113
  // synchronous caller) writes DEFAULT_SITE_CHROME, byte-identical to the
120
114
  // monorepo's own committed apps/web/lib/site-chrome.ts.
121
- writeFileSync(join(targetAbs, SITE_CHROME_PATH), renderSiteChrome(resolveSiteChrome(opts.layout)));
115
+ writeFileSync(join(targetAbs, SITE_CHROME_PATH), renderSiteChrome(resolveSiteChrome(opts.layout, opts.design)));
122
116
  // Opt-in only (#4636). Every write above this line is unconditional, so a
123
117
  // scaffold with `withCatalog` unset touches exactly the files it did before.
124
118
  if (opts.withCatalog === true)
@@ -154,10 +148,21 @@ export function scaffoldSync(opts) {
154
148
  * with a paid module in `opts.modules` exercises the gate at all.
155
149
  */
156
150
  export async function scaffold(opts) {
151
+ // #6683 D9 — refused before anything is fetched or written, like the
152
+ // template and design refusals below. The message never repeats the value.
153
+ if (opts.license !== undefined)
154
+ assertLicenseKeyShape(opts.license);
155
+ const licenseKey = opts.license ?? opts.licenseKey;
157
156
  // Resolved first, and by the registry rather than re-validated here: the
158
157
  // name already passed `parse-args.ts`, and `resolveTemplate` throws a
159
158
  // `TemplateError` for a programmatic caller that skipped the parser.
160
159
  const template = opts.template !== undefined ? resolveTemplate(opts.template) : undefined;
160
+ // #6638 — the design this scaffold renders: the caller's override, else the
161
+ // template's own. Unset for a scaffold naming neither.
162
+ const design = opts.design ?? template?.design;
163
+ if (design !== undefined && !STARTER_DESIGNS.includes(design)) {
164
+ throw new TemplateError(`unknown design "${design}". Declared: ${STARTER_DESIGNS.join(", ")}`);
165
+ }
161
166
  // #5618 D2 — the `extends` chain is resolved in the client registry before
162
167
  // any fetch: an unknown parent or a cycle is refused here, with the target
163
168
  // still non-existent, like the four refusals below.
@@ -176,7 +181,7 @@ export async function scaffold(opts) {
176
181
  const { paid } = classifyModules(modules);
177
182
  const bundles = await resolvePaidModules({
178
183
  paidModules: paid,
179
- licenseKey: opts.licenseKey,
184
+ licenseKey,
180
185
  ...(template !== undefined ? { templateName: template.name } : {}),
181
186
  ...(opts.moduleRegistryUrl !== undefined
182
187
  ? { registryUrl: opts.moduleRegistryUrl }
@@ -197,7 +202,7 @@ export async function scaffold(opts) {
197
202
  const templateBundle = template !== undefined && templateDelivery(template) === "paid"
198
203
  ? await fetchTemplate({
199
204
  templateName: template.name,
200
- token: opts.licenseKey ?? process.env["AILK_LICENSE_KEY"] ?? "",
205
+ token: licenseKey ?? process.env["AILK_LICENSE_KEY"] ?? "",
201
206
  ...(opts.templateRegistryUrl !== undefined
202
207
  ? { registryUrl: opts.templateRegistryUrl }
203
208
  : {}),
@@ -232,15 +237,25 @@ export async function scaffold(opts) {
232
237
  ...(surfaces !== undefined ? { surfaces } : {}),
233
238
  // #5612 D1/D2 — the code registry's own `layout` block, known
234
239
  // synchronously (unlike a bundle's `ailk-template.json`, only readable
235
- // after install below). A template declaring none resolves to the same
236
- // defaults `scaffoldSync` already writes for a templateless scaffold.
240
+ // after install below). A template declaring none resolves to its
241
+ // design's knobs (#6638), and a `standard` one to the same defaults
242
+ // `scaffoldSync` already writes for a templateless scaffold.
237
243
  ...(template?.layout !== undefined ? { layout: template.layout } : {}),
244
+ ...(design !== undefined ? { design } : {}),
238
245
  });
239
246
  // Threaded to extractModuleBundle for `package.json` patch targets'
240
247
  // `workspace:*` substitution (spec 5418 § 3 D2) — the same version map
241
248
  // `scaffoldSync` already reads for `copyTemplate`, re-read here since
242
249
  // `scaffold()` doesn't share `scaffoldSync`'s call frame.
243
250
  const versions = readVersionMap();
251
+ // #6460 — the SAME marker map `scaffoldSync` just substituted the base
252
+ // `templates/` copy with, rebuilt here from the same inputs (`scaffold()`
253
+ // does not share `scaffoldSync`'s call frame, exactly as `versions` above
254
+ // does not) and threaded into every layer that lands on top of it. Without
255
+ // it a `{{MARKER}}` authored in a base-layer or site-template file would
256
+ // survive into the scaffold verbatim — the state before this card, and the
257
+ // reason a template file could not carry the version stamp.
258
+ const markers = buildScaffoldMarkers(opts.projectName ?? basename(result.targetDir));
244
259
  // #5618 D4 — the file-grain ownership registry for THIS scaffold: which
245
260
  // layer wrote which destination. Call-scoped (a fresh Map per scaffold(),
246
261
  // never module-level), threaded through every layer install below in the
@@ -267,6 +282,7 @@ export async function scaffold(opts) {
267
282
  name: BASE_LAYER_DIR,
268
283
  templateRequires: [],
269
284
  versions,
285
+ markers,
270
286
  ownership,
271
287
  });
272
288
  }
@@ -276,6 +292,7 @@ export async function scaffold(opts) {
276
292
  targetDir: result.targetDir,
277
293
  moduleName,
278
294
  versions,
295
+ markers,
279
296
  ownership,
280
297
  });
281
298
  }
@@ -305,6 +322,7 @@ export async function scaffold(opts) {
305
322
  templateRequires: template.requires,
306
323
  templateExtends: template.extends,
307
324
  versions,
325
+ markers,
308
326
  ownership,
309
327
  })
310
328
  : (() => {
@@ -319,15 +337,71 @@ export async function scaffold(opts) {
319
337
  templateRequires: template.requires,
320
338
  templateExtends: template.extends,
321
339
  versions,
340
+ markers,
322
341
  ownership,
323
342
  });
324
343
  })();
325
344
  if (installed.layout !== undefined) {
326
- writeFileSync(join(result.targetDir, SITE_CHROME_PATH), renderSiteChrome(resolveSiteChrome(installed.layout)));
345
+ writeFileSync(join(result.targetDir, SITE_CHROME_PATH), renderSiteChrome(resolveSiteChrome(installed.layout, design)));
327
346
  }
347
+ pruneUnrosteredSeeds(result.targetDir, mergeRoster(template, template.requires), result.surfaces, ownership);
348
+ }
349
+ // #6683 D9 — persisted only after every layer landed, and only when the
350
+ // caller passed `license`: a scaffold without it writes nothing new.
351
+ if (opts.license !== undefined) {
352
+ writeLicenseKeyFiles(result.targetDir, opts.license);
328
353
  }
329
354
  return result;
330
355
  }
356
+ /**
357
+ * The per-row install of the two SHARED seed layers (#6646 D1, disposition
358
+ * (b)): with every layer landed, remove each `content/en/**.mdx` that the
359
+ * `templates/` snapshot (the overlay — no `ownership` entry) or the `_base`
360
+ * layer wrote and no merged roster row names. Those two layers serve every
361
+ * template, so each carries seeds some templates do not roster (`/pricing`
362
+ * on a services site); a template's own tree and its modules' bundles are
363
+ * never pruned — a seed THEY ship without a row is drift, which the manifest
364
+ * generator's `--check` fails on instead.
365
+ *
366
+ * Merged over the template's own `requires`, as `rosterSurfaces` is: the
367
+ * rows that name shared-layer seeds are the template's own. A seed inside
368
+ * the content directory of a surface this scaffold ENABLES is kept too, so
369
+ * an explicit `--surfaces pricing` on a template that does not roster
370
+ * `/pricing` gets its route and its seed together.
371
+ */
372
+ function pruneUnrosteredSeeds(targetDir, roster, surfaces, ownership) {
373
+ // `installBundleTree`'s ownership label for the base layer: it installs
374
+ // with `templateRequires: []`, so it records as a template. The overlay
375
+ // (the `templates/` snapshot) records no owner at all.
376
+ const shared = new Set([undefined, `template "${BASE_LAYER_DIR}"`]);
377
+ const enabledDirs = GATEABLE_SURFACES.filter((surface) => surfaces.includes(surface.id)).flatMap((surface) => surface.content.map((dir) => `${dir.replace("<locale>", "en")}/`));
378
+ const root = join(targetDir, "content", "en");
379
+ if (!existsSync(root))
380
+ return;
381
+ const stack = [root];
382
+ while (stack.length > 0) {
383
+ const dir = stack.pop();
384
+ if (dir === undefined)
385
+ break;
386
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
387
+ const abs = join(dir, entry.name);
388
+ if (entry.isDirectory()) {
389
+ stack.push(abs);
390
+ continue;
391
+ }
392
+ if (!entry.name.endsWith(".mdx"))
393
+ continue;
394
+ const path = relative(targetDir, abs).split(sep).join("/");
395
+ if (shared.has(ownership.get(path)) &&
396
+ !rosterDeclares(roster, path) &&
397
+ !enabledDirs.some((prefix) => path.startsWith(prefix))) {
398
+ rmSync(abs);
399
+ if (readdirSync(dir).length === 0)
400
+ rmdirSync(dir);
401
+ }
402
+ }
403
+ }
404
+ }
331
405
  /**
332
406
  * The MODULE-LESS gateable-surface ids a template's merged roster names
333
407
  * (#5469 D10): every `GATEABLE_SURFACES` entry no module contributes
@@ -474,6 +548,57 @@ export function syncRoutesSync(opts) {
474
548
  versions: readVersionMap(),
475
549
  });
476
550
  }
551
+ /**
552
+ * Apply a purchased module to an already-scaffolded site in place
553
+ * (`create-ailk add-module`, #6653). The thin package-rooted wrapper around
554
+ * `add-module.ts`'s `applyModuleToScaffold`, the `syncRoutesSync` split:
555
+ *
556
+ * 1. the target must be an existing scaffold — the same check as the sync
557
+ * subcommands above;
558
+ * 2. a free module is refused before any key read or network call — it
559
+ * ships with every scaffold, there is no bundle to add;
560
+ * 3. `resolvePaidModules` — the SAME gate `scaffold()` runs, so a missing
561
+ * key or a key that does not cover the module is refused with the
562
+ * scaffold-time message, before anything is written;
563
+ * 4. the fetched bundle is merged under the plus-config policy.
564
+ */
565
+ export async function addModule(opts) {
566
+ const targetAbs = resolve(opts.targetDir);
567
+ if (!existsSync(targetAbs)) {
568
+ throw new Error(`add-module target does not exist: ${targetAbs}. Point add-module at an already-scaffolded repo.`);
569
+ }
570
+ if (!statSync(targetAbs).isDirectory()) {
571
+ throw new Error(`add-module target is not a directory: ${targetAbs}`);
572
+ }
573
+ if (!existsSync(join(targetAbs, "architecture.yaml"))) {
574
+ throw new Error(`not a scaffolded repo: no architecture.yaml at ${targetAbs}. Run \`create-ailk <target-dir>\` to scaffold first.`);
575
+ }
576
+ const { paid } = classifyModules([opts.moduleName]);
577
+ if (paid.length === 0) {
578
+ throw new AddModuleRefusedError(`"${opts.moduleName}" ships with every scaffold — there is no module bundle to add`);
579
+ }
580
+ const bundles = await resolvePaidModules({
581
+ paidModules: paid,
582
+ licenseKey: opts.licenseKey,
583
+ ...(opts.moduleRegistryUrl !== undefined
584
+ ? { registryUrl: opts.moduleRegistryUrl }
585
+ : {}),
586
+ ...(opts.fetchImpl !== undefined ? { fetchImpl: opts.fetchImpl } : {}),
587
+ });
588
+ const bundleBytes = bundles.get(opts.moduleName);
589
+ if (bundleBytes === undefined) {
590
+ throw new Error(`module "${opts.moduleName}": no bundle was resolved`);
591
+ }
592
+ return applyModuleToScaffold({
593
+ targetDir: targetAbs,
594
+ moduleName: opts.moduleName,
595
+ bundleBytes,
596
+ versions: readVersionMap(),
597
+ markers: buildScaffoldMarkers(basename(targetAbs)),
598
+ templatesDir: TEMPLATES_DIR,
599
+ siteTemplatesDir: SITE_TEMPLATES_DIR,
600
+ });
601
+ }
477
602
  /** Re-exported so the CLI can name the file it wrote without duplicating it. */
478
603
  export { CATALOG_TARGET_PATH };
479
604
  /**
@@ -15,11 +15,17 @@
15
15
  * `apps/web/lib/site-chrome.ts` every scaffold gets — beside `nav-config.ts`,
16
16
  * the same GENERATED-header idiom (`surfaces.ts` `renderNavConfig`).
17
17
  *
18
- * Every field is optional and DEFAULTS to today's render (`full` / `columns`
19
- * / `true` / `default`) — a scaffold naming no template, or a template naming
20
- * no `layout` block, gets these defaults written, byte-identical to the
21
- * monorepo's own committed `apps/web/lib/site-chrome.ts`.
18
+ * Every field is optional and DEFAULTS to `full` / `columns` / `false` /
19
+ * `default` — the announcement banner is off unless a template turns it on
20
+ * (#6469). A scaffold naming no template, or a template naming no `layout`
21
+ * block, gets these defaults written, byte-identical to the monorepo's own
22
+ * committed `apps/web/lib/site-chrome.ts`.
23
+ *
24
+ * A template's DESIGN supplies layout knobs too (#6638): `DESIGN_LAYOUTS` in
25
+ * `template-manifest.ts`, read by `resolveSiteChrome`'s optional `design`
26
+ * argument between an explicit `layout` and these defaults.
22
27
  */
28
+ import { type StarterDesign } from "./template-manifest.js";
23
29
  /** The `nav` knob's vocabulary. `mark-only` → the layout passes Nav an empty
24
30
  * `navItems` (brand mark + the existing actions/theme/locale controls only). */
25
31
  export type SiteChromeNav = "full" | "mark-only";
@@ -47,7 +53,7 @@ export interface ResolvedSiteChrome {
47
53
  announcementBanner: boolean;
48
54
  home: SiteChromeHome;
49
55
  }
50
- /** Byte-identical-to-today defaults (#5612 D1). */
56
+ /** Defaults (#5612 D1; banner off since #6469). */
51
57
  export declare const DEFAULT_SITE_CHROME: ResolvedSiteChrome;
52
58
  /** Template-relative path of the generated site-chrome registry — beside `NAV_CONFIG_PATH`. */
53
59
  export declare const SITE_CHROME_PATH = "apps/web/lib/site-chrome.ts";
@@ -68,8 +74,13 @@ export declare class TemplateLayoutError extends Error {
68
74
  * @param subject Caller-supplied context for the error message (e.g. `template "foo"`).
69
75
  */
70
76
  export declare function validateTemplateLayout(value: unknown, subject: string): TemplateLayout;
71
- /** Fill every field a `TemplateLayout` leaves unset with `DEFAULT_SITE_CHROME`'s value. */
72
- export declare function resolveSiteChrome(layout?: TemplateLayout): ResolvedSiteChrome;
77
+ /**
78
+ * Fill every field a `TemplateLayout` leaves unset — from `design`'s
79
+ * `DESIGN_LAYOUTS` entry when one is named (#6638), then from
80
+ * `DEFAULT_SITE_CHROME`. The explicit `layout` wins field by field, so a
81
+ * template can wear a design and still override one knob of it.
82
+ */
83
+ export declare function resolveSiteChrome(layout?: TemplateLayout, design?: StarterDesign): ResolvedSiteChrome;
73
84
  /**
74
85
  * Render `apps/web/lib/site-chrome.ts` for a resolved layout — the same
75
86
  * GENERATED-header idiom `renderNavConfig` uses. `layout.tsx` imports
@@ -15,16 +15,22 @@
15
15
  * `apps/web/lib/site-chrome.ts` every scaffold gets — beside `nav-config.ts`,
16
16
  * the same GENERATED-header idiom (`surfaces.ts` `renderNavConfig`).
17
17
  *
18
- * Every field is optional and DEFAULTS to today's render (`full` / `columns`
19
- * / `true` / `default`) — a scaffold naming no template, or a template naming
20
- * no `layout` block, gets these defaults written, byte-identical to the
21
- * monorepo's own committed `apps/web/lib/site-chrome.ts`.
18
+ * Every field is optional and DEFAULTS to `full` / `columns` / `false` /
19
+ * `default` — the announcement banner is off unless a template turns it on
20
+ * (#6469). A scaffold naming no template, or a template naming no `layout`
21
+ * block, gets these defaults written, byte-identical to the monorepo's own
22
+ * committed `apps/web/lib/site-chrome.ts`.
23
+ *
24
+ * A template's DESIGN supplies layout knobs too (#6638): `DESIGN_LAYOUTS` in
25
+ * `template-manifest.ts`, read by `resolveSiteChrome`'s optional `design`
26
+ * argument between an explicit `layout` and these defaults.
22
27
  */
23
- /** Byte-identical-to-today defaults (#5612 D1). */
28
+ import { DESIGN_LAYOUTS, TemplateError, } from "./template-manifest.js";
29
+ /** Defaults (#5612 D1; banner off since #6469). */
24
30
  export const DEFAULT_SITE_CHROME = {
25
31
  nav: "full",
26
32
  footer: "columns",
27
- announcementBanner: true,
33
+ announcementBanner: false,
28
34
  home: "default",
29
35
  };
30
36
  /** Template-relative path of the generated site-chrome registry — beside `NAV_CONFIG_PATH`. */
@@ -85,13 +91,25 @@ export function validateTemplateLayout(value, subject) {
85
91
  }
86
92
  return result;
87
93
  }
88
- /** Fill every field a `TemplateLayout` leaves unset with `DEFAULT_SITE_CHROME`'s value. */
89
- export function resolveSiteChrome(layout) {
94
+ /**
95
+ * Fill every field a `TemplateLayout` leaves unset — from `design`'s
96
+ * `DESIGN_LAYOUTS` entry when one is named (#6638), then from
97
+ * `DEFAULT_SITE_CHROME`. The explicit `layout` wins field by field, so a
98
+ * template can wear a design and still override one knob of it.
99
+ */
100
+ export function resolveSiteChrome(layout, design) {
101
+ const fromDesign = design !== undefined ? DESIGN_LAYOUTS[design] : {};
102
+ // A JS caller (or a cast) can reach here with an undeclared design.
103
+ if (fromDesign === undefined) {
104
+ throw new TemplateError(`unknown design "${design}". Declared: ${Object.keys(DESIGN_LAYOUTS).join(", ")}`);
105
+ }
90
106
  return {
91
- nav: layout?.nav ?? DEFAULT_SITE_CHROME.nav,
92
- footer: layout?.footer ?? DEFAULT_SITE_CHROME.footer,
93
- announcementBanner: layout?.announcementBanner ?? DEFAULT_SITE_CHROME.announcementBanner,
94
- home: layout?.home ?? DEFAULT_SITE_CHROME.home,
107
+ nav: layout?.nav ?? fromDesign.nav ?? DEFAULT_SITE_CHROME.nav,
108
+ footer: layout?.footer ?? fromDesign.footer ?? DEFAULT_SITE_CHROME.footer,
109
+ announcementBanner: layout?.announcementBanner ??
110
+ fromDesign.announcementBanner ??
111
+ DEFAULT_SITE_CHROME.announcementBanner,
112
+ home: layout?.home ?? fromDesign.home ?? DEFAULT_SITE_CHROME.home,
95
113
  };
96
114
  }
97
115
  /**
@@ -106,8 +124,8 @@ export function renderSiteChrome(resolved) {
106
124
  //
107
125
  // GENERATED by create-ailk from the declared template's \`layout\` block
108
126
  // (#5612). A scaffold with no template, or a template with no \`layout\`
109
- // block, gets these defaults — byte-identical to every scaffold before this
110
- // file existed. \`apps/web/app/[locale]/layout.tsx\` reads this to decide:
127
+ // block, gets the defaults below (the announcement banner is off unless a
128
+ // template turns it on, #6469). \`apps/web/app/[locale]/layout.tsx\` reads this to decide:
111
129
  // - nav: "mark-only" → Nav renders the brand mark + right-hand controls
112
130
  // only (no nav links).
113
131
  // - footer: "centered" → Footer renders its existing centered variant.
@@ -0,0 +1,127 @@
1
+ import { type PageRosterRow } from "./module-architecture.js";
2
+ import type { ModuleName } from "./parse-args.js";
3
+ import { type MarkerMap } from "./substitute.js";
4
+ import { type TemplateManifest } from "./template-manifest.js";
5
+ /** The site-root file that is the roster of record (#6658 D7). */
6
+ export declare const SITE_ROSTER_PATH = ".ailk-site-manifest.json";
7
+ /**
8
+ * Where a site scaffolded before #6658 carries its roster: the template's
9
+ * manifest copied to the root under its own name. Read only when
10
+ * `SITE_ROSTER_PATH` is absent, and written back in place.
11
+ */
12
+ export declare const LEGACY_SITE_ROSTER_PATH = "TEMPLATE-MANIFEST.json";
13
+ /** One `rows[]` element: its parsed value and its verbatim text in the file. */
14
+ export interface RosterRowSlice {
15
+ value: Record<string, unknown>;
16
+ text: string;
17
+ }
18
+ /** A parsed manifest file, with the `rows` array located in its text. */
19
+ interface RosterFile {
20
+ doc: Record<string, unknown>;
21
+ text: string;
22
+ rows: RosterRowSlice[];
23
+ /** Index of the `rows` array's `[`. */
24
+ open: number;
25
+ /** Index of the `rows` array's `]`. */
26
+ close: number;
27
+ /** The whitespace before the first row and after the last — prettier's. */
28
+ lead: string;
29
+ trail: string;
30
+ }
31
+ /** The site's roster of record. */
32
+ export interface SiteRoster extends RosterFile {
33
+ /** The registry entry the file's `template` names. */
34
+ template: TemplateManifest;
35
+ /** The site-relative file it was read from — `SITE_ROSTER_PATH`, or the legacy root name. */
36
+ path: string;
37
+ }
38
+ /** One module contribution, with the generator's row(s) for its route. */
39
+ export interface ModuleRosterRow {
40
+ module: ModuleName;
41
+ contribution: PageRosterRow;
42
+ /**
43
+ * The generator's row for the route. For a `contentDir` contribution this
44
+ * is the code-route form (`kind: "route"`), used where the site has no
45
+ * seeds under the directory.
46
+ */
47
+ row: RosterRowSlice;
48
+ /** A `contentDir` contribution's seeded page form. */
49
+ seeded?: RosterRowSlice;
50
+ }
51
+ /**
52
+ * The site's roster of record, or `undefined` when the site has none (a
53
+ * scaffold that named no template installs no manifest). A file that is not
54
+ * a JSON object, has no `rows` array or names a template the registry does
55
+ * not declare is refused: guessing would silently mis-roster the site.
56
+ */
57
+ export declare function readSiteRoster(targetDir: string): SiteRoster | undefined;
58
+ /**
59
+ * The generator's row for each of a module's `pageContributions`, in
60
+ * declaration order — read from the committed manifest of the first
61
+ * template (in `TEMPLATE_MANIFESTS` order) that requires the module, ships a
62
+ * manifest, and carries the route. A `contentDir` contribution resolves both
63
+ * of its forms. A route no shipped manifest carries is refused.
64
+ */
65
+ export declare function moduleRosterRows(moduleName: ModuleName, siteTemplatesDir: string): ModuleRosterRow[];
66
+ /** What becomes of a superseded template row's seed file. */
67
+ export type SupersededSeed = {
68
+ path: string;
69
+ remove: true;
70
+ } | {
71
+ path: string;
72
+ remove: false;
73
+ wanted: string;
74
+ };
75
+ export interface RosterAppendPlan {
76
+ /** The rewritten file text, or undefined when the rows are unchanged. */
77
+ next: string | undefined;
78
+ /** Routes filled from the shipped reference rows. */
79
+ appended: string[];
80
+ /** The seeds of the template rows a module took over. */
81
+ seeds: SupersededSeed[];
82
+ }
83
+ export interface PlanRosterAppendOptions {
84
+ targetDir: string;
85
+ roster: SiteRoster;
86
+ /** The site's module set after the add. */
87
+ modules: readonly ModuleName[];
88
+ /** `moduleRosterRows` of every module in `modules`, resolved up front. */
89
+ references: readonly ModuleRosterRow[];
90
+ siteTemplatesDir: string;
91
+ templatesDir: string;
92
+ markers: MarkerMap;
93
+ }
94
+ /**
95
+ * The site roster's next `rows`: the template rows in file order, then each
96
+ * module's rows in `normalizeModules` order and, within a module, in
97
+ * `pageContributions` order — `mergeRoster`'s order, so adding A then B
98
+ * gives the roster adding B then A does.
99
+ *
100
+ * A row on a route a module in the set contributes is SUPERSEDED when it is
101
+ * the site template's own shipped row, untouched (the Free `/blog` teaser
102
+ * under docs-blog): the module's row takes its place, and its seed is
103
+ * removed only when its bytes are exactly what the scaffold installed. Any
104
+ * other row on a contributed route is the site's own and is kept as is.
105
+ */
106
+ export declare function planRosterAppend(opts: PlanRosterAppendOptions): RosterAppendPlan;
107
+ export interface SiteRosterCheckOptions {
108
+ targetDir: string;
109
+ siteTemplatesDir: string;
110
+ templatesDir: string;
111
+ }
112
+ /**
113
+ * The site's roster problems — empty means clean. The scaffold-grained twin
114
+ * of `build-template-manifest.mjs --check`, over the same `rosterDeclares`
115
+ * predicate:
116
+ *
117
+ * 1. a route on more than one row;
118
+ * 2. a module in the site's set whose contributed row is missing, or is not
119
+ * the shipped reference row;
120
+ * 3. an installed seed — one some layer lands, present in the site — that
121
+ * the effective roster does not declare. A page the customer wrote is
122
+ * installed by no layer and is never reported.
123
+ *
124
+ * A site with no roster (a template-less scaffold) has no problems.
125
+ */
126
+ export declare function siteRosterProblems(opts: SiteRosterCheckOptions): string[];
127
+ export {};