create-ailk 0.13.0 → 0.15.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 +12 -4
- package/component-catalog.json +250 -36
- package/component-catalog.md +10 -6
- package/dist/add-module.d.ts +7 -0
- package/dist/add-module.js +34 -2
- package/dist/add-route.js +18 -2
- package/dist/apply-design.d.ts +32 -2
- package/dist/apply-design.js +155 -22
- package/dist/cli.js +24 -3
- package/dist/derive-architecture.d.ts +15 -2
- package/dist/derive-architecture.js +34 -6
- package/dist/design-compile.d.ts +70 -2
- package/dist/design-compile.js +97 -1
- package/dist/design-home.d.ts +74 -0
- package/dist/design-home.js +256 -0
- package/dist/design-package.d.ts +39 -2
- package/dist/design-package.js +224 -9
- package/dist/design-presets.d.ts +17 -1
- package/dist/design-presets.js +17 -5
- package/dist/design-scaffold.d.ts +6 -25
- package/dist/design-scaffold.js +26 -74
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/lib/chrome-readers.d.ts +21 -0
- package/dist/lib/chrome-readers.js +100 -0
- package/dist/lib/design-checks.d.ts +12 -1
- package/dist/lib/design-checks.js +38 -2
- package/dist/lib/extract-module-bundle.d.ts +11 -0
- package/dist/lib/extract-module-bundle.js +36 -6
- package/dist/lib/license-key-files.d.ts +6 -1
- package/dist/lib/license-key-files.js +9 -4
- package/dist/lib/module-license-gate.d.ts +9 -0
- package/dist/lib/module-license-gate.js +18 -0
- package/dist/module-architecture.d.ts +9 -0
- package/dist/module-architecture.js +6 -0
- package/dist/parse-args.d.ts +22 -0
- package/dist/parse-args.js +73 -0
- package/dist/programmatic.d.ts +20 -0
- package/dist/programmatic.js +180 -35
- package/dist/site-chrome.d.ts +42 -0
- package/dist/site-chrome.js +140 -2
- package/dist/site-fonts.d.ts +21 -0
- package/dist/site-fonts.js +54 -0
- package/dist/site-manifest.d.ts +12 -5
- package/dist/site-manifest.js +25 -11
- package/dist/site-roster.d.ts +16 -0
- package/dist/site-roster.js +81 -0
- package/dist/starter-grid.js +15 -0
- package/dist/surfaces.js +12 -0
- package/dist/sync-routes.js +11 -2
- package/dist/team-profiles.d.ts +29 -0
- package/dist/team-profiles.js +43 -0
- package/dist/topology.d.ts +131 -0
- package/dist/topology.js +533 -0
- package/package.json +3 -3
- package/release-scope.json +1 -1
- package/site-templates/_base/content/en/TeamList/team.mdx +68 -0
- package/site-templates/_base/content/en/TeamMember/first-member.mdx +22 -0
- package/site-templates/_base/content/en/TeamMember/second-member.mdx +22 -0
- package/site-templates/_base/content/en/TeamMember/third-member.mdx +22 -0
- package/site-templates/designs/minimalism/DESIGN.md +166 -36
- package/site-templates/designs/minimalism/home/local-business-service.mdx +119 -0
- package/site-templates/designs/minimalism/home/organization-product.mdx +132 -0
- package/site-templates/designs/minimalism/home/organization-service.mdx +131 -0
- package/site-templates/designs/minimalism/home/person-service.mdx +101 -0
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +34 -1
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +20 -0
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +34 -1
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +22 -1
- package/site-templates/marketing-site-free/ailk-template.json +1 -1
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +34 -1
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +26 -7
- package/site-templates/shapes/local-business-service/ailk-template.json +1 -1
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +34 -1
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +26 -7
- package/site-templates/shapes/organization-service/ailk-template.json +1 -1
- package/templates/README.md +2 -0
- package/templates/apps/api/CLAUDE.md +6 -1
- package/templates/apps/api/package.json +1 -0
- package/templates/apps/api/src/__tests__/factories/agent-caller.ts +5 -1
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +10 -0
- package/templates/apps/api/src/lib/__tests__/agent-store.test.ts +114 -1
- package/templates/apps/api/src/lib/__tests__/site.test.ts +54 -0
- package/templates/apps/api/src/lib/agent-store.ts +95 -3
- package/templates/apps/api/src/lib/site.ts +37 -0
- package/templates/apps/api/src/lib/spoke-store.ts +18 -2
- package/templates/apps/api/src/middleware/__tests__/auth.bearer-path.test.ts +4 -0
- package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +22 -0
- package/templates/apps/api/src/middleware/auth.ts +3 -0
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +23 -0
- package/templates/apps/api/src/openapi/spec.ts +38 -2
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +50 -2
- package/templates/apps/api/src/routes/checkout/sessions.ts +7 -0
- package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +275 -0
- package/templates/apps/api/src/routes/leads/index.ts +69 -6
- package/templates/apps/api/src/routes/webhooks/README.md +39 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +366 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +61 -1
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +199 -0
- package/templates/apps/api/src/services/default-tenant.ts +41 -7
- package/templates/apps/api/src/services/lead-routing.ts +148 -29
- package/templates/apps/api/src/services/public-site-resolver.ts +48 -0
- package/templates/apps/mcp/__tests__/index.test.ts +5 -5
- package/templates/apps/mcp/package.json +1 -1
- package/templates/apps/mcp/src/index.ts +6 -4
- package/templates/apps/mcp/src/lib/content-adapter.ts +2 -2
- package/templates/apps/mcp/src/lib/topology.generated.ts +24 -0
- package/templates/apps/web/CLAUDE.md +12 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/settings.test.tsx +10 -0
- package/templates/apps/web/app/[locale]/(authed)/account/settings/__tests__/theme-modes.test.ts +42 -0
- package/templates/apps/web/app/[locale]/(authed)/account/settings/theme-modes.ts +8 -0
- package/templates/apps/web/app/[locale]/[slug]/[detail]/__tests__/page.test.tsx +3 -0
- package/templates/apps/web/app/[locale]/[slug]/__tests__/page.test.tsx +3 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +14 -1
- package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +66 -14
- package/templates/apps/web/app/[locale]/contact/__tests__/site-contact-lines.test.ts +163 -0
- package/templates/apps/web/app/[locale]/contact/site-contact-lines.ts +68 -0
- package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +4 -4
- package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +15 -3
- package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +17 -6
- package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +14 -1
- package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +4 -6
- package/templates/apps/web/app/[locale]/layout.tsx +212 -57
- package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +3 -0
- package/templates/apps/web/app/[locale]/page.tsx +37 -0
- package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +4 -6
- package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +5 -2
- package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +15 -2
- package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +43 -6
- package/templates/apps/web/app/[locale]/services/page.tsx +14 -2
- package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/team/[slug]/page.tsx +45 -0
- package/templates/apps/web/app/[locale]/team/__tests__/team-routes.test.tsx +155 -0
- package/templates/apps/web/app/[locale]/team/page.tsx +49 -0
- package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +14 -1
- package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +3 -0
- package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/not-found.tsx +47 -0
- package/templates/apps/web/app/openapi.json/__tests__/route.test.ts +89 -1
- package/templates/apps/web/app/openapi.json/route.ts +69 -1
- package/templates/apps/web/instrumentation.ts +7 -4
- package/templates/apps/web/lib/__tests__/closing-footer-path.test.ts +96 -0
- package/templates/apps/web/lib/__tests__/profile-back-link.test.ts +95 -0
- package/templates/apps/web/lib/__tests__/relay.test.ts +290 -0
- package/templates/apps/web/lib/__tests__/site-brand.test.ts +52 -0
- package/templates/apps/web/lib/__tests__/site-theme-head.test.ts +70 -0
- package/templates/apps/web/lib/__tests__/site-theme.test.ts +64 -1
- package/templates/apps/web/lib/closing-footer-path.ts +50 -0
- package/templates/apps/web/lib/profile-back-link.ts +51 -0
- package/templates/apps/web/lib/relay.ts +206 -2
- package/templates/apps/web/lib/site-brand.tsx +22 -1
- package/templates/apps/web/lib/site-chrome.ts +15 -0
- package/templates/apps/web/lib/site-fonts.ts +15 -0
- package/templates/apps/web/lib/site-logo.tsx +39 -1
- package/templates/apps/web/lib/site-theme-head.ts +33 -0
- package/templates/apps/web/lib/site-theme.ts +13 -1
- package/templates/apps/web/lib/topology.generated.ts +23 -0
- package/templates/apps/web/messages/en.json +7 -1
- package/templates/apps/web/package.json +1 -1
- package/templates/database/CHANGELOG.md +32 -0
- package/templates/database/__tests__/agents.db.test.ts +65 -0
- package/templates/database/__tests__/lead-idempotency-key.db.test.ts +92 -0
- package/templates/database/auth/schema.prisma +23 -0
- package/templates/database/inbox/schema.prisma +13 -0
- package/templates/database/jest.config.cjs +7 -11
- package/templates/database/migrations/20261008120000_user_last_workspace/migration.sql +35 -0
- package/templates/database/migrations/20261008130000_lead_idempotency_key/migration.sql +20 -0
- package/templates/database/migrations/20261009120000_agent_grants/migration.sql +33 -0
- package/templates/database/package.json +1 -1
- package/versions.json +35 -34
- package/site-templates/designs/minimalism/sample/README.md +0 -48
- package/site-templates/designs/minimalism/sample/content/en/HomePage/home.mdx +0 -244
- package/site-templates/designs/minimalism/sample/public/avatar-placeholder-1.svg +0 -7
- package/site-templates/designs/minimalism/sample/public/avatar-placeholder-2.svg +0 -7
- package/site-templates/designs/minimalism/sample/public/avatar-placeholder-3.svg +0 -7
- package/site-templates/designs/minimalism/sample/public/avatar-placeholder-4.svg +0 -7
- package/site-templates/designs/minimalism/sample/public/avatar-placeholder-5.svg +0 -7
- package/site-templates/designs/minimalism/sample/public/avatar-placeholder-6.svg +0 -7
- package/site-templates/designs/minimalism/sample/public/hero-backdrop.svg +0 -17
- package/site-templates/designs/minimalism/sample/public/logo-placeholder-1.svg +0 -6
- package/site-templates/designs/minimalism/sample/public/logo-placeholder-2.svg +0 -6
- package/site-templates/designs/minimalism/sample/public/logo-placeholder-3.svg +0 -6
- package/site-templates/designs/minimalism/sample/public/wordmark-placeholder.svg +0 -28
|
@@ -85,13 +85,18 @@ function upsertEnvLine(existing, key) {
|
|
|
85
85
|
* `apps/api/.env` line (0600, other lines kept), and a `.gitignore` entry for
|
|
86
86
|
* `ailk.key` when no existing line covers it. Idempotent — a second call with
|
|
87
87
|
* the same key changes no byte.
|
|
88
|
+
*
|
|
89
|
+
* `apiEnv: false` skips the `apps/api/.env` line — a spoke (#7426 D6) has no
|
|
90
|
+
* `apps/api`, so it keeps the key in `ailk.key` alone.
|
|
88
91
|
*/
|
|
89
|
-
export function writeLicenseKeyFiles(dir, key) {
|
|
92
|
+
export function writeLicenseKeyFiles(dir, key, opts = {}) {
|
|
90
93
|
assertLicenseKeyShape(key);
|
|
91
94
|
writeOwnerOnly(join(dir, LICENSE_KEY_FILE), `${key}\n`);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
+
if (opts.apiEnv !== false) {
|
|
96
|
+
const envPath = join(dir, LICENSE_ENV_FILE);
|
|
97
|
+
const env = existsSync(envPath) ? readFileSync(envPath, "utf8") : "";
|
|
98
|
+
writeOwnerOnly(envPath, upsertEnvLine(env, key));
|
|
99
|
+
}
|
|
95
100
|
const ignorePath = join(dir, ".gitignore");
|
|
96
101
|
const ignore = existsSync(ignorePath) ? readFileSync(ignorePath, "utf8") : "";
|
|
97
102
|
const covered = ignore
|
|
@@ -139,6 +139,15 @@ export declare function deriveLevel(options: GateOptions): Promise<{
|
|
|
139
139
|
level: Level;
|
|
140
140
|
bundles: Map<ModuleName, Uint8Array>;
|
|
141
141
|
}>;
|
|
142
|
+
/**
|
|
143
|
+
* The level a `--modules`-keep run's chosen non-Free design is gated on
|
|
144
|
+
* (#7010). `resolveModuleSet`'s keep branch composes no level (NFR-2), so
|
|
145
|
+
* `scaffold()` derives one here through `deriveLevel`, as the addition branch
|
|
146
|
+
* does. A key the registry refuses for a Pro module — lapsed, revoked, or one
|
|
147
|
+
* that does not cover Pro — reads as `"free"`, so `assertDesignAllowed`
|
|
148
|
+
* refuses with its own message (fail closed); any other failure propagates.
|
|
149
|
+
*/
|
|
150
|
+
export declare function deriveDesignLevel(options: GateOptions): Promise<Level>;
|
|
142
151
|
/**
|
|
143
152
|
* The one entry point that turns `--modules` + the key into the effective
|
|
144
153
|
* module set and its resolved bundles (D5) — `programmatic.ts`'s `scaffold()`
|
|
@@ -294,6 +294,24 @@ export async function deriveLevel(options) {
|
|
|
294
294
|
throw err;
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
|
+
/**
|
|
298
|
+
* The level a `--modules`-keep run's chosen non-Free design is gated on
|
|
299
|
+
* (#7010). `resolveModuleSet`'s keep branch composes no level (NFR-2), so
|
|
300
|
+
* `scaffold()` derives one here through `deriveLevel`, as the addition branch
|
|
301
|
+
* does. A key the registry refuses for a Pro module — lapsed, revoked, or one
|
|
302
|
+
* that does not cover Pro — reads as `"free"`, so `assertDesignAllowed`
|
|
303
|
+
* refuses with its own message (fail closed); any other failure propagates.
|
|
304
|
+
*/
|
|
305
|
+
export async function deriveDesignLevel(options) {
|
|
306
|
+
try {
|
|
307
|
+
return (await deriveLevel(options)).level;
|
|
308
|
+
}
|
|
309
|
+
catch (err) {
|
|
310
|
+
if (err instanceof LicenseRequiredError)
|
|
311
|
+
return "free";
|
|
312
|
+
throw err;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
297
315
|
/**
|
|
298
316
|
* The one entry point that turns `--modules` + the key into the effective
|
|
299
317
|
* module set and its resolved bundles (D5) — `programmatic.ts`'s `scaffold()`
|
|
@@ -83,6 +83,15 @@ export interface PageRosterRow {
|
|
|
83
83
|
* scaffold carries (#6940).
|
|
84
84
|
*/
|
|
85
85
|
export interface ModuleSlice {
|
|
86
|
+
/**
|
|
87
|
+
* Admits the module on a spoke (`create-ailk --topology spoke`, #7426 D7).
|
|
88
|
+
* `true` → a spoke may carry it, and its bundle installs through the spoke
|
|
89
|
+
* filter (`topology.ts` `isSpokeExcluded`), so any `apps/api` or
|
|
90
|
+
* `database` part it carries is dropped. Unset → refused on a spoke.
|
|
91
|
+
* `topology.ts` `spokeModules()` reads it; a sibling issue admits its
|
|
92
|
+
* module by setting it here, never in a second list.
|
|
93
|
+
*/
|
|
94
|
+
spoke?: true;
|
|
86
95
|
/**
|
|
87
96
|
* The paid UI package base-tree code loads through
|
|
88
97
|
* `apps/web/lib/paid-sections.generated.ts` (#7536). Present → one row in the
|
|
@@ -206,6 +206,7 @@ function payloadPaths(kind, name, destRoot) {
|
|
|
206
206
|
export const MODULE_SLICES = {
|
|
207
207
|
// ── marketing — thin presentation module (the default) ───────────────────
|
|
208
208
|
marketing: {
|
|
209
|
+
spoke: true,
|
|
209
210
|
webImports: [
|
|
210
211
|
WEB_AUTH_ADAPTER_IMPORT,
|
|
211
212
|
"packages/content-adapters",
|
|
@@ -287,6 +288,7 @@ export const MODULE_SLICES = {
|
|
|
287
288
|
},
|
|
288
289
|
// ── docs-blog — Pro module: both fumadocs surfaces (D-46) ────────────────
|
|
289
290
|
"docs-blog": {
|
|
291
|
+
spoke: true,
|
|
290
292
|
webImports: [WEB_AUTH_ADAPTER_IMPORT, "packages/fumadocs-schema"],
|
|
291
293
|
packages: {
|
|
292
294
|
"fumadocs-schema": {
|
|
@@ -1388,6 +1390,10 @@ export const MODULE_SLICES = {
|
|
|
1388
1390
|
"apps/api/src/bin/deliverable-upload.ts",
|
|
1389
1391
|
"apps/api/src/bin/session-retention-sweep.ts",
|
|
1390
1392
|
"apps/api/src/bin/seed-presets.ts",
|
|
1393
|
+
// #7844 D7 — the operator flow-copy CLI and its service: flows code by
|
|
1394
|
+
// subject, and a Free scaffold carries no flows.
|
|
1395
|
+
"apps/api/src/bin/flows-copy.ts",
|
|
1396
|
+
"apps/api/src/services/flow-copy.ts",
|
|
1391
1397
|
"apps/api/src/bin/listing-csv-sweep.ts",
|
|
1392
1398
|
"apps/api/src/bin/followup-sweep.ts",
|
|
1393
1399
|
"apps/api/src/bin/seed-waitlist-experiments.ts",
|
package/dist/parse-args.d.ts
CHANGED
|
@@ -26,6 +26,15 @@ export declare const RESERVED_SHAPES: readonly ["local-business-product"];
|
|
|
26
26
|
/** The `--design` vocabulary (#6754 D1) — `STARTER_DESIGNS`, pinned by test. */
|
|
27
27
|
export declare const RECOGNIZED_DESIGNS: readonly ["standard", "minimalism"];
|
|
28
28
|
export type DesignName = (typeof RECOGNIZED_DESIGNS)[number];
|
|
29
|
+
/**
|
|
30
|
+
* The `--topology` vocabulary (#7426 D1) — orthogonal to `--shape`: any shape
|
|
31
|
+
* can be scaffolded as either. Declared here rather than imported from
|
|
32
|
+
* `topology.ts` for the reason `RECOGNIZED_MODULES` is: that module reads
|
|
33
|
+
* `MODULE_SLICES`, and importing it here closes a runtime cycle through
|
|
34
|
+
* `lib/module-tier.ts`. `spoke-topology.test.ts` pins the two lists equal.
|
|
35
|
+
*/
|
|
36
|
+
export declare const RECOGNIZED_TOPOLOGIES: readonly ["hub", "spoke"];
|
|
37
|
+
export type TopologyName = (typeof RECOGNIZED_TOPOLOGIES)[number];
|
|
29
38
|
export interface CliOptions {
|
|
30
39
|
/** Positional target directory (the dir to scaffold into). Optional only
|
|
31
40
|
* when `help` or `version` is set. */
|
|
@@ -94,6 +103,19 @@ export interface CliOptions {
|
|
|
94
103
|
* flag is.
|
|
95
104
|
*/
|
|
96
105
|
license?: string;
|
|
106
|
+
/**
|
|
107
|
+
* The scaffold's topology (`--topology`, #7426 D1). `hub` — the default,
|
|
108
|
+
* and the only value before #7426 — unless `--topology spoke`.
|
|
109
|
+
*/
|
|
110
|
+
topology: TopologyName;
|
|
111
|
+
/**
|
|
112
|
+
* The raw `--hub-url` value (#7426 D2), present only on a spoke run that
|
|
113
|
+
* passed it. `scaffold()` validates and normalizes it, falling back to
|
|
114
|
+
* `AILK_API_URL`.
|
|
115
|
+
*/
|
|
116
|
+
hubUrl?: string;
|
|
117
|
+
/** Spoke only: `false` with `--without-mcp` (#7426 D3), else `true`. */
|
|
118
|
+
mcp?: boolean;
|
|
97
119
|
}
|
|
98
120
|
export declare class ParseError extends Error {
|
|
99
121
|
constructor(message: string);
|
package/dist/parse-args.js
CHANGED
|
@@ -59,6 +59,14 @@ const SHAPE_DESCRIPTIONS = {
|
|
|
59
59
|
"person-service": "a person selling their own service",
|
|
60
60
|
};
|
|
61
61
|
const SHAPE_LIST = RECOGNIZED_SHAPES.join(", ");
|
|
62
|
+
/**
|
|
63
|
+
* The `--topology` vocabulary (#7426 D1) — orthogonal to `--shape`: any shape
|
|
64
|
+
* can be scaffolded as either. Declared here rather than imported from
|
|
65
|
+
* `topology.ts` for the reason `RECOGNIZED_MODULES` is: that module reads
|
|
66
|
+
* `MODULE_SLICES`, and importing it here closes a runtime cycle through
|
|
67
|
+
* `lib/module-tier.ts`. `spoke-topology.test.ts` pins the two lists equal.
|
|
68
|
+
*/
|
|
69
|
+
export const RECOGNIZED_TOPOLOGIES = ["hub", "spoke"];
|
|
62
70
|
export class ParseError extends Error {
|
|
63
71
|
constructor(message) {
|
|
64
72
|
super(message);
|
|
@@ -158,6 +166,18 @@ Options:
|
|
|
158
166
|
--license <key> Your licence key: fetch paid modules with it and
|
|
159
167
|
write it into the scaffold (apps/api/.env and a
|
|
160
168
|
gitignored ailk.key, both owner-only).
|
|
169
|
+
--topology <hub|spoke> hub (the default) scaffolds the whole site: web,
|
|
170
|
+
API, MCP and database. spoke scaffolds a web-only
|
|
171
|
+
site that sends its forms, flows and listings to a
|
|
172
|
+
hub — no apps/api, no database, no sign-in, and
|
|
173
|
+
apps/mcp kept as a stateless front. Composes with
|
|
174
|
+
any --shape or --template. A spoke carries
|
|
175
|
+
marketing and docs-blog.
|
|
176
|
+
--hub-url <origin> A spoke's hub, e.g. https://hub.example.com (https:,
|
|
177
|
+
or http: on localhost). Defaults to AILK_API_URL;
|
|
178
|
+
a spoke with neither is refused. Written to
|
|
179
|
+
apps/web/.env.local — the spoke key never is.
|
|
180
|
+
--without-mcp A spoke without apps/mcp.
|
|
161
181
|
--install After scaffolding, run \`pnpm install\` in the
|
|
162
182
|
target directory. Off by default.
|
|
163
183
|
--help, -h Print this message.
|
|
@@ -194,6 +214,9 @@ export function parseCliArgs(argv, env = { interactive: false }) {
|
|
|
194
214
|
shape: { type: "string" },
|
|
195
215
|
design: { type: "string" },
|
|
196
216
|
license: { type: "string" },
|
|
217
|
+
topology: { type: "string" },
|
|
218
|
+
"hub-url": { type: "string" },
|
|
219
|
+
"without-mcp": { type: "boolean", default: false },
|
|
197
220
|
surfaces: { type: "string" },
|
|
198
221
|
"sync-architecture": { type: "string" },
|
|
199
222
|
"sync-catalog": { type: "boolean", default: false },
|
|
@@ -224,6 +247,10 @@ export function parseCliArgs(argv, env = { interactive: false }) {
|
|
|
224
247
|
const syncClaudeMd = parsed.values["sync-claude-md"] === true;
|
|
225
248
|
const withCatalog = parsed.values["with-catalog"] === true;
|
|
226
249
|
const license = parseLicense(parsed.values.license);
|
|
250
|
+
const rawTopology = parsed.values.topology;
|
|
251
|
+
const topology = parseTopology(rawTopology);
|
|
252
|
+
const hubUrl = parsed.values["hub-url"];
|
|
253
|
+
const withoutMcp = parsed.values["without-mcp"] === true;
|
|
227
254
|
// The sync modes each exit after rewriting their one file, so asking for
|
|
228
255
|
// more than one in one invocation has no coherent meaning.
|
|
229
256
|
const syncFlags = [
|
|
@@ -234,6 +261,21 @@ export function parseCliArgs(argv, env = { interactive: false }) {
|
|
|
234
261
|
if (syncFlags.length > 1) {
|
|
235
262
|
throw new ParseError(`${syncFlags.join(" and ")} cannot be combined; run them one at a time`);
|
|
236
263
|
}
|
|
264
|
+
// #7426 D1 — a sync reads the topology from the site's manifest; naming one
|
|
265
|
+
// here has no coherent meaning.
|
|
266
|
+
if (rawTopology !== undefined && syncFlags.length > 0) {
|
|
267
|
+
throw new ParseError(`--topology cannot be combined with ${syncFlags[0]}: a sync reads the site's topology from its .ailk-site-manifest.json`);
|
|
268
|
+
}
|
|
269
|
+
// #7426 D2/D3 — the spoke-only flags.
|
|
270
|
+
if (topology !== "spoke") {
|
|
271
|
+
const spokeOnly = [
|
|
272
|
+
hubUrl !== undefined ? "--hub-url" : undefined,
|
|
273
|
+
withoutMcp ? "--without-mcp" : undefined,
|
|
274
|
+
].filter((f) => f !== undefined);
|
|
275
|
+
if (spokeOnly.length > 0) {
|
|
276
|
+
throw new ParseError(`${spokeOnly.join(" and ")} only apply${spokeOnly.length === 1 ? "s" : ""} to a spoke. Pass --topology spoke, or drop ${spokeOnly.length === 1 ? "it" : "them"}.`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
237
279
|
// Validate the sync-architecture value now, through the SAME parseModules
|
|
238
280
|
// path `--modules` uses, so an unknown module throws ParseError at parse time
|
|
239
281
|
// (cli.ts re-parses the raw value to the module list in sync mode).
|
|
@@ -255,6 +297,7 @@ export function parseCliArgs(argv, env = { interactive: false }) {
|
|
|
255
297
|
syncClaudeMd: false,
|
|
256
298
|
withCatalog: false,
|
|
257
299
|
syncArchitecture: undefined,
|
|
300
|
+
topology: "hub",
|
|
258
301
|
};
|
|
259
302
|
}
|
|
260
303
|
// Sync modes: the target dir is optional (positional if given, else cwd).
|
|
@@ -275,6 +318,7 @@ export function parseCliArgs(argv, env = { interactive: false }) {
|
|
|
275
318
|
syncClaudeMd,
|
|
276
319
|
withCatalog,
|
|
277
320
|
syncArchitecture,
|
|
321
|
+
topology: "hub",
|
|
278
322
|
};
|
|
279
323
|
}
|
|
280
324
|
if (parsed.positionals.length === 0) {
|
|
@@ -292,6 +336,13 @@ export function parseCliArgs(argv, env = { interactive: false }) {
|
|
|
292
336
|
: "--design";
|
|
293
337
|
throw new ParseError(`--template cannot be combined with ${flags}: a template already fixes its shape and design. Pass --template alone, or --shape with an optional --design.`);
|
|
294
338
|
}
|
|
339
|
+
// #7426 D2 — a spoke needs its hub's origin. Presence only, here: the value
|
|
340
|
+
// is validated and normalized by `scaffold()` (`topology.ts`
|
|
341
|
+
// `normalizeHubUrl`), still before the target directory exists.
|
|
342
|
+
if (topology === "spoke" &&
|
|
343
|
+
(hubUrl ?? process.env["AILK_API_URL"] ?? "").trim() === "") {
|
|
344
|
+
throw new ParseError("a spoke needs its hub's origin, and none was given. Pass --hub-url <origin>, or set AILK_API_URL (e.g. https://hub.example.com).");
|
|
345
|
+
}
|
|
295
346
|
// R1 — no silent default: with neither a template nor a shape, ask on a
|
|
296
347
|
// terminal and refuse without one.
|
|
297
348
|
const needsShapePrompt = template === undefined && shape === undefined;
|
|
@@ -314,8 +365,22 @@ export function parseCliArgs(argv, env = { interactive: false }) {
|
|
|
314
365
|
withCatalog,
|
|
315
366
|
syncArchitecture: undefined,
|
|
316
367
|
...(license !== undefined ? { license } : {}),
|
|
368
|
+
topology,
|
|
369
|
+
...(topology === "spoke"
|
|
370
|
+
? { ...(hubUrl !== undefined ? { hubUrl } : {}), mcp: !withoutMcp }
|
|
371
|
+
: {}),
|
|
317
372
|
};
|
|
318
373
|
}
|
|
374
|
+
/** Validate `--topology` (#7426 D1): `hub` when absent, trimmed and lowercased. */
|
|
375
|
+
function parseTopology(raw) {
|
|
376
|
+
if (raw === undefined)
|
|
377
|
+
return "hub";
|
|
378
|
+
const name = raw.trim().toLowerCase();
|
|
379
|
+
if (!RECOGNIZED_TOPOLOGIES.includes(name)) {
|
|
380
|
+
throw new ParseError(`unknown topology "${raw}". Recognized: ${RECOGNIZED_TOPOLOGIES.join(", ")}`);
|
|
381
|
+
}
|
|
382
|
+
return name;
|
|
383
|
+
}
|
|
319
384
|
/**
|
|
320
385
|
* Validate `--shape` (#6754 D2 step 2): trimmed and lowercased. A reserved
|
|
321
386
|
* shape is refusal R2; any other unrecognized value names the recognized set.
|
|
@@ -467,6 +532,14 @@ export function toScaffoldOptions(opts) {
|
|
|
467
532
|
...(opts.shape !== undefined ? { shape: opts.shape } : {}),
|
|
468
533
|
...(opts.design !== undefined ? { design: opts.design } : {}),
|
|
469
534
|
...(opts.license !== undefined ? { license: opts.license } : {}),
|
|
535
|
+
// #7426 — a hub run hands `scaffold()` the object it did before.
|
|
536
|
+
...(opts.topology === "spoke"
|
|
537
|
+
? {
|
|
538
|
+
topology: "spoke",
|
|
539
|
+
...(opts.hubUrl !== undefined ? { hubUrl: opts.hubUrl } : {}),
|
|
540
|
+
mcp: opts.mcp !== false,
|
|
541
|
+
}
|
|
542
|
+
: {}),
|
|
470
543
|
};
|
|
471
544
|
}
|
|
472
545
|
/**
|
package/dist/programmatic.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { type ModuleName } from "./parse-args.js";
|
|
|
22
22
|
import { type TemplateLayout } from "./site-chrome.js";
|
|
23
23
|
import { type SyncRoutesResult } from "./sync-routes.js";
|
|
24
24
|
import { type StarterShape } from "./template-manifest.js";
|
|
25
|
+
import { type Topology } from "./topology.js";
|
|
25
26
|
export interface ScaffoldOptions {
|
|
26
27
|
/** Absolute or relative path to the target directory. Must not exist or be empty. */
|
|
27
28
|
targetDir: string;
|
|
@@ -136,6 +137,18 @@ export interface ScaffoldOptions {
|
|
|
136
137
|
* code registry that has since been deleted (#6638's per-name table).
|
|
137
138
|
*/
|
|
138
139
|
designLayout?: TemplateLayout;
|
|
140
|
+
/**
|
|
141
|
+
* The scaffold's topology (`--topology`, #7426 D1). `"hub"` — the default
|
|
142
|
+
* — is the whole site. `"spoke"` is the same scaffold with nodes removed
|
|
143
|
+
* (`topology.ts`): no `apps/api`, no `database/`, no sign-in, `apps/mcp`
|
|
144
|
+
* a stateless front unless `mcp: false`. A spoke needs a `template` or a
|
|
145
|
+
* `shape` (the manifest records the topology) and a hub origin.
|
|
146
|
+
*/
|
|
147
|
+
topology?: Topology;
|
|
148
|
+
/** A spoke's hub origin (`--hub-url`), else `AILK_API_URL` (#7426 D2). */
|
|
149
|
+
hubUrl?: string;
|
|
150
|
+
/** Spoke only: `false` drops `apps/mcp` (`--without-mcp`, #7426 D3). Default true. */
|
|
151
|
+
mcp?: boolean;
|
|
139
152
|
}
|
|
140
153
|
export interface ScaffoldResult {
|
|
141
154
|
/** Resolved absolute path to the scaffolded directory. */
|
|
@@ -161,6 +174,13 @@ export interface ScaffoldResult {
|
|
|
161
174
|
* a keyed run with no `--modules` carries the key's derived level here.
|
|
162
175
|
*/
|
|
163
176
|
modules: ModuleName[];
|
|
177
|
+
/**
|
|
178
|
+
* A spoke only (#7426 D7): modules the key's default set carried that a
|
|
179
|
+
* spoke does not admit, dropped from this scaffold. Absent when none were.
|
|
180
|
+
*/
|
|
181
|
+
droppedModules?: ModuleName[];
|
|
182
|
+
/** A spoke only (#7426 D2): the normalized hub origin written to apps/web/.env.local. */
|
|
183
|
+
hubOrigin?: string;
|
|
164
184
|
}
|
|
165
185
|
/**
|
|
166
186
|
* Scaffold an AI Launch Kit starter project into targetDir (synchronous).
|