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
package/dist/cli.js
CHANGED
|
@@ -201,7 +201,9 @@ async function main() {
|
|
|
201
201
|
targetDir: args.target ?? process.cwd(),
|
|
202
202
|
designsDir: resolve(PKG_ROOT, "site-templates", "designs"),
|
|
203
203
|
siteTemplatesDir: resolve(PKG_ROOT, "site-templates"),
|
|
204
|
+
templatesDir: resolve(PKG_ROOT, "templates"),
|
|
204
205
|
catalogPath: resolve(PKG_ROOT, "component-catalog.json"),
|
|
206
|
+
allowUnreadChrome: args.allowUnreadChrome,
|
|
205
207
|
});
|
|
206
208
|
process.stdout.write(formatApplyDesignReport(result));
|
|
207
209
|
process.exit(0);
|
|
@@ -317,7 +319,8 @@ async function main() {
|
|
|
317
319
|
// marketing-site-free` still rides the no-key case (record D11): its
|
|
318
320
|
// `requires` is all-free, so it installs from the shipped
|
|
319
321
|
// `site-templates/` tree with no key and no request.
|
|
320
|
-
const { targetDir: targetAbs, surfaces, starter, modules: effectiveModules, } = await scaffold(toScaffoldOptions(opts));
|
|
322
|
+
const { targetDir: targetAbs, surfaces, starter, modules: effectiveModules, droppedModules, hubOrigin, } = await scaffold(toScaffoldOptions(opts));
|
|
323
|
+
const isSpoke = opts.topology === "spoke";
|
|
321
324
|
// The starter scaffold() resolved — from --template, or from --shape (#6754 D4).
|
|
322
325
|
const templateName = starter?.template ?? opts.template;
|
|
323
326
|
if (opts.install) {
|
|
@@ -345,6 +348,22 @@ async function main() {
|
|
|
345
348
|
...(starter !== undefined
|
|
346
349
|
? [`Starter: shape ${starter.shape}, design ${starter.design}.`]
|
|
347
350
|
: []),
|
|
351
|
+
// #7426 — a spoke says so, and names what its hosts must carry (D3).
|
|
352
|
+
...(isSpoke
|
|
353
|
+
? [
|
|
354
|
+
`Topology: spoke — no apps/api, no database, no sign-in${opts.mcp === false ? ", no apps/mcp" : "; apps/mcp is a stateless front"}.`,
|
|
355
|
+
...(droppedModules !== undefined && droppedModules.length > 0
|
|
356
|
+
? [
|
|
357
|
+
`Not carried on a spoke: ${droppedModules.join(", ")} (your licence includes ${droppedModules.length === 1 ? "it" : "them"}; a spoke does not admit ${droppedModules.length === 1 ? "it" : "them"} yet).`,
|
|
358
|
+
]
|
|
359
|
+
: []),
|
|
360
|
+
"",
|
|
361
|
+
`Every host serving this spoke's ${opts.mcp === false ? "apps/web" : "apps/web and apps/mcp"} must carry:`,
|
|
362
|
+
` AILK_API_URL=${hubOrigin ?? "<your hub origin>"} # written to apps/web/.env.local`,
|
|
363
|
+
" AILK_SPOKE_KEY # issued by the hub; never written here",
|
|
364
|
+
" AILK_VISITOR_FORWARD_KEY # issued by the hub; never written here",
|
|
365
|
+
]
|
|
366
|
+
: []),
|
|
348
367
|
"",
|
|
349
368
|
];
|
|
350
369
|
// Prerequisites (#6440 AC4) — every one the scaffold enforces, and nothing
|
|
@@ -373,13 +392,15 @@ async function main() {
|
|
|
373
392
|
lines.push("Next steps:");
|
|
374
393
|
lines.push(` cd ${targetAbs}`);
|
|
375
394
|
if (!opts.install) {
|
|
376
|
-
lines.push(" pnpm install # also generates the Prisma client");
|
|
395
|
+
lines.push(isSpoke ? " pnpm install" : " pnpm install # also generates the Prisma client");
|
|
377
396
|
}
|
|
378
397
|
lines.push(" pnpm dev # http://localhost:3000");
|
|
379
398
|
lines.push(" pnpm build # production build, when you are ready to deploy");
|
|
380
399
|
lines.push("");
|
|
381
400
|
lines.push("Open http://localhost:3000 to see the home page.");
|
|
382
|
-
|
|
401
|
+
if (!isSpoke) {
|
|
402
|
+
lines.push("Adding auth, saved flows, checkout or database-backed content later? README.md \u00a7 \"When you need a database\" has the one-command setup.");
|
|
403
|
+
}
|
|
383
404
|
lines.push("");
|
|
384
405
|
lines.push("Claude Code: the AI Launch Kit skills come from the ailk plugin. Trust this folder and Claude Code loads it, or run in a session:");
|
|
385
406
|
for (const line of PLUGIN_INSTALL_LINES)
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import type { Topology } from "./topology.js";
|
|
2
|
+
/** The topology a document is derived for (#7426 D8). */
|
|
3
|
+
export interface DeriveArchitectureOptions {
|
|
4
|
+
/** `"hub"` (the default) or `"spoke"`. */
|
|
5
|
+
topology?: Topology;
|
|
6
|
+
/** Spoke only: whether the spoke keeps `apps/mcp` (default true). */
|
|
7
|
+
mcp?: boolean;
|
|
8
|
+
}
|
|
1
9
|
/**
|
|
2
10
|
* Derive the `architecture.yaml` document for a module set.
|
|
3
11
|
*
|
|
@@ -6,8 +14,13 @@
|
|
|
6
14
|
* @param surfaces the enabled gateable route surfaces. Omitted ⟹ derived from
|
|
7
15
|
* the module set alone (`resolveSurfaces`), so a caller that passes only
|
|
8
16
|
* modules gets exactly what the modules imply.
|
|
17
|
+
* @param options the topology (#7426 D8). Absent or `"hub"` ⟹ the output is
|
|
18
|
+
* byte-identical to the two-argument form. `"spoke"` ⟹ only the spoke's own
|
|
19
|
+
* apps (`web`, and `mcp` unless `mcp: false`), no `namespaces`, no
|
|
20
|
+
* `database` root, a top-level `topology: spoke` and the hub-facing
|
|
21
|
+
* composition tiers.
|
|
9
22
|
* @returns the complete `architecture.yaml` file content (a deterministic
|
|
10
|
-
* function of the module SET
|
|
23
|
+
* function of the module SET, the surface SET and the topology).
|
|
11
24
|
* @throws Error on an unrecognized module (defensive; parse-args rejects first).
|
|
12
25
|
*/
|
|
13
|
-
export declare function deriveArchitecture(modules?: string[], surfaces?: string[]): string;
|
|
26
|
+
export declare function deriveArchitecture(modules?: string[], surfaces?: string[], options?: DeriveArchitectureOptions): string;
|
|
@@ -15,6 +15,12 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { ARCHITECTURE_BASE, MODULE_SLICES, normalizeModules, } from "./module-architecture.js";
|
|
17
17
|
import { CORE_SURFACE_PATHS, RECOGNIZED_SURFACES, resolveSurfaces, } from "./surfaces.js";
|
|
18
|
+
/** A spoke's composition tiers (#7426 D8): its data and API are the hub's. */
|
|
19
|
+
const SPOKE_TIERS = {
|
|
20
|
+
data: "none — the hub's database",
|
|
21
|
+
api: "the hub at AILK_API_URL (apps/mcp is a stateless front)",
|
|
22
|
+
presentation: "apps/web",
|
|
23
|
+
};
|
|
18
24
|
function isScalar(v) {
|
|
19
25
|
return typeof v === "string" || typeof v === "number";
|
|
20
26
|
}
|
|
@@ -150,11 +156,18 @@ function namespacesToYaml(ns) {
|
|
|
150
156
|
* @param surfaces the enabled gateable route surfaces. Omitted ⟹ derived from
|
|
151
157
|
* the module set alone (`resolveSurfaces`), so a caller that passes only
|
|
152
158
|
* modules gets exactly what the modules imply.
|
|
159
|
+
* @param options the topology (#7426 D8). Absent or `"hub"` ⟹ the output is
|
|
160
|
+
* byte-identical to the two-argument form. `"spoke"` ⟹ only the spoke's own
|
|
161
|
+
* apps (`web`, and `mcp` unless `mcp: false`), no `namespaces`, no
|
|
162
|
+
* `database` root, a top-level `topology: spoke` and the hub-facing
|
|
163
|
+
* composition tiers.
|
|
153
164
|
* @returns the complete `architecture.yaml` file content (a deterministic
|
|
154
|
-
* function of the module SET
|
|
165
|
+
* function of the module SET, the surface SET and the topology).
|
|
155
166
|
* @throws Error on an unrecognized module (defensive; parse-args rejects first).
|
|
156
167
|
*/
|
|
157
|
-
export function deriveArchitecture(modules = ["marketing"], surfaces) {
|
|
168
|
+
export function deriveArchitecture(modules = ["marketing"], surfaces, options = {}) {
|
|
169
|
+
const spoke = options.topology === "spoke";
|
|
170
|
+
const keepMcp = options.mcp !== false;
|
|
158
171
|
const selected = normalizeModules(modules);
|
|
159
172
|
const slices = selected.map((m) => MODULE_SLICES[m]);
|
|
160
173
|
// The declared route surface (#4154). A scaffold's live-route set is a
|
|
@@ -180,12 +193,20 @@ export function deriveArchitecture(modules = ["marketing"], surfaces) {
|
|
|
180
193
|
};
|
|
181
194
|
for (const s of slices)
|
|
182
195
|
Object.assign(declared, s.apps ?? {});
|
|
196
|
+
// A spoke carries only its own apps (#7426 D8): web, and mcp unless
|
|
197
|
+
// `--without-mcp` — never api, never a module's admin.
|
|
198
|
+
if (spoke) {
|
|
199
|
+
for (const key of Object.keys(declared)) {
|
|
200
|
+
if (key !== "web" && !(key === "mcp" && keepMcp))
|
|
201
|
+
delete declared[key];
|
|
202
|
+
}
|
|
203
|
+
}
|
|
183
204
|
// A scaffold carries no `packages/` tree — `@working-theory/*` install from
|
|
184
205
|
// npm — so an entry naming a monorepo folder is dropped rather than shipped
|
|
185
206
|
// as a rule the linter can never apply (#6940). What survives names a path
|
|
186
207
|
// the scaffold has: a declared app, or `database/`.
|
|
187
208
|
const roots = [
|
|
188
|
-
"database",
|
|
209
|
+
...(spoke ? [] : ["database"]),
|
|
189
210
|
...Object.values(declared).map((a) => a.path),
|
|
190
211
|
];
|
|
191
212
|
const inScaffold = (p) => {
|
|
@@ -209,8 +230,11 @@ export function deriveArchitecture(modules = ["marketing"], surfaces) {
|
|
|
209
230
|
forbidden_patterns: mergeForbidden([...ARCHITECTURE_BASE.appForbiddenPatterns], a.forbidden_patterns ?? []).map(scoped),
|
|
210
231
|
});
|
|
211
232
|
}
|
|
212
|
-
// namespaces — present only if a data-tier module contributes one
|
|
213
|
-
|
|
233
|
+
// namespaces — present only if a data-tier module contributes one, and
|
|
234
|
+
// never on a spoke, which has no database/ of its own.
|
|
235
|
+
const nsSlice = spoke
|
|
236
|
+
? undefined
|
|
237
|
+
: slices.find((s) => s.namespaces)?.namespaces;
|
|
214
238
|
// forbidden_patterns — base floor first, then the selected modules' patterns.
|
|
215
239
|
const extraForbidden = [];
|
|
216
240
|
for (const s of slices)
|
|
@@ -218,8 +242,11 @@ export function deriveArchitecture(modules = ["marketing"], surfaces) {
|
|
|
218
242
|
const forbidden = mergeForbidden([...ARCHITECTURE_BASE.forbiddenPatterns], extraForbidden).map(scoped);
|
|
219
243
|
const doc = {
|
|
220
244
|
version: ARCHITECTURE_BASE.version,
|
|
245
|
+
...(spoke ? { topology: "spoke" } : {}),
|
|
221
246
|
composition_model: {
|
|
222
|
-
tiers:
|
|
247
|
+
tiers: spoke
|
|
248
|
+
? { ...SPOKE_TIERS }
|
|
249
|
+
: { ...ARCHITECTURE_BASE.composition_model.tiers },
|
|
223
250
|
},
|
|
224
251
|
surfaces: {
|
|
225
252
|
core: [...CORE_SURFACE_PATHS],
|
|
@@ -240,6 +267,7 @@ export function deriveArchitecture(modules = ["marketing"], surfaces) {
|
|
|
240
267
|
"#",
|
|
241
268
|
`# Modules: ${selected.join(", ")}`,
|
|
242
269
|
`# Surfaces: ${enabledSurfaces.length > 0 ? enabledSurfaces.join(", ") : "core only"}`,
|
|
270
|
+
...(spoke ? ["# Topology: spoke"] : []),
|
|
243
271
|
"",
|
|
244
272
|
].join("\n");
|
|
245
273
|
return header + emitDocument(doc);
|
package/dist/design-compile.d.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* this compiles to what `SiteThemeSchema` accepts is test-only, by relative
|
|
16
16
|
* path, in `design-compile.test.ts`.
|
|
17
17
|
*/
|
|
18
|
-
import type
|
|
18
|
+
import { type DesignPackage } from "./design-package.js";
|
|
19
19
|
/** The nine runtime colour slots this compile emits, per mode (D4). */
|
|
20
20
|
export type RuntimeColorSlot = "bg" | "bgMuted" | "surface" | "border" | "text" | "textMuted" | "primary" | "primaryFg" | "accent";
|
|
21
21
|
/** One mode's compiled palette — the shape `SiteThemeSchema`'s partial palette accepts. */
|
|
@@ -28,11 +28,72 @@ export interface CompiledDesignTheme {
|
|
|
28
28
|
eyebrowCase?: "upper" | "sentence";
|
|
29
29
|
placeholders?: "outline" | "fill";
|
|
30
30
|
checkmarks?: "circle" | "plain";
|
|
31
|
+
/** #7741 D7 — the button shape, copied from `treatments.buttonShape`. */
|
|
32
|
+
buttonShape?: "control" | "pill";
|
|
33
|
+
/** #7739 D2 — the ink of headings and labels on a dark ground, verbatim from the package. */
|
|
34
|
+
darkInk?: "fg" | "accent";
|
|
35
|
+
/** #7739 D2 — the stat-numeral treatment, verbatim from the package. */
|
|
36
|
+
figures?: "platform" | "display";
|
|
37
|
+
/**
|
|
38
|
+
* #7720 D2 — the display leading pair, present only when the display role
|
|
39
|
+
* declares `lineHeightMobile`; or, under `typeScale: design` (#7739 D2.2),
|
|
40
|
+
* every declared field of the five compiled roles.
|
|
41
|
+
*/
|
|
42
|
+
typography?: CompiledTypography;
|
|
43
|
+
/**
|
|
44
|
+
* #7719 D6 — `"site"` paints dark-scoped bands with the site's own dark
|
|
45
|
+
* colours. Derived (never authored): emitted when the package names the
|
|
46
|
+
* `solid-dark` surface or declares `darkInk: accent` (#7739 D2.3); absent otherwise.
|
|
47
|
+
*/
|
|
48
|
+
darkBands?: "platform" | "site";
|
|
31
49
|
colors: {
|
|
32
50
|
light: CompiledColorPalette;
|
|
33
51
|
dark: CompiledColorPalette;
|
|
34
52
|
};
|
|
35
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* The compiled type roles (#7720, #7739 D2.2) — the shape `SiteThemeSchema`'s
|
|
56
|
+
* `typography` accepts. Each role is present only when it carries a field.
|
|
57
|
+
*/
|
|
58
|
+
export interface CompiledTypography {
|
|
59
|
+
display?: {
|
|
60
|
+
fontSize?: string;
|
|
61
|
+
fontSizeMobile?: string;
|
|
62
|
+
fontWeight?: number;
|
|
63
|
+
lineHeight?: number;
|
|
64
|
+
lineHeightMobile?: number;
|
|
65
|
+
};
|
|
66
|
+
"heading-section"?: {
|
|
67
|
+
fontSize?: string;
|
|
68
|
+
fontSizeMobile?: string;
|
|
69
|
+
fontWeight?: number;
|
|
70
|
+
};
|
|
71
|
+
"heading-item"?: {
|
|
72
|
+
fontSize?: string;
|
|
73
|
+
fontSizeMobile?: string;
|
|
74
|
+
fontWeight?: number;
|
|
75
|
+
};
|
|
76
|
+
body?: {
|
|
77
|
+
fontSize?: string;
|
|
78
|
+
lineHeight?: number;
|
|
79
|
+
};
|
|
80
|
+
eyebrow?: {
|
|
81
|
+
fontSize?: string;
|
|
82
|
+
fontWeight?: number;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/** One sourced face (#7720 D3.1): the first family, unquoted, and the rest of the authored stack. */
|
|
86
|
+
export interface CompiledFace {
|
|
87
|
+
source: "google";
|
|
88
|
+
family: string;
|
|
89
|
+
/** The authored stack after the first comma, trimmed and verbatim; "" when there is none. */
|
|
90
|
+
fallback: string;
|
|
91
|
+
}
|
|
92
|
+
/** `compileDesignFonts`'s return shape — the faces `apps/web/lib/site-fonts.ts` loads. */
|
|
93
|
+
export interface CompiledDesignFonts {
|
|
94
|
+
display?: CompiledFace;
|
|
95
|
+
body?: CompiledFace;
|
|
96
|
+
}
|
|
36
97
|
/** Every authored colour key the map may read from (design-package.ts D3). */
|
|
37
98
|
type DesignAuthoredColorKey = "bg" | "fg" | "surface" | "surface-secondary" | "border" | "text-muted" | "brand" | "fg-on-brand" | "accent" | "bg-dark" | "fg-dark" | "border-dark" | "brand-dark" | "surface-dark" | "surface-secondary-dark" | "text-muted-dark" | "fg-on-brand-dark" | "accent-dark";
|
|
38
99
|
/** The five named lossy cases with a row (D4) — an authored key absent falls back. */
|
|
@@ -59,7 +120,7 @@ export declare const DESIGN_COLOR_MAP: readonly DesignColorMapRow[];
|
|
|
59
120
|
*/
|
|
60
121
|
export declare const DESIGN_COMPILE_LOSSY: {
|
|
61
122
|
readonly L6: "accentFg, destructive and destructiveFg have no authored source and are never emitted by this compile — the partial palette inherits the platform default.";
|
|
62
|
-
readonly L7: "typography, rounded and spacing are carried in the package and not compiled — SiteThemeSchema rejects fonts/radius/spacing by name.";
|
|
123
|
+
readonly L7: "typography, rounded and spacing are carried in the package and not compiled — SiteThemeSchema rejects fonts/radius/spacing by name. Three exceptions: the display role's leading pair (lineHeight, lineHeightMobile) compiles into theme.typography.display when lineHeightMobile is declared (#7720); the display/body faces a fontSource names compile to apps/web/lib/site-fonts.ts (#7720); and under treatments.typeScale: design the display, heading-section, heading-item, body and eyebrow roles' sizes, phone sizes, weights and leadings compile into theme.typography (#7739) — never their families or letterSpacing.";
|
|
63
124
|
readonly L8: "an absent treatments key is omitted from the compiled theme block, not defaulted.";
|
|
64
125
|
};
|
|
65
126
|
/**
|
|
@@ -68,4 +129,11 @@ export declare const DESIGN_COMPILE_LOSSY: {
|
|
|
68
129
|
* performs no I/O.
|
|
69
130
|
*/
|
|
70
131
|
export declare function compileDesignTheme(pkg: DesignPackage): CompiledDesignTheme;
|
|
132
|
+
/**
|
|
133
|
+
* The faces a package's display and body roles source (#7720 D3.1), or
|
|
134
|
+
* `undefined` when neither declares `fontSource`. Pure, like
|
|
135
|
+
* `compileDesignTheme`; `readDesignPackage` has already constrained every
|
|
136
|
+
* stack it reads here.
|
|
137
|
+
*/
|
|
138
|
+
export declare function compileDesignFonts(pkg: DesignPackage): CompiledDesignFonts | undefined;
|
|
71
139
|
export {};
|
package/dist/design-compile.js
CHANGED
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compile a `DesignPackage` (design-package.ts) into the `theme:` block a
|
|
3
|
+
* site's `_site.mdx` carries (#6812 D4, spec docs/specs/6812-the-design-package-design-md-spec.md).
|
|
4
|
+
*
|
|
5
|
+
* `compileDesignTheme` is pure and idempotent: it reads `pkg` only, never
|
|
6
|
+
* mutates it, and has no I/O. The authored→runtime key map is the single
|
|
7
|
+
* exported table `DESIGN_COLOR_MAP` below — every lossy translation (a
|
|
8
|
+
* runtime slot the authored quartet has no direct source for) is named as a
|
|
9
|
+
* `fallback` + `lossy` case, and the cases with no row at all are named in
|
|
10
|
+
* `DESIGN_COMPILE_LOSSY`.
|
|
11
|
+
*
|
|
12
|
+
* This module imports nothing from `@working-theory/site-config` — the
|
|
13
|
+
* `theme:` block's shape is reproduced structurally here so `create-ailk`
|
|
14
|
+
* gains no runtime dependency (§ 1.5); the cross-package read that proves
|
|
15
|
+
* this compiles to what `SiteThemeSchema` accepts is test-only, by relative
|
|
16
|
+
* path, in `design-compile.test.ts`.
|
|
17
|
+
*/
|
|
18
|
+
import { unquoteFamily } from "./design-package.js";
|
|
19
|
+
/**
|
|
20
|
+
* #7739 D2.2 — the fields `typeScale: design` compiles, per role, in emission
|
|
21
|
+
* order. Roles are emitted in this table's order.
|
|
22
|
+
*/
|
|
23
|
+
const COMPILED_TYPE_FIELDS = {
|
|
24
|
+
display: ["fontSize", "fontSizeMobile", "fontWeight", "lineHeight", "lineHeightMobile"],
|
|
25
|
+
"heading-section": ["fontSize", "fontSizeMobile", "fontWeight"],
|
|
26
|
+
"heading-item": ["fontSize", "fontSizeMobile", "fontWeight"],
|
|
27
|
+
body: ["fontSize", "lineHeight"],
|
|
28
|
+
eyebrow: ["fontSize", "fontWeight"],
|
|
29
|
+
};
|
|
1
30
|
/**
|
|
2
31
|
* The authored→runtime key map (D4) — 18 rows, one per emitted runtime slot
|
|
3
32
|
* per mode, in the overlay block's key order (bg bgMuted surface border text
|
|
@@ -62,9 +91,46 @@ export const DESIGN_COLOR_MAP = [
|
|
|
62
91
|
*/
|
|
63
92
|
export const DESIGN_COMPILE_LOSSY = {
|
|
64
93
|
L6: "accentFg, destructive and destructiveFg have no authored source and are never emitted by this compile — the partial palette inherits the platform default.",
|
|
65
|
-
L7: "typography, rounded and spacing are carried in the package and not compiled — SiteThemeSchema rejects fonts/radius/spacing by name.",
|
|
94
|
+
L7: "typography, rounded and spacing are carried in the package and not compiled — SiteThemeSchema rejects fonts/radius/spacing by name. Three exceptions: the display role's leading pair (lineHeight, lineHeightMobile) compiles into theme.typography.display when lineHeightMobile is declared (#7720); the display/body faces a fontSource names compile to apps/web/lib/site-fonts.ts (#7720); and under treatments.typeScale: design the display, heading-section, heading-item, body and eyebrow roles' sizes, phone sizes, weights and leadings compile into theme.typography (#7739) — never their families or letterSpacing.",
|
|
66
95
|
L8: "an absent treatments key is omitted from the compiled theme block, not defaulted.",
|
|
67
96
|
};
|
|
97
|
+
/** Whether a package names the `solid-dark` surface — in its deck grounds or any section preset (#7719 D6). */
|
|
98
|
+
function usesSolidDark(pkg) {
|
|
99
|
+
if (pkg.layout.deckGrounds?.includes("solid-dark"))
|
|
100
|
+
return true;
|
|
101
|
+
return Object.values(pkg.sections).some((sections) => Object.values(sections).some((preset) => typeof preset === "object" &&
|
|
102
|
+
preset !== null &&
|
|
103
|
+
preset.surface === "solid-dark"));
|
|
104
|
+
}
|
|
105
|
+
/** The `typography` group a package compiles (#7720 D2, #7739 D2.2), or `undefined`. */
|
|
106
|
+
function compileTypography(pkg) {
|
|
107
|
+
if (pkg.treatments?.typeScale === "design") {
|
|
108
|
+
const result = {};
|
|
109
|
+
for (const [role, fields] of Object.entries(COMPILED_TYPE_FIELDS)) {
|
|
110
|
+
const def = pkg.typography[role];
|
|
111
|
+
if (def === undefined)
|
|
112
|
+
continue;
|
|
113
|
+
const compiled = {};
|
|
114
|
+
for (const field of fields) {
|
|
115
|
+
const value = def[field];
|
|
116
|
+
if (value !== undefined)
|
|
117
|
+
compiled[field] = value;
|
|
118
|
+
}
|
|
119
|
+
if (Object.keys(compiled).length > 0)
|
|
120
|
+
result[role] = compiled;
|
|
121
|
+
}
|
|
122
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
123
|
+
}
|
|
124
|
+
const display = pkg.typography["display"];
|
|
125
|
+
if (display?.lineHeightMobile === undefined)
|
|
126
|
+
return undefined;
|
|
127
|
+
return {
|
|
128
|
+
display: {
|
|
129
|
+
...(display.lineHeight !== undefined && { lineHeight: display.lineHeight }),
|
|
130
|
+
lineHeightMobile: display.lineHeightMobile,
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
68
134
|
/**
|
|
69
135
|
* Translate a `DesignPackage` into the `theme:` block `SiteThemeSchema.parse`
|
|
70
136
|
* accepts (D4). Pure and idempotent — reads `pkg` only, mutates nothing, and
|
|
@@ -88,11 +154,41 @@ export function compileDesignTheme(pkg) {
|
|
|
88
154
|
target[row.runtime] = value;
|
|
89
155
|
}
|
|
90
156
|
const treatments = pkg.treatments ?? {};
|
|
157
|
+
const typography = compileTypography(pkg);
|
|
158
|
+
// #7739 D2.3 — accent ink needs the site's own palette on the band, or the
|
|
159
|
+
// accent there would be the platform's.
|
|
160
|
+
const darkBands = usesSolidDark(pkg) || treatments.darkInk === "accent";
|
|
91
161
|
return {
|
|
92
162
|
...(treatments.corners !== undefined && { corners: treatments.corners }),
|
|
93
163
|
...(treatments.eyebrowCase !== undefined && { eyebrowCase: treatments.eyebrowCase }),
|
|
94
164
|
...(treatments.placeholders !== undefined && { placeholders: treatments.placeholders }),
|
|
95
165
|
...(treatments.checkmarks !== undefined && { checkmarks: treatments.checkmarks }),
|
|
166
|
+
...(treatments.buttonShape !== undefined && { buttonShape: treatments.buttonShape }),
|
|
167
|
+
...(treatments.darkInk !== undefined && { darkInk: treatments.darkInk }),
|
|
168
|
+
...(treatments.figures !== undefined && { figures: treatments.figures }),
|
|
169
|
+
...(typography !== undefined && { typography }),
|
|
170
|
+
...(darkBands && { darkBands: "site" }),
|
|
96
171
|
colors: { light, dark },
|
|
97
172
|
};
|
|
98
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* The faces a package's display and body roles source (#7720 D3.1), or
|
|
176
|
+
* `undefined` when neither declares `fontSource`. Pure, like
|
|
177
|
+
* `compileDesignTheme`; `readDesignPackage` has already constrained every
|
|
178
|
+
* stack it reads here.
|
|
179
|
+
*/
|
|
180
|
+
export function compileDesignFonts(pkg) {
|
|
181
|
+
const fonts = {};
|
|
182
|
+
for (const role of ["display", "body"]) {
|
|
183
|
+
const def = pkg.typography[role];
|
|
184
|
+
if (def?.fontSource === undefined || def.fontFamily === undefined)
|
|
185
|
+
continue;
|
|
186
|
+
const comma = def.fontFamily.indexOf(",");
|
|
187
|
+
fonts[role] = {
|
|
188
|
+
source: def.fontSource,
|
|
189
|
+
family: unquoteFamily(comma === -1 ? def.fontFamily : def.fontFamily.slice(0, comma)),
|
|
190
|
+
fallback: comma === -1 ? "" : def.fontFamily.slice(comma + 1).trim(),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
return fonts.display !== undefined || fonts.body !== undefined ? fonts : undefined;
|
|
194
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { type SectionLookCatalog } from "./design-presets.js";
|
|
2
|
+
import type { ResolvedDesign } from "./design-scaffold.js";
|
|
3
|
+
/** Where a site's home page lives, site-relative. */
|
|
4
|
+
export declare const HOME_PAGE_PATH = "content/en/HomePage/home.mdx";
|
|
5
|
+
/** One `home/<shape>.mdx` file of a design package (D1). */
|
|
6
|
+
export interface HomeSeed {
|
|
7
|
+
readonly shape: string;
|
|
8
|
+
/** Absolute path, for refusals. */
|
|
9
|
+
readonly file: string;
|
|
10
|
+
readonly text: string;
|
|
11
|
+
}
|
|
12
|
+
export type HomeSeedOutcome = "seeded" | "restored" | "unchanged" | "authored" | "no-shape" | "no-home";
|
|
13
|
+
/**
|
|
14
|
+
* Read and validate `<dir>/home/*` (D1). Refuses with a `DesignPackageError`
|
|
15
|
+
* naming the file, before any write: a basename that is not a recognized
|
|
16
|
+
* shape, a page whose `type` is not `HomePage`, `sections` that cannot be
|
|
17
|
+
* located (or are not in the flow form a carry can splice), a section type
|
|
18
|
+
* the catalog does not carry, or a `logoCloud` whose `logos` is not `[]`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function readDesignHomeSeeds(dir: string, opts: {
|
|
21
|
+
catalog: () => SectionLookCatalog;
|
|
22
|
+
}): HomeSeed[];
|
|
23
|
+
/**
|
|
24
|
+
* The seeds a resolved design uses (D1): its own `home/`, else — a
|
|
25
|
+
* byte-identical copy of a shipped design IS that design — the shipped
|
|
26
|
+
* design's, else none.
|
|
27
|
+
*/
|
|
28
|
+
export declare function designHomeSeeds(resolved: ResolvedDesign, opts: {
|
|
29
|
+
designsDir: string;
|
|
30
|
+
catalog: () => SectionLookCatalog;
|
|
31
|
+
}): HomeSeed[];
|
|
32
|
+
/** Every shipped design's seeds (D3's placeholder set reads them all). */
|
|
33
|
+
export declare function shippedHomeSeeds(opts: {
|
|
34
|
+
designsDir: string;
|
|
35
|
+
catalog: () => SectionLookCatalog;
|
|
36
|
+
}): HomeSeed[];
|
|
37
|
+
/** The shape's own home: its shape tree's, else the overlay's (spec § 1.4). */
|
|
38
|
+
export declare function shapeHomePath(shape: string, opts: {
|
|
39
|
+
siteTemplatesDir: string;
|
|
40
|
+
templatesDir: string;
|
|
41
|
+
}): string;
|
|
42
|
+
/**
|
|
43
|
+
* A home's content fingerprint (D3): per section in order, its type and its
|
|
44
|
+
* non-look `data` entries as `[key, normalized raw value]`, sorted by key.
|
|
45
|
+
* A reflow, a look-field change or a preset leaves it unchanged; ids, the
|
|
46
|
+
* title, the description and the body are not in it.
|
|
47
|
+
*/
|
|
48
|
+
export declare function fingerprint(text: string, catalog: SectionLookCatalog, file?: string): string;
|
|
49
|
+
/**
|
|
50
|
+
* The seed's `sections:` value with D4's carry applied from `home`: a seed
|
|
51
|
+
* `logoCloud` (its `logos: []`) takes the raw `logos` of the home's first
|
|
52
|
+
* `logoCloud`, or is dropped when the home has none.
|
|
53
|
+
*/
|
|
54
|
+
export declare function applySeed(home: string, seed: HomeSeed, file?: string): string;
|
|
55
|
+
/**
|
|
56
|
+
* Plan a site home under a design (D3). Writes nothing: `text` is the new
|
|
57
|
+
* home when the outcome is `seeded` or `restored`, and absent otherwise.
|
|
58
|
+
*/
|
|
59
|
+
export declare function planDesignHome(opts: {
|
|
60
|
+
/** The site's content/en/HomePage/home.mdx, if any. */
|
|
61
|
+
current: string | undefined;
|
|
62
|
+
/** The roster's template shape. */
|
|
63
|
+
shape: string | undefined;
|
|
64
|
+
/** The applied design's seed for that shape (D1). */
|
|
65
|
+
target: HomeSeed | undefined;
|
|
66
|
+
/** The text of the shape's own home file (spec § 1.4). */
|
|
67
|
+
shapeHome: string;
|
|
68
|
+
/** Every shipped design's seeds (filtered to `shape` here). */
|
|
69
|
+
shippedSeeds: readonly HomeSeed[];
|
|
70
|
+
catalog: SectionLookCatalog;
|
|
71
|
+
}): {
|
|
72
|
+
outcome: HomeSeedOutcome;
|
|
73
|
+
text?: string;
|
|
74
|
+
};
|