create-ailk 0.20.0 → 0.22.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 +21 -1
- package/component-catalog.json +220 -8
- package/component-catalog.md +21 -9
- package/dist/add-module.js +9 -5
- package/dist/apply-design.d.ts +9 -0
- package/dist/apply-design.js +78 -31
- package/dist/derive-architecture.d.ts +8 -1
- package/dist/derive-architecture.js +38 -3
- package/dist/design-home.d.ts +54 -11
- package/dist/design-home.js +160 -22
- package/dist/design-package.js +7 -2
- package/dist/lib/design-checks.d.ts +6 -1
- package/dist/lib/design-checks.js +23 -2
- package/dist/lib/env-catalog.js +5 -1
- package/dist/module-architecture.js +4 -2
- package/dist/programmatic.js +63 -25
- package/dist/site-chrome.d.ts +8 -0
- package/dist/site-chrome.js +17 -1
- package/dist/site-roster.d.ts +4 -2
- package/dist/site-roster.js +7 -5
- package/dist/spoke-checkout.d.ts +39 -18
- package/dist/spoke-checkout.js +177 -236
- package/dist/starter-grid.js +16 -0
- package/dist/surfaces.js +30 -14
- package/dist/sync-routes.d.ts +28 -0
- package/dist/sync-routes.js +124 -1
- package/dist/template-record.d.ts +4 -3
- package/dist/template-record.js +6 -3
- package/dist/topology.js +25 -1
- package/dist/update.d.ts +6 -1
- package/dist/update.js +125 -18
- package/package.json +3 -3
- package/site-templates/_base/ailk-template.json +2 -2
- package/site-templates/_base/content/en/WorkingAgreementPage/working-agreement.mdx +78 -0
- package/site-templates/designs/minimalism/DESIGN.md +1 -1
- package/site-templates/designs/minimalism/contact/local-business-service.mdx +23 -0
- package/site-templates/designs/minimalism/contact/organization-product.mdx +23 -0
- package/site-templates/designs/minimalism/contact/organization-service.mdx +23 -0
- package/site-templates/designs/minimalism/contact/person-service.mdx +23 -0
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +18 -1
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +11 -0
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +18 -1
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +34 -23
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +18 -1
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +33 -23
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +18 -1
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +33 -23
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +18 -1
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +31 -21
- package/templates/README.md +26 -25
- package/templates/apps/api/CLAUDE.md +20 -7
- package/templates/apps/api/jest.config.cjs +1 -0
- package/templates/apps/api/src/lib/__tests__/consent-store.test.ts +19 -0
- package/templates/apps/api/src/lib/__tests__/flow-completion-store.test.ts +149 -0
- package/templates/apps/api/src/lib/consent-store.ts +28 -0
- package/templates/apps/api/src/lib/flow-completion-store.ts +136 -0
- package/templates/apps/api/src/lib/hub-sign-in-store.ts +38 -2
- package/templates/apps/api/src/lib/proposal-paid-store.ts +21 -4
- package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +14 -3
- package/templates/apps/api/src/middleware/auth.ts +13 -6
- package/templates/apps/api/src/openapi/spec.ts +30 -3
- package/templates/apps/api/src/routes/auth/__tests__/hub-sign-in.test.ts +49 -0
- package/templates/apps/api/src/routes/auth/hub-sign-in.ts +41 -0
- package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +386 -0
- package/templates/apps/api/src/routes/consent/account.ts +266 -0
- package/templates/apps/api/src/routes/consent/index.ts +15 -7
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +36 -0
- package/templates/apps/api/src/routes/content/get-page.ts +4 -0
- package/templates/apps/api/src/routes/leads/__tests__/magnet-completion.test.ts +515 -0
- package/templates/apps/api/src/routes/leads/index.ts +161 -7
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +26 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-checkout-consent.test.ts +649 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +154 -19
- package/templates/apps/api/src/server.ts +28 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +1038 -118
- package/templates/apps/api/src/services/__tests__/magnet-completion.test.ts +389 -0
- package/templates/apps/api/src/services/checkout-buyer.ts +100 -4
- package/templates/apps/api/src/services/consent.ts +95 -0
- package/templates/apps/api/src/services/lead-routing.ts +337 -8
- package/templates/apps/api/src/services/magnet-completion.ts +242 -0
- package/templates/apps/api/src/services/person.ts +40 -8
- package/templates/apps/api/src/services/plan-offerings.ts +59 -0
- package/templates/apps/api/src/services/project-listings.ts +6 -1
- package/templates/apps/api/src/services/public-site-resolver.ts +8 -1
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +69 -61
- package/templates/apps/mcp/jest.config.cjs +1 -0
- package/templates/apps/web/.env.example +14 -0
- package/templates/apps/web/CLAUDE.md +139 -3
- package/templates/apps/web/__tests__/__mocks__/next-intl-server.ts +6 -2
- package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +5 -4
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +5 -1
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/page.tsx +2 -0
- package/templates/apps/web/app/[locale]/(auth)/layout.tsx +3 -14
- package/templates/apps/web/app/[locale]/(auth)/reset-password/page.tsx +2 -0
- package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +5 -1
- package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/page.test.tsx +160 -8
- package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/sign-in-card.test.tsx +12 -9
- package/templates/apps/web/app/[locale]/(auth)/sign-in/page.tsx +105 -2
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +36 -3
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-text.ts +47 -1
- package/templates/apps/web/app/[locale]/(auth)/sign-up/__tests__/sign-up-card.test.tsx +60 -22
- package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +16 -8
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +85 -31
- package/templates/apps/web/app/[locale]/(auth)/use-auth-card-flow.ts +18 -4
- package/templates/apps/web/app/[locale]/(auth)/verify-email/page.tsx +3 -1
- package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +7 -4
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/agreements-prompt.test.tsx +227 -207
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout-sign-out.test.tsx +17 -1
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout.test.tsx +143 -1
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/new-menu.test.tsx +4 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +385 -89
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/popups.test.tsx +5 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/privacy.test.tsx +44 -5
- package/templates/apps/web/app/[locale]/(authed)/account/account-frame.tsx +68 -34
- package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +0 -8
- package/templates/apps/web/app/[locale]/(authed)/account/agreements-actions.ts +39 -151
- package/templates/apps/web/app/[locale]/(authed)/account/agreements-gate.ts +61 -0
- package/templates/apps/web/app/[locale]/(authed)/account/agreements-prompt.tsx +143 -140
- package/templates/apps/web/app/[locale]/(authed)/account/layout.tsx +40 -6
- package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +68 -54
- package/templates/apps/web/app/[locale]/(authed)/account/privacy/page.tsx +5 -1
- package/templates/apps/web/app/[locale]/about/[slug]/page.tsx +46 -0
- package/templates/apps/web/app/[locale]/about/__tests__/about-routes.test.tsx +350 -0
- package/templates/apps/web/app/[locale]/about/page.tsx +83 -1
- package/templates/apps/web/app/[locale]/careers/[slug]/apply.actions.ts +293 -0
- package/templates/apps/web/app/[locale]/careers/[slug]/job-application-form.ts +115 -0
- package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +146 -1
- package/templates/apps/web/app/[locale]/careers/__tests__/apply.actions.test.ts +310 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/job-application-form.test.ts +143 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/job-apply-page.test.tsx +350 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +29 -3
- package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/checkout/__tests__/pages.test.tsx +139 -0
- package/templates/apps/web/app/[locale]/checkout/__tests__/thank-you-account.test.tsx +136 -0
- package/templates/apps/web/app/[locale]/checkout/thank-you/page.tsx +114 -0
- package/templates/apps/web/app/[locale]/checkout/unavailable/page.tsx +51 -0
- package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +24 -1
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +79 -4
- package/templates/apps/web/app/[locale]/contact/__tests__/contact-form-magnet.test.ts +138 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/contact-form-text.test.ts +17 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/contact-magnet-page.test.tsx +386 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +504 -24
- package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +12 -3
- package/templates/apps/web/app/[locale]/contact/contact-form-magnet.ts +77 -0
- package/templates/apps/web/app/[locale]/contact/contact-form-text.ts +2 -0
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +184 -41
- package/templates/apps/web/app/[locale]/contact/intake-script.ts +26 -0
- package/templates/apps/web/app/[locale]/contact/page.tsx +120 -16
- package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +106 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.invite.test.tsx +257 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/account-invite.actions.test.ts +69 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/account-invite.actions.ts +35 -0
- package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/layout.tsx +23 -2
- package/templates/apps/web/app/[locale]/page.tsx +13 -1
- 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 +2 -2
- package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/thanks/[slug]/AccountInviteLink.tsx +89 -0
- package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/AccountInviteLink.test.tsx +104 -0
- package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +38 -0
- package/templates/apps/web/app/[locale]/thanks/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/working-agreement/__tests__/page.test.tsx +63 -0
- package/templates/apps/web/app/[locale]/working-agreement/page.tsx +41 -0
- package/templates/apps/web/app/api/auth/hub/authorize/__tests__/route.test.ts +114 -0
- package/templates/apps/web/app/api/auth/hub/authorize/route.ts +20 -3
- package/templates/apps/web/app/api/checkout/[offering]/route.ts +48 -0
- package/templates/apps/web/app/api/checkout/__tests__/route.test.ts +120 -0
- package/templates/apps/web/app/api/checkout/__tests__/success-path.test.ts +86 -0
- package/templates/apps/web/app/api/hub-session/__tests__/routes.test.ts +44 -1
- package/templates/apps/web/app/api/hub-session/sign-in/route.ts +22 -3
- package/templates/apps/web/app/api/webhooks/stripe/__tests__/route.test.ts +92 -0
- package/templates/apps/web/app/api/webhooks/stripe/route.ts +31 -0
- package/templates/apps/web/auth.ts +28 -2
- package/templates/apps/web/jest.config.cjs +1 -0
- package/templates/apps/web/lib/__tests__/account-invite.test.ts +63 -0
- package/templates/apps/web/lib/__tests__/checkout.test.ts +243 -0
- package/templates/apps/web/lib/__tests__/consent-policies.test.ts +23 -0
- package/templates/apps/web/lib/__tests__/deck-page.test.ts +249 -0
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +38 -19
- package/templates/apps/web/lib/account-invite.ts +37 -0
- package/templates/apps/web/lib/checkout-config.ts +27 -0
- package/templates/apps/web/lib/checkout.ts +93 -0
- package/templates/apps/web/lib/consent-policies.ts +22 -0
- package/templates/apps/web/lib/deck-page.ts +192 -0
- package/templates/apps/web/lib/hub-session/__tests__/sign-in-gate.test.tsx +111 -0
- package/templates/apps/web/lib/hub-session/sign-in-gate.tsx +88 -32
- package/templates/apps/web/lib/prisma-for-auth.ts +8 -0
- package/templates/apps/web/lib/sign-up-agreements.ts +447 -0
- package/templates/apps/web/lib/site-chrome.ts +2 -0
- package/templates/apps/web/lib/telemetry-scrub.ts +5 -1
- package/templates/apps/web/messages/en.json +40 -12
- package/templates/apps/web/package.json +1 -0
- package/templates/apps/web/src/lib/__tests__/lead-write.test.ts +187 -0
- package/templates/apps/web/src/lib/__tests__/rate-limit.test.ts +67 -0
- package/templates/apps/web/src/lib/lead-write.ts +90 -0
- package/templates/apps/web/src/lib/rate-limit.ts +79 -0
- package/templates/database/CHANGELOG.md +47 -0
- package/templates/database/__tests__/consent-source-checkout-migration.test.ts +79 -0
- package/templates/database/__tests__/flow-completion-migration.test.ts +115 -0
- package/templates/database/__tests__/flow-completion.db.test.ts +136 -0
- package/templates/database/__tests__/seed.test.ts +72 -2
- package/templates/database/inbox/schema.prisma +17 -2
- package/templates/database/jest.config.cjs +1 -0
- package/templates/database/migrations/20261018120000_flow_completion/migration.sql +52 -0
- package/templates/database/migrations/20261019120000_consent_source_checkout/migration.sql +27 -0
- package/templates/database/package.json +1 -1
- package/templates/database/seed.ts +60 -0
- package/templates/tools/jest/pin-timezone.cjs +34 -0
- package/versions.json +34 -34
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up.actions.ts +0 -125
package/dist/design-home.js
CHANGED
|
@@ -10,17 +10,41 @@
|
|
|
10
10
|
* fingerprint is a known placeholder (the shape's own home, or a shipped
|
|
11
11
|
* design's arc of it); any authored home is left alone.
|
|
12
12
|
*
|
|
13
|
+
* #8661 D11 — a design may also carry `contact/<shape>.mdx`, a complete
|
|
14
|
+
* `ContactPage` seeded the same way, with two differences: its
|
|
15
|
+
* `{{SITE_NAME}}` is filled from the site's `siteIdentity.name`, and it
|
|
16
|
+
* replaces the page's `title:` line as well as its `sections:` value (and a
|
|
17
|
+
* placeholder `description:` line), so the page is a placeholder only while
|
|
18
|
+
* its title is one too. Every reader and
|
|
19
|
+
* planner below takes the seeded page as `page` (default `"home"`).
|
|
20
|
+
*
|
|
13
21
|
* Pure apart from `readDesignHomeSeeds`/`designHomeSeeds`: every directory it
|
|
14
22
|
* reads is passed in (no `import.meta.url` — Jest's CJS transform).
|
|
15
23
|
*/
|
|
16
24
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
17
25
|
import { join } from "node:path";
|
|
26
|
+
import { buildScaffoldMarkers } from "./copy.js";
|
|
18
27
|
import { DesignPackageError } from "./design-package.js";
|
|
19
28
|
import { locatePageSections, pageTypeOf, } from "./design-presets.js";
|
|
20
29
|
import { RECOGNIZED_SHAPES } from "./parse-args.js";
|
|
30
|
+
import { substituteMarkers } from "./substitute.js";
|
|
21
31
|
import { STARTER_DESIGNS, TemplateError } from "./template-manifest.js";
|
|
22
32
|
/** Where a site's home page lives, site-relative. */
|
|
23
33
|
export const HOME_PAGE_PATH = "content/en/HomePage/home.mdx";
|
|
34
|
+
/** Where a site's contact page lives, site-relative (#8661 D11). */
|
|
35
|
+
export const CONTACT_PAGE_PATH = "content/en/ContactPage/contact.mdx";
|
|
36
|
+
/**
|
|
37
|
+
* The pages a design may seed (#8661 D11), keyed by the design-package
|
|
38
|
+
* directory that holds their seeds (`home/<shape>.mdx`, `contact/<shape>.mdx`).
|
|
39
|
+
*/
|
|
40
|
+
export const DESIGN_SEED_PAGES = {
|
|
41
|
+
home: { pageType: "HomePage", path: HOME_PAGE_PATH, route: "/" },
|
|
42
|
+
contact: { pageType: "ContactPage", path: CONTACT_PAGE_PATH, route: "/contact" },
|
|
43
|
+
};
|
|
44
|
+
/** The marker a contact seed's heading and title carry for the site's name (#8661 D11, C8). */
|
|
45
|
+
const SITE_NAME_MARKER = "{{SITE_NAME}}";
|
|
46
|
+
/** The name a contact seed takes when the site records none (#8661 D11). */
|
|
47
|
+
const NO_SITE_NAME = "{Your Name}";
|
|
24
48
|
// ── D1: reading a design's seeds ───────────────────────────────────────────
|
|
25
49
|
/** Flow punctuation: whitespace beside it is layout, never content. */
|
|
26
50
|
const FLOW_PUNCTUATION = new Set(["{", "}", "[", "]", ",", ":"]);
|
|
@@ -72,14 +96,18 @@ function rawEntry(text, sections, index, key) {
|
|
|
72
96
|
return entry === undefined ? undefined : text.slice(entry.valueStart, entry.valueEnd);
|
|
73
97
|
}
|
|
74
98
|
/**
|
|
75
|
-
* Read and validate `<dir>/home/*` (D1)
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
99
|
+
* Read and validate `<dir>/home/*` (D1), or `<dir>/contact/*` for
|
|
100
|
+
* `page: "contact"` (#8661 D11). Refuses with a `DesignPackageError` naming
|
|
101
|
+
* the file, before any write: a basename that is not a recognized shape, a
|
|
102
|
+
* page whose `type` is not the page's (`HomePage`, `ContactPage`), `sections`
|
|
103
|
+
* that cannot be located (or are not in the flow form a carry can splice), a
|
|
104
|
+
* section type the catalog does not carry, or a `logoCloud` whose `logos` is
|
|
105
|
+
* not `[]`.
|
|
80
106
|
*/
|
|
81
107
|
export function readDesignHomeSeeds(dir, opts) {
|
|
82
|
-
const
|
|
108
|
+
const page = opts.page ?? "home";
|
|
109
|
+
const expectedType = DESIGN_SEED_PAGES[page].pageType;
|
|
110
|
+
const homeDir = join(dir, page);
|
|
83
111
|
if (!existsSync(homeDir))
|
|
84
112
|
return [];
|
|
85
113
|
const seeds = [];
|
|
@@ -87,7 +115,7 @@ export function readDesignHomeSeeds(dir, opts) {
|
|
|
87
115
|
const file = join(homeDir, name);
|
|
88
116
|
const shape = name.replace(/\.mdx$/, "");
|
|
89
117
|
if (!name.endsWith(".mdx") || !RECOGNIZED_SHAPES.includes(shape)) {
|
|
90
|
-
throw new DesignPackageError(file,
|
|
118
|
+
throw new DesignPackageError(file, page, `is not named after a recognized shape (expected <shape>.mdx, one of ${RECOGNIZED_SHAPES.join(", ")})`);
|
|
91
119
|
}
|
|
92
120
|
const text = readFileSync(file, "utf8");
|
|
93
121
|
let pageType;
|
|
@@ -101,8 +129,8 @@ export function readDesignHomeSeeds(dir, opts) {
|
|
|
101
129
|
throw err;
|
|
102
130
|
throw new DesignPackageError(file, "sections", `cannot be located (${err.message})`);
|
|
103
131
|
}
|
|
104
|
-
if (pageType !==
|
|
105
|
-
throw new DesignPackageError(file, "type", `must be "
|
|
132
|
+
if (pageType !== expectedType) {
|
|
133
|
+
throw new DesignPackageError(file, "type", `must be "${expectedType}" (got ${JSON.stringify(pageType ?? null)})`);
|
|
106
134
|
}
|
|
107
135
|
if (sections === undefined || sections.some((s) => s.form !== "flow")) {
|
|
108
136
|
throw new DesignPackageError(file, "sections", "must be a flow sequence of sections");
|
|
@@ -127,12 +155,12 @@ export function readDesignHomeSeeds(dir, opts) {
|
|
|
127
155
|
return seeds;
|
|
128
156
|
}
|
|
129
157
|
/**
|
|
130
|
-
* The seeds a resolved design uses (D1): its own `home
|
|
131
|
-
* byte-identical copy of a shipped design IS that design — the
|
|
132
|
-
* design's, else none.
|
|
158
|
+
* The seeds a resolved design uses (D1): its own `home/` (or `contact/`),
|
|
159
|
+
* else — a byte-identical copy of a shipped design IS that design — the
|
|
160
|
+
* shipped design's, else none.
|
|
133
161
|
*/
|
|
134
162
|
export function designHomeSeeds(resolved, opts) {
|
|
135
|
-
if (existsSync(join(resolved.dir, "home")))
|
|
163
|
+
if (existsSync(join(resolved.dir, opts.page ?? "home")))
|
|
136
164
|
return readDesignHomeSeeds(resolved.dir, opts);
|
|
137
165
|
if (STARTER_DESIGNS.includes(resolved.recorded)) {
|
|
138
166
|
return readDesignHomeSeeds(join(opts.designsDir, resolved.recorded), opts);
|
|
@@ -143,10 +171,87 @@ export function designHomeSeeds(resolved, opts) {
|
|
|
143
171
|
export function shippedHomeSeeds(opts) {
|
|
144
172
|
return STARTER_DESIGNS.flatMap((name) => readDesignHomeSeeds(join(opts.designsDir, name), opts));
|
|
145
173
|
}
|
|
146
|
-
/** The shape's own home: its shape tree's, else the overlay's (spec § 1.4). */
|
|
174
|
+
/** The shape's own home (or contact page): its shape tree's, else the overlay's (spec § 1.4). */
|
|
147
175
|
export function shapeHomePath(shape, opts) {
|
|
148
|
-
const
|
|
149
|
-
|
|
176
|
+
const path = DESIGN_SEED_PAGES[opts.page ?? "home"].path.split("/");
|
|
177
|
+
const own = join(opts.siteTemplatesDir, "shapes", shape, ...path);
|
|
178
|
+
return existsSync(own) ? own : join(opts.templatesDir, ...path);
|
|
179
|
+
}
|
|
180
|
+
// ── #8661 D11: the contact page's name and title ───────────────────────────
|
|
181
|
+
/** The front matter's body, or undefined when the text opens with none. */
|
|
182
|
+
function frontMatterOf(text) {
|
|
183
|
+
const fm = /^---\r?\n([\s\S]*?)\r?\n---[ \t]*\r?$/m.exec(text);
|
|
184
|
+
return fm && fm.index === 0 ? fm[1] : undefined;
|
|
185
|
+
}
|
|
186
|
+
/** A one-line YAML scalar's string value: a double-quoted value is JSON-read, a single-quoted one unescaped. */
|
|
187
|
+
function unquote(raw) {
|
|
188
|
+
const value = raw.trim();
|
|
189
|
+
if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
|
|
190
|
+
try {
|
|
191
|
+
return JSON.parse(value);
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return value.slice(1, -1);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (value.startsWith("'") && value.endsWith("'") && value.length >= 2)
|
|
198
|
+
return value.slice(1, -1).replace(/''/g, "'");
|
|
199
|
+
return value;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* A site's `siteIdentity.name` (#8661 D11, C8): the first `name:` line
|
|
203
|
+
* indented directly under `siteIdentity:` in `content/_site.mdx`'s front
|
|
204
|
+
* matter, quotes stripped. Undefined when there is no such line.
|
|
205
|
+
*/
|
|
206
|
+
export function siteIdentityName(siteMdx) {
|
|
207
|
+
const fm = siteMdx === undefined ? undefined : frontMatterOf(siteMdx);
|
|
208
|
+
if (fm === undefined)
|
|
209
|
+
return undefined;
|
|
210
|
+
const lines = fm.split(/\r?\n/);
|
|
211
|
+
const start = lines.findIndex((line) => /^siteIdentity:[ \t]*$/.test(line));
|
|
212
|
+
if (start < 0)
|
|
213
|
+
return undefined;
|
|
214
|
+
let indent;
|
|
215
|
+
for (const line of lines.slice(start + 1)) {
|
|
216
|
+
if (line.trim() === "" || line.trimStart().startsWith("#"))
|
|
217
|
+
continue;
|
|
218
|
+
const lead = line.length - line.trimStart().length;
|
|
219
|
+
if (lead === 0)
|
|
220
|
+
return undefined;
|
|
221
|
+
indent ??= lead;
|
|
222
|
+
if (lead !== indent)
|
|
223
|
+
continue;
|
|
224
|
+
const m = /^[ \t]+name:[ \t]*(.+?)[ \t]*$/.exec(line);
|
|
225
|
+
if (m) {
|
|
226
|
+
const name = unquote(m[1]);
|
|
227
|
+
return name === "" ? undefined : name;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
/** `seed` with its `{{SITE_NAME}}` filled, escaped for the double-quoted strings it sits in. */
|
|
233
|
+
function fillSiteName(seed, name) {
|
|
234
|
+
const escaped = JSON.stringify(name).slice(1, -1);
|
|
235
|
+
return { ...seed, text: seed.text.split(SITE_NAME_MARKER).join(escaped) };
|
|
236
|
+
}
|
|
237
|
+
/** The `<key>:` line's span in the front matter, or undefined. */
|
|
238
|
+
function metaLine(text, key) {
|
|
239
|
+
const fm = frontMatterOf(text);
|
|
240
|
+
if (fm === undefined)
|
|
241
|
+
return undefined;
|
|
242
|
+
const m = new RegExp(`^${key}:[ \\t]*(.*?)[ \\t]*$`, "m").exec(fm);
|
|
243
|
+
if (!m)
|
|
244
|
+
return undefined;
|
|
245
|
+
const offset = text.indexOf(fm);
|
|
246
|
+
return { start: offset + m.index, end: offset + m.index + m[0].length, value: unquote(m[1]) };
|
|
247
|
+
}
|
|
248
|
+
/** `page` with its `<key>:` line replaced by `from`'s (or left as it is when either has none). */
|
|
249
|
+
function spliceMeta(page, from, key) {
|
|
250
|
+
const at = metaLine(page, key);
|
|
251
|
+
const source = metaLine(from, key);
|
|
252
|
+
if (at === undefined || source === undefined)
|
|
253
|
+
return page;
|
|
254
|
+
return page.slice(0, at.start) + from.slice(source.start, source.end) + page.slice(at.end);
|
|
150
255
|
}
|
|
151
256
|
// ── D3: the content fingerprint ────────────────────────────────────────────
|
|
152
257
|
function locate(text, file) {
|
|
@@ -219,16 +324,32 @@ export function applySeed(home, seed, file = "home.mdx") {
|
|
|
219
324
|
/**
|
|
220
325
|
* Plan a site home under a design (D3). Writes nothing: `text` is the new
|
|
221
326
|
* home when the outcome is `seeded` or `restored`, and absent otherwise.
|
|
327
|
+
*
|
|
328
|
+
* `page: "contact"` (#8661 D11) plans the contact page instead. The seeds'
|
|
329
|
+
* `{{SITE_NAME}}` is filled with `siteName` (absent: `{Your Name}`) before
|
|
330
|
+
* anything is compared; the shape's own page is read with the scaffold
|
|
331
|
+
* markers substituted, as a scaffold installed it; the page is a placeholder
|
|
332
|
+
* only while its title is one too (the shape's own, or a seed's); and a write
|
|
333
|
+
* replaces the `title:` line as well as the sections, and the `description:`
|
|
334
|
+
* line while it is still a placeholder (the shape's own, or a seed's).
|
|
222
335
|
*/
|
|
223
336
|
export function planDesignHome(opts) {
|
|
224
|
-
const
|
|
337
|
+
const page = opts.page ?? "home";
|
|
338
|
+
const contact = page === "contact";
|
|
339
|
+
const pagePath = DESIGN_SEED_PAGES[page].path;
|
|
340
|
+
const { current, shape, catalog } = opts;
|
|
225
341
|
if (current === undefined)
|
|
226
342
|
return { outcome: "no-home" };
|
|
227
343
|
if (shape === undefined)
|
|
228
344
|
return { outcome: "no-shape" };
|
|
345
|
+
const name = opts.siteName ?? NO_SITE_NAME;
|
|
346
|
+
const fill = (seed) => (contact ? fillSiteName(seed, name) : seed);
|
|
347
|
+
const target = opts.target === undefined ? undefined : fill(opts.target);
|
|
348
|
+
// The scaffold's own markers carry no project-specific value a contact page uses.
|
|
349
|
+
const shapeHome = contact ? substituteMarkers(opts.shapeHome, buildScaffoldMarkers("")) : opts.shapeHome;
|
|
229
350
|
let currentPrint;
|
|
230
351
|
try {
|
|
231
|
-
currentPrint = fingerprint(current, catalog,
|
|
352
|
+
currentPrint = fingerprint(current, catalog, pagePath);
|
|
232
353
|
}
|
|
233
354
|
catch (err) {
|
|
234
355
|
// A home whose sections cannot be read is not a placeholder: never write it.
|
|
@@ -236,21 +357,38 @@ export function planDesignHome(opts) {
|
|
|
236
357
|
return { outcome: "authored" };
|
|
237
358
|
throw err;
|
|
238
359
|
}
|
|
239
|
-
const seeds = [
|
|
360
|
+
const seeds = [
|
|
361
|
+
...opts.shippedSeeds.filter((s) => s.shape === shape).map(fill),
|
|
362
|
+
...(target !== undefined ? [target] : []),
|
|
363
|
+
];
|
|
240
364
|
const placeholders = new Set([
|
|
241
365
|
fingerprint(shapeHome, catalog),
|
|
242
366
|
...seeds.map((seed) => fingerprint(spliceSections(shapeHome, applySeed(shapeHome, seed), "shape home"), catalog)),
|
|
243
367
|
]);
|
|
244
368
|
if (!placeholders.has(currentPrint))
|
|
245
369
|
return { outcome: "authored" };
|
|
370
|
+
// #8661 D11 — a contact page whose title was retitled is authored, whatever its sections.
|
|
371
|
+
const placeholderTexts = [shapeHome, ...seeds.map((seed) => seed.text)];
|
|
372
|
+
const placeholderMeta = (key) => new Set(placeholderTexts.map((text) => metaLine(text, key)?.value));
|
|
373
|
+
if (contact && !placeholderMeta("title").has(metaLine(current, "title")?.value))
|
|
374
|
+
return { outcome: "authored" };
|
|
246
375
|
const wanted = target !== undefined
|
|
247
|
-
? applySeed(current, target,
|
|
376
|
+
? applySeed(current, target, pagePath)
|
|
248
377
|
: (() => {
|
|
249
378
|
const own = locate(shapeHome, "shape home");
|
|
250
379
|
return shapeHome.slice(own.valueStart, own.valueEnd);
|
|
251
380
|
})();
|
|
252
|
-
|
|
253
|
-
if (
|
|
381
|
+
let text = spliceSections(current, wanted, pagePath);
|
|
382
|
+
if (contact) {
|
|
383
|
+
const from = target?.text ?? shapeHome;
|
|
384
|
+
text = spliceMeta(text, from, "title");
|
|
385
|
+
// An edited description is the customer's: only a placeholder one is replaced.
|
|
386
|
+
if (placeholderMeta("description").has(metaLine(current, "description")?.value)) {
|
|
387
|
+
text = spliceMeta(text, from, "description");
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
const sameMeta = ["title", "description"].every((key) => metaLine(text, key)?.value === metaLine(current, key)?.value);
|
|
391
|
+
if (fingerprint(text, catalog) === currentPrint && sameMeta)
|
|
254
392
|
return { outcome: "unchanged" };
|
|
255
393
|
return { outcome: target !== undefined ? "seeded" : "restored", text };
|
|
256
394
|
}
|
package/dist/design-package.js
CHANGED
|
@@ -486,13 +486,14 @@ function validateStringMap(raw, file, field) {
|
|
|
486
486
|
}
|
|
487
487
|
const LAYOUT_KEYS = ["nav", "footer", "announcementBanner", "home"];
|
|
488
488
|
/** Every key a package's `layout` may carry — #7719 D3 adds the optional `deckGrounds` (a deck home only),
|
|
489
|
-
* #7721 D1 the optional `innerPages`, #7740 D1 the optional `deckPresentation` (a deck home only),
|
|
489
|
+
* #7721 D1 the optional `innerPages`, #7740 D1 the optional `deckPresentation` (a deck home only), #8773 the optional `deckAnchors` (a presenting deck only),
|
|
490
490
|
* #7742 D1 the optional `colorMode` and `chromeStyle`, #7797 D1 the optional `closingFooterPaths`. */
|
|
491
491
|
const ALLOWED_LAYOUT_KEYS = new Set([
|
|
492
492
|
...LAYOUT_KEYS,
|
|
493
493
|
"deckGrounds",
|
|
494
494
|
"innerPages",
|
|
495
495
|
"deckPresentation",
|
|
496
|
+
"deckAnchors",
|
|
496
497
|
"colorMode",
|
|
497
498
|
"chromeStyle",
|
|
498
499
|
"closingFooterPaths",
|
|
@@ -524,11 +525,12 @@ function validateLayout(raw, file) {
|
|
|
524
525
|
// `ResolvedSiteChrome` shape; an explicit "default" is the absent key.
|
|
525
526
|
// #7740 D1 — and `deckPresentation` only as `true`.
|
|
526
527
|
// #7742 D1 — and `colorMode` only as "light", `chromeStyle` only as "minimal".
|
|
527
|
-
const { innerPages, deckPresentation, colorMode, chromeStyle, ...rest } = validated;
|
|
528
|
+
const { innerPages, deckPresentation, deckAnchors, colorMode, chromeStyle, ...rest } = validated;
|
|
528
529
|
const layout = {
|
|
529
530
|
...rest,
|
|
530
531
|
...(innerPages === "bare" ? { innerPages } : {}),
|
|
531
532
|
...(deckPresentation === true ? { deckPresentation } : {}),
|
|
533
|
+
...(deckAnchors === "bare" ? { deckAnchors } : {}),
|
|
532
534
|
...(colorMode === "light" ? { colorMode } : {}),
|
|
533
535
|
...(chromeStyle === "minimal" ? { chromeStyle } : {}),
|
|
534
536
|
};
|
|
@@ -538,6 +540,9 @@ function validateLayout(raw, file) {
|
|
|
538
540
|
if (deckPresentation === true && layout.home !== "deck") {
|
|
539
541
|
refuse(file, "layout.deckPresentation", "applies only to home: deck");
|
|
540
542
|
}
|
|
543
|
+
if (deckAnchors === "bare" && deckPresentation !== true) {
|
|
544
|
+
refuse(file, "layout.deckAnchors", "applies only with deckPresentation: true");
|
|
545
|
+
}
|
|
541
546
|
return layout;
|
|
542
547
|
}
|
|
543
548
|
const TREATMENTS_ENUMS = {
|
|
@@ -54,9 +54,14 @@ export type JsonLdDriftVerdict = {
|
|
|
54
54
|
* returned as it is.
|
|
55
55
|
*/
|
|
56
56
|
export declare function admitSeededPeopleSlide(before: ComposedPages, after: ComposedPages): ComposedPages;
|
|
57
|
+
/**
|
|
58
|
+
* `after` with the standard contact page put back when the seeded one differs
|
|
59
|
+
* only in `name` and `description` (#8661 D11).
|
|
60
|
+
*/
|
|
61
|
+
export declare function admitSeededContactPage(before: ComposedPages, after: ComposedPages): ComposedPages;
|
|
57
62
|
/**
|
|
58
63
|
* Decide whether `apply-design <design>` left every page's JSON-LD unchanged
|
|
59
|
-
* (logo excluded), the seeded people slide's Persons admitted.
|
|
64
|
+
* (logo excluded), the seeded people slide's Persons and contact page admitted.
|
|
60
65
|
*/
|
|
61
66
|
export declare function assessJsonLdDrift(design: string, before: ComposedPages, after: ComposedPages): JsonLdDriftVerdict;
|
|
62
67
|
export interface MeasureApplyDesignDriftOptions {
|
|
@@ -21,11 +21,14 @@
|
|
|
21
21
|
* organization home's people slide adds one `Person` per seeded team member to
|
|
22
22
|
* the home's graph. `admitSeededPeopleSlide` puts the standard home back only
|
|
23
23
|
* when the seeded home is exactly that, so any other change still counts.
|
|
24
|
+
* Likewise a design that seeds the contact page renames and re-describes it
|
|
25
|
+
* (#8661 D11); `admitSeededContactPage` admits only those two fields.
|
|
24
26
|
*/
|
|
25
27
|
import { execFileSync } from "node:child_process";
|
|
26
28
|
import { writeFileSync } from "node:fs";
|
|
27
29
|
import { join } from "node:path";
|
|
28
30
|
import { isDeepStrictEqual } from "node:util";
|
|
31
|
+
import { CONTACT_PAGE_PATH } from "../design-home.js";
|
|
29
32
|
import { TEAM_SEED_MEMBERS } from "../team-profiles.js";
|
|
30
33
|
function describePair(row) {
|
|
31
34
|
const measured = row.status === "unmeasurable" || row.ratio === null
|
|
@@ -140,18 +143,36 @@ export function admitSeededPeopleSlide(before, after) {
|
|
|
140
143
|
const isPeopleSlide = isDeepStrictEqual(page, JSON.parse(standard.jsonld)) && isDeepStrictEqual(people, expected);
|
|
141
144
|
return isPeopleSlide ? { ...after, [HOME_PAGE]: standard } : after;
|
|
142
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* `after` with the standard contact page put back when the seeded one differs
|
|
148
|
+
* only in `name` and `description` (#8661 D11).
|
|
149
|
+
*/
|
|
150
|
+
export function admitSeededContactPage(before, after) {
|
|
151
|
+
const standard = before[CONTACT_PAGE_PATH];
|
|
152
|
+
const seeded = after[CONTACT_PAGE_PATH];
|
|
153
|
+
if (standard === undefined || seeded === undefined || !seeded.sectionsValid)
|
|
154
|
+
return after;
|
|
155
|
+
const strip = (jsonld) => ({
|
|
156
|
+
...JSON.parse(jsonld),
|
|
157
|
+
name: undefined,
|
|
158
|
+
description: undefined,
|
|
159
|
+
});
|
|
160
|
+
return isDeepStrictEqual(strip(seeded.jsonld), strip(standard.jsonld))
|
|
161
|
+
? { ...after, [CONTACT_PAGE_PATH]: standard }
|
|
162
|
+
: after;
|
|
163
|
+
}
|
|
143
164
|
/** How many changed nodes a drift reason lists before it stops. */
|
|
144
165
|
const REASON_NODE_LIMIT = 10;
|
|
145
166
|
/**
|
|
146
167
|
* Decide whether `apply-design <design>` left every page's JSON-LD unchanged
|
|
147
|
-
* (logo excluded), the seeded people slide's Persons admitted.
|
|
168
|
+
* (logo excluded), the seeded people slide's Persons and contact page admitted.
|
|
148
169
|
*/
|
|
149
170
|
export function assessJsonLdDrift(design, before, after) {
|
|
150
171
|
const pages = Object.keys(before).length;
|
|
151
172
|
if (pages === 0) {
|
|
152
173
|
return { ok: false, pages, changed: [], reason: `design ${design}: no page was composed` };
|
|
153
174
|
}
|
|
154
|
-
const changed = jsonLdDrift(before, admitSeededPeopleSlide(before, after));
|
|
175
|
+
const changed = jsonLdDrift(before, admitSeededContactPage(before, admitSeededPeopleSlide(before, after)));
|
|
155
176
|
if (changed.length === 0)
|
|
156
177
|
return { ok: true, pages };
|
|
157
178
|
const listed = changed
|
package/dist/lib/env-catalog.js
CHANGED
|
@@ -52,7 +52,7 @@ export const ENV_CATALOG = {
|
|
|
52
52
|
secret: false,
|
|
53
53
|
},
|
|
54
54
|
AILK_LEAD_SITE: {
|
|
55
|
-
description: "
|
|
55
|
+
description: "This site's slug, written as payload.site on every lead its /contact form and MCP server capture, so a hub can tell its sites apart; unset leaves the lead unmarked.",
|
|
56
56
|
secret: false,
|
|
57
57
|
},
|
|
58
58
|
AILK_LICENSE_STORAGE_PATH: {
|
|
@@ -71,6 +71,10 @@ export const ENV_CATALOG = {
|
|
|
71
71
|
description: "The scope captured leads are stamped with; on a single-tenant install, the default workspace id.",
|
|
72
72
|
secret: false,
|
|
73
73
|
},
|
|
74
|
+
AILK_SPOKE_KEY: {
|
|
75
|
+
description: "The hub-issued key this site sends as x-ailk-spoke-key; the checkout fulfilment refuses to run without it. Never committed.",
|
|
76
|
+
secret: true,
|
|
77
|
+
},
|
|
74
78
|
AILK_VISITOR_FORWARD_KEY: {
|
|
75
79
|
description: "This spoke's key for forwarding each visitor's IP to the hub, so the hub rate-limits per visitor. Server-only.",
|
|
76
80
|
secret: true,
|
|
@@ -1813,8 +1813,10 @@ export const MODULE_SLICES = {
|
|
|
1813
1813
|
// the flow results pages, dev/purchase — spoke-intake Rule 3). What lands
|
|
1814
1814
|
// is the scaffold-commerce skill, the commerce-boundary rule and the
|
|
1815
1815
|
// @working-theory/ui-commerce dependency (`patches` below); the spoke's own
|
|
1816
|
-
// checkout —
|
|
1817
|
-
//
|
|
1816
|
+
// checkout — the standard spoke checkout, `apps/web` template source
|
|
1817
|
+
// (two routes, lib/checkout.ts, checkout-config.ts, two return pages and
|
|
1818
|
+
// their tests) — is copied by `writeSpokeCheckoutSlice`
|
|
1819
|
+
// (`spoke-checkout.ts`, #8748), not carried here.
|
|
1818
1820
|
spoke: true,
|
|
1819
1821
|
paidSectionsPackage: "@working-theory/ui-commerce",
|
|
1820
1822
|
apps: {
|
package/dist/programmatic.js
CHANGED
|
@@ -25,7 +25,7 @@ import { CATALOG_TARGET_PATH, writeComponentCatalog, } from "./component-catalog
|
|
|
25
25
|
import { buildScaffoldMarkers, copyTemplate } from "./copy.js";
|
|
26
26
|
import { deriveArchitecture } from "./derive-architecture.js";
|
|
27
27
|
import { compileDesignFonts, compileDesignTheme } from "./design-compile.js";
|
|
28
|
-
import {
|
|
28
|
+
import { DESIGN_SEED_PAGES, designHomeSeeds, planDesignHome, shapeHomePath, shippedHomeSeeds, siteIdentityName, } from "./design-home.js";
|
|
29
29
|
import { applyDesignPresets, readSectionLookCatalog, shippedPageTypes, validateDesignPresets, } from "./design-presets.js";
|
|
30
30
|
import { resolveDesign, spliceThemeBlock, } from "./design-scaffold.js";
|
|
31
31
|
import { writeAuthSecretFile } from "./lib/auth-secret-file.js";
|
|
@@ -44,7 +44,7 @@ import { SITE_CHROME_PATH, renderSiteChrome, resolveSiteChrome, } from "./site-c
|
|
|
44
44
|
import { SITE_FONTS_PATH, renderSiteFonts } from "./site-fonts.js";
|
|
45
45
|
import { planSiteManifest } from "./site-manifest.js";
|
|
46
46
|
import { SITE_ROSTER_PATH, planHomeRecord, planSiteManifestDesign, readSiteRoster, withMeetPlaceholderSeeds, } from "./site-roster.js";
|
|
47
|
-
import { spokeCheckoutSite, spokeEnvRowOptions, writeSpokeCheckoutSlice, } from "./spoke-checkout.js";
|
|
47
|
+
import { SPOKE_CHECKOUT_TEMPLATE_PATHS, dropSpokeCheckoutDependency, spokeCheckoutSite, spokeEnvRowOptions, writeSpokeCheckoutSlice, } from "./spoke-checkout.js";
|
|
48
48
|
import { GATEABLE_SURFACES, MODULE_SURFACES, NAV_CONFIG_PATH, excludedRoutePaths, isUnservedSurfaceContent, renderNavConfig, resolveSurfaces, } from "./surfaces.js";
|
|
49
49
|
import { syncRoutes } from "./sync-routes.js";
|
|
50
50
|
import { BASE_LAYER_DIR, SHAPE_STARTERS, TEMPLATE_MANIFESTS, TemplateError, mergeRoster, resolveTemplate, resolveTemplateLineage, rosterDeclares, templateDelivery, templateTreeDir, } from "./template-manifest.js";
|
|
@@ -182,6 +182,9 @@ export function scaffoldSync(opts, copied) {
|
|
|
182
182
|
...(spoke !== undefined
|
|
183
183
|
? spokeExclusions(TEMPLATES_DIR, spoke)
|
|
184
184
|
: SPOKE_ONLY_FILES),
|
|
185
|
+
// #8748 D3 — the standard spoke checkout lands only through
|
|
186
|
+
// `writeSpokeCheckoutSlice`, on a spoke carrying commerce.
|
|
187
|
+
...SPOKE_CHECKOUT_TEMPLATE_PATHS,
|
|
185
188
|
],
|
|
186
189
|
markers: buildScaffoldMarkers(projectName),
|
|
187
190
|
...(copied !== undefined
|
|
@@ -193,6 +196,9 @@ export function scaffoldSync(opts, copied) {
|
|
|
193
196
|
}
|
|
194
197
|
: {}),
|
|
195
198
|
});
|
|
199
|
+
// #8748 D3 — the copied apps/web depends on the checkout package; only a
|
|
200
|
+
// commerce spoke keeps it, re-added by `writeSpokeCheckoutSlice` in scaffold().
|
|
201
|
+
dropSpokeCheckoutDependency(targetAbs);
|
|
196
202
|
// #7426 D5/D6 — the root files a spoke rewrites, and the spoke seams.
|
|
197
203
|
if (spoke !== undefined)
|
|
198
204
|
applySpokeTransforms(targetAbs, spoke);
|
|
@@ -330,6 +336,10 @@ export async function scaffold(opts) {
|
|
|
330
336
|
const designSeeds = resolvedDesign !== undefined
|
|
331
337
|
? designHomeSeeds(resolvedDesign, { designsDir: DESIGNS_DIR, catalog })
|
|
332
338
|
: [];
|
|
339
|
+
// #8661 D11 — and its contact seeds, read and validated the same way.
|
|
340
|
+
const designContactSeeds = resolvedDesign !== undefined
|
|
341
|
+
? designHomeSeeds(resolvedDesign, { designsDir: DESIGNS_DIR, catalog, page: "contact" })
|
|
342
|
+
: [];
|
|
333
343
|
// #6813 D7 — the resolved design only when the CALLER chose one (`opts.design`);
|
|
334
344
|
// a template's own declared design is never gated (#6778 option (c)).
|
|
335
345
|
const chosenDesign = opts.design !== undefined ? resolvedDesign : undefined;
|
|
@@ -719,21 +729,35 @@ export async function scaffold(opts) {
|
|
|
719
729
|
// every layer has landed, before the presets dress it. A home that is not
|
|
720
730
|
// the shape's placeholder is never written; `--design standard` (no
|
|
721
731
|
// seeds) leaves the shape's home byte-identical. The one
|
|
722
|
-
// `planDesignHome` call site in scaffold().
|
|
723
|
-
|
|
724
|
-
|
|
732
|
+
// `planDesignHome` call site in scaffold(). #8661 D11 — the contact page
|
|
733
|
+
// is planned right after the home, its heading named from the scaffold's
|
|
734
|
+
// `siteIdentity.name`.
|
|
735
|
+
const seededPages = [];
|
|
725
736
|
if (template !== undefined) {
|
|
726
|
-
const
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
+
const siteName = siteIdentityName(existsSync(siteMdxPath) ? readFileSync(siteMdxPath, "utf8") : undefined);
|
|
738
|
+
for (const [page, pageSeeds] of [
|
|
739
|
+
["home", designSeeds],
|
|
740
|
+
["contact", designContactSeeds],
|
|
741
|
+
]) {
|
|
742
|
+
const pagePath = join(result.targetDir, ...DESIGN_SEED_PAGES[page].path.split("/"));
|
|
743
|
+
const plan = planDesignHome({
|
|
744
|
+
page,
|
|
745
|
+
siteName,
|
|
746
|
+
current: existsSync(pagePath) ? readFileSync(pagePath, "utf8") : undefined,
|
|
747
|
+
shape: template.shape,
|
|
748
|
+
target: pageSeeds.find((seed) => seed.shape === template.shape),
|
|
749
|
+
shapeHome: readFileSync(shapeHomePath(template.shape, {
|
|
750
|
+
siteTemplatesDir: SITE_TEMPLATES_DIR,
|
|
751
|
+
templatesDir: TEMPLATES_DIR,
|
|
752
|
+
page,
|
|
753
|
+
}), "utf8"),
|
|
754
|
+
shippedSeeds: shippedHomeSeeds({ designsDir: DESIGNS_DIR, catalog, page }),
|
|
755
|
+
catalog: catalog(),
|
|
756
|
+
});
|
|
757
|
+
if (plan.outcome === "seeded" && plan.text !== undefined) {
|
|
758
|
+
writeFileSync(pagePath, plan.text);
|
|
759
|
+
seededPages.push(page);
|
|
760
|
+
}
|
|
737
761
|
}
|
|
738
762
|
}
|
|
739
763
|
// #6816 D6 step 10b — the design's section presets, written onto the
|
|
@@ -761,26 +785,40 @@ export async function scaffold(opts) {
|
|
|
761
785
|
}
|
|
762
786
|
}
|
|
763
787
|
// #7743 D6 — the manifest's `/` row records the seeded home's sections,
|
|
764
|
-
// as the presets left them.
|
|
788
|
+
// as the presets left them. #8661 — and the `/contact` row the seeded
|
|
789
|
+
// contact page's.
|
|
765
790
|
const rosterPath = join(result.targetDir, SITE_ROSTER_PATH);
|
|
766
|
-
if (
|
|
767
|
-
const
|
|
768
|
-
|
|
769
|
-
|
|
791
|
+
if (seededPages.length > 0 && existsSync(rosterPath)) {
|
|
792
|
+
const recorded = readFileSync(rosterPath, "utf8");
|
|
793
|
+
let manifestText = recorded;
|
|
794
|
+
for (const page of seededPages) {
|
|
795
|
+
const pagePath = join(result.targetDir, ...DESIGN_SEED_PAGES[page].path.split("/"));
|
|
796
|
+
manifestText = planHomeRecord(manifestText, readFileSync(pagePath, "utf8"), catalog(), page) ?? manifestText;
|
|
797
|
+
}
|
|
798
|
+
if (manifestText !== recorded)
|
|
799
|
+
writeFileSync(rosterPath, manifestText);
|
|
770
800
|
}
|
|
771
801
|
}
|
|
772
|
-
// #7838 D8 — a commerce spoke's own checkout:
|
|
773
|
-
//
|
|
774
|
-
//
|
|
775
|
-
// code-route rows then record
|
|
802
|
+
// #7838 D8 — a commerce spoke's own checkout: the standard spoke checkout
|
|
803
|
+
// copied from the template (#8748 D3), its config stamped, once every layer
|
|
804
|
+
// has landed (so the spoke filter has already dropped the hub-checkout
|
|
805
|
+
// files) and before the manifest, whose code-route rows then record
|
|
806
|
+
// /checkout/thank-you and /checkout/unavailable. Each copied file is
|
|
807
|
+
// recorded like the base copy's, so `update` keeps it current.
|
|
776
808
|
// #8217 D8 — a spoke that signs in through its hub also gets the billing
|
|
777
809
|
// action and the web seam render that reaches it.
|
|
778
810
|
const spokeCommerce = spoke !== undefined && result.modules.includes("commerce");
|
|
779
811
|
if (spokeCommerce) {
|
|
780
812
|
writeSpokeCheckoutSlice(result.targetDir, {
|
|
781
813
|
site: spokeCheckoutSite(projectName),
|
|
814
|
+
projectName,
|
|
782
815
|
versions,
|
|
816
|
+
templatesDir: TEMPLATES_DIR,
|
|
783
817
|
signIn: spoke.signIn,
|
|
818
|
+
onWrite: (relPath, bytes) => {
|
|
819
|
+
if (isRecordable(relPath))
|
|
820
|
+
copied.set(relPath, sha256Hex(bytes));
|
|
821
|
+
},
|
|
784
822
|
});
|
|
785
823
|
}
|
|
786
824
|
// #6944 — the manifest records what this scaffold IS, once every layer has
|
package/dist/site-chrome.d.ts
CHANGED
|
@@ -41,6 +41,10 @@ export type SiteChromeHome = "default" | "deck";
|
|
|
41
41
|
/** The `innerPages` knob's vocabulary (#7721). `bare` → every non-home page
|
|
42
42
|
* gets the bare header and the one-bar footer. */
|
|
43
43
|
export type SiteChromeInnerPages = "default" | "bare";
|
|
44
|
+
/** The `deckAnchors` knob's vocabulary (#8773). `bare` → the deck home names
|
|
45
|
+
* its slides `#<id>` instead of `#slide-<id>`, keeping the `slide-` name only
|
|
46
|
+
* where the bare id would collide (the page renderer's `deckSlideAnchors`). */
|
|
47
|
+
export type SiteChromeDeckAnchors = "prefixed" | "bare";
|
|
44
48
|
/** The `colorMode` knob's vocabulary (#7742). `light` pins the site to light
|
|
45
49
|
* and removes every theme switch; dark then appears only as a deck ground. */
|
|
46
50
|
export type SiteChromeColorMode = "user" | "light";
|
|
@@ -72,6 +76,8 @@ export interface TemplateLayout {
|
|
|
72
76
|
innerPages?: SiteChromeInnerPages;
|
|
73
77
|
/** #7740 D1 — `true`: the deck home presents slide by slide (a deck home only). */
|
|
74
78
|
deckPresentation?: boolean;
|
|
79
|
+
/** #8773 — `bare`: a presenting deck names its slides `#<id>` (a presenting deck home only). */
|
|
80
|
+
deckAnchors?: SiteChromeDeckAnchors;
|
|
75
81
|
/** #7742 D1 — `light`: the site is pinned to light, with no theme switch. */
|
|
76
82
|
colorMode?: SiteChromeColorMode;
|
|
77
83
|
/** #7742 D1 — `minimal`: the minimal closing slide, consent bar and themed 404. */
|
|
@@ -91,6 +97,8 @@ export interface ResolvedSiteChrome {
|
|
|
91
97
|
innerPages?: "bare";
|
|
92
98
|
/** Present only when it resolves to `true` AND `home` is `deck` (#7740 D1) — never `false`. */
|
|
93
99
|
deckPresentation?: true;
|
|
100
|
+
/** Present only when it resolves to "bare" on a presenting deck home (#8773) — never "prefixed". */
|
|
101
|
+
deckAnchors?: "bare";
|
|
94
102
|
/** Present only when it resolves to "light" — never "user" (#7742 D1). */
|
|
95
103
|
colorMode?: "light";
|
|
96
104
|
/** Present only when it resolves to "minimal" — never "default" (#7742 D1). */
|