create-ailk 0.12.1 → 0.14.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 +247 -33
- package/component-catalog.md +10 -6
- package/dist/apply-design.d.ts +21 -2
- package/dist/apply-design.js +120 -19
- package/dist/cli.js +1 -0
- 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 +223 -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 +1 -0
- package/dist/lib/design-checks.d.ts +12 -1
- package/dist/lib/design-checks.js +38 -2
- package/dist/lib/module-license-gate.d.ts +9 -0
- package/dist/lib/module-license-gate.js +18 -0
- package/dist/module-architecture.js +27 -0
- package/dist/programmatic.js +70 -26
- package/dist/site-chrome.d.ts +38 -0
- package/dist/site-chrome.js +109 -2
- package/dist/site-fonts.d.ts +21 -0
- package/dist/site-fonts.js +54 -0
- 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/team-profiles.d.ts +29 -0
- package/dist/team-profiles.js +43 -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 +164 -34
- 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/.env.example +23 -9
- package/templates/README.md +2 -0
- package/templates/apps/api/CLAUDE.md +24 -17
- package/templates/apps/api/src/__tests__/factories/agent-caller.ts +98 -0
- package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +321 -0
- package/templates/apps/api/src/__tests__/schedule-forwarded-rate-limit.test.ts +117 -0
- package/templates/apps/api/src/config/__tests__/hub-require-spoke-key.test.ts +39 -14
- package/templates/apps/api/src/config/index.ts +35 -20
- package/templates/apps/api/src/lib/__mocks__/prisma-client.js +3 -0
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +14 -0
- package/templates/apps/api/src/lib/__tests__/agent-store.test.ts +468 -0
- package/templates/apps/api/src/lib/__tests__/spoke-store.test.ts +32 -2
- package/templates/apps/api/src/lib/agent-store.ts +521 -0
- package/templates/apps/api/src/lib/spoke-store.ts +13 -2
- package/templates/apps/api/src/middleware/__tests__/auth.bearer-path.test.ts +384 -1
- package/templates/apps/api/src/middleware/__tests__/auth.cookie-path.test.ts +61 -0
- package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +176 -0
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +76 -0
- package/templates/apps/api/src/middleware/auth.ts +219 -5
- package/templates/apps/api/src/middleware/default-tenant.ts +29 -8
- package/templates/apps/api/src/middleware/rate-limit.ts +35 -13
- package/templates/apps/api/src/routes/leads/__tests__/spoke.test.ts +76 -15
- package/templates/apps/api/src/routes/leads/index.ts +14 -6
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +5 -6
- package/templates/apps/api/src/routes/project-listings/index.ts +14 -9
- package/templates/apps/api/src/routes/project-listings/me.ts +4 -3
- package/templates/apps/api/src/routes/project-listings/patch-copy.ts +7 -5
- package/templates/apps/api/src/routes/workspaces/__tests__/inbox-wire-schemas.test.ts +198 -0
- package/templates/apps/api/src/server.ts +27 -0
- package/templates/apps/api/src/services/__tests__/audit-log.test.ts +374 -57
- package/templates/apps/api/src/services/__tests__/leads-inbox.test.ts +25 -2
- package/templates/apps/api/src/services/__tests__/spoke-resolution.test.ts +303 -17
- package/templates/apps/api/src/services/audit-log.ts +278 -47
- package/templates/apps/api/src/services/default-tenant.ts +55 -5
- package/templates/apps/api/src/services/leads-inbox.ts +12 -21
- package/templates/apps/api/src/services/project-listings.ts +1 -21
- package/templates/apps/api/src/services/public-site-resolver.ts +103 -13
- package/templates/apps/mcp/CLAUDE.md +44 -5
- package/templates/apps/mcp/__tests__/parity.test.ts +53 -1
- package/templates/apps/mcp/__tests__/workspace-surface.test.ts +364 -0
- package/templates/apps/mcp/package.json +1 -1
- package/templates/apps/mcp/src/http-transport.ts +107 -1
- package/templates/apps/mcp/src/workspace-server.ts +193 -0
- package/templates/apps/web/.env.example +64 -37
- package/templates/apps/web/CLAUDE.md +11 -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]/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__/site-contact-lines.test.ts +69 -0
- package/templates/apps/web/app/[locale]/contact/site-contact-lines.ts +64 -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 +156 -53
- 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]/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]/whitepapers/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/not-found.tsx +47 -0
- package/templates/apps/web/app/v1/[...path]/route.ts +16 -0
- package/templates/apps/web/lib/__tests__/profile-back-link.test.ts +95 -0
- package/templates/apps/web/lib/__tests__/relay.test.ts +515 -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/profile-back-link.ts +51 -0
- package/templates/apps/web/lib/relay.ts +279 -0
- package/templates/apps/web/lib/site-chrome.ts +12 -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/messages/en.json +7 -1
- package/templates/apps/web/middleware.ts +5 -4
- package/templates/apps/web/next.config.mjs +6 -0
- package/templates/database/CHANGELOG.md +22 -0
- package/templates/database/__tests__/agents.db.test.ts +235 -0
- package/templates/database/auth/schema.prisma +61 -0
- package/templates/database/migrations/20261005120000_agents/migration.sql +84 -0
- package/templates/database/package.json +1 -1
- package/versions.json +35 -35
- 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/programmatic.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* import { scaffoldSync } from './programmatic.js';
|
|
16
16
|
* const result = scaffoldSync({ targetDir: '/tmp/my-site', projectName: 'my-site' });
|
|
17
17
|
*/
|
|
18
|
-
import { existsSync, readdirSync, readFileSync, rmdirSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
18
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmdirSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
19
19
|
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
20
20
|
import { fileURLToPath } from "node:url";
|
|
21
21
|
import { AddModuleRefusedError, applyModuleToScaffold, } from "./add-module.js";
|
|
@@ -24,21 +24,23 @@ import { renderClaudeMd, syncClaudeMd, } from "./claude-md.js";
|
|
|
24
24
|
import { CATALOG_TARGET_PATH, writeComponentCatalog, } from "./component-catalog.js";
|
|
25
25
|
import { buildScaffoldMarkers, copyTemplate } from "./copy.js";
|
|
26
26
|
import { deriveArchitecture } from "./derive-architecture.js";
|
|
27
|
-
import { compileDesignTheme } from "./design-compile.js";
|
|
27
|
+
import { compileDesignFonts, compileDesignTheme } from "./design-compile.js";
|
|
28
|
+
import { HOME_PAGE_PATH, designHomeSeeds, planDesignHome, shapeHomePath, shippedHomeSeeds, } from "./design-home.js";
|
|
28
29
|
import { applyDesignPresets, readSectionLookCatalog, shippedPageTypes, validateDesignPresets, } from "./design-presets.js";
|
|
29
|
-
import {
|
|
30
|
+
import { resolveDesign, spliceThemeBlock, } from "./design-scaffold.js";
|
|
30
31
|
import { writeAuthSecretFile } from "./lib/auth-secret-file.js";
|
|
31
32
|
import { extractModuleBundle, installBundleTree, } from "./lib/extract-module-bundle.js";
|
|
32
33
|
import { fetchTemplate } from "./lib/fetch-template.js";
|
|
33
34
|
import { assertLicenseKeyShape, writeLicenseKeyFiles, } from "./lib/license-key-files.js";
|
|
34
|
-
import { assertDesignAllowed, assertReleased, deriveLevel, resolveModuleSet, resolvePaidModules, } from "./lib/module-license-gate.js";
|
|
35
|
+
import { assertDesignAllowed, assertReleased, deriveDesignLevel, deriveLevel, resolveModuleSet, resolvePaidModules, } from "./lib/module-license-gate.js";
|
|
35
36
|
import { classifyModules } from "./lib/module-tier.js";
|
|
36
37
|
import { normalizeModules } from "./module-architecture.js";
|
|
37
38
|
import { writePaidSections } from "./paid-sections.js";
|
|
38
39
|
import { RECOGNIZED_SHAPES, RESERVED_SHAPES, } from "./parse-args.js";
|
|
39
40
|
import { SITE_CHROME_PATH, renderSiteChrome, resolveSiteChrome, } from "./site-chrome.js";
|
|
41
|
+
import { SITE_FONTS_PATH, renderSiteFonts } from "./site-fonts.js";
|
|
40
42
|
import { planSiteManifest } from "./site-manifest.js";
|
|
41
|
-
import { SITE_ROSTER_PATH, planSiteManifestDesign, readSiteRoster, withMeetPlaceholderSeeds, } from "./site-roster.js";
|
|
43
|
+
import { SITE_ROSTER_PATH, planHomeRecord, planSiteManifestDesign, readSiteRoster, withMeetPlaceholderSeeds, } from "./site-roster.js";
|
|
42
44
|
import { GATEABLE_SURFACES, MODULE_SURFACES, NAV_CONFIG_PATH, excludedRoutePaths, renderNavConfig, resolveSurfaces, } from "./surfaces.js";
|
|
43
45
|
import { syncRoutes } from "./sync-routes.js";
|
|
44
46
|
import { BASE_LAYER_DIR, SHAPE_STARTERS, TEMPLATE_MANIFESTS, TemplateError, mergeRoster, resolveTemplate, resolveTemplateLineage, rosterDeclares, templateDelivery, templateTreeDir, } from "./template-manifest.js";
|
|
@@ -267,14 +269,22 @@ export async function scaffold(opts) {
|
|
|
267
269
|
// (D2): a non-look field or unknown variant throws `DesignPackageError`
|
|
268
270
|
// naming page type, section type and field, and the target is never
|
|
269
271
|
// created. A design with no `sections` block executes no new read (D8).
|
|
272
|
+
let lookCatalog;
|
|
273
|
+
const catalog = () => (lookCatalog ??= readSectionLookCatalog(join(PKG_ROOT, "component-catalog.json")));
|
|
270
274
|
if (resolvedDesign !== undefined &&
|
|
271
275
|
Object.keys(resolvedDesign.pkg.sections).length > 0) {
|
|
272
276
|
validateDesignPresets(resolvedDesign.pkg.sections, {
|
|
273
|
-
catalog:
|
|
277
|
+
catalog: catalog(),
|
|
274
278
|
pageTypes: shippedPageTypes(SITE_TEMPLATES_DIR),
|
|
275
279
|
file: join(resolvedDesign.dir, "DESIGN.md"),
|
|
276
280
|
});
|
|
277
281
|
}
|
|
282
|
+
// #7743 D5 — the design's home seeds are read and validated beside its
|
|
283
|
+
// presets: a malformed seed throws `DesignPackageError`, naming the file,
|
|
284
|
+
// before the target is created.
|
|
285
|
+
const designSeeds = resolvedDesign !== undefined
|
|
286
|
+
? designHomeSeeds(resolvedDesign, { designsDir: DESIGNS_DIR, catalog })
|
|
287
|
+
: [];
|
|
278
288
|
// #6813 D7 — the resolved design only when the CALLER chose one (`opts.design`);
|
|
279
289
|
// a template's own declared design is never gated (#6778 option (c)).
|
|
280
290
|
const chosenDesign = opts.design !== undefined ? resolvedDesign : undefined;
|
|
@@ -301,7 +311,7 @@ export async function scaffold(opts) {
|
|
|
301
311
|
// The run's derived level (#6759), for `assertDesignAllowed` below.
|
|
302
312
|
// Undefined on the alias branch and on a `--modules`-keep run (D2/D4 in
|
|
303
313
|
// module-license-gate.ts — the level plays no part in composing either),
|
|
304
|
-
// so a non-`standard` design on either path derives it separately
|
|
314
|
+
// so a non-`standard` design on either path derives it separately.
|
|
305
315
|
let level;
|
|
306
316
|
if (opts.template !== undefined) {
|
|
307
317
|
// Alias run — untouched (NFR-4): the union line and its
|
|
@@ -350,6 +360,18 @@ export async function scaffold(opts) {
|
|
|
350
360
|
});
|
|
351
361
|
bundles = chosen.bundles;
|
|
352
362
|
level = chosen.level;
|
|
363
|
+
// #7010 — a `--modules`-keep run composes no level (NFR-2), so a chosen
|
|
364
|
+
// non-Free design derives one from the key here, as the addition branch
|
|
365
|
+
// does; a plain keep run makes no extra network call.
|
|
366
|
+
if (level === undefined && chosenDesign !== undefined && !chosenDesign.free) {
|
|
367
|
+
level = await deriveDesignLevel({
|
|
368
|
+
licenseKey,
|
|
369
|
+
...(opts.moduleRegistryUrl !== undefined
|
|
370
|
+
? { registryUrl: opts.moduleRegistryUrl }
|
|
371
|
+
: {}),
|
|
372
|
+
...(opts.fetchImpl !== undefined ? { fetchImpl: opts.fetchImpl } : {}),
|
|
373
|
+
});
|
|
374
|
+
}
|
|
353
375
|
modules =
|
|
354
376
|
template !== undefined
|
|
355
377
|
? normalizeModules([...template.requires, ...chosen.modules])
|
|
@@ -378,9 +400,9 @@ export async function scaffold(opts) {
|
|
|
378
400
|
}
|
|
379
401
|
// #6760 D4 — after the level is resolved, before any fetch or write below.
|
|
380
402
|
// Only the caller's chosen design is gated (#6778 option (c)); a template's
|
|
381
|
-
// own declared design is not. `level ?? "free"` fails closed: a
|
|
382
|
-
// `--modules`-keep run
|
|
383
|
-
//
|
|
403
|
+
// own declared design is not. `level ?? "free"` fails closed: a level left
|
|
404
|
+
// underived (a plain alias or `--modules`-keep run with no chosen non-Free
|
|
405
|
+
// design) is never trusted. The
|
|
384
406
|
// refusal names `chosen.display` — the name, or the absolute directory
|
|
385
407
|
// (#6813 D7) — and `chosen.free` decides Free-ness by identity (D2), never
|
|
386
408
|
// by `FREE_DESIGNS.includes(name)` alone (a path is never in that list).
|
|
@@ -568,22 +590,6 @@ export async function scaffold(opts) {
|
|
|
568
590
|
: [];
|
|
569
591
|
writeFileSync(join(result.targetDir, NAV_CONFIG_PATH), renderNavConfig(result.surfaces, { serviceSlugs }));
|
|
570
592
|
pruneUnrosteredSeeds(result.targetDir, mergeRoster(template, template.requires), result.surfaces, ownership);
|
|
571
|
-
// spec 6892 D4 — the resolved design's own sample (the minimalism design's
|
|
572
|
-
// nine-slide deck home + its SVGs; no template declares `minimalism`
|
|
573
|
-
// since #6976, so none installs it today), installed AFTER the template
|
|
574
|
-
// layer so it wins over the shape's `HomePage/home.mdx`, and ONLY when
|
|
575
|
-
// the resolved design is the template's OWN declared design: a caller
|
|
576
|
-
// combination like `--shape organization-product --design minimalism`
|
|
577
|
-
// installs no sample (its roster carries neither the deck's link targets
|
|
578
|
-
// nor, in its generated record, the deck's sections).
|
|
579
|
-
if (resolvedDesign !== undefined &&
|
|
580
|
-
resolvedDesign.recorded === template.design) {
|
|
581
|
-
installDesignSample({
|
|
582
|
-
designDir: resolvedDesign.dir,
|
|
583
|
-
targetDir: result.targetDir,
|
|
584
|
-
ownership,
|
|
585
|
-
});
|
|
586
|
-
}
|
|
587
593
|
}
|
|
588
594
|
// #6813 D5/D8 steps 9-10 — whenever a design is resolved (the caller's,
|
|
589
595
|
// else the template's): the design's DESIGN.md (#6939); the compiled theme,
|
|
@@ -595,6 +601,15 @@ export async function scaffold(opts) {
|
|
|
595
601
|
// #6939 — the scaffold's DESIGN.md is the design it wears, not the one
|
|
596
602
|
// its template layer shipped; the site's own header lines are kept.
|
|
597
603
|
writeFileSync(join(result.targetDir, "DESIGN.md"), planDesignMd(resolvedDesign.dir, result.targetDir));
|
|
604
|
+
// #7720 D4.1 — the faces the design sources, beside site-chrome.ts. A
|
|
605
|
+
// design that sources none renders the overlay's committed default, so
|
|
606
|
+
// the file is left as the template tree wrote it.
|
|
607
|
+
const siteFontsPath = join(result.targetDir, SITE_FONTS_PATH);
|
|
608
|
+
const siteFonts = renderSiteFonts(compileDesignFonts(resolvedDesign.pkg));
|
|
609
|
+
if (!existsSync(siteFontsPath) || readFileSync(siteFontsPath, "utf8") !== siteFonts) {
|
|
610
|
+
mkdirSync(dirname(siteFontsPath), { recursive: true });
|
|
611
|
+
writeFileSync(siteFontsPath, siteFonts);
|
|
612
|
+
}
|
|
598
613
|
const compiledTheme = compileDesignTheme(resolvedDesign.pkg);
|
|
599
614
|
const siteMdxPath = join(result.targetDir, "content", "_site.mdx");
|
|
600
615
|
if (existsSync(siteMdxPath)) {
|
|
@@ -613,6 +628,27 @@ export async function scaffold(opts) {
|
|
|
613
628
|
writeFileSync(rosterPath, nextManifest);
|
|
614
629
|
}
|
|
615
630
|
}
|
|
631
|
+
// #7743 D5 — the design's home arc, seeded over a placeholder home once
|
|
632
|
+
// every layer has landed, before the presets dress it. A home that is not
|
|
633
|
+
// the shape's placeholder is never written; `--design standard` (no
|
|
634
|
+
// seeds) leaves the shape's home byte-identical. The one
|
|
635
|
+
// `planDesignHome` call site in scaffold().
|
|
636
|
+
const homePath = join(result.targetDir, ...HOME_PAGE_PATH.split("/"));
|
|
637
|
+
let homeSeeded = false;
|
|
638
|
+
if (template !== undefined) {
|
|
639
|
+
const homePlan = planDesignHome({
|
|
640
|
+
current: existsSync(homePath) ? readFileSync(homePath, "utf8") : undefined,
|
|
641
|
+
shape: template.shape,
|
|
642
|
+
target: designSeeds.find((seed) => seed.shape === template.shape),
|
|
643
|
+
shapeHome: readFileSync(shapeHomePath(template.shape, { siteTemplatesDir: SITE_TEMPLATES_DIR, templatesDir: TEMPLATES_DIR }), "utf8"),
|
|
644
|
+
shippedSeeds: shippedHomeSeeds({ designsDir: DESIGNS_DIR, catalog }),
|
|
645
|
+
catalog: catalog(),
|
|
646
|
+
});
|
|
647
|
+
if (homePlan.outcome === "seeded" && homePlan.text !== undefined) {
|
|
648
|
+
writeFileSync(homePath, homePlan.text);
|
|
649
|
+
homeSeeded = true;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
616
652
|
// #6816 D6 step 10b — the design's section presets, written onto the
|
|
617
653
|
// pages of the page types the roster of record places, once every layer
|
|
618
654
|
// has landed. The one `applyDesignPresets` call site. A scaffold with no
|
|
@@ -637,6 +673,14 @@ export async function scaffold(opts) {
|
|
|
637
673
|
writeFileSync(page.path, page.text);
|
|
638
674
|
}
|
|
639
675
|
}
|
|
676
|
+
// #7743 D6 — the manifest's `/` row records the seeded home's sections,
|
|
677
|
+
// as the presets left them.
|
|
678
|
+
const rosterPath = join(result.targetDir, SITE_ROSTER_PATH);
|
|
679
|
+
if (homeSeeded && existsSync(rosterPath)) {
|
|
680
|
+
const nextManifest = planHomeRecord(readFileSync(rosterPath, "utf8"), readFileSync(homePath, "utf8"), catalog());
|
|
681
|
+
if (nextManifest !== undefined)
|
|
682
|
+
writeFileSync(rosterPath, nextManifest);
|
|
683
|
+
}
|
|
640
684
|
}
|
|
641
685
|
// #6944 — the manifest records what this scaffold IS, once every layer has
|
|
642
686
|
// landed: the effective module set, a row for every route on disk, and the
|
package/dist/site-chrome.d.ts
CHANGED
|
@@ -38,6 +38,24 @@ export type SiteChromeFooter = "columns" | "centered";
|
|
|
38
38
|
/** The `home` knob's vocabulary. `deck` → the locale-root route mounts
|
|
39
39
|
* MarketingShell with `snap="mandatory"` + `deckHero` (spec 5426 D7). */
|
|
40
40
|
export type SiteChromeHome = "default" | "deck";
|
|
41
|
+
/** The `innerPages` knob's vocabulary (#7721). `bare` → every non-home page
|
|
42
|
+
* gets the bare header and the one-bar footer. */
|
|
43
|
+
export type SiteChromeInnerPages = "default" | "bare";
|
|
44
|
+
/** The `colorMode` knob's vocabulary (#7742). `light` pins the site to light
|
|
45
|
+
* and removes every theme switch; dark then appears only as a deck ground. */
|
|
46
|
+
export type SiteChromeColorMode = "user" | "light";
|
|
47
|
+
/** The `chromeStyle` knob's vocabulary (#7742). `minimal` → the deck's closing
|
|
48
|
+
* slide is the wordmark + contact lines + one legal line, and the consent bar
|
|
49
|
+
* is a ground-coloured hairline bar that never covers the down cue. */
|
|
50
|
+
export type SiteChromeChromeStyle = "default" | "minimal";
|
|
51
|
+
/**
|
|
52
|
+
* The `deckGrounds` knob's vocabulary (#7719 D3) — the shared section surface
|
|
53
|
+
* enum, duplicated from `@working-theory/validation`'s `SectionSurfaceSchema`
|
|
54
|
+
* because `create-ailk` cannot import it (one runtime dependency, § 1.5);
|
|
55
|
+
* `site-chrome.test.ts` proves the two lists equal.
|
|
56
|
+
*/
|
|
57
|
+
export declare const DECK_GROUND_VALUES: readonly ["none", "gray-100", "wash", "wash-secondary", "gradient-brand", "gradient-dark", "solid-dark"];
|
|
58
|
+
export type DeckGround = (typeof DECK_GROUND_VALUES)[number];
|
|
41
59
|
/**
|
|
42
60
|
* A template's declared layout block — every field optional. Lives on
|
|
43
61
|
* `TemplateManifest.layout` (the code registry, `template-manifest.ts`) and
|
|
@@ -48,6 +66,16 @@ export interface TemplateLayout {
|
|
|
48
66
|
footer?: SiteChromeFooter;
|
|
49
67
|
announcementBanner?: boolean;
|
|
50
68
|
home?: SiteChromeHome;
|
|
69
|
+
/** #7719 D3 — the surfaces a deck home cycles, slide by slide, onto slides that author none. */
|
|
70
|
+
deckGrounds?: readonly DeckGround[];
|
|
71
|
+
/** #7721 D1 — `bare`: every non-home page wears the bare header and the one-bar footer. */
|
|
72
|
+
innerPages?: SiteChromeInnerPages;
|
|
73
|
+
/** #7740 D1 — `true`: the deck home presents slide by slide (a deck home only). */
|
|
74
|
+
deckPresentation?: boolean;
|
|
75
|
+
/** #7742 D1 — `light`: the site is pinned to light, with no theme switch. */
|
|
76
|
+
colorMode?: SiteChromeColorMode;
|
|
77
|
+
/** #7742 D1 — `minimal`: the minimal closing slide, consent bar and themed 404. */
|
|
78
|
+
chromeStyle?: SiteChromeChromeStyle;
|
|
51
79
|
}
|
|
52
80
|
/** `TemplateLayout` with every field resolved — what `site-chrome.ts` exports. */
|
|
53
81
|
export interface ResolvedSiteChrome {
|
|
@@ -55,6 +83,16 @@ export interface ResolvedSiteChrome {
|
|
|
55
83
|
footer: SiteChromeFooter;
|
|
56
84
|
announcementBanner: boolean;
|
|
57
85
|
home: SiteChromeHome;
|
|
86
|
+
/** Present only when a list resolves AND `home` is `deck` (#7719 D3) — never an `undefined` key. */
|
|
87
|
+
deckGrounds?: readonly DeckGround[];
|
|
88
|
+
/** Present only when it resolves to "bare" — never "default" (#7721 D1). */
|
|
89
|
+
innerPages?: "bare";
|
|
90
|
+
/** Present only when it resolves to `true` AND `home` is `deck` (#7740 D1) — never `false`. */
|
|
91
|
+
deckPresentation?: true;
|
|
92
|
+
/** Present only when it resolves to "light" — never "user" (#7742 D1). */
|
|
93
|
+
colorMode?: "light";
|
|
94
|
+
/** Present only when it resolves to "minimal" — never "default" (#7742 D1). */
|
|
95
|
+
chromeStyle?: "minimal";
|
|
58
96
|
}
|
|
59
97
|
/** Defaults (#5612 D1; banner off since #6469). */
|
|
60
98
|
export declare const DEFAULT_SITE_CHROME: ResolvedSiteChrome;
|
package/dist/site-chrome.js
CHANGED
|
@@ -29,6 +29,21 @@
|
|
|
29
29
|
* close): an unrecognized design NAME now fails in `resolveDesign`, before
|
|
30
30
|
* this function ever sees one.
|
|
31
31
|
*/
|
|
32
|
+
/**
|
|
33
|
+
* The `deckGrounds` knob's vocabulary (#7719 D3) — the shared section surface
|
|
34
|
+
* enum, duplicated from `@working-theory/validation`'s `SectionSurfaceSchema`
|
|
35
|
+
* because `create-ailk` cannot import it (one runtime dependency, § 1.5);
|
|
36
|
+
* `site-chrome.test.ts` proves the two lists equal.
|
|
37
|
+
*/
|
|
38
|
+
export const DECK_GROUND_VALUES = [
|
|
39
|
+
"none",
|
|
40
|
+
"gray-100",
|
|
41
|
+
"wash",
|
|
42
|
+
"wash-secondary",
|
|
43
|
+
"gradient-brand",
|
|
44
|
+
"gradient-dark",
|
|
45
|
+
"solid-dark",
|
|
46
|
+
];
|
|
32
47
|
/** Defaults (#5612 D1; banner off since #6469). */
|
|
33
48
|
export const DEFAULT_SITE_CHROME = {
|
|
34
49
|
nav: "full",
|
|
@@ -48,6 +63,9 @@ export class TemplateLayoutError extends Error {
|
|
|
48
63
|
const NAV_VALUES = ["full", "mark-only"];
|
|
49
64
|
const FOOTER_VALUES = ["columns", "centered"];
|
|
50
65
|
const HOME_VALUES = ["default", "deck"];
|
|
66
|
+
const INNER_PAGES_VALUES = ["default", "bare"];
|
|
67
|
+
const COLOR_MODE_VALUES = ["user", "light"];
|
|
68
|
+
const CHROME_STYLE_VALUES = ["default", "minimal"];
|
|
51
69
|
/**
|
|
52
70
|
* Validate an UNTRUSTED `layout` value (a bundle's `ailk-template.json`
|
|
53
71
|
* `layout` field — arbitrary JSON, never compile-time checked the way the
|
|
@@ -92,6 +110,40 @@ export function validateTemplateLayout(value, subject) {
|
|
|
92
110
|
}
|
|
93
111
|
result.home = raw.home;
|
|
94
112
|
}
|
|
113
|
+
if ("deckGrounds" in raw && raw.deckGrounds !== undefined) {
|
|
114
|
+
const grounds = raw.deckGrounds;
|
|
115
|
+
const badIndex = Array.isArray(grounds)
|
|
116
|
+
? grounds.findIndex((g) => !DECK_GROUND_VALUES.includes(g))
|
|
117
|
+
: -1;
|
|
118
|
+
if (!Array.isArray(grounds) || grounds.length === 0 || badIndex !== -1) {
|
|
119
|
+
throw new TemplateLayoutError(`${subject}: layout.deckGrounds must be a non-empty list of ${DECK_GROUND_VALUES.join(", ")} — got ${JSON.stringify(badIndex !== -1 ? grounds[badIndex] : grounds)}`);
|
|
120
|
+
}
|
|
121
|
+
result.deckGrounds = grounds;
|
|
122
|
+
}
|
|
123
|
+
if ("innerPages" in raw && raw.innerPages !== undefined) {
|
|
124
|
+
if (!INNER_PAGES_VALUES.includes(raw.innerPages)) {
|
|
125
|
+
throw new TemplateLayoutError(`${subject}: layout.innerPages must be one of ${INNER_PAGES_VALUES.join(", ")} — got ${JSON.stringify(raw.innerPages)}`);
|
|
126
|
+
}
|
|
127
|
+
result.innerPages = raw.innerPages;
|
|
128
|
+
}
|
|
129
|
+
if ("deckPresentation" in raw && raw.deckPresentation !== undefined) {
|
|
130
|
+
if (typeof raw.deckPresentation !== "boolean") {
|
|
131
|
+
throw new TemplateLayoutError(`${subject}: layout.deckPresentation must be a boolean — got ${JSON.stringify(raw.deckPresentation)}`);
|
|
132
|
+
}
|
|
133
|
+
result.deckPresentation = raw.deckPresentation;
|
|
134
|
+
}
|
|
135
|
+
if ("colorMode" in raw && raw.colorMode !== undefined) {
|
|
136
|
+
if (!COLOR_MODE_VALUES.includes(raw.colorMode)) {
|
|
137
|
+
throw new TemplateLayoutError(`${subject}: layout.colorMode must be one of ${COLOR_MODE_VALUES.join(", ")} — got ${JSON.stringify(raw.colorMode)}`);
|
|
138
|
+
}
|
|
139
|
+
result.colorMode = raw.colorMode;
|
|
140
|
+
}
|
|
141
|
+
if ("chromeStyle" in raw && raw.chromeStyle !== undefined) {
|
|
142
|
+
if (!CHROME_STYLE_VALUES.includes(raw.chromeStyle)) {
|
|
143
|
+
throw new TemplateLayoutError(`${subject}: layout.chromeStyle must be one of ${CHROME_STYLE_VALUES.join(", ")} — got ${JSON.stringify(raw.chromeStyle)}`);
|
|
144
|
+
}
|
|
145
|
+
result.chromeStyle = raw.chromeStyle;
|
|
146
|
+
}
|
|
95
147
|
return result;
|
|
96
148
|
}
|
|
97
149
|
/**
|
|
@@ -113,7 +165,7 @@ export function resolveSiteChrome(layout, designLayout) {
|
|
|
113
165
|
throw new TypeError(`resolveSiteChrome: designLayout must be a layout object, got ${JSON.stringify(designLayout)}`);
|
|
114
166
|
}
|
|
115
167
|
const fromDesign = designLayout ?? {};
|
|
116
|
-
|
|
168
|
+
const resolved = {
|
|
117
169
|
nav: layout?.nav ?? fromDesign.nav ?? DEFAULT_SITE_CHROME.nav,
|
|
118
170
|
footer: layout?.footer ?? fromDesign.footer ?? DEFAULT_SITE_CHROME.footer,
|
|
119
171
|
announcementBanner: layout?.announcementBanner ??
|
|
@@ -121,6 +173,29 @@ export function resolveSiteChrome(layout, designLayout) {
|
|
|
121
173
|
DEFAULT_SITE_CHROME.announcementBanner,
|
|
122
174
|
home: layout?.home ?? fromDesign.home ?? DEFAULT_SITE_CHROME.home,
|
|
123
175
|
};
|
|
176
|
+
// #7719 D3 — the grounds resolve as a whole list, and only a deck keeps them.
|
|
177
|
+
const deckGrounds = layout?.deckGrounds ?? fromDesign.deckGrounds;
|
|
178
|
+
if (deckGrounds !== undefined && resolved.home === "deck") {
|
|
179
|
+
resolved.deckGrounds = deckGrounds;
|
|
180
|
+
}
|
|
181
|
+
// #7721 D1 — only "bare" is ever a key, so a chrome that does not opt in
|
|
182
|
+
// (unset, or an explicit "default") resolves exactly as before.
|
|
183
|
+
if ((layout?.innerPages ?? fromDesign.innerPages) === "bare") {
|
|
184
|
+
resolved.innerPages = "bare";
|
|
185
|
+
}
|
|
186
|
+
// #7740 D1 — only `true` on a deck is ever a key: an explicit layout
|
|
187
|
+
// `false` wins over the design's `true`, and a non-deck home drops it.
|
|
188
|
+
if ((layout?.deckPresentation ?? fromDesign.deckPresentation) === true && resolved.home === "deck") {
|
|
189
|
+
resolved.deckPresentation = true;
|
|
190
|
+
}
|
|
191
|
+
// #7742 D1 — as with `innerPages`, only the opt-in value is ever a key.
|
|
192
|
+
if ((layout?.colorMode ?? fromDesign.colorMode) === "light") {
|
|
193
|
+
resolved.colorMode = "light";
|
|
194
|
+
}
|
|
195
|
+
if ((layout?.chromeStyle ?? fromDesign.chromeStyle) === "minimal") {
|
|
196
|
+
resolved.chromeStyle = "minimal";
|
|
197
|
+
}
|
|
198
|
+
return resolved;
|
|
124
199
|
}
|
|
125
200
|
/**
|
|
126
201
|
* Render `apps/web/lib/site-chrome.ts` for a resolved layout — the same
|
|
@@ -130,6 +205,26 @@ export function resolveSiteChrome(layout, designLayout) {
|
|
|
130
205
|
* `snap="mandatory"` + `deckHero` to MarketingShell.
|
|
131
206
|
*/
|
|
132
207
|
export function renderSiteChrome(resolved) {
|
|
208
|
+
// #7719 D3 — each `deckGrounds` line is emitted only when the chrome has one,
|
|
209
|
+
// so a chrome without it renders today's text exactly.
|
|
210
|
+
const grounds = resolved.deckGrounds;
|
|
211
|
+
const groundsComment = grounds === undefined
|
|
212
|
+
? ""
|
|
213
|
+
: "\n// - deckGrounds → the deck home cycles these surfaces, slide by slide, onto slides that author none";
|
|
214
|
+
const groundsMember = grounds === undefined
|
|
215
|
+
? ""
|
|
216
|
+
: `\n deckGrounds?: readonly (${DECK_GROUND_VALUES.map((g) => JSON.stringify(g)).join(" | ")})[];`;
|
|
217
|
+
const groundsValue = grounds === undefined ? "" : `\n deckGrounds: [${grounds.map((g) => JSON.stringify(g)).join(", ")}],`;
|
|
218
|
+
// #7721 D1 — the value line only for a bare chrome; the interface member
|
|
219
|
+
// and comment bullet always, so the layout reads `siteChrome.innerPages`.
|
|
220
|
+
const innerPagesValue = resolved.innerPages === "bare" ? '\n innerPages: "bare",' : "";
|
|
221
|
+
// #7740 D1 — `innerPages`' form: the member and bullet always, the value
|
|
222
|
+
// line only when the chrome presents.
|
|
223
|
+
const presentationValue = resolved.deckPresentation === true ? "\n deckPresentation: true," : "";
|
|
224
|
+
// #7742 D1 — the same form again: member and bullet always, value lines
|
|
225
|
+
// only for the opt-in values.
|
|
226
|
+
const colorModeValue = resolved.colorMode === "light" ? '\n colorMode: "light",' : "";
|
|
227
|
+
const chromeStyleValue = resolved.chromeStyle === "minimal" ? '\n chromeStyle: "minimal",' : "";
|
|
133
228
|
return `// site-chrome.ts — resolved template layout knobs.
|
|
134
229
|
//
|
|
135
230
|
// GENERATED by create-ailk from the declared template's \`layout\` block
|
|
@@ -145,19 +240,31 @@ export function renderSiteChrome(resolved) {
|
|
|
145
240
|
// - home: "deck" → the locale-root route mounts MarketingShell with
|
|
146
241
|
// snap="mandatory" + deckHero (spec 5426 D7); every other route is
|
|
147
242
|
// unaffected.
|
|
243
|
+
// - innerPages: "bare" → every non-home page renders the bare header and
|
|
244
|
+
// the one-bar footer.
|
|
245
|
+
// - deckPresentation → the deck home presents slide by slide: fitted slides, slide anchors, the mark follows the ground
|
|
246
|
+
// - colorMode: "light" → the site is pinned to light and shows no theme
|
|
247
|
+
// switch; dark appears only as a deck ground.
|
|
248
|
+
// - chromeStyle: "minimal" → the deck's closing slide is the wordmark,
|
|
249
|
+
// contact lines and one legal line; the consent bar is a hairline bar on
|
|
250
|
+
// the page ground; the 404 renders in the site's theme.${groundsComment}
|
|
148
251
|
|
|
149
252
|
export interface SiteChrome {
|
|
150
253
|
nav: "full" | "mark-only";
|
|
151
254
|
footer: "columns" | "centered";
|
|
152
255
|
announcementBanner: boolean;
|
|
153
256
|
home: "default" | "deck";
|
|
257
|
+
innerPages?: "bare";
|
|
258
|
+
deckPresentation?: true;
|
|
259
|
+
colorMode?: "light";
|
|
260
|
+
chromeStyle?: "minimal";${groundsMember}
|
|
154
261
|
}
|
|
155
262
|
|
|
156
263
|
export const siteChrome: SiteChrome = {
|
|
157
264
|
nav: ${JSON.stringify(resolved.nav)},
|
|
158
265
|
footer: ${JSON.stringify(resolved.footer)},
|
|
159
266
|
announcementBanner: ${JSON.stringify(resolved.announcementBanner)},
|
|
160
|
-
home: ${JSON.stringify(resolved.home)}
|
|
267
|
+
home: ${JSON.stringify(resolved.home)},${groundsValue}${innerPagesValue}${presentationValue}${colorModeValue}${chromeStyleValue}
|
|
161
268
|
};
|
|
162
269
|
`;
|
|
163
270
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The web faces a design's typography sources (#7720, spec
|
|
3
|
+
* docs/specs/7720-a-web-font-source-and-spec.md D3.2) — rendered into the
|
|
4
|
+
* generated `apps/web/lib/site-fonts.ts` every scaffold gets, beside
|
|
5
|
+
* `site-chrome.ts` and in the same GENERATED-header idiom.
|
|
6
|
+
*
|
|
7
|
+
* next/font/google picks a face by the imported function's name and needs
|
|
8
|
+
* literal options at a module-scope call site, so a face cannot be chosen
|
|
9
|
+
* from the request-time `theme:` block — it is chosen here, at scaffold and
|
|
10
|
+
* apply-design time, as source text. The faces download at the site's
|
|
11
|
+
* `next build` and are served from its own origin, which the CSP's
|
|
12
|
+
* `font-src 'self'` already allows.
|
|
13
|
+
*
|
|
14
|
+
* With no sourced face the render is the empty default, byte-identical to the
|
|
15
|
+
* monorepo's own committed `apps/web/lib/site-fonts.ts`.
|
|
16
|
+
*/
|
|
17
|
+
import type { CompiledDesignFonts } from "./design-compile.js";
|
|
18
|
+
/** Template-relative path of the generated site-fonts module — beside `SITE_CHROME_PATH`. */
|
|
19
|
+
export declare const SITE_FONTS_PATH = "apps/web/lib/site-fonts.ts";
|
|
20
|
+
/** Render `apps/web/lib/site-fonts.ts` for `fonts` (D3.2). Ends with a trailing newline. */
|
|
21
|
+
export declare function renderSiteFonts(fonts: CompiledDesignFonts | undefined): string;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** Template-relative path of the generated site-fonts module — beside `SITE_CHROME_PATH`. */
|
|
2
|
+
export const SITE_FONTS_PATH = "apps/web/lib/site-fonts.ts";
|
|
3
|
+
const HEADER = `// site-fonts.ts — the web faces the site's design sources.
|
|
4
|
+
//
|
|
5
|
+
// GENERATED by create-ailk from the design's \`typography.<role>.fontSource\`
|
|
6
|
+
// (#7720). A design that sources no face gets the empty default.
|
|
7
|
+
// \`apps/web/app/[locale]/layout.tsx\` passes this to \`applyDesignFonts\`
|
|
8
|
+
// (@working-theory/theme): \`families\` fills --font-display / --font-body
|
|
9
|
+
// unless the site's own \`brand.fonts\` sets them, and \`className\` (the
|
|
10
|
+
// next/font variable classes) goes on <html>.
|
|
11
|
+
`;
|
|
12
|
+
const INTERFACE = `export interface SiteFonts {
|
|
13
|
+
className: string;
|
|
14
|
+
families: { display?: string | undefined; body?: string | undefined };
|
|
15
|
+
}
|
|
16
|
+
`;
|
|
17
|
+
/** Render `apps/web/lib/site-fonts.ts` for `fonts` (D3.2). Ends with a trailing newline. */
|
|
18
|
+
export function renderSiteFonts(fonts) {
|
|
19
|
+
const faces = ["display", "body"].flatMap((role) => {
|
|
20
|
+
const face = fonts?.[role];
|
|
21
|
+
return face !== undefined ? [{ role, face }] : [];
|
|
22
|
+
});
|
|
23
|
+
if (faces.length === 0) {
|
|
24
|
+
return `${HEADER}\n${INTERFACE}\nexport const siteFonts: SiteFonts = { className: "", families: {} };\n`;
|
|
25
|
+
}
|
|
26
|
+
// One loader per distinct family, named for the first role using it (display before body).
|
|
27
|
+
const slotOf = new Map();
|
|
28
|
+
for (const { role, face } of faces) {
|
|
29
|
+
if (!slotOf.has(face.family))
|
|
30
|
+
slotOf.set(face.family, role);
|
|
31
|
+
}
|
|
32
|
+
const exportName = (family) => family.replace(/ /g, "_");
|
|
33
|
+
const imports = [...slotOf.keys()].map(exportName).sort();
|
|
34
|
+
const loaders = [...slotOf].map(([family, slot]) => `const ${slot}Face = ${exportName(family)}({ subsets: ${JSON.stringify(["latin"])}, variable: ${JSON.stringify(`--font-design-${slot}`)} });`);
|
|
35
|
+
const className = [...slotOf.values()].map((slot) => `${slot}Face.variable`).join(", ");
|
|
36
|
+
const families = faces.map(({ role, face }) => {
|
|
37
|
+
const slot = slotOf.get(face.family) ?? role;
|
|
38
|
+
const value = `var(--font-design-${slot})${face.fallback === "" ? "" : `, ${face.fallback}`}`;
|
|
39
|
+
return ` ${role}: ${JSON.stringify(value)},`;
|
|
40
|
+
});
|
|
41
|
+
return `${HEADER}
|
|
42
|
+
import { ${imports.join(", ")} } from "next/font/google";
|
|
43
|
+
|
|
44
|
+
${INTERFACE}
|
|
45
|
+
${loaders.join("\n")}
|
|
46
|
+
|
|
47
|
+
export const siteFonts: SiteFonts = {
|
|
48
|
+
className: [${className}].join(" "),
|
|
49
|
+
families: {
|
|
50
|
+
${families.join("\n")}
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
`;
|
|
54
|
+
}
|
package/dist/site-roster.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CompiledDesignTheme } from "./design-compile.js";
|
|
2
|
+
import { type SectionLookCatalog } from "./design-presets.js";
|
|
2
3
|
import { type PageRosterRow } from "./module-architecture.js";
|
|
3
4
|
import type { ModuleName } from "./parse-args.js";
|
|
4
5
|
import type { ResolvedSiteChrome } from "./site-chrome.js";
|
|
@@ -76,6 +77,21 @@ export interface SiteManifestDesignNext {
|
|
|
76
77
|
* never reaches this function (`scaffold()` guards on the file's presence).
|
|
77
78
|
*/
|
|
78
79
|
export declare function planSiteManifestDesign(text: string, next: SiteManifestDesignNext, label: string): string | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* `build-template-manifest.mjs`'s `OPTION_KEYS` — the section `data` keys
|
|
82
|
+
* whose scalar values a manifest row's `sections[].options` records. A
|
|
83
|
+
* duplicate (the script is not importable here); a test pins the two equal.
|
|
84
|
+
*/
|
|
85
|
+
export declare const MANIFEST_OPTION_KEYS: readonly ["itemStyle", "actionsPosition", "gapBelow", "measure", "cardLayout", "layout", "size", "surface", "imagerySlot", "headingLevel", "align", "tone", "columns", "density", "fill", "accentStyle"];
|
|
86
|
+
/**
|
|
87
|
+
* Plan the manifest's `/` row `sections` as the home's sections (#7743 D6):
|
|
88
|
+
* `[{ type, component, options? }]`, `options` being the section's scalar
|
|
89
|
+
* `MANIFEST_OPTION_KEYS` values, as the generator writes them. `undefined`
|
|
90
|
+
* when the record already says so, or the manifest has no `/` row with a
|
|
91
|
+
* `sections` key. The value is spliced in place at its own depth; no other
|
|
92
|
+
* byte moves.
|
|
93
|
+
*/
|
|
94
|
+
export declare function planHomeRecord(manifestText: string, homeText: string, catalog: SectionLookCatalog): string | undefined;
|
|
79
95
|
/**
|
|
80
96
|
* The generator's row for each of a module's `pageContributions`, in
|
|
81
97
|
* declaration order — read from the committed manifest of the first
|
package/dist/site-roster.js
CHANGED
|
@@ -27,6 +27,8 @@ import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
|
27
27
|
import { join, relative, sep } from "node:path";
|
|
28
28
|
import { isDeepStrictEqual } from "node:util";
|
|
29
29
|
import { AddModuleRefusedError, readArchitectureHeader, } from "./add-module-core.js";
|
|
30
|
+
import { HOME_PAGE_PATH } from "./design-home.js";
|
|
31
|
+
import { locatePageSections } from "./design-presets.js";
|
|
30
32
|
import { diffTwoStrings } from "./lib/extract-module-bundle.js";
|
|
31
33
|
import { MODULE_SLICES, normalizeModules, } from "./module-architecture.js";
|
|
32
34
|
import { substituteMarkers } from "./substitute.js";
|
|
@@ -234,6 +236,85 @@ export function planSiteManifestDesign(text, next, label) {
|
|
|
234
236
|
}
|
|
235
237
|
return result;
|
|
236
238
|
}
|
|
239
|
+
// ── #7743 D6: the `/` row records the home's sections ─────────────────────
|
|
240
|
+
/**
|
|
241
|
+
* `build-template-manifest.mjs`'s `OPTION_KEYS` — the section `data` keys
|
|
242
|
+
* whose scalar values a manifest row's `sections[].options` records. A
|
|
243
|
+
* duplicate (the script is not importable here); a test pins the two equal.
|
|
244
|
+
*/
|
|
245
|
+
export const MANIFEST_OPTION_KEYS = [
|
|
246
|
+
"itemStyle",
|
|
247
|
+
"actionsPosition",
|
|
248
|
+
"gapBelow",
|
|
249
|
+
"measure",
|
|
250
|
+
"cardLayout",
|
|
251
|
+
"layout",
|
|
252
|
+
"size",
|
|
253
|
+
"surface",
|
|
254
|
+
"imagerySlot",
|
|
255
|
+
"headingLevel",
|
|
256
|
+
"align",
|
|
257
|
+
"tone",
|
|
258
|
+
"columns",
|
|
259
|
+
"density",
|
|
260
|
+
"fill",
|
|
261
|
+
"accentStyle",
|
|
262
|
+
];
|
|
263
|
+
/** A raw YAML scalar's value — a JSON scalar, a single-quoted string or a plain one — or undefined for a collection or null. */
|
|
264
|
+
function scalarValue(raw) {
|
|
265
|
+
const text = raw.trim();
|
|
266
|
+
if (text.startsWith("{") || text.startsWith("["))
|
|
267
|
+
return undefined;
|
|
268
|
+
if (text.startsWith("'"))
|
|
269
|
+
return text.slice(1, -1).replace(/''/g, "'");
|
|
270
|
+
try {
|
|
271
|
+
const value = JSON.parse(text);
|
|
272
|
+
return value === null || typeof value === "object" ? undefined : value;
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
return text;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Plan the manifest's `/` row `sections` as the home's sections (#7743 D6):
|
|
280
|
+
* `[{ type, component, options? }]`, `options` being the section's scalar
|
|
281
|
+
* `MANIFEST_OPTION_KEYS` values, as the generator writes them. `undefined`
|
|
282
|
+
* when the record already says so, or the manifest has no `/` row with a
|
|
283
|
+
* `sections` key. The value is spliced in place at its own depth; no other
|
|
284
|
+
* byte moves.
|
|
285
|
+
*/
|
|
286
|
+
export function planHomeRecord(manifestText, homeText, catalog) {
|
|
287
|
+
const file = parseRosterFile(manifestText, SITE_ROSTER_PATH);
|
|
288
|
+
const row = file.rows.find((r) => r.value["route"] === "/");
|
|
289
|
+
if (row === undefined || !("sections" in row.value))
|
|
290
|
+
return undefined;
|
|
291
|
+
const sections = (locatePageSections(homeText, HOME_PAGE_PATH) ?? []).map((section) => {
|
|
292
|
+
const options = {};
|
|
293
|
+
for (const key of MANIFEST_OPTION_KEYS) {
|
|
294
|
+
const entry = section.entries.find((e) => e.key === key);
|
|
295
|
+
const value = entry === undefined ? undefined : scalarValue(homeText.slice(entry.valueStart, entry.valueEnd));
|
|
296
|
+
if (value !== undefined)
|
|
297
|
+
options[key] = value;
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
type: section.type,
|
|
301
|
+
component: catalog[section.type]?.component ?? section.type,
|
|
302
|
+
...(Object.keys(options).length > 0 ? { options } : {}),
|
|
303
|
+
};
|
|
304
|
+
});
|
|
305
|
+
if (isDeepStrictEqual(row.value["sections"], sections))
|
|
306
|
+
return undefined;
|
|
307
|
+
const rowStart = manifestText.indexOf(row.text, file.open);
|
|
308
|
+
const at = findKey(row.text, 0, "sections");
|
|
309
|
+
const end = endOfValue(row.text, at.valueStart);
|
|
310
|
+
const column = at.keyStart - (row.text.lastIndexOf("\n", at.keyStart) + 1);
|
|
311
|
+
const rendered = JSON.stringify(sections, null, 2)
|
|
312
|
+
.split("\n")
|
|
313
|
+
.map((line, i) => (i === 0 ? line : `${" ".repeat(column)}${line}`))
|
|
314
|
+
.join("\n");
|
|
315
|
+
const rowText = row.text.slice(0, at.valueStart) + rendered + row.text.slice(end);
|
|
316
|
+
return manifestText.slice(0, rowStart) + rowText + manifestText.slice(rowStart + row.text.length);
|
|
317
|
+
}
|
|
237
318
|
// ── D2: a module's rows, from the shipped manifests ───────────────────────
|
|
238
319
|
/** A template's shipped manifest, or undefined when its tree ships none. */
|
|
239
320
|
function readShippedManifest(siteTemplatesDir, templateName) {
|
package/dist/starter-grid.js
CHANGED
|
@@ -170,6 +170,18 @@ const PORTFOLIO_ROWS = [
|
|
|
170
170
|
{ route: "/portfolio", content: ["content/en/PortfolioList/portfolio.mdx"] },
|
|
171
171
|
{ route: "/portfolio/[slug]", contentDir: "content/en/PortfolioItem" },
|
|
172
172
|
];
|
|
173
|
+
/**
|
|
174
|
+
* The team pages (#7744, checklist D1) — a TeamList hub at `/team` and one
|
|
175
|
+
* TeamMember profile per person at `/team/<slug>`, each emitting a
|
|
176
|
+
* `ProfilePage` whose main entity is that `Person`. They belong to the three
|
|
177
|
+
* ORGANIZATION shapes; person-service is one person and carries none. The
|
|
178
|
+
* seeds live in the `_base` layer under `content/en/TeamMember/`; the detail
|
|
179
|
+
* row's `contentDir` installs them, exactly as `PORTFOLIO_ROWS` does.
|
|
180
|
+
*/
|
|
181
|
+
const TEAM_ROWS = [
|
|
182
|
+
{ route: "/team", content: ["content/en/TeamList/team.mdx"] },
|
|
183
|
+
{ route: "/team/[slug]", contentDir: "content/en/TeamMember" },
|
|
184
|
+
];
|
|
173
185
|
/**
|
|
174
186
|
* The two report/referral sample rows. Neither carries a `GATEABLE_SURFACES`
|
|
175
187
|
* entry: `/referral` is a single-segment `LandingPage` slug served by the
|
|
@@ -308,18 +320,21 @@ export const SHAPE_ROSTERS = Object.freeze({
|
|
|
308
320
|
FREE_BLOG_TEASER_ROW,
|
|
309
321
|
...FREE_MEET_PLACEHOLDER_ROWS,
|
|
310
322
|
...PORTFOLIO_ROWS,
|
|
323
|
+
...TEAM_ROWS,
|
|
311
324
|
],
|
|
312
325
|
"organization-service": [
|
|
313
326
|
...ORGANIZATION_SERVICE_ROWS,
|
|
314
327
|
FREE_BLOG_TEASER_ROW,
|
|
315
328
|
...FREE_MEET_PLACEHOLDER_ROWS,
|
|
316
329
|
...PORTFOLIO_ROWS,
|
|
330
|
+
...TEAM_ROWS,
|
|
317
331
|
],
|
|
318
332
|
"local-business-service": [
|
|
319
333
|
...LOCAL_BUSINESS_SERVICE_ROWS,
|
|
320
334
|
FREE_BLOG_TEASER_ROW,
|
|
321
335
|
...FREE_MEET_PLACEHOLDER_ROWS,
|
|
322
336
|
...PORTFOLIO_ROWS,
|
|
337
|
+
...TEAM_ROWS,
|
|
323
338
|
],
|
|
324
339
|
"person-service": [
|
|
325
340
|
...PERSON_SERVICE_ROWS,
|