create-ailk 0.2.0 → 0.4.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/component-catalog.md +50 -53
- package/dist/cli.js +19 -9
- package/dist/lib/apply-module-patches.d.ts +47 -0
- package/dist/lib/apply-module-patches.js +370 -0
- package/dist/lib/extract-module-bundle.d.ts +51 -3
- package/dist/lib/extract-module-bundle.js +327 -28
- package/dist/lib/fetch-module.d.ts +3 -38
- package/dist/lib/fetch-module.js +26 -8
- package/dist/lib/fetch-template.d.ts +20 -0
- package/dist/lib/fetch-template.js +129 -0
- package/dist/lib/module-license-gate.d.ts +10 -0
- package/dist/lib/module-license-gate.js +13 -4
- package/dist/lib/paid-paths.d.ts +16 -0
- package/dist/lib/paid-paths.js +90 -0
- package/dist/module-architecture.d.ts +197 -23
- package/dist/module-architecture.js +820 -25
- package/dist/parse-args.d.ts +9 -2
- package/dist/parse-args.js +46 -1
- package/dist/programmatic.d.ts +28 -2
- package/dist/programmatic.js +115 -8
- package/dist/surfaces.d.ts +12 -36
- package/dist/surfaces.js +77 -5
- package/dist/sync-routes.js +31 -1
- package/dist/template-manifest.d.ts +92 -0
- package/dist/template-manifest.js +168 -0
- package/dist/template-names.d.ts +41 -0
- package/dist/template-names.js +44 -0
- package/package.json +3 -2
- package/site-templates/marketing-site-free/ailk-template.json +8 -0
- package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +103 -0
- package/site-templates/marketing-site-free/content/en/PrivacyPage/privacy.mdx +79 -0
- package/site-templates/marketing-site-free/content/en/TermsPage/terms.mdx +80 -0
- package/site-templates/marketing-site-free/provenance.json +4 -0
- package/templates/.claude/agents/web.md +2 -3
- package/templates/.claude/rules/architecture.md +5 -5
- package/templates/.claude/skills/README.md +2 -1
- package/templates/.env.example +21 -9
- package/templates/CONVENTIONS.md +6 -7
- package/templates/apps/api/.env.example +9 -0
- package/templates/apps/api/CLAUDE.md +33 -5
- package/templates/apps/api/package.json +0 -1
- package/templates/apps/api/src/__tests__/cors.test.ts +195 -0
- package/templates/apps/api/src/__tests__/module-exclusion.test.ts +60 -2
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +159 -0
- package/templates/apps/api/src/__tests__/workspace-module-registration.test.ts +166 -0
- package/templates/apps/api/src/config/__tests__/modules.test.ts +76 -0
- package/templates/apps/api/src/config/index.ts +19 -0
- package/templates/apps/api/src/config/modules.ts +12 -14
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +3 -0
- package/templates/apps/api/src/lib/__tests__/slice-load.test.ts +89 -0
- package/templates/apps/api/src/lib/slice-load.ts +90 -0
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +52 -32
- package/templates/apps/api/src/openapi/__tests__/spec-identity.test.ts +182 -0
- package/templates/apps/api/src/openapi/__tests__/surface-drift.test.ts +293 -0
- package/templates/apps/api/src/openapi/index.ts +9 -3
- package/templates/apps/api/src/openapi/spec.ts +505 -65
- package/templates/apps/api/src/openapi/surface-drift.ts +195 -0
- package/templates/apps/api/src/routes/content/__tests__/create.test.ts +14 -0
- package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +14 -0
- package/templates/apps/api/src/routes/content/__tests__/update.test.ts +14 -0
- package/templates/apps/api/src/routes/content/index.ts +38 -11
- package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +17 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +17 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +796 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +10 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +38 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +814 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +10 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +24 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +17 -0
- package/templates/apps/api/src/routes/project-listings/index.ts +37 -10
- package/templates/apps/api/src/routes/project-listings/me.ts +10 -0
- package/templates/apps/api/src/routes/project-listings/patch-draft.ts +5 -0
- package/templates/apps/api/src/routes/project-listings/public.ts +5 -0
- package/templates/apps/api/src/routes/project-listings/respond.ts +11 -0
- package/templates/apps/api/src/routes/project-listings/resume-token.ts +5 -0
- package/templates/apps/api/src/routes/project-listings/start.ts +30 -0
- package/templates/apps/api/src/routes/project-listings/submit.ts +5 -0
- package/templates/apps/api/src/routes/project-listings/verify-email.ts +150 -0
- package/templates/apps/api/src/server.ts +504 -137
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +10 -0
- package/templates/apps/api/src/services/listing-config.ts +58 -0
- package/templates/apps/api/src/services/project-listing-resume-email.ts +326 -0
- package/templates/apps/api/src/services/project-listings.ts +229 -4
- package/templates/apps/api/src/vercel-handler.ts +60 -26
- package/templates/apps/mcp/.env.example +8 -0
- package/templates/apps/mcp/CLAUDE.md +2 -2
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +21 -0
- package/templates/apps/mcp/__tests__/config/modules.test.ts +78 -0
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +1 -1
- package/templates/apps/mcp/src/config/modules.ts +14 -13
- package/templates/apps/mcp/src/server.ts +8 -4
- package/templates/apps/mcp/src/tools/index.ts +7 -22
- package/templates/apps/web/.env.example +15 -0
- package/templates/apps/web/app/[locale]/(authed)/{waitlist/__tests__ → __tests__}/gate.test.tsx +4 -4
- package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +2 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx +2 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.test.tsx +2 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/flow.actions.test.ts +1 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +1 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +2 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +1 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +1 -0
- package/templates/apps/web/app/[locale]/pricing/page.tsx +40 -0
- package/templates/apps/web/app/[locale]/privacy/page.tsx +40 -0
- package/templates/apps/web/app/[locale]/terms/page.tsx +40 -0
- package/templates/apps/web/app/llms.txt/route.ts +12 -9
- package/templates/apps/web/jest.config.cjs +19 -13
- package/templates/apps/web/lib/__tests__/server-api.test.ts +61 -0
- package/templates/apps/web/lib/server-api.ts +28 -12
- package/templates/apps/web/messages/en.json +58 -0
- package/templates/apps/web/next.config.mjs +1 -3
- package/templates/apps/web/package.json +0 -6
- package/templates/apps/web/public/android-chrome-192x192.png +0 -0
- package/templates/apps/web/public/android-chrome-512x512.png +0 -0
- package/templates/apps/web/public/apple-touch-icon.png +0 -0
- package/templates/apps/web/public/favicon-16x16.png +0 -0
- package/templates/apps/web/public/favicon-32x32.png +0 -0
- package/templates/apps/web/public/favicon.ico +0 -0
- package/templates/apps/web/public/favicon.svg +6 -3
- package/templates/apps/web/public/lockup-horizontal.svg +4 -0
- package/templates/apps/web/public/logomark.svg +4 -0
- package/templates/apps/web/public/site.webmanifest +2 -2
- package/templates/apps/web/public/wordmark.svg +4 -0
- package/templates/database/CHANGELOG.md +47 -0
- package/templates/database/inbox/schema.prisma +67 -0
- package/templates/database/migrations/20260914120000_listing_founder_identity/migration.sql +123 -0
- package/templates/database/package.json +1 -1
- package/templates/package.json +1 -1
- package/templates/.claude/skills/scaffold-commerce/SKILL.md +0 -807
- package/templates/.claude/skills/scaffold-commerce/references/lookup-keys-template.md +0 -71
- package/templates/.claude/skills/scaffold-commerce/references/marketplace-brand-pages.md +0 -83
- package/templates/.claude/skills/scaffold-commerce/templates/checkout-route.template.ts +0 -370
- package/templates/.claude/skills/scaffold-commerce/templates/invoice-route.template.ts +0 -376
- package/templates/.claude/skills/scaffold-commerce/templates/marketplace-cta-route.tsx.tmpl +0 -72
- package/templates/.claude/skills/scaffold-commerce/templates/payment-link-route.template.ts +0 -471
- package/templates/.claude/skills/scaffold-commerce/templates/portal-route.template.ts +0 -365
- package/templates/.claude/skills/scaffold-commerce/templates/stripe-config.template.ts +0 -60
- package/templates/.claude/skills/scaffold-commerce/templates/subscription-route.template.ts +0 -446
- package/templates/apps/api/src/__tests__/server.test.ts +0 -253
- package/templates/apps/api/src/bin/deliverable-resend.ts +0 -144
- package/templates/apps/api/src/bin/deliverable-upload.ts +0 -114
- package/templates/apps/api/src/bin/followup-sweep.ts +0 -44
- package/templates/apps/api/src/bin/listing-csv-sweep.ts +0 -46
- package/templates/apps/api/src/bin/seed-presets.ts +0 -58
- package/templates/apps/api/src/bin/seed-waitlist-experiments.ts +0 -104
- package/templates/apps/api/src/bin/session-retention-sweep.ts +0 -46
- package/templates/apps/api/src/lib/__tests__/stripe.test.ts +0 -74
- package/templates/apps/api/src/lib/stripe.ts +0 -52
- package/templates/apps/api/src/lib/tenant-db.ts +0 -225
- package/templates/apps/api/src/lib/ws-token.ts +0 -91
- package/templates/apps/api/src/middleware/tenant.ts +0 -99
- package/templates/apps/api/src/routes/billing/__tests__/portal.test.ts +0 -450
- package/templates/apps/api/src/routes/billing/__tests__/read.test.ts +0 -262
- package/templates/apps/api/src/routes/billing/__tests__/usage.test.ts +0 -436
- package/templates/apps/api/src/routes/billing/index.ts +0 -36
- package/templates/apps/api/src/routes/billing/portal.ts +0 -182
- package/templates/apps/api/src/routes/billing/read.ts +0 -75
- package/templates/apps/api/src/routes/billing/usage.ts +0 -153
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +0 -687
- package/templates/apps/api/src/routes/checkout/index.ts +0 -13
- package/templates/apps/api/src/routes/checkout/sessions.ts +0 -197
- package/templates/apps/api/src/routes/deliverables/__tests__/index.test.ts +0 -393
- package/templates/apps/api/src/routes/deliverables/index.ts +0 -200
- package/templates/apps/api/src/routes/flow-checkouts/__tests__/index.test.ts +0 -443
- package/templates/apps/api/src/routes/flow-checkouts/index.ts +0 -82
- package/templates/apps/api/src/routes/flows/README.md +0 -147
- package/templates/apps/api/src/routes/flows/__tests__/index.test.ts +0 -752
- package/templates/apps/api/src/routes/flows/__tests__/recommender.test.ts +0 -671
- package/templates/apps/api/src/routes/flows/index.ts +0 -202
- package/templates/apps/api/src/routes/flows/recommender.ts +0 -443
- package/templates/apps/api/src/routes/project-listings/__tests__/copy-edit.test.ts +0 -815
- package/templates/apps/api/src/routes/project-listings/__tests__/me-route-precedence.test.ts +0 -221
- package/templates/apps/api/src/routes/project-listings/__tests__/tenant-isolation.test.ts +0 -566
- package/templates/apps/api/src/routes/project-listings/get.ts +0 -70
- package/templates/apps/api/src/routes/project-listings/list.ts +0 -47
- package/templates/apps/api/src/routes/project-listings/patch.ts +0 -136
- package/templates/apps/api/src/routes/schedule/__tests__/index.test.ts +0 -490
- package/templates/apps/api/src/routes/schedule/index.ts +0 -249
- package/templates/apps/api/src/routes/slack/__tests__/actions.test.ts +0 -385
- package/templates/apps/api/src/routes/slack/actions.ts +0 -177
- package/templates/apps/api/src/routes/slack/index.ts +0 -38
- package/templates/apps/api/src/routes/waitlist-experiments/__tests__/tenant-isolation.test.ts +0 -577
- package/templates/apps/api/src/routes/waitlist-experiments/comparison.ts +0 -78
- package/templates/apps/api/src/routes/waitlist-experiments/index.ts +0 -41
- package/templates/apps/api/src/routes/waitlist-experiments/list.ts +0 -64
- package/templates/apps/api/src/routes/waitlist-experiments/respond.ts +0 -42
- package/templates/apps/api/src/routes/waitlist-experiments/signup.ts +0 -84
- package/templates/apps/api/src/routes/waitlist-experiments/signups.ts +0 -119
- package/templates/apps/api/src/routes/waitlist-signups/__tests__/index.test.ts +0 -238
- package/templates/apps/api/src/routes/waitlist-signups/index.ts +0 -101
- package/templates/apps/api/src/routes/webhooks/README.md +0 -80
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +0 -315
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +0 -247
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +0 -270
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +0 -198
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +0 -633
- package/templates/apps/api/src/routes/webhooks/index.ts +0 -41
- package/templates/apps/api/src/routes/webhooks/stripe.ts +0 -481
- package/templates/apps/api/src/routes/workspaces/__tests__/config-tenant-isolation.test.ts +0 -356
- package/templates/apps/api/src/routes/workspaces/__tests__/tenant-isolation.test.ts +0 -671
- package/templates/apps/api/src/routes/workspaces/config.ts +0 -125
- package/templates/apps/api/src/routes/workspaces/index.ts +0 -194
- package/templates/apps/api/src/routes/workspaces/sessions.ts +0 -218
- package/templates/apps/api/src/services/__tests__/flow-aggregate.test.ts +0 -425
- package/templates/apps/api/src/services/__tests__/flow-engine.test.ts +0 -2840
- package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +0 -393
- package/templates/apps/api/src/services/__tests__/listing-csv-sweep.test.ts +0 -560
- package/templates/apps/api/src/services/__tests__/listing-promotion.test.ts +0 -684
- package/templates/apps/api/src/services/__tests__/playbook-compile.test.ts +0 -406
- package/templates/apps/api/src/services/__tests__/playbook-render.test.ts +0 -290
- package/templates/apps/api/src/services/__tests__/project-listing-decision.test.ts +0 -736
- package/templates/apps/api/src/services/__tests__/project-listing-flow.test.ts +0 -475
- package/templates/apps/api/src/services/__tests__/project-listing-issue.test.ts +0 -340
- package/templates/apps/api/src/services/__tests__/recommender-capture.test.ts +0 -983
- package/templates/apps/api/src/services/__tests__/tenant-context-cascade.test.ts +0 -71
- package/templates/apps/api/src/services/__tests__/tenant-context.test.ts +0 -379
- package/templates/apps/api/src/services/__tests__/usage-metering.test.ts +0 -768
- package/templates/apps/api/src/services/__tests__/waitlist-dashboard.test.ts +0 -1314
- package/templates/apps/api/src/services/__tests__/waitlist-experiments.test.ts +0 -341
- package/templates/apps/api/src/services/__tests__/waitlist-followup.test.ts +0 -567
- package/templates/apps/api/src/services/__tests__/waitlist-scoring.test.ts +0 -474
- package/templates/apps/api/src/services/__tests__/waitlist-signups.test.ts +0 -354
- package/templates/apps/api/src/services/flow-aggregate.ts +0 -232
- package/templates/apps/api/src/services/flow-checkouts.ts +0 -123
- package/templates/apps/api/src/services/flow-engine.ts +0 -1296
- package/templates/apps/api/src/services/lead-promotion.ts +0 -176
- package/templates/apps/api/src/services/listing-csv-sweep.ts +0 -455
- package/templates/apps/api/src/services/listing-promotion.ts +0 -342
- package/templates/apps/api/src/services/playbook-compile.ts +0 -398
- package/templates/apps/api/src/services/playbook-render.ts +0 -263
- package/templates/apps/api/src/services/project-listing-decision.ts +0 -510
- package/templates/apps/api/src/services/project-listing-flow.ts +0 -426
- package/templates/apps/api/src/services/project-listing-issue.ts +0 -251
- package/templates/apps/api/src/services/recommender-capture.ts +0 -835
- package/templates/apps/api/src/services/scheduling/cal-provider.ts +0 -392
- package/templates/apps/api/src/services/scheduling/index.ts +0 -63
- package/templates/apps/api/src/services/scheduling/types.ts +0 -88
- package/templates/apps/api/src/services/tenant-context.ts +0 -451
- package/templates/apps/api/src/services/usage-metering.ts +0 -699
- package/templates/apps/api/src/services/waitlist-dashboard.ts +0 -947
- package/templates/apps/api/src/services/waitlist-experiments.ts +0 -213
- package/templates/apps/api/src/services/waitlist-followup.ts +0 -486
- package/templates/apps/api/src/services/waitlist-scoring.ts +0 -166
- package/templates/apps/api/src/services/waitlist-signups.ts +0 -165
- package/templates/apps/mcp/__tests__/schedule_tools.test.ts +0 -242
- package/templates/apps/mcp/src/tools/create_booking.ts +0 -58
- package/templates/apps/mcp/src/tools/get_event_meta.ts +0 -55
- package/templates/apps/mcp/src/tools/get_flow.ts +0 -61
- package/templates/apps/mcp/src/tools/list_availability.ts +0 -67
- package/templates/apps/mcp/src/tools/submit_flow_step.ts +0 -83
- package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/page.tsx +0 -309
- package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/signups/[leadId]/page.tsx +0 -149
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/comparison.test.tsx +0 -273
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/list.test.tsx +0 -171
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/signup.test.tsx +0 -179
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ExperimentsTable.tsx +0 -116
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/OfferFunnelTable.tsx +0 -66
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/RollupTicker.tsx +0 -61
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ScoreTrace.tsx +0 -114
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/SignupsTable.tsx +0 -139
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/WaitlistComparisonTabs.tsx +0 -85
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/status-badges.tsx +0 -67
- package/templates/apps/web/app/[locale]/(authed)/waitlist/page.tsx +0 -179
- package/templates/apps/web/app/[locale]/blog/[[...slug]]/page.tsx +0 -178
- package/templates/apps/web/app/[locale]/dev/purchase/__tests__/actions.test.ts +0 -160
- package/templates/apps/web/app/[locale]/dev/purchase/error.tsx +0 -25
- package/templates/apps/web/app/[locale]/dev/purchase/page.tsx +0 -117
- package/templates/apps/web/app/[locale]/dev/purchase/purchase.actions.ts +0 -58
- package/templates/apps/web/app/[locale]/docs/[[...slug]]/page.tsx +0 -119
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/checkout.actions.test.ts +0 -287
- package/templates/apps/web/app/[locale]/flows/[slug]/checkout.actions.ts +0 -254
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx +0 -532
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsEmailStep.tsx +0 -121
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/ResultsClient.checkout.test.tsx +0 -457
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/page.test.tsx +0 -396
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx +0 -114
- package/templates/apps/web/app/[locale]/projects/apply/ListingApplicationClient.tsx +0 -110
- package/templates/apps/web/app/[locale]/projects/apply/__tests__/ListingApplicationClient.test.tsx +0 -96
- package/templates/apps/web/app/[locale]/schedule/__tests__/page.test.tsx +0 -117
- package/templates/apps/web/app/[locale]/schedule/confirmed/__tests__/page.test.tsx +0 -96
- package/templates/apps/web/app/[locale]/schedule/confirmed/page.tsx +0 -92
- package/templates/apps/web/app/[locale]/schedule/page.tsx +0 -82
- package/templates/apps/web/content/en/blog/README.txt +0 -25
- package/templates/apps/web/content/en/docs/index.mdx +0 -28
- package/templates/apps/web/lib/__mocks__/source-server.js +0 -31
- package/templates/apps/web/lib/blog-list-data.ts +0 -95
- package/templates/apps/web/lib/blog-post-data.ts +0 -79
- package/templates/apps/web/lib/source.ts +0 -34
- package/templates/apps/web/public/logomark-dark.svg +0 -4
- package/templates/apps/web/public/logomark-light.svg +0 -4
- package/templates/apps/web/source.config.ts +0 -78
package/dist/parse-args.d.ts
CHANGED
|
@@ -7,16 +7,23 @@
|
|
|
7
7
|
* composition is downstream work, so the default (`marketing`) reproduces
|
|
8
8
|
* today's fixed-tree scaffold exactly.
|
|
9
9
|
*/
|
|
10
|
-
export declare const RECOGNIZED_MODULES: readonly ["marketing", "docs", "multi-tenant", "scheduling"];
|
|
10
|
+
export declare const RECOGNIZED_MODULES: readonly ["marketing", "docs-blog", "multi-tenant", "workspace", "scheduling", "signal-collectors", "commerce"];
|
|
11
11
|
export type ModuleName = (typeof RECOGNIZED_MODULES)[number];
|
|
12
12
|
export interface CliOptions {
|
|
13
13
|
/** Positional target directory (the dir to scaffold into). Optional only
|
|
14
14
|
* when `help` or `version` is set. */
|
|
15
15
|
target: string | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* The site template to install (`--template`), validated against
|
|
18
|
+
* `RECOGNIZED_TEMPLATES` at parse time. Undefined when the flag is absent —
|
|
19
|
+
* and a run naming no template scaffolds exactly what it did before the
|
|
20
|
+
* flag existed (spec 5481 § 3 DD3).
|
|
21
|
+
*/
|
|
22
|
+
template: string | undefined;
|
|
16
23
|
install: boolean;
|
|
17
24
|
help: boolean;
|
|
18
25
|
version: boolean;
|
|
19
|
-
/** Selected modules (recognized: marketing, docs, multi-tenant, scheduling).
|
|
26
|
+
/** Selected modules (recognized: marketing, docs-blog, multi-tenant, scheduling).
|
|
20
27
|
* Defaults to ['marketing'] when the flag is absent. */
|
|
21
28
|
modules: string[];
|
|
22
29
|
/** Explicitly requested route surfaces (`--surfaces`). Empty when the flag is
|
package/dist/parse-args.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { parseArgs } from "node:util";
|
|
11
11
|
import { RECOGNIZED_SURFACES, parseSurfaces } from "./surfaces.js";
|
|
12
|
+
import { RECOGNIZED_TEMPLATES } from "./template-names.js";
|
|
12
13
|
/**
|
|
13
14
|
* The recognized module vocabulary for the `--modules` selector.
|
|
14
15
|
*
|
|
@@ -20,9 +21,12 @@ import { RECOGNIZED_SURFACES, parseSurfaces } from "./surfaces.js";
|
|
|
20
21
|
*/
|
|
21
22
|
export const RECOGNIZED_MODULES = [
|
|
22
23
|
"marketing",
|
|
23
|
-
"docs",
|
|
24
|
+
"docs-blog",
|
|
24
25
|
"multi-tenant",
|
|
26
|
+
"workspace",
|
|
25
27
|
"scheduling",
|
|
28
|
+
"signal-collectors",
|
|
29
|
+
"commerce",
|
|
26
30
|
];
|
|
27
31
|
/** The default module selection when `--modules` is absent (reproduces today's scaffold). */
|
|
28
32
|
const DEFAULT_MODULES = ["marketing"];
|
|
@@ -49,6 +53,16 @@ Options:
|
|
|
49
53
|
Defaults to \`marketing\`, which reproduces today's
|
|
50
54
|
scaffold. A module is a composable unit across the
|
|
51
55
|
three tiers (data · API · presentation).
|
|
56
|
+
--template <name> The site template to install (recognized:
|
|
57
|
+
${RECOGNIZED_TEMPLATES.join(", ")}).
|
|
58
|
+
A template is a page roster plus seed content and
|
|
59
|
+
config — never code. Its required modules are ADDED
|
|
60
|
+
to whatever \`--modules\` names, so the two compose.
|
|
61
|
+
A template needing only free modules installs from
|
|
62
|
+
the tree this package ships, with no key and no
|
|
63
|
+
network call; one needing a paid module needs
|
|
64
|
+
AILK_LICENSE_KEY to cover every module it requires,
|
|
65
|
+
and refuses naming whichever it does not.
|
|
52
66
|
--surfaces <csv> Comma-separated route surfaces to scaffold IN
|
|
53
67
|
ADDITION to the core set (/, /about, /contact, /faq,
|
|
54
68
|
/thanks). Recognized: ${RECOGNIZED_SURFACES.join(", ")}.
|
|
@@ -100,6 +114,7 @@ export function parseCliArgs(argv) {
|
|
|
100
114
|
allowPositionals: true,
|
|
101
115
|
options: {
|
|
102
116
|
modules: { type: "string" },
|
|
117
|
+
template: { type: "string" },
|
|
103
118
|
surfaces: { type: "string" },
|
|
104
119
|
"sync-architecture": { type: "string" },
|
|
105
120
|
"sync-catalog": { type: "boolean", default: false },
|
|
@@ -117,6 +132,7 @@ export function parseCliArgs(argv) {
|
|
|
117
132
|
const version = parsed.values.version === true;
|
|
118
133
|
const install = parsed.values.install === true;
|
|
119
134
|
const modules = parseModules(parsed.values.modules);
|
|
135
|
+
const template = parseTemplate(parsed.values.template);
|
|
120
136
|
const surfaces = parseSurfaceList(parsed.values.surfaces);
|
|
121
137
|
const syncArchitecture = parsed.values["sync-architecture"];
|
|
122
138
|
const syncCatalog = parsed.values["sync-catalog"] === true;
|
|
@@ -136,6 +152,7 @@ export function parseCliArgs(argv) {
|
|
|
136
152
|
if (help || version) {
|
|
137
153
|
return {
|
|
138
154
|
target: undefined,
|
|
155
|
+
template: undefined,
|
|
139
156
|
install,
|
|
140
157
|
help,
|
|
141
158
|
version,
|
|
@@ -153,6 +170,7 @@ export function parseCliArgs(argv) {
|
|
|
153
170
|
}
|
|
154
171
|
return {
|
|
155
172
|
target: parsed.positionals[0],
|
|
173
|
+
template,
|
|
156
174
|
install,
|
|
157
175
|
help,
|
|
158
176
|
version,
|
|
@@ -171,6 +189,7 @@ export function parseCliArgs(argv) {
|
|
|
171
189
|
}
|
|
172
190
|
return {
|
|
173
191
|
target: parsed.positionals[0],
|
|
192
|
+
template,
|
|
174
193
|
install,
|
|
175
194
|
help,
|
|
176
195
|
version,
|
|
@@ -181,6 +200,32 @@ export function parseCliArgs(argv) {
|
|
|
181
200
|
syncArchitecture: undefined,
|
|
182
201
|
};
|
|
183
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Validate the `--template` value against the declared template set
|
|
205
|
+
* (spec 5481 § 3 DD1).
|
|
206
|
+
*
|
|
207
|
+
* Validated HERE, in the parser, so an unrecognized template fails at parse
|
|
208
|
+
* time as a `ParseError` exactly like an unrecognized module or surface does.
|
|
209
|
+
* Splitting it out to `programmatic.ts` would make `--template` the one flag
|
|
210
|
+
* in this CLI whose bad value surfaces somewhere else.
|
|
211
|
+
*
|
|
212
|
+
* The names come from `template-names.ts` rather than from
|
|
213
|
+
* `TEMPLATE_MANIFESTS` itself, and that file's doc comment records the
|
|
214
|
+
* measured reason: importing the registry here closes a runtime cycle
|
|
215
|
+
* through `lib/module-tier.ts`. `TEMPLATE_MANIFESTS` remains the registry;
|
|
216
|
+
* the two sets are asserted equal by the suite.
|
|
217
|
+
*
|
|
218
|
+
* @throws ParseError on an undeclared template name, naming the declared set.
|
|
219
|
+
*/
|
|
220
|
+
function parseTemplate(raw) {
|
|
221
|
+
if (raw === undefined)
|
|
222
|
+
return undefined;
|
|
223
|
+
const name = raw.trim();
|
|
224
|
+
if (!RECOGNIZED_TEMPLATES.includes(name)) {
|
|
225
|
+
throw new ParseError(`unknown template "${raw}". Recognized: ${RECOGNIZED_TEMPLATES.join(", ")}`);
|
|
226
|
+
}
|
|
227
|
+
return name;
|
|
228
|
+
}
|
|
184
229
|
/**
|
|
185
230
|
* Parse + validate the `--surfaces` CSV, re-throwing an unrecognized-surface
|
|
186
231
|
* failure as a ParseError so every CLI parse failure reaches the orchestrator's
|
package/dist/programmatic.d.ts
CHANGED
|
@@ -29,7 +29,23 @@ export interface ScaffoldOptions {
|
|
|
29
29
|
*/
|
|
30
30
|
preset?: string;
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
32
|
+
* The site template to install (`--template`), a `TEMPLATE_MANIFESTS` key.
|
|
33
|
+
*
|
|
34
|
+
* Consumed ONLY by the async `scaffold()` below. `scaffoldSync` stays
|
|
35
|
+
* template-oblivious for exactly the reason it stays licensing-oblivious
|
|
36
|
+
* (spec 5481 § 3 DD3): a call naming no template takes a code path with no
|
|
37
|
+
* new statement in it, which is a stronger byte-identity guarantee than a
|
|
38
|
+
* test asserting the output happens to match.
|
|
39
|
+
*
|
|
40
|
+
* What a template DOES change is `modules` — `scaffold()` unions the
|
|
41
|
+
* template's `requires` with whatever `--modules` named and passes THAT
|
|
42
|
+
* list to `scaffoldSync` (DD2), so the resolved surfaces, the emitted
|
|
43
|
+
* `architecture.yaml` and the generated nav are all functions of the union.
|
|
44
|
+
*/
|
|
45
|
+
template?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Selected modules (recognized: marketing, docs-blog, multi-tenant,
|
|
48
|
+
* scheduling). Defaults to
|
|
33
49
|
* ['marketing']. The scaffolder does NOT yet vary the copied template tree by
|
|
34
50
|
* module (per-module template composition is downstream work), so the copied
|
|
35
51
|
* files are today's fixed-tree scaffold — BUT the derived `architecture.yaml`
|
|
@@ -69,6 +85,8 @@ export interface ScaffoldOptions {
|
|
|
69
85
|
licenseKey?: string;
|
|
70
86
|
/** Overrides `AILK_MODULE_REGISTRY_URL` / the built-in module-distribution default. */
|
|
71
87
|
moduleRegistryUrl?: string;
|
|
88
|
+
/** Overrides `AILK_TEMPLATE_REGISTRY_URL` / the built-in template-distribution default. */
|
|
89
|
+
templateRegistryUrl?: string;
|
|
72
90
|
/** Injected fetch for tests. Never a live network call in CI or in this repo's evidence runs. */
|
|
73
91
|
fetchImpl?: typeof fetch;
|
|
74
92
|
}
|
|
@@ -97,9 +115,17 @@ export declare function scaffoldSync(opts: ScaffoldOptions): ScaffoldResult;
|
|
|
97
115
|
* read and no network call (the regression floor). A paid module with
|
|
98
116
|
* no key, or a licence the module distribution rejects, throws
|
|
99
117
|
* `LicenseRequiredError` HERE — before anything is written to disk.
|
|
100
|
-
* 3. `scaffoldSync` — unchanged, today's local `templates/` copy
|
|
118
|
+
* 3. `scaffoldSync` — unchanged, today's local `templates/` copy, over the
|
|
119
|
+
* EFFECTIVE module set (see 0 below).
|
|
101
120
|
* 4. Merge each resolved paid module's fetched bundle into the freshly
|
|
102
121
|
* scaffolded tree (`extractModuleBundle`).
|
|
122
|
+
* 5. Install the named site template LAST (#5481; parent record D9), from
|
|
123
|
+
* its fetched bundle or — when it requires only free modules — from the
|
|
124
|
+
* `site-templates/` tree this package ships.
|
|
125
|
+
*
|
|
126
|
+
* And, ahead of all five when `opts.template` is set (0): resolve the
|
|
127
|
+
* template and union its `requires` with `opts.modules`, so every step above
|
|
128
|
+
* runs over the EFFECTIVE module set rather than the requested one.
|
|
103
129
|
*
|
|
104
130
|
* `scaffoldSync` itself stays licensing-oblivious on purpose: every existing
|
|
105
131
|
* synchronous caller (this package's own test suite, overwhelmingly) is
|
package/dist/programmatic.js
CHANGED
|
@@ -21,15 +21,26 @@ import { fileURLToPath } from "node:url";
|
|
|
21
21
|
import { CATALOG_TARGET_PATH, writeComponentCatalog, } from "./component-catalog.js";
|
|
22
22
|
import { copyTemplate } from "./copy.js";
|
|
23
23
|
import { deriveArchitecture } from "./derive-architecture.js";
|
|
24
|
-
import { extractModuleBundle } from "./lib/extract-module-bundle.js";
|
|
24
|
+
import { extractModuleBundle, installBundleTree, } from "./lib/extract-module-bundle.js";
|
|
25
|
+
import { fetchTemplate } from "./lib/fetch-template.js";
|
|
25
26
|
import { resolvePaidModules } from "./lib/module-license-gate.js";
|
|
26
27
|
import { classifyModules } from "./lib/module-tier.js";
|
|
27
28
|
import { normalizeModules } from "./module-architecture.js";
|
|
28
29
|
import { NAV_CONFIG_PATH, excludedRoutePaths, renderNavConfig, resolveSurfaces, } from "./surfaces.js";
|
|
29
30
|
import { syncRoutes } from "./sync-routes.js";
|
|
31
|
+
import { resolveTemplate, templateDelivery } from "./template-manifest.js";
|
|
30
32
|
import { validateTarget } from "./validate.js";
|
|
31
33
|
const PKG_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
32
34
|
const TEMPLATES_DIR = resolve(PKG_ROOT, "templates");
|
|
35
|
+
/**
|
|
36
|
+
* Where a Free site template's files are read from on the keyless path — and
|
|
37
|
+
* why it is BESIDE `templates/` rather than inside it (parent record D14):
|
|
38
|
+
* `copyTemplate` copies the whole `templates/` tree into EVERY scaffold, so a
|
|
39
|
+
* per-template subtree there would land in scaffolds that asked for a
|
|
40
|
+
* different template, or for none. It reaches the published tarball through
|
|
41
|
+
* `package.json` `files` instead.
|
|
42
|
+
*/
|
|
43
|
+
const SITE_TEMPLATES_DIR = resolve(PKG_ROOT, "site-templates");
|
|
33
44
|
const VERSIONS_FILE = resolve(PKG_ROOT, "versions.json");
|
|
34
45
|
function readVersionMap() {
|
|
35
46
|
if (!existsSync(VERSIONS_FILE)) {
|
|
@@ -114,9 +125,17 @@ export function scaffoldSync(opts) {
|
|
|
114
125
|
* read and no network call (the regression floor). A paid module with
|
|
115
126
|
* no key, or a licence the module distribution rejects, throws
|
|
116
127
|
* `LicenseRequiredError` HERE — before anything is written to disk.
|
|
117
|
-
* 3. `scaffoldSync` — unchanged, today's local `templates/` copy
|
|
128
|
+
* 3. `scaffoldSync` — unchanged, today's local `templates/` copy, over the
|
|
129
|
+
* EFFECTIVE module set (see 0 below).
|
|
118
130
|
* 4. Merge each resolved paid module's fetched bundle into the freshly
|
|
119
131
|
* scaffolded tree (`extractModuleBundle`).
|
|
132
|
+
* 5. Install the named site template LAST (#5481; parent record D9), from
|
|
133
|
+
* its fetched bundle or — when it requires only free modules — from the
|
|
134
|
+
* `site-templates/` tree this package ships.
|
|
135
|
+
*
|
|
136
|
+
* And, ahead of all five when `opts.template` is set (0): resolve the
|
|
137
|
+
* template and union its `requires` with `opts.modules`, so every step above
|
|
138
|
+
* runs over the EFFECTIVE module set rather than the requested one.
|
|
120
139
|
*
|
|
121
140
|
* `scaffoldSync` itself stays licensing-oblivious on purpose: every existing
|
|
122
141
|
* synchronous caller (this package's own test suite, overwhelmingly) is
|
|
@@ -124,23 +143,106 @@ export function scaffoldSync(opts) {
|
|
|
124
143
|
* with a paid module in `opts.modules` exercises the gate at all.
|
|
125
144
|
*/
|
|
126
145
|
export async function scaffold(opts) {
|
|
127
|
-
|
|
146
|
+
// Resolved first, and by the registry rather than re-validated here: the
|
|
147
|
+
// name already passed `parse-args.ts`, and `resolveTemplate` throws a
|
|
148
|
+
// `TemplateError` for a programmatic caller that skipped the parser.
|
|
149
|
+
const template = opts.template !== undefined ? resolveTemplate(opts.template) : undefined;
|
|
150
|
+
// The effective module set: union(template.requires, parsed --modules),
|
|
151
|
+
// normalized through `normalizeModules` — the single ordering authority
|
|
152
|
+
// (parent record D7). Order-independence falls out rather than being
|
|
153
|
+
// engineered: it returns `RECOGNIZED_MODULES.filter(...)`, declaration-
|
|
154
|
+
// ordered and deduped, so the same SET in any argument order is the same
|
|
155
|
+
// list.
|
|
156
|
+
const modules = normalizeModules([
|
|
157
|
+
...(template?.requires ?? []),
|
|
158
|
+
...(opts.modules ?? ["marketing"]),
|
|
159
|
+
]);
|
|
160
|
+
const { paid } = classifyModules(modules);
|
|
128
161
|
const bundles = await resolvePaidModules({
|
|
129
162
|
paidModules: paid,
|
|
130
163
|
licenseKey: opts.licenseKey,
|
|
164
|
+
...(template !== undefined ? { templateName: template.name } : {}),
|
|
131
165
|
...(opts.moduleRegistryUrl !== undefined
|
|
132
166
|
? { registryUrl: opts.moduleRegistryUrl }
|
|
133
167
|
: {}),
|
|
134
168
|
...(opts.fetchImpl !== undefined ? { fetchImpl: opts.fetchImpl } : {}),
|
|
135
169
|
});
|
|
136
|
-
|
|
170
|
+
// The template's OWN bundle, fetched here — before `scaffoldSync` — so the
|
|
171
|
+
// fourth refusal (the template's bundle unpublished, parent record D17 /
|
|
172
|
+
// spec 5481 § 3 DD5 row 4) throws with the target still non-existent, like
|
|
173
|
+
// the three module refusals above it (DD6). It is EXTRACTED last, below.
|
|
174
|
+
//
|
|
175
|
+
// The keyless path never reaches this at all: a template requiring only
|
|
176
|
+
// free modules classifies to an empty paid set, so `resolvePaidModules`
|
|
177
|
+
// returned at its first branch without reading `process.env` or the
|
|
178
|
+
// network, and `templateDelivery` sends us past this too (DD7). That is
|
|
179
|
+
// what makes record D11's no-network guarantee structural rather than
|
|
180
|
+
// merely likely.
|
|
181
|
+
const templateBundle = template !== undefined && templateDelivery(template) === "paid"
|
|
182
|
+
? await fetchTemplate({
|
|
183
|
+
templateName: template.name,
|
|
184
|
+
token: opts.licenseKey ?? process.env["AILK_LICENSE_KEY"] ?? "",
|
|
185
|
+
...(opts.templateRegistryUrl !== undefined
|
|
186
|
+
? { registryUrl: opts.templateRegistryUrl }
|
|
187
|
+
: {}),
|
|
188
|
+
...(opts.fetchImpl !== undefined ? { fetchImpl: opts.fetchImpl } : {}),
|
|
189
|
+
})
|
|
190
|
+
: undefined;
|
|
191
|
+
// `modules` — the union — is what `scaffoldSync` receives, not `opts`
|
|
192
|
+
// (spec 5481 § 3 DD2). `resolveSurfaces`, `deriveArchitecture` and
|
|
193
|
+
// `renderNavConfig` are all computed INSIDE `scaffoldSync` from
|
|
194
|
+
// `opts.modules`, so a union computed for the licence classification alone
|
|
195
|
+
// would gate correctly and emit the wrong surfaces, the wrong
|
|
196
|
+
// `architecture.yaml` and the wrong nav — a silent wrong result, not a
|
|
197
|
+
// failure.
|
|
198
|
+
const result = scaffoldSync({ ...opts, modules });
|
|
199
|
+
// Threaded to extractModuleBundle for `package.json` patch targets'
|
|
200
|
+
// `workspace:*` substitution (spec 5418 § 3 D2) — the same version map
|
|
201
|
+
// `scaffoldSync` already reads for `copyTemplate`, re-read here since
|
|
202
|
+
// `scaffold()` doesn't share `scaffoldSync`'s call frame.
|
|
203
|
+
const versions = readVersionMap();
|
|
137
204
|
for (const [moduleName, bytes] of bundles) {
|
|
138
205
|
await extractModuleBundle({
|
|
139
206
|
bytes,
|
|
140
207
|
targetDir: result.targetDir,
|
|
141
208
|
moduleName,
|
|
209
|
+
versions,
|
|
142
210
|
});
|
|
143
211
|
}
|
|
212
|
+
// The template lands LAST — after `scaffoldSync` and after every module
|
|
213
|
+
// bundle (parent record D9) — so its content and config files sit on top of
|
|
214
|
+
// everything derived. It shapes the derived artifacts by DECLARING (its
|
|
215
|
+
// `requires` are in the union above; its roster rows are what the nav is
|
|
216
|
+
// generated from), never by landing a code file: the nav is already written
|
|
217
|
+
// by `scaffoldSync` from `renderNavConfig(enabledSurfaces)`, and record
|
|
218
|
+
// §5.2 forbids a `.ts` in any `site-templates/` `installMap` destination.
|
|
219
|
+
//
|
|
220
|
+
// One install step, two source roots (DD7): an extracted staging dir on the
|
|
221
|
+
// paid path, `site-templates/<name>/` on disk on the Free one.
|
|
222
|
+
if (template !== undefined) {
|
|
223
|
+
if (templateBundle !== undefined) {
|
|
224
|
+
await extractModuleBundle({
|
|
225
|
+
bytes: templateBundle,
|
|
226
|
+
targetDir: result.targetDir,
|
|
227
|
+
moduleName: template.name,
|
|
228
|
+
templateRequires: template.requires,
|
|
229
|
+
versions,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
const sourceRoot = join(SITE_TEMPLATES_DIR, template.name);
|
|
234
|
+
if (!existsSync(sourceRoot)) {
|
|
235
|
+
throw new Error(`template "${template.name}": ${sourceRoot} is missing — the published package must carry site-templates/ in its "files" list`);
|
|
236
|
+
}
|
|
237
|
+
installBundleTree({
|
|
238
|
+
sourceRoot,
|
|
239
|
+
targetDir: result.targetDir,
|
|
240
|
+
name: template.name,
|
|
241
|
+
templateRequires: template.requires,
|
|
242
|
+
versions,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
144
246
|
return result;
|
|
145
247
|
}
|
|
146
248
|
// Re-exported so a consumer of the async `scaffold()` above can type its own
|
|
@@ -194,10 +296,15 @@ export function syncArchitectureSync(opts) {
|
|
|
194
296
|
* stats paths and returns strings, and never writes or deletes.
|
|
195
297
|
*
|
|
196
298
|
* Routes only, deliberately. A surface's content directories are informational
|
|
197
|
-
* (`Surface.content`), not drift
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
299
|
+
* (`Surface.content`), not drift, so sweeping content here would tell an
|
|
300
|
+
* operator to delete directories that merely hold their own copy.
|
|
301
|
+
*
|
|
302
|
+
* This used to rest on a stronger claim — that `apps/web/content/<locale>/
|
|
303
|
+
* {blog,docs}` must exist in EVERY scaffold, because `source.config.ts`
|
|
304
|
+
* resolved those paths at build time whether or not the route mounted. D-46
|
|
305
|
+
* retired it: `source.config.ts` leaves with the docs + blog module (#5365 D3),
|
|
306
|
+
* so a Free scaffold defines no Fumadocs collection and carries neither
|
|
307
|
+
* directory.
|
|
201
308
|
*/
|
|
202
309
|
function findUndeclaredSurfacePaths(targetAbs, enabled) {
|
|
203
310
|
return excludedRoutePaths(enabled)
|
package/dist/surfaces.d.ts
CHANGED
|
@@ -1,36 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Route-surface registry — which public routes a scaffolded site carries.
|
|
3
|
-
*
|
|
4
|
-
* A SURFACE is one public route surface of the presentation tier (see
|
|
5
|
-
* architecture.yaml `composition_model`): a hub route plus the detail route and
|
|
6
|
-
* content directories that feed it. The registry is the single source of the
|
|
7
|
-
* surface vocabulary, in the same spirit as `module-architecture.ts` is the
|
|
8
|
-
* single source of the module→architecture-slice mapping — a code registry, not
|
|
9
|
-
* N hand-maintained template trees.
|
|
10
|
-
*
|
|
11
|
-
* Two tiers:
|
|
12
|
-
*
|
|
13
|
-
* - CORE_SURFACE_PATHS — the routes every AILK site has regardless of what
|
|
14
|
-
* the business is (`/`, `/about`, `/contact`, `/faq`, `/thanks/{slug}`).
|
|
15
|
-
* Always scaffolded, never gateable.
|
|
16
|
-
* - GATEABLE_SURFACES — everything else. A surface is scaffolded ONLY when
|
|
17
|
-
* the declared architecture carries it; otherwise its route directories are
|
|
18
|
-
* skipped by `copyTemplate` and the routes are ABSENT from the scaffold,
|
|
19
|
-
* not present-and-placeholdering (spec #4154 D1).
|
|
20
|
-
*
|
|
21
|
-
* The default (`--modules marketing`) enables NO gateable surface: every one of
|
|
22
|
-
* them is a claim about the business that the scaffolder cannot make. The path
|
|
23
|
-
* from there is the `suggest-site-pages` → `scaffold-page` skill pair that
|
|
24
|
-
* already ships in `templates/.claude/skills/`.
|
|
25
|
-
*
|
|
26
|
-
* Zero runtime dependencies — create-ailk is a published, standalone CLI.
|
|
27
|
-
*
|
|
28
|
-
* Deliberately imports nothing from `parse-args.ts`: that module imports THIS
|
|
29
|
-
* one (for the `--surfaces` vocabulary in its help text), so a back-import
|
|
30
|
-
* would close a cycle. The module vocabulary is re-stated here as the keys of
|
|
31
|
-
* `MODULE_SURFACES`, and `__tests__/surface-gating.test.ts` asserts those keys
|
|
32
|
-
* equal `RECOGNIZED_MODULES` so the two cannot drift.
|
|
33
|
-
*/
|
|
34
1
|
/**
|
|
35
2
|
* Route paths (site-relative, for the record written into `architecture.yaml`)
|
|
36
3
|
* that every scaffold carries.
|
|
@@ -88,9 +55,18 @@ export declare const RECOGNIZED_SURFACES: readonly string[];
|
|
|
88
55
|
* Surfaces each recognized module contributes.
|
|
89
56
|
*
|
|
90
57
|
* `marketing` (the default) contributes NONE — a default scaffold is core-only.
|
|
91
|
-
* `docs` mounts
|
|
92
|
-
*
|
|
93
|
-
*
|
|
58
|
+
* `docs-blog` mounts BOTH Fumadocs presentation surfaces: D-46 made docs and
|
|
59
|
+
* blog one Pro module, so `/blog` is no longer a Free surface that any scaffold
|
|
60
|
+
* can turn on. `multi-tenant` contributes no surface of its own — it USED to
|
|
61
|
+
* carry `flows`, but D-48 reassigns that surface to `signal-collectors`
|
|
62
|
+
* (#5366), which now brings the flow-stepper surface with its API-backed
|
|
63
|
+
* engine; `scheduling` brings the booking page and the confirmation page the
|
|
64
|
+
* module owns.
|
|
65
|
+
*
|
|
66
|
+
* A surface named here is MODULE-GATED: `resolveSurfaces` refuses it as an
|
|
67
|
+
* explicit `--surfaces` value unless the module that owns it was selected
|
|
68
|
+
* (#5365 FR5). Without that, `--surfaces blog` on a keyless scaffold would
|
|
69
|
+
* declare a surface whose route files the starter no longer contains.
|
|
94
70
|
*/
|
|
95
71
|
export declare const MODULE_SURFACES: Record<string, readonly string[]>;
|
|
96
72
|
/** Thrown on an unrecognized surface id. Mirrors parse-args' ParseError shape. */
|
package/dist/surfaces.js
CHANGED
|
@@ -164,6 +164,19 @@ export const GATEABLE_SURFACES = [
|
|
|
164
164
|
routes: [`${ROUTE_ROOT}/partners`],
|
|
165
165
|
content: ["content/<locale>/PartnersList", "content/<locale>/Partner"],
|
|
166
166
|
},
|
|
167
|
+
{
|
|
168
|
+
// Module-LESS by construction (#5479 D16): no MODULE_SURFACES value names
|
|
169
|
+
// it, so a site template may enable it by roster alone. Same for
|
|
170
|
+
// `privacy` and `terms` below.
|
|
171
|
+
id: "pricing",
|
|
172
|
+
routes: [`${ROUTE_ROOT}/pricing`],
|
|
173
|
+
content: ["content/<locale>/Pricing"],
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
id: "privacy",
|
|
177
|
+
routes: [`${ROUTE_ROOT}/privacy`],
|
|
178
|
+
content: ["content/<locale>/PrivacyPage"],
|
|
179
|
+
},
|
|
167
180
|
{
|
|
168
181
|
id: "products",
|
|
169
182
|
routes: [`${ROUTE_ROOT}/products`],
|
|
@@ -208,6 +221,11 @@ export const GATEABLE_SURFACES = [
|
|
|
208
221
|
routes: [`${ROUTE_ROOT}/tech-articles`],
|
|
209
222
|
content: ["content/<locale>/TechArticle"],
|
|
210
223
|
},
|
|
224
|
+
{
|
|
225
|
+
id: "terms",
|
|
226
|
+
routes: [`${ROUTE_ROOT}/terms`],
|
|
227
|
+
content: ["content/<locale>/TermsPage"],
|
|
228
|
+
},
|
|
211
229
|
{
|
|
212
230
|
id: "testimonials",
|
|
213
231
|
routes: [`${ROUTE_ROOT}/testimonials`],
|
|
@@ -230,15 +248,32 @@ export const RECOGNIZED_SURFACES = GATEABLE_SURFACES.map((s) => s.id);
|
|
|
230
248
|
* Surfaces each recognized module contributes.
|
|
231
249
|
*
|
|
232
250
|
* `marketing` (the default) contributes NONE — a default scaffold is core-only.
|
|
233
|
-
* `docs` mounts
|
|
234
|
-
*
|
|
235
|
-
*
|
|
251
|
+
* `docs-blog` mounts BOTH Fumadocs presentation surfaces: D-46 made docs and
|
|
252
|
+
* blog one Pro module, so `/blog` is no longer a Free surface that any scaffold
|
|
253
|
+
* can turn on. `multi-tenant` contributes no surface of its own — it USED to
|
|
254
|
+
* carry `flows`, but D-48 reassigns that surface to `signal-collectors`
|
|
255
|
+
* (#5366), which now brings the flow-stepper surface with its API-backed
|
|
256
|
+
* engine; `scheduling` brings the booking page and the confirmation page the
|
|
257
|
+
* module owns.
|
|
258
|
+
*
|
|
259
|
+
* A surface named here is MODULE-GATED: `resolveSurfaces` refuses it as an
|
|
260
|
+
* explicit `--surfaces` value unless the module that owns it was selected
|
|
261
|
+
* (#5365 FR5). Without that, `--surfaces blog` on a keyless scaffold would
|
|
262
|
+
* declare a surface whose route files the starter no longer contains.
|
|
236
263
|
*/
|
|
237
264
|
export const MODULE_SURFACES = {
|
|
238
265
|
marketing: [],
|
|
239
|
-
docs: ["docs"],
|
|
240
|
-
"multi-tenant": [
|
|
266
|
+
"docs-blog": ["docs", "blog"],
|
|
267
|
+
"multi-tenant": [],
|
|
268
|
+
// `workspace` contributes zero gateable surfaces (#5371 D4): every route it
|
|
269
|
+
// owns lives in the `(authed)` segment, never in the public marketing route
|
|
270
|
+
// tree a template's roster composes — the same reason `multi-tenant` above
|
|
271
|
+
// carries an empty list. The entry itself is still required: surface-gating's
|
|
272
|
+
// vocabulary assertion covers exactly the recognized module set.
|
|
273
|
+
workspace: [],
|
|
241
274
|
scheduling: ["schedule"],
|
|
275
|
+
"signal-collectors": ["flows"],
|
|
276
|
+
commerce: [],
|
|
242
277
|
};
|
|
243
278
|
/** Thrown on an unrecognized surface id. Mirrors parse-args' ParseError shape. */
|
|
244
279
|
export class SurfaceError extends Error {
|
|
@@ -290,14 +325,33 @@ export function resolveSurfaces(modules = ["marketing"], explicit = []) {
|
|
|
290
325
|
for (const s of contributed)
|
|
291
326
|
set.add(s);
|
|
292
327
|
}
|
|
328
|
+
const selected = new Set(modules.map((m) => m.trim().toLowerCase()));
|
|
293
329
|
for (const s of explicit) {
|
|
294
330
|
if (!RECOGNIZED_SURFACES.includes(s)) {
|
|
295
331
|
throw new SurfaceError(`unknown surface "${s}". Recognized: ${RECOGNIZED_SURFACES.join(", ")}`);
|
|
296
332
|
}
|
|
333
|
+
// A surface a module OWNS cannot be declared without that module (#5365
|
|
334
|
+
// FR5/D12). The starter no longer carries the route files, so a bare
|
|
335
|
+
// `--surfaces blog` would declare a surface that serves 404 with no error
|
|
336
|
+
// anywhere — the silent-wrong-result this refusal closes. Keyed on
|
|
337
|
+
// MODULE_SURFACES, never on a surface id, so every module inherits it.
|
|
338
|
+
const owner = moduleOwningSurface(s);
|
|
339
|
+
if (owner !== undefined && !selected.has(owner)) {
|
|
340
|
+
throw new SurfaceError(`surface "${s}" is delivered by the "${owner}" module — add it with ` +
|
|
341
|
+
`--modules ${owner} (a paid module also needs AILK_LICENSE_KEY).`);
|
|
342
|
+
}
|
|
297
343
|
set.add(s);
|
|
298
344
|
}
|
|
299
345
|
return [...set].sort();
|
|
300
346
|
}
|
|
347
|
+
/** The module that owns a gateable surface, or undefined if no module does. */
|
|
348
|
+
function moduleOwningSurface(surface) {
|
|
349
|
+
for (const [moduleName, contributed] of Object.entries(MODULE_SURFACES)) {
|
|
350
|
+
if (contributed.includes(surface))
|
|
351
|
+
return moduleName;
|
|
352
|
+
}
|
|
353
|
+
return undefined;
|
|
354
|
+
}
|
|
301
355
|
/**
|
|
302
356
|
* The template-relative route directories to SKIP when copying the template —
|
|
303
357
|
* every gateable surface not in `enabled`.
|
|
@@ -379,6 +433,11 @@ export const ROUTE_SINCE = {
|
|
|
379
433
|
"launch-service": "0.0.0",
|
|
380
434
|
partners: "0.0.0",
|
|
381
435
|
"partners/[slug]": "0.0.0",
|
|
436
|
+
// #5479 — three genuinely new route directories, so they attach at today's
|
|
437
|
+
// fixed-group version rather than the "predates the manifest" 0.0.0 above,
|
|
438
|
+
// the same rule #5048 ("projects/apply") and #5014 ("(authed)") applied.
|
|
439
|
+
pricing: "0.22.0",
|
|
440
|
+
privacy: "0.22.0",
|
|
382
441
|
products: "0.0.0",
|
|
383
442
|
"products/[slug]": "0.0.0",
|
|
384
443
|
projects: "0.0.0",
|
|
@@ -398,6 +457,7 @@ export const ROUTE_SINCE = {
|
|
|
398
457
|
"software/[slug]": "0.0.0",
|
|
399
458
|
"tech-articles": "0.0.0",
|
|
400
459
|
"tech-articles/[slug]": "0.0.0",
|
|
460
|
+
terms: "0.22.0",
|
|
401
461
|
testimonials: "0.0.0",
|
|
402
462
|
"thanks/[slug]": "0.14.0",
|
|
403
463
|
"use-cases": "0.0.0",
|
|
@@ -414,6 +474,12 @@ export const ROUTE_SINCE = {
|
|
|
414
474
|
// choice #5048's "projects/apply" entry already made for the same reason.
|
|
415
475
|
"(authed)": "0.18.0",
|
|
416
476
|
"(authed)/account": "0.18.0",
|
|
477
|
+
// #5371 — the workspace module's three settings routes. Genuinely new route
|
|
478
|
+
// directories, so they attach at today's fixed-group version rather than the
|
|
479
|
+
// "predates the manifest" 0.0.0 above, the same rule #5479 and #5048 applied.
|
|
480
|
+
"(authed)/workspace": "0.22.1",
|
|
481
|
+
"(authed)/workspace/site": "0.22.1",
|
|
482
|
+
"(authed)/workspace/team": "0.22.1",
|
|
417
483
|
"(authed)/waitlist": "0.18.0",
|
|
418
484
|
"(authed)/waitlist/[experimentId]": "0.18.0",
|
|
419
485
|
"(authed)/waitlist/[experimentId]/signups/[leadId]": "0.18.0",
|
|
@@ -460,6 +526,9 @@ export const ROUTE_DEPENDENCIES = {
|
|
|
460
526
|
`${ROUTE_ROOT}/(authed)/waitlist/components/ForbiddenState.tsx`,
|
|
461
527
|
`${ROUTE_ROOT}/(authed)/waitlist/format.ts`,
|
|
462
528
|
],
|
|
529
|
+
"(authed)/workspace": ["apps/web/lib/server-api.ts"],
|
|
530
|
+
"(authed)/workspace/site": ["apps/web/lib/server-api.ts"],
|
|
531
|
+
"(authed)/workspace/team": ["apps/web/lib/server-api.ts"],
|
|
463
532
|
"(authed)/waitlist": ["apps/web/lib/server-api.ts"],
|
|
464
533
|
};
|
|
465
534
|
/**
|
|
@@ -474,6 +543,9 @@ export const ROUTE_DEPENDENCIES = {
|
|
|
474
543
|
export const ROUTE_MESSAGE_NAMESPACES = {
|
|
475
544
|
"sign-in": ["signIn"],
|
|
476
545
|
"(authed)/account": ["account"],
|
|
546
|
+
"(authed)/workspace": ["workspace"],
|
|
547
|
+
"(authed)/workspace/site": ["workspace"],
|
|
548
|
+
"(authed)/workspace/team": ["workspace"],
|
|
477
549
|
"(authed)/waitlist": ["waitlistDashboard.gate"],
|
|
478
550
|
};
|
|
479
551
|
/** Template-relative path of the generated site-nav registry. */
|
package/dist/sync-routes.js
CHANGED
|
@@ -35,6 +35,18 @@ import { substituteMarkers } from "./substitute.js";
|
|
|
35
35
|
import { GATEABLE_SURFACES, NON_ROUTE_DIRS, ROUTE_DEPENDENCIES, ROUTE_MESSAGE_NAMESPACES, ROUTE_SINCE, surfaceForRouteDir, } from "./surfaces.js";
|
|
36
36
|
/** Template-relative prefix every `apps/web` route directory sits under. */
|
|
37
37
|
const ROUTE_ROOT = "apps/web/app/[locale]";
|
|
38
|
+
/**
|
|
39
|
+
* `<surface id>/<entry name>` pairs that live directly under a GATEABLE
|
|
40
|
+
* surface's top-level directory but belong to a DIFFERENT module (#5366).
|
|
41
|
+
* `projects/apply` (#5048) is signal-collectors' own owned file
|
|
42
|
+
* (`ListingApplicationClient.tsx`, D-48) — its module bundle restores it
|
|
43
|
+
* whenever signal-collectors is selected, independent of the unrelated
|
|
44
|
+
* "projects" content surface's own selection. See the doc comment on
|
|
45
|
+
* `syncRoutes`'s `carried` computation.
|
|
46
|
+
*/
|
|
47
|
+
const NESTED_FOREIGN_MODULE_ENTRIES = new Set([
|
|
48
|
+
"projects/apply",
|
|
49
|
+
]);
|
|
38
50
|
/**
|
|
39
51
|
* The template's route directories, ROUTE_ROOT-relative and sorted.
|
|
40
52
|
*
|
|
@@ -120,12 +132,30 @@ export function syncRoutes(opts) {
|
|
|
120
132
|
throw new Error(`not a scaffolded repo: no architecture.yaml at ${targetAbs}. Run \`create-ailk <target-dir>\` to scaffold first.`);
|
|
121
133
|
}
|
|
122
134
|
// Presence of any of a surface's route dirs means the site carries it.
|
|
135
|
+
// A surface's directory containing ONLY entries in
|
|
136
|
+
// `NESTED_FOREIGN_MODULE_ENTRIES` does not count as present (#5366): a
|
|
137
|
+
// DIFFERENT module can own a companion sub-route nested under an unrelated
|
|
138
|
+
// content surface's URL segment — `projects/apply` (#5048) is
|
|
139
|
+
// signal-collectors' own owned file (D-48), and its module bundle restores
|
|
140
|
+
// it whenever signal-collectors is selected, regardless of whether the
|
|
141
|
+
// unrelated "projects" CONTENT surface is separately requested. Without
|
|
142
|
+
// this exclusion, a signal-collectors-only scaffold would falsely read as
|
|
143
|
+
// "carries projects" the moment its bundle lands that one file.
|
|
123
144
|
const carried = new Set();
|
|
124
145
|
const skippedSurfaces = [];
|
|
125
146
|
for (const surface of GATEABLE_SURFACES) {
|
|
126
147
|
const present = surface.routes.some((r) => {
|
|
127
148
|
const p = join(targetAbs, r);
|
|
128
|
-
|
|
149
|
+
if (!existsSync(p) || !statSync(p).isDirectory())
|
|
150
|
+
return false;
|
|
151
|
+
const entries = readdirSync(p);
|
|
152
|
+
// An ancestor dir left empty by a consumer-side deletion (AC-2's
|
|
153
|
+
// "ancestor also missing" case) still counts as carried — only a
|
|
154
|
+
// NON-empty directory whose entries are entirely foreign (the
|
|
155
|
+
// `projects`/`apply` case above) does not.
|
|
156
|
+
if (entries.length === 0)
|
|
157
|
+
return true;
|
|
158
|
+
return entries.some((name) => !NESTED_FOREIGN_MODULE_ENTRIES.has(`${surface.id}/${name}`));
|
|
129
159
|
});
|
|
130
160
|
if (present)
|
|
131
161
|
carried.add(surface.id);
|