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/apply-design.d.ts
CHANGED
|
@@ -42,6 +42,11 @@ export type ApplyDesignChange = {
|
|
|
42
42
|
kind: "home";
|
|
43
43
|
action: "seeded" | "restored";
|
|
44
44
|
fields: string[];
|
|
45
|
+
} | {
|
|
46
|
+
path: string;
|
|
47
|
+
kind: "contact";
|
|
48
|
+
action: "seeded" | "restored";
|
|
49
|
+
fields: string[];
|
|
45
50
|
} | {
|
|
46
51
|
path: string;
|
|
47
52
|
kind: "manifest";
|
|
@@ -57,6 +62,10 @@ export interface ApplyDesignResult {
|
|
|
57
62
|
home: HomeSeedOutcome;
|
|
58
63
|
/** #7743 D7 — the design ships a home arc for this site, so a home it leaves alone is reported. */
|
|
59
64
|
homeArc?: true;
|
|
65
|
+
/** #8661 D11 — what the design did to the contact page's sections and title. */
|
|
66
|
+
contact: HomeSeedOutcome;
|
|
67
|
+
/** #8661 D11 — the design ships a contact page for this site, so a contact page it leaves alone is reported. */
|
|
68
|
+
contactSeed?: true;
|
|
60
69
|
/** The site-relative roster file, when there is one. */
|
|
61
70
|
rosterPath?: string;
|
|
62
71
|
/** #7720 D4.2 — the distinct families written to site-fonts.ts, display first; set only when faces are written. */
|
package/dist/apply-design.js
CHANGED
|
@@ -19,7 +19,7 @@ import { dirname, join, relative, resolve, sep } from "node:path";
|
|
|
19
19
|
import { parseArgs } from "node:util";
|
|
20
20
|
import { planDesignMd } from "./apply-design-md.js";
|
|
21
21
|
import { compileDesignFonts, compileDesignTheme } from "./design-compile.js";
|
|
22
|
-
import { HOME_PAGE_PATH, designHomeSeeds, planDesignHome, shapeHomePath, shippedHomeSeeds, } from "./design-home.js";
|
|
22
|
+
import { CONTACT_PAGE_PATH, DESIGN_SEED_PAGES, HOME_PAGE_PATH, designHomeSeeds, planDesignHome, shapeHomePath, shippedHomeSeeds, siteIdentityName, } from "./design-home.js";
|
|
23
23
|
import { applyDesignPresets, readSectionLookCatalog, shippedPageTypes, validateDesignPresets, } from "./design-presets.js";
|
|
24
24
|
import { resolveDesign, spliceThemeBlock } from "./design-scaffold.js";
|
|
25
25
|
import { chromeKnobReaders, unreadChromeKnobs } from "./lib/chrome-readers.js";
|
|
@@ -194,6 +194,9 @@ export async function applyDesign(opts) {
|
|
|
194
194
|
// #7743 D7 — the home seeds (the design's and every shipped design's), validated beside the presets.
|
|
195
195
|
const designSeeds = designHomeSeeds(resolved, { designsDir: opts.designsDir, catalog });
|
|
196
196
|
const shippedSeeds = shippedHomeSeeds({ designsDir: opts.designsDir, catalog });
|
|
197
|
+
// #8661 D11 — and the contact seeds, the same way.
|
|
198
|
+
const contactSeeds = designHomeSeeds(resolved, { designsDir: opts.designsDir, catalog, page: "contact" });
|
|
199
|
+
const shippedContactSeeds = shippedHomeSeeds({ designsDir: opts.designsDir, catalog, page: "contact" });
|
|
197
200
|
// C5 — the design gate, fed the site's own key (D6). A Free design reads no key.
|
|
198
201
|
if (!resolved.free) {
|
|
199
202
|
const key = readSiteKey(targetAbs, env);
|
|
@@ -243,28 +246,44 @@ export async function applyDesign(opts) {
|
|
|
243
246
|
const designMdNext = planDesignMd(resolved.dir, targetAbs);
|
|
244
247
|
// #7743 D7 — the home is planned before the presets, so a seeded home is
|
|
245
248
|
// written once, carrying both. The one `planDesignHome` call site here.
|
|
249
|
+
// #8661 D11 — the contact page is planned right after it, its heading named
|
|
250
|
+
// from the site's `siteIdentity.name`.
|
|
246
251
|
const shape = roster?.template.shape;
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
252
|
+
const siteName = siteIdentityName(siteMdxCurrent);
|
|
253
|
+
const plans = [
|
|
254
|
+
["home", designSeeds, shippedSeeds],
|
|
255
|
+
["contact", contactSeeds, shippedContactSeeds],
|
|
256
|
+
].map(([page, pageSeeds, shippedPageSeeds]) => {
|
|
257
|
+
const target = pageSeeds.find((seed) => seed.shape === shape);
|
|
258
|
+
const path = join(targetAbs, ...DESIGN_SEED_PAGES[page].path.split("/"));
|
|
259
|
+
const current = existsSync(path) ? readFileSync(path, "utf8") : undefined;
|
|
260
|
+
const plan = planDesignHome({
|
|
261
|
+
page,
|
|
262
|
+
siteName,
|
|
263
|
+
current,
|
|
264
|
+
shape,
|
|
265
|
+
target,
|
|
266
|
+
shapeHome: shape === undefined || current === undefined
|
|
267
|
+
? ""
|
|
268
|
+
: readFileSync(shapeHomePath(shape, { siteTemplatesDir: opts.siteTemplatesDir, templatesDir, page }), "utf8"),
|
|
269
|
+
shippedSeeds: shippedPageSeeds,
|
|
270
|
+
catalog: catalog(),
|
|
271
|
+
});
|
|
272
|
+
// The page this site would take: the shape's seed, or (with no shape to pick one) any seed at all.
|
|
273
|
+
const offered = target !== undefined || (shape === undefined && pageSeeds.length > 0);
|
|
274
|
+
const action = plan.outcome === "seeded" || plan.outcome === "restored" ? plan.outcome : undefined;
|
|
275
|
+
return { path, plan, offered, action };
|
|
264
276
|
});
|
|
265
|
-
const
|
|
277
|
+
const [homeSeeding, contactSeeding] = plans;
|
|
278
|
+
const homePlan = homeSeeding.plan;
|
|
279
|
+
const homeArc = homeSeeding.offered;
|
|
280
|
+
const homePath = homeSeeding.path;
|
|
281
|
+
const homeAction = homeSeeding.action;
|
|
282
|
+
const contactPlan = contactSeeding.plan;
|
|
283
|
+
const contactPath = contactSeeding.path;
|
|
284
|
+
const contactAction = contactSeeding.action;
|
|
266
285
|
const pageWrites = [];
|
|
267
|
-
if (roster !== undefined && (hasPresets || homeAction !== undefined)) {
|
|
286
|
+
if (roster !== undefined && (hasPresets || homeAction !== undefined || contactAction !== undefined)) {
|
|
268
287
|
const pageTypes = new Set();
|
|
269
288
|
for (const row of roster.rows) {
|
|
270
289
|
const pageType = row.value["pageType"];
|
|
@@ -274,11 +293,17 @@ export async function applyDesign(opts) {
|
|
|
274
293
|
const contentDir = join(targetAbs, "content");
|
|
275
294
|
// An authored home kept from a design's arc is reported as kept, so no preset dresses it either (D3, F4).
|
|
276
295
|
const homeKept = homePlan.outcome === "authored" && homeArc;
|
|
296
|
+
// #8661 D11 — likewise an authored contact page kept from the design's own.
|
|
297
|
+
const contactKept = contactPlan.outcome === "authored" && contactSeeding.offered;
|
|
277
298
|
const pages = listMdxFiles(contentDir)
|
|
278
|
-
.filter((path) => path !== siteMdxPath && !(homeKept && path === homePath))
|
|
299
|
+
.filter((path) => path !== siteMdxPath && !(homeKept && path === homePath) && !(contactKept && path === contactPath))
|
|
279
300
|
.map((path) => ({
|
|
280
301
|
path,
|
|
281
|
-
text: path === homePath && homePlan.text !== undefined
|
|
302
|
+
text: path === homePath && homePlan.text !== undefined
|
|
303
|
+
? homePlan.text
|
|
304
|
+
: path === contactPath && contactPlan.text !== undefined
|
|
305
|
+
? contactPlan.text
|
|
306
|
+
: readFileSync(path, "utf8"),
|
|
282
307
|
}));
|
|
283
308
|
const presetResult = hasPresets
|
|
284
309
|
? applyDesignPresets(pages, pkg.sections, { pageTypes })
|
|
@@ -286,14 +311,21 @@ export async function applyDesign(opts) {
|
|
|
286
311
|
for (const page of pages) {
|
|
287
312
|
const preset = presetResult.pages.find((p) => p.path === page.path);
|
|
288
313
|
const home = page.path === homePath ? homeAction : undefined;
|
|
289
|
-
|
|
314
|
+
const contact = page.path === contactPath ? contactAction : undefined;
|
|
315
|
+
if (preset === undefined && home === undefined && contact === undefined)
|
|
290
316
|
continue;
|
|
291
317
|
const fields = [
|
|
292
318
|
...new Set(presetResult.applied
|
|
293
319
|
.filter((a) => a.path === page.path)
|
|
294
320
|
.map((a) => `${a.sectionType}.${a.field}`)),
|
|
295
321
|
].sort();
|
|
296
|
-
pageWrites.push({
|
|
322
|
+
pageWrites.push({
|
|
323
|
+
path: page.path,
|
|
324
|
+
text: preset?.text ?? page.text,
|
|
325
|
+
fields,
|
|
326
|
+
...(home !== undefined ? { home } : {}),
|
|
327
|
+
...(contact !== undefined ? { contact } : {}),
|
|
328
|
+
});
|
|
297
329
|
}
|
|
298
330
|
}
|
|
299
331
|
let manifest = "no-roster";
|
|
@@ -303,10 +335,14 @@ export async function applyDesign(opts) {
|
|
|
303
335
|
if (roster.path === SITE_ROSTER_PATH) {
|
|
304
336
|
manifestNext = planSiteManifestDesign(readFileSync(rosterAbs, "utf8"), { design: resolved.recorded, layout: chrome, theme }, rosterAbs);
|
|
305
337
|
// #7743 D6 — the `/` row records the home as written, in the same single write.
|
|
306
|
-
|
|
307
|
-
|
|
338
|
+
// #8661 — and the `/contact` row the contact page.
|
|
339
|
+
for (const page of ["home", "contact"]) {
|
|
340
|
+
const written = pageWrites.find((write) => write[page] !== undefined);
|
|
341
|
+
if (written === undefined)
|
|
342
|
+
continue;
|
|
308
343
|
manifestNext =
|
|
309
|
-
planHomeRecord(manifestNext ?? readFileSync(rosterAbs, "utf8"),
|
|
344
|
+
planHomeRecord(manifestNext ?? readFileSync(rosterAbs, "utf8"), written.text, catalog(), page) ??
|
|
345
|
+
manifestNext;
|
|
310
346
|
}
|
|
311
347
|
manifest = manifestNext === undefined ? "unchanged" : "recorded";
|
|
312
348
|
}
|
|
@@ -321,7 +357,9 @@ export async function applyDesign(opts) {
|
|
|
321
357
|
const path = siteRelative(targetAbs, page.path);
|
|
322
358
|
changes.push(page.home !== undefined
|
|
323
359
|
? { path, kind: "home", action: page.home, fields: page.fields }
|
|
324
|
-
:
|
|
360
|
+
: page.contact !== undefined
|
|
361
|
+
? { path, kind: "contact", action: page.contact, fields: page.fields }
|
|
362
|
+
: { path, kind: "preset", fields: page.fields });
|
|
325
363
|
}
|
|
326
364
|
if (chromeNext !== chromeCurrent) {
|
|
327
365
|
mkdirSync(dirname(chromePath), { recursive: true });
|
|
@@ -353,6 +391,8 @@ export async function applyDesign(opts) {
|
|
|
353
391
|
manifest,
|
|
354
392
|
home: homePlan.outcome,
|
|
355
393
|
...(homeArc ? { homeArc: true } : {}),
|
|
394
|
+
contact: contactPlan.outcome,
|
|
395
|
+
...(contactSeeding.offered ? { contactSeed: true } : {}),
|
|
356
396
|
...(roster !== undefined ? { rosterPath: roster.path } : {}),
|
|
357
397
|
...(fontFamilies !== undefined ? { fonts: fontFamilies } : {}),
|
|
358
398
|
...(fontsUnwired ? { fontsUnwired: true } : {}),
|
|
@@ -370,16 +410,20 @@ export function formatApplyDesignReport(result) {
|
|
|
370
410
|
for (const change of result.changes) {
|
|
371
411
|
const suffix = change.kind === "preset"
|
|
372
412
|
? ` (${change.fields.join(", ")})`
|
|
373
|
-
: change.kind === "home"
|
|
413
|
+
: change.kind === "home" || change.kind === "contact"
|
|
374
414
|
? ` (${[change.action, change.fields.join(", ")].filter((part) => part !== "").join("; ")})`
|
|
375
415
|
: "";
|
|
376
416
|
lines.push(` ${change.kind.padEnd(8)} ${change.path}${suffix}`);
|
|
377
417
|
}
|
|
378
418
|
const n = result.changes.length;
|
|
379
419
|
const homeWritten = result.changes.some((change) => change.kind === "home");
|
|
420
|
+
const contactWritten = result.changes.some((change) => change.kind === "contact");
|
|
421
|
+
const contactNote = "the contact page's sections and title were replaced.";
|
|
380
422
|
lines.push(`${n} file${n === 1 ? "" : "s"} changed. ${homeWritten
|
|
381
|
-
?
|
|
382
|
-
:
|
|
423
|
+
? `Routes are unchanged; the home page's sections were replaced.${contactWritten ? ` And ${contactNote}` : ""}`
|
|
424
|
+
: contactWritten
|
|
425
|
+
? `Routes are unchanged; ${contactNote}`
|
|
426
|
+
: "Routes and structured data are unchanged."}`);
|
|
383
427
|
}
|
|
384
428
|
if (result.home === "authored" && result.homeArc === true) {
|
|
385
429
|
lines.push(`home kept: ${HOME_PAGE_PATH} has authored sections — the ${result.design} home arc was not applied`);
|
|
@@ -387,6 +431,9 @@ export function formatApplyDesignReport(result) {
|
|
|
387
431
|
else if (result.home === "no-shape" && result.homeArc === true) {
|
|
388
432
|
lines.push("home not seeded: the site records no shape");
|
|
389
433
|
}
|
|
434
|
+
if (result.contact === "authored" && result.contactSeed === true) {
|
|
435
|
+
lines.push(`contact kept: ${CONTACT_PAGE_PATH} has an authored title or sections — the ${result.design} contact page was not applied`);
|
|
436
|
+
}
|
|
390
437
|
if (result.fonts !== undefined) {
|
|
391
438
|
lines.push(`fonts: ${result.fonts.join(", ")} load at next build through next/font/google — a family that is not a variable Google font fails there`);
|
|
392
439
|
}
|
|
@@ -28,7 +28,14 @@ export interface DeriveArchitectureOptions {
|
|
|
28
28
|
* hub — the billing action (#8217 D9, a reviewed change to the #7873 rule).
|
|
29
29
|
*/
|
|
30
30
|
export declare const SPOKE_CHECKOUT_ROUTE_FILES: readonly string[];
|
|
31
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* The spoke files that may import @working-theory/spoke-checkout to READ a
|
|
33
|
+
* paid session, and nothing else: the thank-you page's `readPaidCheckoutEmail`
|
|
34
|
+
* (#8757 D13). Kept apart from SPOKE_CHECKOUT_ROUTE_FILES so it does not
|
|
35
|
+
* also admit them to `lib/checkout`, the one file that fulfils an order.
|
|
36
|
+
*/
|
|
37
|
+
export declare const SPOKE_CHECKOUT_READER_FILES: readonly string[];
|
|
38
|
+
/** The forbidden patterns a spoke's apps/web carries (#7873, brief Part 2 §2; #8748). */
|
|
32
39
|
export declare const SPOKE_WEB_FORBIDDEN_PATTERNS: readonly ForbiddenPattern[];
|
|
33
40
|
/** The same three for a spoke's kept apps/mcp — no MCP file may import the package (#7873). */
|
|
34
41
|
export declare const SPOKE_MCP_FORBIDDEN_PATTERNS: readonly ForbiddenPattern[];
|
|
@@ -37,6 +37,15 @@ export const SPOKE_CHECKOUT_ROUTE_FILES = Object.freeze([
|
|
|
37
37
|
"apps/web/app/api/webhooks/stripe/route.ts",
|
|
38
38
|
"apps/web/app/[locale]/(authed)/account/billing/spoke-billing.action.ts",
|
|
39
39
|
]);
|
|
40
|
+
/**
|
|
41
|
+
* The spoke files that may import @working-theory/spoke-checkout to READ a
|
|
42
|
+
* paid session, and nothing else: the thank-you page's `readPaidCheckoutEmail`
|
|
43
|
+
* (#8757 D13). Kept apart from SPOKE_CHECKOUT_ROUTE_FILES so it does not
|
|
44
|
+
* also admit them to `lib/checkout`, the one file that fulfils an order.
|
|
45
|
+
*/
|
|
46
|
+
export const SPOKE_CHECKOUT_READER_FILES = Object.freeze([
|
|
47
|
+
"apps/web/app/[locale]/checkout/thank-you/page.tsx",
|
|
48
|
+
]);
|
|
40
49
|
/**
|
|
41
50
|
* A line-level regex for an import of `specifier` or one of its subpaths, in
|
|
42
51
|
* every form the line can take: `from "…"` (a static or re-export, including
|
|
@@ -53,7 +62,7 @@ function specifierForm(specifier) {
|
|
|
53
62
|
/** P1 with no exclusions — a kept apps/mcp's form; apps/web adds the route files. */
|
|
54
63
|
const SPOKE_CHECKOUT_PATTERN = Object.freeze({
|
|
55
64
|
pattern: specifierForm("@working-theory/spoke-checkout"),
|
|
56
|
-
reason: "Spoke checkout: only the buy route, the webhook route
|
|
65
|
+
reason: "Spoke checkout: only the buy route, the webhook route, the billing action and the thank-you page may import @working-theory/spoke-checkout (brief Part 2 §2, #7873, #8217, #8757)",
|
|
57
66
|
});
|
|
58
67
|
const STRIPE_CLIENT_PATTERN = Object.freeze({
|
|
59
68
|
pattern: specifierForm("@working-theory/stripe-client"),
|
|
@@ -63,11 +72,37 @@ const STRIPE_SDK_PATTERN = Object.freeze({
|
|
|
63
72
|
pattern: specifierForm("stripe"),
|
|
64
73
|
reason: "A spoke never imports the stripe SDK directly — it reaches Stripe only through @working-theory/spoke-checkout (#7873)",
|
|
65
74
|
});
|
|
66
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* The standard spoke checkout's fulfilment (`lib/checkout.ts`) sends the one
|
|
77
|
+
* `Idempotency-Key` the hub reads as a purchase, so only the files allowed to
|
|
78
|
+
* import the package may reach it (#8748 D3, SEC-8748-A): any relative or
|
|
79
|
+
* `@/`-aliased import (apps/web's tsconfig maps `@/*` to the app root) whose
|
|
80
|
+
* last segment is `checkout` (with or without a `.js`/`.ts`-family
|
|
81
|
+
* extension, which `bundler` resolution maps to the same file), in every form
|
|
82
|
+
* `specifierForm` covers. The
|
|
83
|
+
* closing quote straight after `checkout` keeps `checkout-config` and
|
|
84
|
+
* `checkout.actions` out of it. The checkout tests the writer copies are
|
|
85
|
+
* excluded too: the scaffold's lint reads test files.
|
|
86
|
+
*/
|
|
87
|
+
const SPOKE_FULFILMENT_PATTERN = Object.freeze({
|
|
88
|
+
pattern: "\\b(?:from|import|require)\\s*\\(?\\s*['\"`](?:(?:\\.{1,2}/)+|@/)(?:[^'\"`]*/)?checkout(?:\\.[cm]?[jt]sx?)?['\"`]",
|
|
89
|
+
paths_excluded: [
|
|
90
|
+
...SPOKE_CHECKOUT_ROUTE_FILES,
|
|
91
|
+
"apps/web/app/api/checkout/__tests__/**",
|
|
92
|
+
"apps/web/app/api/webhooks/stripe/__tests__/**",
|
|
93
|
+
"apps/web/lib/__tests__/checkout.test.ts",
|
|
94
|
+
],
|
|
95
|
+
reason: "Spoke checkout: only the buy route, the webhook route and the billing action may import lib/checkout, the one file that sends the hub an Idempotency-Key (#8748)",
|
|
96
|
+
});
|
|
97
|
+
/** The forbidden patterns a spoke's apps/web carries (#7873, brief Part 2 §2; #8748). */
|
|
67
98
|
export const SPOKE_WEB_FORBIDDEN_PATTERNS = Object.freeze([
|
|
68
|
-
Object.freeze({
|
|
99
|
+
Object.freeze({
|
|
100
|
+
...SPOKE_CHECKOUT_PATTERN,
|
|
101
|
+
paths_excluded: [...SPOKE_CHECKOUT_ROUTE_FILES, ...SPOKE_CHECKOUT_READER_FILES],
|
|
102
|
+
}),
|
|
69
103
|
STRIPE_CLIENT_PATTERN,
|
|
70
104
|
STRIPE_SDK_PATTERN,
|
|
105
|
+
SPOKE_FULFILMENT_PATTERN,
|
|
71
106
|
]);
|
|
72
107
|
/** The same three for a spoke's kept apps/mcp — no MCP file may import the package (#7873). */
|
|
73
108
|
export const SPOKE_MCP_FORBIDDEN_PATTERNS = Object.freeze([
|
package/dist/design-home.d.ts
CHANGED
|
@@ -2,7 +2,26 @@ import { type SectionLookCatalog } from "./design-presets.js";
|
|
|
2
2
|
import type { ResolvedDesign } from "./design-scaffold.js";
|
|
3
3
|
/** Where a site's home page lives, site-relative. */
|
|
4
4
|
export declare const HOME_PAGE_PATH = "content/en/HomePage/home.mdx";
|
|
5
|
-
/**
|
|
5
|
+
/** Where a site's contact page lives, site-relative (#8661 D11). */
|
|
6
|
+
export declare const CONTACT_PAGE_PATH = "content/en/ContactPage/contact.mdx";
|
|
7
|
+
/**
|
|
8
|
+
* The pages a design may seed (#8661 D11), keyed by the design-package
|
|
9
|
+
* directory that holds their seeds (`home/<shape>.mdx`, `contact/<shape>.mdx`).
|
|
10
|
+
*/
|
|
11
|
+
export declare const DESIGN_SEED_PAGES: {
|
|
12
|
+
readonly home: {
|
|
13
|
+
readonly pageType: "HomePage";
|
|
14
|
+
readonly path: "content/en/HomePage/home.mdx";
|
|
15
|
+
readonly route: "/";
|
|
16
|
+
};
|
|
17
|
+
readonly contact: {
|
|
18
|
+
readonly pageType: "ContactPage";
|
|
19
|
+
readonly path: "content/en/ContactPage/contact.mdx";
|
|
20
|
+
readonly route: "/contact";
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
export type DesignSeedPage = keyof typeof DESIGN_SEED_PAGES;
|
|
24
|
+
/** One `home/<shape>.mdx` (or `contact/<shape>.mdx`, #8661 D11) file of a design package (D1). */
|
|
6
25
|
export interface HomeSeed {
|
|
7
26
|
readonly shape: string;
|
|
8
27
|
/** Absolute path, for refusals. */
|
|
@@ -11,34 +30,46 @@ export interface HomeSeed {
|
|
|
11
30
|
}
|
|
12
31
|
export type HomeSeedOutcome = "seeded" | "restored" | "unchanged" | "authored" | "no-shape" | "no-home";
|
|
13
32
|
/**
|
|
14
|
-
* Read and validate `<dir>/home/*` (D1)
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
33
|
+
* Read and validate `<dir>/home/*` (D1), or `<dir>/contact/*` for
|
|
34
|
+
* `page: "contact"` (#8661 D11). Refuses with a `DesignPackageError` naming
|
|
35
|
+
* the file, before any write: a basename that is not a recognized shape, a
|
|
36
|
+
* page whose `type` is not the page's (`HomePage`, `ContactPage`), `sections`
|
|
37
|
+
* that cannot be located (or are not in the flow form a carry can splice), a
|
|
38
|
+
* section type the catalog does not carry, or a `logoCloud` whose `logos` is
|
|
39
|
+
* not `[]`.
|
|
19
40
|
*/
|
|
20
41
|
export declare function readDesignHomeSeeds(dir: string, opts: {
|
|
21
42
|
catalog: () => SectionLookCatalog;
|
|
43
|
+
page?: DesignSeedPage;
|
|
22
44
|
}): HomeSeed[];
|
|
23
45
|
/**
|
|
24
|
-
* The seeds a resolved design uses (D1): its own `home
|
|
25
|
-
* byte-identical copy of a shipped design IS that design — the
|
|
26
|
-
* design's, else none.
|
|
46
|
+
* The seeds a resolved design uses (D1): its own `home/` (or `contact/`),
|
|
47
|
+
* else — a byte-identical copy of a shipped design IS that design — the
|
|
48
|
+
* shipped design's, else none.
|
|
27
49
|
*/
|
|
28
50
|
export declare function designHomeSeeds(resolved: ResolvedDesign, opts: {
|
|
29
51
|
designsDir: string;
|
|
30
52
|
catalog: () => SectionLookCatalog;
|
|
53
|
+
page?: DesignSeedPage;
|
|
31
54
|
}): HomeSeed[];
|
|
32
55
|
/** Every shipped design's seeds (D3's placeholder set reads them all). */
|
|
33
56
|
export declare function shippedHomeSeeds(opts: {
|
|
34
57
|
designsDir: string;
|
|
35
58
|
catalog: () => SectionLookCatalog;
|
|
59
|
+
page?: DesignSeedPage;
|
|
36
60
|
}): HomeSeed[];
|
|
37
|
-
/** The shape's own home: its shape tree's, else the overlay's (spec § 1.4). */
|
|
61
|
+
/** The shape's own home (or contact page): its shape tree's, else the overlay's (spec § 1.4). */
|
|
38
62
|
export declare function shapeHomePath(shape: string, opts: {
|
|
39
63
|
siteTemplatesDir: string;
|
|
40
64
|
templatesDir: string;
|
|
65
|
+
page?: DesignSeedPage;
|
|
41
66
|
}): string;
|
|
67
|
+
/**
|
|
68
|
+
* A site's `siteIdentity.name` (#8661 D11, C8): the first `name:` line
|
|
69
|
+
* indented directly under `siteIdentity:` in `content/_site.mdx`'s front
|
|
70
|
+
* matter, quotes stripped. Undefined when there is no such line.
|
|
71
|
+
*/
|
|
72
|
+
export declare function siteIdentityName(siteMdx: string | undefined): string | undefined;
|
|
42
73
|
/**
|
|
43
74
|
* A home's content fingerprint (D3): per section in order, its type and its
|
|
44
75
|
* non-look `data` entries as `[key, normalized raw value]`, sorted by key.
|
|
@@ -55,9 +86,21 @@ export declare function applySeed(home: string, seed: HomeSeed, file?: string):
|
|
|
55
86
|
/**
|
|
56
87
|
* Plan a site home under a design (D3). Writes nothing: `text` is the new
|
|
57
88
|
* home when the outcome is `seeded` or `restored`, and absent otherwise.
|
|
89
|
+
*
|
|
90
|
+
* `page: "contact"` (#8661 D11) plans the contact page instead. The seeds'
|
|
91
|
+
* `{{SITE_NAME}}` is filled with `siteName` (absent: `{Your Name}`) before
|
|
92
|
+
* anything is compared; the shape's own page is read with the scaffold
|
|
93
|
+
* markers substituted, as a scaffold installed it; the page is a placeholder
|
|
94
|
+
* only while its title is one too (the shape's own, or a seed's); and a write
|
|
95
|
+
* replaces the `title:` line as well as the sections, and the `description:`
|
|
96
|
+
* line while it is still a placeholder (the shape's own, or a seed's).
|
|
58
97
|
*/
|
|
59
98
|
export declare function planDesignHome(opts: {
|
|
60
|
-
/** The
|
|
99
|
+
/** The page planned (#8661 D11). Default `"home"`. */
|
|
100
|
+
page?: DesignSeedPage;
|
|
101
|
+
/** The site's name, for a contact seed's `{{SITE_NAME}}` (#8661 D11). */
|
|
102
|
+
siteName?: string | undefined;
|
|
103
|
+
/** The site's content/en/HomePage/home.mdx (or ContactPage/contact.mdx), if any. */
|
|
61
104
|
current: string | undefined;
|
|
62
105
|
/** The roster's template shape. */
|
|
63
106
|
shape: string | undefined;
|