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.
- package/README.md +71 -3
- package/component-catalog.json +279 -45
- package/dist/add-module.d.ts +20 -0
- package/dist/add-module.js +43 -7
- package/dist/add-route.d.ts +56 -0
- package/dist/add-route.js +286 -0
- package/dist/apply-design-md.d.ts +7 -0
- package/dist/apply-design-md.js +50 -0
- package/dist/apply-design.d.ts +64 -0
- package/dist/apply-design.js +273 -0
- package/dist/claude-md.d.ts +111 -0
- package/dist/claude-md.js +334 -0
- package/dist/cli.js +104 -5
- package/dist/derive-architecture.js +41 -36
- package/dist/design-compile.d.ts +71 -0
- package/dist/design-compile.js +98 -0
- package/dist/design-package.d.ts +83 -0
- package/dist/design-package.js +468 -0
- package/dist/design-presets.d.ts +86 -0
- package/dist/design-presets.js +642 -0
- package/dist/design-scaffold.d.ts +76 -0
- package/dist/design-scaffold.js +220 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +4 -1
- package/dist/legacy-nav-renderers.d.ts +45 -0
- package/dist/legacy-nav-renderers.js +545 -0
- package/dist/lib/auth-secret-file.d.ts +19 -0
- package/dist/lib/auth-secret-file.js +112 -0
- package/dist/lib/design-checks.d.ts +78 -0
- package/dist/lib/design-checks.js +174 -0
- package/dist/lib/license-key-files.d.ts +9 -0
- package/dist/lib/license-key-files.js +2 -2
- package/dist/lib/module-license-gate.d.ts +48 -32
- package/dist/lib/module-license-gate.js +117 -8
- package/dist/lib/render-verdict.d.ts +52 -0
- package/dist/lib/render-verdict.js +77 -0
- package/dist/lib/scaffold-render-sweep.d.ts +105 -0
- package/dist/lib/scaffold-render-sweep.js +236 -0
- package/dist/module-architecture.d.ts +19 -16
- package/dist/module-architecture.js +67 -23
- package/dist/parse-args.d.ts +12 -2
- package/dist/parse-args.js +74 -13
- package/dist/programmatic.d.ts +30 -6
- package/dist/programmatic.js +216 -19
- package/dist/site-chrome.d.ts +19 -9
- package/dist/site-chrome.js +23 -13
- package/dist/site-manifest.d.ts +25 -0
- package/dist/site-manifest.js +254 -0
- package/dist/site-roster.d.ts +41 -0
- package/dist/site-roster.js +141 -8
- package/dist/starter-grid.d.ts +16 -63
- package/dist/starter-grid.js +102 -84
- package/dist/surfaces.d.ts +41 -9
- package/dist/surfaces.js +200 -83
- package/dist/template-manifest.d.ts +30 -13
- package/dist/template-manifest.js +53 -28
- package/package.json +6 -3
- package/release-scope.json +25 -0
- package/site-templates/_base/content/en/UseCase/persona-1.mdx +7 -6
- package/site-templates/_base/content/en/UseCase/persona-2.mdx +7 -6
- package/site-templates/_base/content/en/UseCase/persona-3.mdx +7 -6
- package/site-templates/designs/minimalism/DESIGN.md +98 -6
- package/site-templates/designs/minimalism/sample/README.md +48 -0
- package/site-templates/{minimalism-site → designs/minimalism/sample}/content/en/HomePage/home.mdx +1 -1
- package/site-templates/designs/standard/DESIGN.md +98 -6
- package/site-templates/marketing-site/DESIGN.md +98 -6
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +60 -27
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +50 -30
- package/site-templates/marketing-site-free/DESIGN.md +98 -6
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +60 -24
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +44 -27
- package/site-templates/marketing-site-free/ailk-template.json +1 -1
- package/site-templates/saas-product/DESIGN.md +98 -6
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +127 -18
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +74 -20
- package/site-templates/shapes/local-business-service/ailk-template.json +1 -1
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +127 -18
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +74 -20
- package/site-templates/shapes/organization-service/ailk-template.json +1 -1
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +127 -18
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +75 -21
- package/site-templates/shapes/person-service/ailk-template.json +1 -1
- package/site-templates/shapes/person-service/content/en/AboutPage/about.mdx +1 -1
- package/site-templates/shapes/person-service/content/en/HomePage/home.mdx +2 -2
- package/site-templates/shapes/person-service/content/en/PortfolioList/portfolio.mdx +4 -2
- package/site-templates/shapes/person-service/content/en/ServicesList/services.mdx +1 -1
- package/templates/.claude/agents/web.md +10 -8
- package/templates/.claude/rules/architecture.md +17 -7
- package/templates/.claude/skills/README.md +3 -2
- package/templates/.claude/skills/add-auth/SKILL.md +142 -90
- package/templates/.claude/skills/add-auth/references/env-vars.md +32 -17
- package/templates/.claude/skills/add-auth/references/oauth-app-setup.md +17 -8
- package/templates/.claude/skills/add-page/SKILL.md +241 -238
- package/templates/.claude/skills/add-page/references/page-types.md +94 -86
- package/templates/.claude/skills/add-page/scripts/site-pages.mjs +87 -0
- package/templates/.claude/skills/add-page/templates/comparison-page.mdx.tmpl +99 -0
- package/templates/.claude/skills/add-page/templates/content.mdx.tmpl +8 -18
- package/templates/.claude/skills/add-page/templates/flow-landing-page.mdx.tmpl +43 -0
- package/templates/.claude/skills/add-page/templates/report-page.mdx.tmpl +52 -0
- package/templates/.claude/skills/add-page/templates/reports-list.mdx.tmpl +38 -0
- package/templates/.claude/skills/check-site/SKILL.md +51 -18
- package/templates/.claude/skills/check-site/references/common-errors.md +12 -9
- package/templates/.claude/skills/check-site/references/findings-glossary.md +4 -4
- package/templates/.claude/skills/check-site/references/severity-levels.md +3 -3
- package/templates/.claude/skills/fill-page/SKILL.md +26 -7
- package/templates/.claude/skills/fill-page/profiles/plumbing.yaml +1 -1
- package/templates/.claude/skills/setup-deploy/SKILL.md +75 -38
- package/templates/.claude/skills/update-page/SKILL.md +117 -0
- package/templates/.claude/skills/update-page/scripts/update-page.ts +640 -0
- package/templates/.claude/skills/update-page/templates/redirects.mjs.tmpl +31 -0
- package/templates/.claude/skills/upgrade-plan/SKILL.md +12 -8
- package/templates/.env.example +23 -2
- package/templates/.github/workflows/ci.yml +4 -0
- package/templates/.prettierignore +1 -0
- package/templates/CLAUDE.md +146 -103
- package/templates/DESIGN.md +98 -6
- package/templates/README.md +50 -38
- package/templates/apps/api/.env.example +2 -1
- package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +2 -0
- package/templates/apps/api/src/__tests__/server.test.ts +116 -0
- package/templates/apps/api/src/lib/stripe.ts +9 -0
- package/templates/apps/api/src/middleware/__tests__/auth.optional-cookie.test.ts +140 -0
- package/templates/apps/api/src/middleware/auth.ts +54 -0
- package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +5 -2
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +628 -0
- package/templates/apps/api/src/routes/checkout/sessions.ts +222 -24
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +1 -1
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +5 -1
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +521 -20
- package/templates/apps/api/src/routes/webhooks/stripe.ts +124 -32
- package/templates/apps/api/src/server.ts +28 -0
- package/templates/apps/api/src/services/__tests__/revalidation.test.ts +90 -15
- package/templates/apps/api/src/services/checkout-buyer.ts +72 -0
- package/templates/apps/api/src/services/revalidation.ts +60 -15
- package/templates/apps/mcp/__tests__/tools/create_project.test.ts +30 -24
- package/templates/apps/mcp/src/tools/purchase.ts +4 -2
- package/templates/apps/web/.env.example +13 -0
- package/templates/apps/web/CLAUDE.md +22 -28
- package/templates/apps/web/app/[locale]/(auth)/layout.tsx +1 -1
- package/templates/apps/web/app/[locale]/(authed)/leads/__tests__/page.test.tsx +24 -0
- package/templates/apps/web/app/[locale]/(authed)/leads/page.tsx +17 -6
- package/templates/apps/web/app/[locale]/[slug]/[detail]/__tests__/page.test.tsx +110 -0
- package/templates/apps/web/app/[locale]/[slug]/[detail]/page.tsx +60 -0
- package/templates/apps/web/app/[locale]/[slug]/page.tsx +1 -1
- package/templates/apps/web/app/[locale]/alternatives/__tests__/page.test.tsx +161 -0
- package/templates/apps/web/app/[locale]/alternatives/page.tsx +104 -0
- package/templates/apps/web/app/[locale]/{use-cases → for}/[slug]/page.tsx +4 -3
- package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +73 -0
- package/templates/apps/web/app/[locale]/{use-cases → for}/page.tsx +8 -7
- package/templates/apps/web/app/[locale]/glossary/page.tsx +1 -1
- package/templates/apps/web/app/[locale]/layout.tsx +35 -19
- package/templates/apps/web/app/[locale]/newsletter/page.tsx +1 -1
- package/templates/apps/web/app/[locale]/portfolio/[slug]/page.tsx +1 -1
- package/templates/apps/web/app/[locale]/portfolio/page.tsx +1 -1
- package/templates/apps/web/app/[locale]/vs/[slug]/__tests__/page.test.tsx +112 -0
- package/templates/apps/web/app/[locale]/vs/[slug]/page.tsx +70 -0
- package/templates/apps/web/app/api/revalidate/__tests__/route.test.ts +236 -44
- package/templates/apps/web/app/api/revalidate/route.ts +78 -36
- package/templates/apps/web/lib/__tests__/site-brand.test.ts +79 -0
- package/templates/apps/web/lib/__tests__/site-theme.test.ts +104 -11
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +21 -4
- package/templates/apps/web/lib/locale-guard.ts +18 -0
- package/templates/apps/web/lib/meet-placeholder.ts +5 -0
- package/templates/apps/web/lib/nav-config.ts +14 -13
- package/templates/apps/web/lib/site-brand.tsx +13 -9
- package/templates/apps/web/lib/site-logo.tsx +36 -0
- package/templates/apps/web/lib/site-theme.ts +46 -26
- package/templates/apps/web/messages/en.json +23 -9
- package/templates/apps/web/middleware.ts +28 -4
- package/templates/content/en/BookCall/book-a-call.mdx +80 -0
- package/templates/content/en/BookCall/onsite-estimate.mdx +56 -0
- package/templates/content/en/BookCall/onsite-service.mdx +56 -0
- package/templates/content/en/BookCall/start-project.mdx +56 -0
- package/templates/content/en/ContactPage/contact.mdx +40 -1
- package/templates/content/en/HomePage/home.mdx +2 -6
- package/templates/content/en/LandingPage/meet.mdx +59 -0
- package/templates/content/en/UseCasesList/{use-cases.mdx → for.mdx} +19 -18
- package/templates/database/CHANGELOG.md +7 -0
- package/templates/database/package.json +1 -1
- package/templates/package.json +4 -1
- package/templates/tools/check-claude-md.ts +128 -0
- package/versions.json +33 -33
- package/dist/modules/commerce/0.6.0.tar.gz +0 -0
- package/dist/modules/commerce/0.6.1.tar.gz +0 -0
- package/dist/modules/docs-blog/0.6.0.tar.gz +0 -0
- package/dist/modules/docs-blog/0.6.1.tar.gz +0 -0
- package/dist/modules/marketing-site/0.6.1.tar.gz +0 -0
- package/dist/modules/minimalism-site/0.6.1.tar.gz +0 -0
- package/dist/modules/multi-tenant/0.6.0.tar.gz +0 -0
- package/dist/modules/multi-tenant/0.6.1.tar.gz +0 -0
- package/dist/modules/saas-product/0.6.1.tar.gz +0 -0
- package/dist/modules/scheduling/0.6.0.tar.gz +0 -0
- package/dist/modules/scheduling/0.6.1.tar.gz +0 -0
- package/dist/modules/signal-collectors/0.6.0.tar.gz +0 -0
- package/dist/modules/signal-collectors/0.6.1.tar.gz +0 -0
- package/dist/modules/simple-service/0.6.1.tar.gz +0 -0
- package/dist/modules/workspace/0.6.0.tar.gz +0 -0
- package/dist/modules/workspace/0.6.1.tar.gz +0 -0
- package/site-templates/_base/content/en/BookCall/book-call.mdx +0 -52
- package/site-templates/minimalism-site/DESIGN.md +0 -162
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +0 -506
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +0 -458
- package/site-templates/minimalism-site/ailk-template.json +0 -23
- package/site-templates/minimalism-site/content/en/AboutPage/about.mdx +0 -110
- package/site-templates/minimalism-site/content/en/BookCall/book-call.mdx +0 -52
- package/site-templates/minimalism-site/content/en/ContactPage/contact.mdx +0 -34
- package/site-templates/minimalism-site/content/en/FAQPage/faq.mdx +0 -57
- package/site-templates/minimalism-site/content/en/LandingPage/referral.mdx +0 -77
- package/site-templates/minimalism-site/content/en/LandingPage/reports.mdx +0 -73
- package/site-templates/minimalism-site/content/en/PurchasePolicyPage/purchase-policy.mdx +0 -123
- package/site-templates/minimalism-site/content/en/ReportPage/annual-letter-2024.mdx +0 -52
- package/site-templates/minimalism-site/content/en/ReportPage/field-notes-2023.mdx +0 -52
- package/site-templates/minimalism-site/content/en/ReportPage/platform-shift-2025.mdx +0 -52
- package/site-templates/minimalism-site/content/en/ReportPage/state-of-the-market-2026.mdx +0 -52
- package/site-templates/minimalism-site/content/en/Service/first-engagement.mdx +0 -38
- package/site-templates/minimalism-site/content/en/Service/second-engagement.mdx +0 -38
- package/site-templates/minimalism-site/content/en/Service/third-engagement.mdx +0 -38
- package/site-templates/minimalism-site/content/en/ServicesList/services.mdx +0 -73
- package/site-templates/minimalism-site/flows/referral.flow.json +0 -71
- package/site-templates/minimalism-site/provenance.json +0 -4
- package/templates/.claude/skills/add-page/templates/route.tsx.tmpl +0 -28
- package/templates/.claude/skills/setup-deploy/references/env-vars-template.md +0 -175
- package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-31.552Z-fallback-1789916251590-037e9005.json +0 -8
- package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.840Z-fallback-1789916253842-b52659e9.json +0 -12
- package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.844Z-fallback-1789916253845-6b266f8c.json +0 -12
- package/templates/apps/web/app/[locale]/book-a-call/page.tsx +0 -50
- package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +0 -72
- /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-1.svg +0 -0
- /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-2.svg +0 -0
- /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-3.svg +0 -0
- /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-4.svg +0 -0
- /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-5.svg +0 -0
- /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-6.svg +0 -0
- /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/hero-backdrop.svg +0 -0
- /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/logo-placeholder-1.svg +0 -0
- /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/logo-placeholder-2.svg +0 -0
- /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/logo-placeholder-3.svg +0 -0
- /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/wordmark-placeholder.svg +0 -0
package/dist/parse-args.js
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* On any parse / validation failure: throws ParseError. The orchestrator's
|
|
8
8
|
* top-level catch turns this into a stderr write + non-zero exit.
|
|
9
9
|
*/
|
|
10
|
+
import { existsSync, statSync } from "node:fs";
|
|
11
|
+
import { join, resolve } from "node:path";
|
|
10
12
|
import { createInterface } from "node:readline/promises";
|
|
11
13
|
import { parseArgs } from "node:util";
|
|
12
14
|
import { assertLicenseKeyShape } from "./lib/license-key-files.js";
|
|
@@ -95,8 +97,9 @@ Options:
|
|
|
95
97
|
Installs that shape's Free starter. A run naming
|
|
96
98
|
neither --shape nor --template asks for a shape on
|
|
97
99
|
a terminal, and refuses without one.
|
|
98
|
-
--design <design> The look to render the starter in
|
|
99
|
-
${RECOGNIZED_DESIGNS.join(", ")})
|
|
100
|
+
--design <design> The look to render the starter in: a design name
|
|
101
|
+
(recognized: ${RECOGNIZED_DESIGNS.join(", ")}) or a
|
|
102
|
+
directory holding a DESIGN.md. Defaults to the
|
|
100
103
|
starter's own.
|
|
101
104
|
--template <name> The site template to install (recognized:
|
|
102
105
|
${RECOGNIZED_TEMPLATES.join(", ")}).
|
|
@@ -120,8 +123,9 @@ Options:
|
|
|
120
123
|
Surfaces the declared architecture does not carry are
|
|
121
124
|
NOT scaffolded — their routes are absent, so a fresh
|
|
122
125
|
site never serves a stock page with placeholder
|
|
123
|
-
content. Add one later with
|
|
124
|
-
|
|
126
|
+
content. Add one later with
|
|
127
|
+
\`create-ailk add-route <surface>\`, or re-run with
|
|
128
|
+
the surface named here.
|
|
125
129
|
--sync-architecture <csv> Regenerate architecture.yaml in place in an
|
|
126
130
|
already-scaffolded repo from the given module set
|
|
127
131
|
(recognized: ${RECOGNIZED_MODULES.join(", ")}), then
|
|
@@ -140,6 +144,12 @@ Options:
|
|
|
140
144
|
file, at the target dir (positional arg, else cwd).
|
|
141
145
|
Bypasses the scaffold path — copies no template, runs
|
|
142
146
|
no install. Re-run after upgrading create-ailk.
|
|
147
|
+
--sync-claude-md Rewrite the AI Launch Kit sections of CLAUDE.md in
|
|
148
|
+
an already-scaffolded repo to the template this
|
|
149
|
+
create-ailk ships, then exit. Only CLAUDE.md
|
|
150
|
+
changes; your Site notes and anything between the
|
|
151
|
+
sections are kept byte for byte. Target dir is the
|
|
152
|
+
positional arg, else cwd.
|
|
143
153
|
--license <key> Your licence key: fetch paid modules with it and
|
|
144
154
|
write it into the scaffold (apps/api/.env and a
|
|
145
155
|
gitignored ailk.key, both owner-only).
|
|
@@ -180,6 +190,7 @@ export function parseCliArgs(argv, env = { interactive: false }) {
|
|
|
180
190
|
surfaces: { type: "string" },
|
|
181
191
|
"sync-architecture": { type: "string" },
|
|
182
192
|
"sync-catalog": { type: "boolean", default: false },
|
|
193
|
+
"sync-claude-md": { type: "boolean", default: false },
|
|
183
194
|
"with-catalog": { type: "boolean", default: false },
|
|
184
195
|
install: { type: "boolean", default: false },
|
|
185
196
|
help: { type: "boolean", default: false, short: "h" },
|
|
@@ -203,12 +214,18 @@ export function parseCliArgs(argv, env = { interactive: false }) {
|
|
|
203
214
|
const surfaces = parseSurfaceList(parsed.values.surfaces);
|
|
204
215
|
const syncArchitecture = parsed.values["sync-architecture"];
|
|
205
216
|
const syncCatalog = parsed.values["sync-catalog"] === true;
|
|
217
|
+
const syncClaudeMd = parsed.values["sync-claude-md"] === true;
|
|
206
218
|
const withCatalog = parsed.values["with-catalog"] === true;
|
|
207
219
|
const license = parseLicense(parsed.values.license);
|
|
208
|
-
// The
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
|
|
220
|
+
// The sync modes each exit after rewriting their one file, so asking for
|
|
221
|
+
// more than one in one invocation has no coherent meaning.
|
|
222
|
+
const syncFlags = [
|
|
223
|
+
syncCatalog ? "--sync-catalog" : undefined,
|
|
224
|
+
syncArchitecture !== undefined ? "--sync-architecture" : undefined,
|
|
225
|
+
syncClaudeMd ? "--sync-claude-md" : undefined,
|
|
226
|
+
].filter((f) => f !== undefined);
|
|
227
|
+
if (syncFlags.length > 1) {
|
|
228
|
+
throw new ParseError(`${syncFlags.join(" and ")} cannot be combined; run them one at a time`);
|
|
212
229
|
}
|
|
213
230
|
// Validate the sync-architecture value now, through the SAME parseModules
|
|
214
231
|
// path `--modules` uses, so an unknown module throws ParseError at parse time
|
|
@@ -228,12 +245,13 @@ export function parseCliArgs(argv, env = { interactive: false }) {
|
|
|
228
245
|
modules,
|
|
229
246
|
surfaces,
|
|
230
247
|
syncCatalog: false,
|
|
248
|
+
syncClaudeMd: false,
|
|
231
249
|
withCatalog: false,
|
|
232
250
|
syncArchitecture: undefined,
|
|
233
251
|
};
|
|
234
252
|
}
|
|
235
253
|
// Sync modes: the target dir is optional (positional if given, else cwd).
|
|
236
|
-
if (syncArchitecture !== undefined || syncCatalog) {
|
|
254
|
+
if (syncArchitecture !== undefined || syncCatalog || syncClaudeMd) {
|
|
237
255
|
if (parsed.positionals.length > 1) {
|
|
238
256
|
throw new ParseError(`expected at most one target directory, got ${parsed.positionals.length}`);
|
|
239
257
|
}
|
|
@@ -247,6 +265,7 @@ export function parseCliArgs(argv, env = { interactive: false }) {
|
|
|
247
265
|
modules,
|
|
248
266
|
surfaces,
|
|
249
267
|
syncCatalog,
|
|
268
|
+
syncClaudeMd,
|
|
250
269
|
withCatalog,
|
|
251
270
|
syncArchitecture,
|
|
252
271
|
};
|
|
@@ -284,6 +303,7 @@ export function parseCliArgs(argv, env = { interactive: false }) {
|
|
|
284
303
|
modules,
|
|
285
304
|
surfaces,
|
|
286
305
|
syncCatalog,
|
|
306
|
+
syncClaudeMd,
|
|
287
307
|
withCatalog,
|
|
288
308
|
syncArchitecture: undefined,
|
|
289
309
|
...(license !== undefined ? { license } : {}),
|
|
@@ -305,15 +325,56 @@ function parseShape(raw) {
|
|
|
305
325
|
}
|
|
306
326
|
return name;
|
|
307
327
|
}
|
|
308
|
-
/**
|
|
328
|
+
/** `path` is an existing directory. */
|
|
329
|
+
function isExistingDir(path) {
|
|
330
|
+
try {
|
|
331
|
+
return statSync(path).isDirectory();
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* `dir` (resolved from `raw`) as a design path: an existing directory
|
|
339
|
+
* holding a `DESIGN.md`, else `ParseError` naming `raw` (#6813 D1 rule 2).
|
|
340
|
+
*/
|
|
341
|
+
function validateDesignDir(dir, raw) {
|
|
342
|
+
if (!isExistingDir(dir)) {
|
|
343
|
+
throw new ParseError(`design directory "${raw}" does not exist`);
|
|
344
|
+
}
|
|
345
|
+
if (!existsSync(join(dir, "DESIGN.md"))) {
|
|
346
|
+
throw new ParseError(`design directory "${raw}" holds no DESIGN.md`);
|
|
347
|
+
}
|
|
348
|
+
return dir;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Validate `--design` (#6754 D2 step 2, widened #6813 D1): a recognized
|
|
352
|
+
* name, or a directory holding a `DESIGN.md`. A value with no `/` or `\` is
|
|
353
|
+
* classified as a name first (trimmed and lowercased, filesystem never
|
|
354
|
+
* consulted) — falling back to a path only when it names an existing
|
|
355
|
+
* directory in `process.cwd()`, so `--design mydesign` and `--design
|
|
356
|
+
* ./mydesign` can differ. A value with a separator is always a path,
|
|
357
|
+
* resolved against `process.cwd()`, case preserved. The package's CONTENT is
|
|
358
|
+
* validated later, in `scaffold()` (`resolveDesign`) — this parser checks
|
|
359
|
+
* presence only.
|
|
360
|
+
*/
|
|
309
361
|
function parseDesign(raw) {
|
|
310
362
|
if (raw === undefined)
|
|
311
363
|
return undefined;
|
|
312
|
-
const
|
|
313
|
-
|
|
364
|
+
const trimmed = raw.trim();
|
|
365
|
+
const hasSeparator = trimmed.includes("/") || trimmed.includes("\\");
|
|
366
|
+
if (!hasSeparator) {
|
|
367
|
+
const name = trimmed.toLowerCase();
|
|
368
|
+
if (RECOGNIZED_DESIGNS.includes(name)) {
|
|
369
|
+
return name;
|
|
370
|
+
}
|
|
371
|
+
const dir = resolve(process.cwd(), trimmed);
|
|
372
|
+
if (isExistingDir(dir)) {
|
|
373
|
+
return validateDesignDir(dir, raw);
|
|
374
|
+
}
|
|
314
375
|
throw new ParseError(`unknown design "${raw}". Recognized: ${RECOGNIZED_DESIGNS.join(", ")}`);
|
|
315
376
|
}
|
|
316
|
-
return
|
|
377
|
+
return validateDesignDir(resolve(process.cwd(), trimmed), raw);
|
|
317
378
|
}
|
|
318
379
|
/** How many answers the prompt takes before it gives up. */
|
|
319
380
|
const PROMPT_ATTEMPTS = 3;
|
package/dist/programmatic.d.ts
CHANGED
|
@@ -16,11 +16,12 @@
|
|
|
16
16
|
* const result = scaffoldSync({ targetDir: '/tmp/my-site', projectName: 'my-site' });
|
|
17
17
|
*/
|
|
18
18
|
import { type AddModuleResult } from "./add-module.js";
|
|
19
|
+
import { type SyncClaudeMdResult } from "./claude-md.js";
|
|
19
20
|
import { CATALOG_TARGET_PATH } from "./component-catalog.js";
|
|
20
21
|
import { type ModuleName } from "./parse-args.js";
|
|
21
22
|
import { type TemplateLayout } from "./site-chrome.js";
|
|
22
23
|
import { type SyncRoutesResult } from "./sync-routes.js";
|
|
23
|
-
import { type
|
|
24
|
+
import { type StarterShape } from "./template-manifest.js";
|
|
24
25
|
export interface ScaffoldOptions {
|
|
25
26
|
/** Absolute or relative path to the target directory. Must not exist or be empty. */
|
|
26
27
|
targetDir: string;
|
|
@@ -118,15 +119,23 @@ export interface ScaffoldOptions {
|
|
|
118
119
|
*/
|
|
119
120
|
layout?: TemplateLayout;
|
|
120
121
|
/**
|
|
121
|
-
*
|
|
122
|
-
*
|
|
122
|
+
* A design name or a directory holding a DESIGN.md (#6638; widened to a
|
|
123
|
+
* path #6813 D1/D3). `scaffold()` sets it from the resolved template's
|
|
123
124
|
* `design`; a caller may name one to OVERRIDE the template's — the
|
|
124
125
|
* shape × design pairing `verify-scaffold.mjs --design` renders (#6638
|
|
125
126
|
* DD-4), and the CLI's `--design` beside `--shape` (#6754). Unset (every
|
|
126
127
|
* existing `scaffoldSync` caller) resolves exactly as before. Gated
|
|
127
|
-
* (#6760): a design
|
|
128
|
+
* (#6760): a non-Free design needs a Pro or Pro Plus level.
|
|
128
129
|
*/
|
|
129
|
-
design?:
|
|
130
|
+
design?: string;
|
|
131
|
+
/**
|
|
132
|
+
* INTERNAL — the resolved design's own layout knobs (#6813 D2/D4/D8),
|
|
133
|
+
* populated by `scaffold()` from `resolveDesign(...).pkg.layout` before
|
|
134
|
+
* calling `scaffoldSync`. Never set by a CLI/MCP caller. Replaces the
|
|
135
|
+
* design-NAME lookup `scaffoldSync` used to do internally through the
|
|
136
|
+
* code registry that has since been deleted (#6638's per-name table).
|
|
137
|
+
*/
|
|
138
|
+
designLayout?: TemplateLayout;
|
|
130
139
|
}
|
|
131
140
|
export interface ScaffoldResult {
|
|
132
141
|
/** Resolved absolute path to the scaffolded directory. */
|
|
@@ -138,11 +147,13 @@ export interface ScaffoldResult {
|
|
|
138
147
|
/**
|
|
139
148
|
* The starter this scaffold resolved (#6754 D5) — set whenever a template
|
|
140
149
|
* was resolved, from `template` or from `shape`; absent for the bare overlay.
|
|
150
|
+
* `design` is the applied design's recorded name (#6813 D6): the shipped
|
|
151
|
+
* registry name, or the package's own `name` for an unrecognized one.
|
|
141
152
|
*/
|
|
142
153
|
starter?: {
|
|
143
154
|
template: string;
|
|
144
155
|
shape: StarterShape;
|
|
145
|
-
design:
|
|
156
|
+
design: string;
|
|
146
157
|
};
|
|
147
158
|
/**
|
|
148
159
|
* The effective module set this scaffold installed (#6759 D6) — what
|
|
@@ -258,6 +269,19 @@ export interface SyncCatalogResult {
|
|
|
258
269
|
export declare function syncCatalogSync(opts: {
|
|
259
270
|
targetDir: string;
|
|
260
271
|
}): SyncCatalogResult;
|
|
272
|
+
/**
|
|
273
|
+
* Rewrite the template-owned sections of an already-scaffolded repo's root
|
|
274
|
+
* `CLAUDE.md` to the template this create-ailk ships (#6904 D3). The logic
|
|
275
|
+
* lives in `claude-md.ts` (a module jest imports directly); this wrapper
|
|
276
|
+
* only supplies the package's `templates/CLAUDE.md` — the `syncRoutesSync`
|
|
277
|
+
* split. `templateText` stands in for the shipped template in tests.
|
|
278
|
+
*/
|
|
279
|
+
export declare function syncClaudeMdSync(opts: {
|
|
280
|
+
targetDir: string;
|
|
281
|
+
templateText?: string;
|
|
282
|
+
}): SyncClaudeMdResult;
|
|
283
|
+
/** Re-exported beside the other sync results so consumers see one surface. */
|
|
284
|
+
export type { SyncClaudeMdResult } from "./claude-md.js";
|
|
261
285
|
export interface SyncRoutesSyncOptions {
|
|
262
286
|
/** The already-scaffolded repo to sync (absolute or relative). */
|
|
263
287
|
targetDir: string;
|
package/dist/programmatic.js
CHANGED
|
@@ -19,20 +19,28 @@ import { existsSync, readdirSync, readFileSync, rmdirSync, rmSync, statSync, wri
|
|
|
19
19
|
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
20
20
|
import { fileURLToPath } from "node:url";
|
|
21
21
|
import { AddModuleRefusedError, applyModuleToScaffold, } from "./add-module.js";
|
|
22
|
+
import { planDesignMd } from "./apply-design-md.js";
|
|
23
|
+
import { renderClaudeMd, syncClaudeMd, } from "./claude-md.js";
|
|
22
24
|
import { CATALOG_TARGET_PATH, writeComponentCatalog, } from "./component-catalog.js";
|
|
23
25
|
import { buildScaffoldMarkers, copyTemplate } from "./copy.js";
|
|
24
26
|
import { deriveArchitecture } from "./derive-architecture.js";
|
|
27
|
+
import { compileDesignTheme } from "./design-compile.js";
|
|
28
|
+
import { applyDesignPresets, readSectionLookCatalog, shippedPageTypes, validateDesignPresets, } from "./design-presets.js";
|
|
29
|
+
import { installDesignSample, resolveDesign, spliceThemeBlock, } from "./design-scaffold.js";
|
|
30
|
+
import { writeAuthSecretFile } from "./lib/auth-secret-file.js";
|
|
25
31
|
import { extractModuleBundle, installBundleTree, } from "./lib/extract-module-bundle.js";
|
|
26
32
|
import { fetchTemplate } from "./lib/fetch-template.js";
|
|
27
33
|
import { assertLicenseKeyShape, writeLicenseKeyFiles, } from "./lib/license-key-files.js";
|
|
28
|
-
import {
|
|
34
|
+
import { assertDesignAllowed, assertReleased, deriveLevel, resolveModuleSet, resolvePaidModules, } from "./lib/module-license-gate.js";
|
|
29
35
|
import { classifyModules } from "./lib/module-tier.js";
|
|
30
36
|
import { normalizeModules } from "./module-architecture.js";
|
|
31
37
|
import { RECOGNIZED_SHAPES, RESERVED_SHAPES, } from "./parse-args.js";
|
|
32
38
|
import { SITE_CHROME_PATH, renderSiteChrome, resolveSiteChrome, } from "./site-chrome.js";
|
|
39
|
+
import { planSiteManifest } from "./site-manifest.js";
|
|
40
|
+
import { SITE_ROSTER_PATH, planSiteManifestDesign, readSiteRoster, withMeetPlaceholderSeeds, } from "./site-roster.js";
|
|
33
41
|
import { GATEABLE_SURFACES, MODULE_SURFACES, NAV_CONFIG_PATH, excludedRoutePaths, renderNavConfig, resolveSurfaces, } from "./surfaces.js";
|
|
34
42
|
import { syncRoutes } from "./sync-routes.js";
|
|
35
|
-
import { BASE_LAYER_DIR, SHAPE_STARTERS,
|
|
43
|
+
import { BASE_LAYER_DIR, SHAPE_STARTERS, TEMPLATE_MANIFESTS, TemplateError, mergeRoster, resolveTemplate, resolveTemplateLineage, rosterDeclares, templateDelivery, templateTreeDir, } from "./template-manifest.js";
|
|
36
44
|
import { validateTarget } from "./validate.js";
|
|
37
45
|
const PKG_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
38
46
|
const TEMPLATES_DIR = resolve(PKG_ROOT, "templates");
|
|
@@ -45,6 +53,8 @@ const TEMPLATES_DIR = resolve(PKG_ROOT, "templates");
|
|
|
45
53
|
* `package.json` `files` instead.
|
|
46
54
|
*/
|
|
47
55
|
const SITE_TEMPLATES_DIR = resolve(PKG_ROOT, "site-templates");
|
|
56
|
+
/** Where shipped design packages live (#6813 D2) — `resolveDesign`'s `designsDir`. */
|
|
57
|
+
const DESIGNS_DIR = join(SITE_TEMPLATES_DIR, "designs");
|
|
48
58
|
const VERSIONS_FILE = resolve(PKG_ROOT, "versions.json");
|
|
49
59
|
function readVersionMap() {
|
|
50
60
|
if (!existsSync(VERSIONS_FILE)) {
|
|
@@ -76,6 +86,24 @@ function countFiles(dir) {
|
|
|
76
86
|
}
|
|
77
87
|
return count;
|
|
78
88
|
}
|
|
89
|
+
/** Every `.mdx` file under `dir`, recursively, sorted (#6816 D6). */
|
|
90
|
+
function listMdxFiles(dir) {
|
|
91
|
+
if (!existsSync(dir))
|
|
92
|
+
return [];
|
|
93
|
+
const out = [];
|
|
94
|
+
const stack = [dir];
|
|
95
|
+
while (stack.length > 0) {
|
|
96
|
+
const current = stack.pop();
|
|
97
|
+
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
98
|
+
const abs = join(current, entry.name);
|
|
99
|
+
if (entry.isDirectory())
|
|
100
|
+
stack.push(abs);
|
|
101
|
+
else if (entry.name.endsWith(".mdx"))
|
|
102
|
+
out.push(abs);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return out.sort();
|
|
106
|
+
}
|
|
79
107
|
/**
|
|
80
108
|
* The template a `shape` scaffolds (#6754 D5, extended #6757 D6 row 1): the
|
|
81
109
|
* one FREE-delivery manifest of that shape — a declared `--template` alias
|
|
@@ -128,6 +156,9 @@ export function scaffoldSync(opts) {
|
|
|
128
156
|
excludePaths: excludedRoutePaths(enabledSurfaces),
|
|
129
157
|
markers: buildScaffoldMarkers(projectName),
|
|
130
158
|
});
|
|
159
|
+
// Stamp the copied CLAUDE.md's template regions with their hashes (#6904
|
|
160
|
+
// D2). No later layer writes a root CLAUDE.md, so this is the final file.
|
|
161
|
+
writeFileSync(join(targetAbs, "CLAUDE.md"), renderClaudeMd(readFileSync(join(TEMPLATES_DIR, "CLAUDE.md"), "utf8"), projectName));
|
|
131
162
|
// Derive + write the module-set-driven architecture.yaml (spec #3787). This
|
|
132
163
|
// is the single source of the scaffolded site's code-boundary config and
|
|
133
164
|
// closes the dangling `architecture_rules: architecture.yaml` reference in
|
|
@@ -144,7 +175,7 @@ export function scaffoldSync(opts) {
|
|
|
144
175
|
// template's `layout` block; a bare `scaffoldSync` call (every existing
|
|
145
176
|
// synchronous caller) writes DEFAULT_SITE_CHROME, byte-identical to the
|
|
146
177
|
// monorepo's own committed apps/web/lib/site-chrome.ts.
|
|
147
|
-
writeFileSync(join(targetAbs, SITE_CHROME_PATH), renderSiteChrome(resolveSiteChrome(opts.layout, opts.
|
|
178
|
+
writeFileSync(join(targetAbs, SITE_CHROME_PATH), renderSiteChrome(resolveSiteChrome(opts.layout, opts.designLayout)));
|
|
148
179
|
// Opt-in only (#4636). Every write above this line is unconditional, so a
|
|
149
180
|
// scaffold with `withCatalog` unset touches exactly the files it did before.
|
|
150
181
|
if (opts.withCatalog === true)
|
|
@@ -204,17 +235,44 @@ export async function scaffold(opts) {
|
|
|
204
235
|
if (opts.template !== undefined && opts.shape !== undefined) {
|
|
205
236
|
throw new TemplateError("pass a template or a shape, not both");
|
|
206
237
|
}
|
|
238
|
+
// #6984 — a surface `release-scope.json` does not release is refused here,
|
|
239
|
+
// after parsing and before the licence gate, fetch or write (spec 6977 D3).
|
|
240
|
+
assertReleased("templates", [opts.template]);
|
|
241
|
+
assertReleased("shapes", [opts.shape]);
|
|
207
242
|
const template = opts.template !== undefined
|
|
208
243
|
? resolveTemplate(opts.template)
|
|
209
244
|
: opts.shape !== undefined
|
|
210
245
|
? resolveShapeTemplate(opts.shape)
|
|
211
246
|
: undefined;
|
|
212
|
-
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
247
|
+
assertReleased("modules", [...(opts.modules ?? []), ...(template?.requires ?? [])]);
|
|
248
|
+
// #6813 D8 step 3 — the design this scaffold renders, resolved from its
|
|
249
|
+
// package ONCE, before any fetch or write: the caller's override, else the
|
|
250
|
+
// template's own. Unset for a scaffold naming neither. Replaces the
|
|
251
|
+
// `STARTER_DESIGNS.includes` name check: an unrecognized name or a missing
|
|
252
|
+
// directory throws `TemplateError` here; a malformed package throws
|
|
253
|
+
// `DesignPackageError` (never caught), each naming the field.
|
|
254
|
+
const designRef = opts.design ?? template?.design;
|
|
255
|
+
const resolvedDesign = designRef !== undefined
|
|
256
|
+
? resolveDesign(designRef, { designsDir: DESIGNS_DIR })
|
|
257
|
+
: undefined;
|
|
258
|
+
// By registry name, so a byte-identical copy of a gated design is refused too.
|
|
259
|
+
assertReleased("designs", [resolvedDesign?.recorded]);
|
|
260
|
+
// #6816 D6 step 3b — a design's section presets are validated here, before
|
|
261
|
+
// any fetch or write, against the look groups the shipped catalog carries
|
|
262
|
+
// (D2): a non-look field or unknown variant throws `DesignPackageError`
|
|
263
|
+
// naming page type, section type and field, and the target is never
|
|
264
|
+
// created. A design with no `sections` block executes no new read (D8).
|
|
265
|
+
if (resolvedDesign !== undefined &&
|
|
266
|
+
Object.keys(resolvedDesign.pkg.sections).length > 0) {
|
|
267
|
+
validateDesignPresets(resolvedDesign.pkg.sections, {
|
|
268
|
+
catalog: readSectionLookCatalog(join(PKG_ROOT, "component-catalog.json")),
|
|
269
|
+
pageTypes: shippedPageTypes(SITE_TEMPLATES_DIR),
|
|
270
|
+
file: join(resolvedDesign.dir, "DESIGN.md"),
|
|
271
|
+
});
|
|
217
272
|
}
|
|
273
|
+
// #6813 D7 — the resolved design only when the CALLER chose one (`opts.design`);
|
|
274
|
+
// a template's own declared design is never gated (#6778 option (c)).
|
|
275
|
+
const chosenDesign = opts.design !== undefined ? resolvedDesign : undefined;
|
|
218
276
|
// #5618 D2 — the `extends` chain is resolved in the client registry before
|
|
219
277
|
// any fetch: an unknown parent or a cycle is refused here, with the target
|
|
220
278
|
// still non-existent, like the four refusals below. The registry is the
|
|
@@ -260,11 +318,11 @@ export async function scaffold(opts) {
|
|
|
260
318
|
// #6760 — `resolveModuleSet` never runs on this branch (NFR-4), so no
|
|
261
319
|
// level was derived above. Derive one — from the registry's per-module
|
|
262
320
|
// answers, never a key claim (D2) — only when the caller CHOSE a
|
|
263
|
-
// non
|
|
321
|
+
// non-Free design alongside the template (`opts.design`, #6813 D7). An
|
|
264
322
|
// alias's own declared design is never gated (#6778 option (c)), so a
|
|
265
|
-
// plain `--template
|
|
266
|
-
//
|
|
267
|
-
if (
|
|
323
|
+
// plain `--template <alias>` run makes no extra network call and is
|
|
324
|
+
// never refused on its design.
|
|
325
|
+
if (chosenDesign !== undefined && !chosenDesign.free) {
|
|
268
326
|
({ level } = await deriveLevel({
|
|
269
327
|
licenseKey,
|
|
270
328
|
...(opts.moduleRegistryUrl !== undefined
|
|
@@ -317,8 +375,11 @@ export async function scaffold(opts) {
|
|
|
317
375
|
// Only the caller's chosen design is gated (#6778 option (c)); a template's
|
|
318
376
|
// own declared design is not. `level ?? "free"` fails closed: a
|
|
319
377
|
// `--modules`-keep run under-reports its level by design (NFR-2), so a
|
|
320
|
-
// chosen non
|
|
321
|
-
|
|
378
|
+
// chosen non-Free design on that path is refused rather than trusted. The
|
|
379
|
+
// refusal names `chosen.display` — the name, or the absolute directory
|
|
380
|
+
// (#6813 D7) — and `chosen.free` decides Free-ness by identity (D2), never
|
|
381
|
+
// by `FREE_DESIGNS.includes(name)` alone (a path is never in that list).
|
|
382
|
+
assertDesignAllowed(chosenDesign?.display, level ?? "free", chosenDesign?.free);
|
|
322
383
|
// The template's OWN bundle, fetched here — before `scaffoldSync` — so the
|
|
323
384
|
// fourth refusal (the template's bundle unpublished, parent record D17 /
|
|
324
385
|
// spec 5481 § 3 DD5 row 4) throws with the target still non-existent, like
|
|
@@ -372,7 +433,9 @@ export async function scaffold(opts) {
|
|
|
372
433
|
// design's knobs (#6638), and a `standard` one to the same defaults
|
|
373
434
|
// `scaffoldSync` already writes for a templateless scaffold.
|
|
374
435
|
...(template?.layout !== undefined ? { layout: template.layout } : {}),
|
|
375
|
-
|
|
436
|
+
// #6813 D8 step 6 — the resolved design's OWN layout (never its name):
|
|
437
|
+
// `resolveSiteChrome` reads a layout object now, not a `StarterDesign`.
|
|
438
|
+
...(resolvedDesign !== undefined ? { designLayout: resolvedDesign.pkg.layout } : {}),
|
|
376
439
|
});
|
|
377
440
|
// Threaded to extractModuleBundle for `package.json` patch targets'
|
|
378
441
|
// `workspace:*` substitution (spec 5418 § 3 D2) — the same version map
|
|
@@ -437,6 +500,11 @@ export async function scaffold(opts) {
|
|
|
437
500
|
//
|
|
438
501
|
// One install step, two source roots (DD7): an extracted staging dir on the
|
|
439
502
|
// paid path, `site-templates/<name>/` on disk on the Free one.
|
|
503
|
+
// #6813 D6 — captured for the site-manifest record below, which needs the
|
|
504
|
+
// chrome ACTUALLY written (`installedLayout ?? template.layout`), readable
|
|
505
|
+
// only after this block (the bundle's own `layout`, when it declares one,
|
|
506
|
+
// is unreachable before install).
|
|
507
|
+
let installedLayout;
|
|
440
508
|
if (template !== undefined) {
|
|
441
509
|
// #5612 D1/D2 — the INSTALLED template's own `ailk-template.json` layout
|
|
442
510
|
// (validated by `readAilkTemplateManifest`), if it declares one. Read
|
|
@@ -472,21 +540,132 @@ export async function scaffold(opts) {
|
|
|
472
540
|
ownership,
|
|
473
541
|
});
|
|
474
542
|
})();
|
|
543
|
+
installedLayout = installed.layout;
|
|
475
544
|
if (installed.layout !== undefined) {
|
|
476
|
-
writeFileSync(join(result.targetDir, SITE_CHROME_PATH), renderSiteChrome(resolveSiteChrome(installed.layout,
|
|
545
|
+
writeFileSync(join(result.targetDir, SITE_CHROME_PATH), renderSiteChrome(resolveSiteChrome(installed.layout, resolvedDesign?.pkg.layout)));
|
|
477
546
|
}
|
|
547
|
+
// #6878 D6 — re-render the nav with the SERVICE slugs the installed tree
|
|
548
|
+
// just landed (`content/en/Service/*.mdx`, absent on a product shape),
|
|
549
|
+
// now that `content/en/Service` exists to read. `scaffoldSync`'s own
|
|
550
|
+
// one-argument render above already wrote a nav for `result.surfaces`
|
|
551
|
+
// BEFORE the template tree existed — byte-identical to this one on a
|
|
552
|
+
// product shape (no Service directory to read), and this is what makes a
|
|
553
|
+
// service shape's flyout show its own Services column instead of an
|
|
554
|
+
// empty offering column. Unconditional (every templated scaffold gets
|
|
555
|
+
// this second render, not only one that resolved a `layout`), and BEFORE
|
|
556
|
+
// `pruneUnrosteredSeeds` below: the read reflects every seed the install
|
|
557
|
+
// step actually wrote, not what a later prune leaves behind.
|
|
558
|
+
const serviceContentDir = join(result.targetDir, "content", "en", "Service");
|
|
559
|
+
const serviceSlugs = existsSync(serviceContentDir)
|
|
560
|
+
? readdirSync(serviceContentDir)
|
|
561
|
+
.filter((f) => f.endsWith(".mdx"))
|
|
562
|
+
.map((f) => f.slice(0, -".mdx".length))
|
|
563
|
+
: [];
|
|
564
|
+
writeFileSync(join(result.targetDir, NAV_CONFIG_PATH), renderNavConfig(result.surfaces, { serviceSlugs }));
|
|
478
565
|
pruneUnrosteredSeeds(result.targetDir, mergeRoster(template, template.requires), result.surfaces, ownership);
|
|
566
|
+
// spec 6892 D4 — the resolved design's own sample (the minimalism design's
|
|
567
|
+
// nine-slide deck home + its SVGs; no template declares `minimalism`
|
|
568
|
+
// since #6976, so none installs it today), installed AFTER the template
|
|
569
|
+
// layer so it wins over the shape's `HomePage/home.mdx`, and ONLY when
|
|
570
|
+
// the resolved design is the template's OWN declared design: a caller
|
|
571
|
+
// combination like `--shape organization-product --design minimalism`
|
|
572
|
+
// installs no sample (its roster carries neither the deck's link targets
|
|
573
|
+
// nor, in its generated record, the deck's sections).
|
|
574
|
+
if (resolvedDesign !== undefined &&
|
|
575
|
+
resolvedDesign.recorded === template.design) {
|
|
576
|
+
installDesignSample({
|
|
577
|
+
designDir: resolvedDesign.dir,
|
|
578
|
+
targetDir: result.targetDir,
|
|
579
|
+
ownership,
|
|
580
|
+
});
|
|
581
|
+
}
|
|
479
582
|
}
|
|
583
|
+
// #6813 D5/D8 steps 9-10 — whenever a design is resolved (the caller's,
|
|
584
|
+
// else the template's): the design's DESIGN.md (#6939); the compiled theme,
|
|
585
|
+
// spliced into the scaffold's `content/_site.mdx` LAST; then the site manifest records the APPLIED
|
|
586
|
+
// design, only when a template installed one (`saas-product` ships none).
|
|
587
|
+
// A scaffold naming neither a design nor a template (the bare overlay)
|
|
588
|
+
// executes no new statement.
|
|
589
|
+
if (resolvedDesign !== undefined) {
|
|
590
|
+
// #6939 — the scaffold's DESIGN.md is the design it wears, not the one
|
|
591
|
+
// its template layer shipped; the site's own header lines are kept.
|
|
592
|
+
writeFileSync(join(result.targetDir, "DESIGN.md"), planDesignMd(resolvedDesign.dir, result.targetDir));
|
|
593
|
+
const compiledTheme = compileDesignTheme(resolvedDesign.pkg);
|
|
594
|
+
const siteMdxPath = join(result.targetDir, "content", "_site.mdx");
|
|
595
|
+
if (existsSync(siteMdxPath)) {
|
|
596
|
+
const current = readFileSync(siteMdxPath, "utf8");
|
|
597
|
+
const spliced = spliceThemeBlock(current, compiledTheme, siteMdxPath);
|
|
598
|
+
if (spliced !== current)
|
|
599
|
+
writeFileSync(siteMdxPath, spliced);
|
|
600
|
+
}
|
|
601
|
+
if (template !== undefined) {
|
|
602
|
+
const rosterPath = join(result.targetDir, SITE_ROSTER_PATH);
|
|
603
|
+
if (existsSync(rosterPath)) {
|
|
604
|
+
const rosterText = readFileSync(rosterPath, "utf8");
|
|
605
|
+
const chromeWritten = resolveSiteChrome(installedLayout ?? template.layout, resolvedDesign.pkg.layout);
|
|
606
|
+
const nextManifest = planSiteManifestDesign(rosterText, { design: resolvedDesign.recorded, layout: chromeWritten, theme: compiledTheme }, rosterPath);
|
|
607
|
+
if (nextManifest !== undefined)
|
|
608
|
+
writeFileSync(rosterPath, nextManifest);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
// #6816 D6 step 10b — the design's section presets, written onto the
|
|
612
|
+
// pages of the page types the roster of record places, once every layer
|
|
613
|
+
// has landed. The one `applyDesignPresets` call site. A scaffold with no
|
|
614
|
+
// roster (the bare overlay, `saas-product`) validated its presets at
|
|
615
|
+
// step 3b and applies none: nothing "the shape placed" to resolve against.
|
|
616
|
+
if (template !== undefined &&
|
|
617
|
+
Object.keys(resolvedDesign.pkg.sections).length > 0) {
|
|
618
|
+
const roster = readSiteRoster(result.targetDir);
|
|
619
|
+
if (roster !== undefined) {
|
|
620
|
+
const pageTypes = new Set();
|
|
621
|
+
for (const row of roster.rows) {
|
|
622
|
+
const pageType = row.value["pageType"];
|
|
623
|
+
if (typeof pageType === "string")
|
|
624
|
+
pageTypes.add(pageType);
|
|
625
|
+
}
|
|
626
|
+
const contentDir = join(result.targetDir, "content");
|
|
627
|
+
const pages = listMdxFiles(contentDir)
|
|
628
|
+
.filter((path) => path !== join(contentDir, "_site.mdx"))
|
|
629
|
+
.map((path) => ({ path, text: readFileSync(path, "utf8") }));
|
|
630
|
+
const presetResult = applyDesignPresets(pages, resolvedDesign.pkg.sections, { pageTypes });
|
|
631
|
+
for (const page of presetResult.pages)
|
|
632
|
+
writeFileSync(page.path, page.text);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
// #6944 — the manifest records what this scaffold IS, once every layer has
|
|
637
|
+
// landed: the effective module set, a row for every route on disk, and the
|
|
638
|
+
// level only when the key path above derived one — `resolveModuleSet`, or
|
|
639
|
+
// `deriveLevel` for a chosen non-Free design. `level` stays undefined on a
|
|
640
|
+
// plain alias or `--modules`-keep run, and is never inferred from `modules`.
|
|
641
|
+
// A scaffold with no manifest (the bare overlay, `saas-product`) writes none.
|
|
642
|
+
if (template !== undefined) {
|
|
643
|
+
const roster = readSiteRoster(result.targetDir);
|
|
644
|
+
if (roster !== undefined) {
|
|
645
|
+
writeFileSync(join(result.targetDir, roster.path), planSiteManifest({
|
|
646
|
+
targetDir: result.targetDir,
|
|
647
|
+
roster,
|
|
648
|
+
modules: result.modules,
|
|
649
|
+
level,
|
|
650
|
+
siteTemplatesDir: SITE_TEMPLATES_DIR,
|
|
651
|
+
templatesDir: TEMPLATES_DIR,
|
|
652
|
+
markers,
|
|
653
|
+
}));
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
// #6107 D2 — after every layer landed, so nothing later overwrites it: a
|
|
657
|
+
// served scaffold needs BETTER_AUTH_SECRET (#6044), and this is its value.
|
|
658
|
+
writeAuthSecretFile(result.targetDir);
|
|
480
659
|
// #6683 D9 — persisted only after every layer landed, and only when the
|
|
481
660
|
// caller passed `license`: a scaffold without it writes nothing new.
|
|
482
661
|
if (opts.license !== undefined) {
|
|
483
662
|
writeLicenseKeyFiles(result.targetDir, opts.license);
|
|
484
663
|
}
|
|
485
664
|
// #6754 D4/D5 — the resolved starter, so the CLI names what it built.
|
|
486
|
-
if (template !== undefined &&
|
|
665
|
+
if (template !== undefined && resolvedDesign !== undefined) {
|
|
487
666
|
return {
|
|
488
667
|
...result,
|
|
489
|
-
starter: { template: template.name, shape: template.shape, design },
|
|
668
|
+
starter: { template: template.name, shape: template.shape, design: resolvedDesign.recorded },
|
|
490
669
|
};
|
|
491
670
|
}
|
|
492
671
|
return result;
|
|
@@ -506,12 +685,17 @@ export async function scaffold(opts) {
|
|
|
506
685
|
* the content directory of a surface this scaffold ENABLES is kept too, so
|
|
507
686
|
* an explicit `--surfaces pricing` on a template that does not roster
|
|
508
687
|
* `/pricing` gets its route and its seed together.
|
|
688
|
+
*
|
|
689
|
+
* The Free `/meet` placeholder seeds count as declared wherever the roster
|
|
690
|
+
* claims their route, the `scheduling` module's rows included (#6942): an
|
|
691
|
+
* unconfigured scheduling site's /meet pages hand off to them.
|
|
509
692
|
*/
|
|
510
693
|
function pruneUnrosteredSeeds(targetDir, roster, surfaces, ownership) {
|
|
511
694
|
// `installBundleTree`'s ownership label for the base layer: it installs
|
|
512
695
|
// with `templateRequires: []`, so it records as a template. The overlay
|
|
513
696
|
// (the `templates/` snapshot) records no owner at all.
|
|
514
697
|
const shared = new Set([undefined, `template "${BASE_LAYER_DIR}"`]);
|
|
698
|
+
const declared = withMeetPlaceholderSeeds(roster);
|
|
515
699
|
const enabledDirs = GATEABLE_SURFACES.filter((surface) => surfaces.includes(surface.id)).flatMap((surface) => surface.content.map((dir) => `${dir.replace("<locale>", "en")}/`));
|
|
516
700
|
const root = join(targetDir, "content", "en");
|
|
517
701
|
if (!existsSync(root))
|
|
@@ -531,7 +715,7 @@ function pruneUnrosteredSeeds(targetDir, roster, surfaces, ownership) {
|
|
|
531
715
|
continue;
|
|
532
716
|
const path = relative(targetDir, abs).split(sep).join("/");
|
|
533
717
|
if (shared.has(ownership.get(path)) &&
|
|
534
|
-
!rosterDeclares(
|
|
718
|
+
!rosterDeclares(declared, path) &&
|
|
535
719
|
!enabledDirs.some((prefix) => path.startsWith(prefix))) {
|
|
536
720
|
rmSync(abs);
|
|
537
721
|
if (readdirSync(dir).length === 0)
|
|
@@ -664,6 +848,19 @@ export function syncCatalogSync(opts) {
|
|
|
664
848
|
catalogPath: writeComponentCatalog(targetAbs),
|
|
665
849
|
};
|
|
666
850
|
}
|
|
851
|
+
/**
|
|
852
|
+
* Rewrite the template-owned sections of an already-scaffolded repo's root
|
|
853
|
+
* `CLAUDE.md` to the template this create-ailk ships (#6904 D3). The logic
|
|
854
|
+
* lives in `claude-md.ts` (a module jest imports directly); this wrapper
|
|
855
|
+
* only supplies the package's `templates/CLAUDE.md` — the `syncRoutesSync`
|
|
856
|
+
* split. `templateText` stands in for the shipped template in tests.
|
|
857
|
+
*/
|
|
858
|
+
export function syncClaudeMdSync(opts) {
|
|
859
|
+
return syncClaudeMd({
|
|
860
|
+
targetDir: opts.targetDir,
|
|
861
|
+
templateText: opts.templateText ?? readFileSync(join(TEMPLATES_DIR, "CLAUDE.md"), "utf8"),
|
|
862
|
+
});
|
|
863
|
+
}
|
|
667
864
|
/**
|
|
668
865
|
* Compare an already-scaffolded repo's route tree with the template this
|
|
669
866
|
* create-ailk ships and, with `apply`, copy in the route dirs it is missing
|