create-ailk 0.19.1 → 0.21.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 +84 -7
- package/component-catalog.json +184 -6
- package/component-catalog.md +20 -9
- package/dist/apply-design.d.ts +9 -0
- package/dist/apply-design.js +78 -31
- package/dist/cli.js +35 -0
- package/dist/copy.d.ts +41 -0
- package/dist/copy.js +87 -21
- package/dist/design-home.d.ts +54 -11
- package/dist/design-home.js +160 -22
- package/dist/lib/design-checks.d.ts +6 -1
- package/dist/lib/design-checks.js +23 -2
- package/dist/lib/fetch-template-snapshot.d.ts +21 -0
- package/dist/lib/fetch-template-snapshot.js +205 -0
- package/dist/lib/merge-three-way.d.ts +31 -0
- package/dist/lib/merge-three-way.js +65 -0
- package/dist/module-architecture.js +55 -2
- package/dist/programmatic.d.ts +7 -1
- package/dist/programmatic.js +76 -22
- package/dist/site-roster.d.ts +4 -2
- package/dist/site-roster.js +7 -5
- package/dist/spoke-checkout.d.ts +7 -0
- package/dist/spoke-checkout.js +8 -1
- package/dist/starter-grid.js +16 -0
- package/dist/surfaces.js +78 -2
- package/dist/sync-routes.d.ts +28 -0
- package/dist/sync-routes.js +123 -1
- package/dist/template-record.d.ts +59 -0
- package/dist/template-record.js +183 -0
- package/dist/update.d.ts +53 -0
- package/dist/update.js +606 -0
- 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/.claude/cloudparity-repo-hooks.json +9 -0
- package/templates/.claude/hooks/architecture-check.sh +8 -0
- package/templates/.claude/rules/loop-contract.md +129 -0
- package/templates/.claude/settings.json +3 -2
- package/templates/.env.example +13 -0
- package/templates/CLAUDE.md +1 -1
- package/templates/CONVENTIONS.md +3 -2
- package/templates/README.md +26 -25
- package/templates/_gitignore +162 -0
- package/templates/apps/api/CLAUDE.md +18 -10
- package/templates/apps/api/jest.config.cjs +1 -0
- package/templates/apps/api/package.json +2 -1
- package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +22 -2
- package/templates/apps/api/src/__tests__/setup.ts +7 -0
- package/templates/apps/api/src/bin/__tests__/rate-limit-probe.test.ts +184 -0
- package/templates/apps/api/src/bin/rate-limit-probe.ts +53 -0
- package/templates/apps/api/src/lib/proposal-paid-store.ts +49 -0
- package/templates/apps/api/src/lib/rate-limit-probe.ts +165 -0
- package/templates/apps/api/src/lib/rate-limit-store.ts +142 -0
- package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +5 -2
- package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +2 -0
- package/templates/apps/api/src/middleware/__tests__/rate-limit-cross-isolate.db.test.ts +220 -0
- package/templates/apps/api/src/middleware/__tests__/rate-limit-store.test.ts +430 -0
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +21 -14
- package/templates/apps/api/src/middleware/auth.ts +8 -4
- package/templates/apps/api/src/middleware/default-tenant.ts +9 -0
- package/templates/apps/api/src/middleware/rate-limit.ts +290 -82
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +26 -0
- package/templates/apps/api/src/routes/webhooks/README.md +33 -5
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-proposal-paid.test.ts +369 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +442 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +184 -6
- package/templates/apps/api/src/server.ts +14 -2
- package/templates/apps/api/src/services/__tests__/audit-log.test.ts +91 -0
- package/templates/apps/api/src/services/audit-log.ts +72 -10
- package/templates/apps/api/src/services/default-tenant.ts +12 -3
- package/templates/apps/api/src/services/hub-sign-in.ts +2 -1
- package/templates/apps/api/src/services/project-listings.ts +6 -1
- package/templates/apps/api/src/services/public-site-resolver.ts +21 -2
- package/templates/apps/api/vercel.json +1 -1
- package/templates/apps/mcp/CLAUDE.md +19 -7
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +139 -44
- package/templates/apps/mcp/jest.config.cjs +1 -0
- package/templates/apps/mcp/src/tools/person_notes.ts +25 -0
- package/templates/apps/mcp/src/tools/proposals.ts +27 -0
- package/templates/apps/mcp/src/workspace-server.ts +16 -0
- package/templates/apps/web/CLAUDE.md +26 -1
- package/templates/apps/web/_gitignore +6 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-up/__tests__/sign-up-card.test.tsx +165 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +19 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +106 -23
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up.actions.ts +125 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/agreements-prompt.test.tsx +281 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/general.test.tsx +28 -83
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/hub-mode.test.tsx +27 -13
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/item.test.tsx +57 -2
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout.test.tsx +2 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/notifications.test.tsx +102 -18
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/onboarding.test.tsx +323 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/privacy.test.tsx +365 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/settings.test.tsx +3 -2
- package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +8 -0
- package/templates/apps/web/app/[locale]/(authed)/account/agreements-actions.ts +170 -0
- package/templates/apps/web/app/[locale]/(authed)/account/agreements-prompt.tsx +181 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/[pieceId]/page.tsx +88 -3
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/[pieceId]/pay-form.tsx +65 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/[pieceId]/review-form.tsx +27 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/__tests__/page.test.tsx +176 -3
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/__tests__/pay.test.tsx +193 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/__tests__/review.test.tsx +188 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/artifact-actions.ts +65 -4
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/artifact-data.ts +17 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/page.tsx +11 -2
- package/templates/apps/web/app/[locale]/(authed)/account/general/page.tsx +5 -24
- package/templates/apps/web/app/[locale]/(authed)/account/notifications/page.tsx +64 -18
- package/templates/apps/web/app/[locale]/(authed)/account/onboarding/actions.ts +120 -0
- package/templates/apps/web/app/[locale]/(authed)/account/onboarding/onboarding-form.tsx +322 -0
- package/templates/apps/web/app/[locale]/(authed)/account/onboarding/page.tsx +126 -0
- package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +43 -1
- package/templates/apps/web/app/[locale]/(authed)/account/privacy/actions.ts +65 -0
- package/templates/apps/web/app/[locale]/(authed)/account/privacy/page.tsx +226 -0
- package/templates/apps/web/app/[locale]/(authed)/account/settings-frame.tsx +5 -3
- 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/__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]/consent-banner.tsx +32 -0
- package/templates/apps/web/app/[locale]/consent.actions.ts +71 -0
- package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +7 -0
- 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 +347 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +73 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +5 -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 +48 -4
- package/templates/apps/web/app/[locale]/contact/page.tsx +81 -9
- package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +25 -3
- 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 +46 -4
- package/templates/apps/web/app/[locale]/newsletter/newsletter.actions.ts +12 -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]/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]/unsubscribe/__tests__/unsubscribe-rsc-boundary.test.tsx +209 -0
- package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe.test.tsx +158 -0
- package/templates/apps/web/app/[locale]/unsubscribe/page.tsx +54 -0
- package/templates/apps/web/app/[locale]/unsubscribe/unsubscribe-button.tsx +73 -0
- package/templates/apps/web/app/[locale]/unsubscribe/unsubscribe.actions.ts +55 -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/robots.txt/route.ts +13 -10
- package/templates/apps/web/jest.config.cjs +4 -0
- package/templates/apps/web/lib/__tests__/consent-visitor.test.ts +163 -0
- package/templates/apps/web/lib/__tests__/deck-page.test.ts +244 -0
- package/templates/apps/web/lib/__tests__/relay.test.ts +49 -0
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +100 -9
- package/templates/apps/web/lib/account-config.ts +31 -16
- package/templates/apps/web/lib/consent-visitor.ts +103 -0
- package/templates/apps/web/lib/deck-page.ts +186 -0
- package/templates/apps/web/lib/relay.ts +20 -0
- package/templates/apps/web/messages/en.json +83 -9
- package/templates/apps/web/next.config.mjs +8 -2
- package/templates/database/CHANGELOG.md +48 -0
- package/templates/database/__tests__/outbound-message-action.db.test.ts +80 -0
- package/templates/database/__tests__/person-notes.db.test.ts +249 -0
- package/templates/database/__tests__/piece-kind-migration.test.ts +67 -0
- package/templates/database/__tests__/piece-kind.db.test.ts +208 -0
- package/templates/database/__tests__/seed.test.ts +39 -2
- package/templates/database/_gitignore +8 -0
- package/templates/database/inbox/schema.prisma +38 -12
- package/templates/database/jest.config.cjs +1 -0
- package/templates/database/migrations/20261015120000_person_notes/migration.sql +77 -0
- package/templates/database/migrations/20261015120000_rate_limit_counters/migration.sql +43 -0
- package/templates/database/migrations/20261016120000_piece_kind/migration.sql +95 -0
- package/templates/database/migrations/20261017120000_outbound_message_action/migration.sql +30 -0
- package/templates/database/ops/schema.prisma +16 -0
- package/templates/database/package.json +1 -1
- package/templates/database/seed.ts +29 -0
- package/templates/project.yaml +6 -3
- package/templates/tools/jest/pin-timezone.cjs +34 -0
- package/versions.json +35 -35
- package/templates/.claude/rules/preserve-existing-style.md +0 -51
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
|
}
|
package/dist/cli.js
CHANGED
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
* to a linked Vercel project and redeploy, exit (#6683 D9)
|
|
20
20
|
* on an `apply-design` subcommand: rewrite an existing site's look,
|
|
21
21
|
* print the changed-file report, exit (#6819)
|
|
22
|
+
* on an `update` subcommand: bring template fixes into a site's
|
|
23
|
+
* already-copied apps/web, apps/api and apps/mcp code, print the
|
|
24
|
+
* per-file report, exit (2 on a refusal it names) (#8522)
|
|
22
25
|
* 1. parse argv → CliOptions
|
|
23
26
|
* 2. on --help: print helpText, exit 0
|
|
24
27
|
* 3. on --version: print version, exit 0
|
|
@@ -58,6 +61,7 @@ import { addModule, formatSurfaceDrift, scaffold, syncArchitectureSync, syncCata
|
|
|
58
61
|
import { SEED_FLOWS_HELP, SeedFlowsRefusedError, parseSeedFlowsArgs, seedFlows, } from "./seed-flows.js";
|
|
59
62
|
import { CORE_SURFACE_PATHS, SURFACE_ALIASES } from "./surfaces.js";
|
|
60
63
|
import { SYNC_ROUTES_HELP, formatSyncRoutesReport, parseSyncRoutesArgs, } from "./sync-routes.js";
|
|
64
|
+
import { UPDATE_HELP, UpdateRefusedError, formatUpdateReport, parseUpdateArgs, updateSite, } from "./update.js";
|
|
61
65
|
const PKG_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
62
66
|
const PKG_JSON = resolve(PKG_ROOT, "package.json");
|
|
63
67
|
function readPackageVersion() {
|
|
@@ -272,6 +276,35 @@ async function main() {
|
|
|
272
276
|
process.stdout.write(formatApplyDesignReport(result));
|
|
273
277
|
process.exit(0);
|
|
274
278
|
}
|
|
279
|
+
// `update [<target-dir>]` (#8522): bring template fixes into a site's
|
|
280
|
+
// already-copied apps/web, apps/api and apps/mcp code. A refusal it names
|
|
281
|
+
// (no record and no patternVersion, an unparseable record, a downgrade, an
|
|
282
|
+
// unreadable architecture.yaml or manifest topology, a backfill whose
|
|
283
|
+
// fetch fails) exits 2 before anything is written. A clean run exits 0
|
|
284
|
+
// even when it lists files under "Needs review" — the review list is the
|
|
285
|
+
// hand-off, not a failure.
|
|
286
|
+
if (argv[0] === "update") {
|
|
287
|
+
const args = parseUpdateArgs(argv.slice(1));
|
|
288
|
+
if (args.help) {
|
|
289
|
+
process.stdout.write(UPDATE_HELP);
|
|
290
|
+
process.exit(0);
|
|
291
|
+
}
|
|
292
|
+
try {
|
|
293
|
+
const result = await updateSite({
|
|
294
|
+
targetDir: args.target ?? process.cwd(),
|
|
295
|
+
templatesDir: resolve(PKG_ROOT, "templates"),
|
|
296
|
+
version: readPackageVersion(),
|
|
297
|
+
});
|
|
298
|
+
process.stdout.write(formatUpdateReport(result));
|
|
299
|
+
}
|
|
300
|
+
catch (err) {
|
|
301
|
+
if (!(err instanceof UpdateRefusedError))
|
|
302
|
+
throw err;
|
|
303
|
+
process.stderr.write(`[create-ailk] ${err.message}\n`);
|
|
304
|
+
process.exit(err.exitCode);
|
|
305
|
+
}
|
|
306
|
+
process.exit(0);
|
|
307
|
+
}
|
|
275
308
|
// No silent default (#6754 D2/D3): a scaffold run naming neither --shape
|
|
276
309
|
// nor --template asks for a shape on a terminal and is refused without one.
|
|
277
310
|
// A --design or --modules outside the release scope is refused before that
|
|
@@ -317,6 +350,8 @@ async function main() {
|
|
|
317
350
|
"\n" +
|
|
318
351
|
APPLY_DESIGN_HELP +
|
|
319
352
|
"\n" +
|
|
353
|
+
UPDATE_HELP +
|
|
354
|
+
"\n" +
|
|
320
355
|
LICENSE_SET_HELP);
|
|
321
356
|
process.exit(0);
|
|
322
357
|
}
|
package/dist/copy.d.ts
CHANGED
|
@@ -19,11 +19,52 @@ export interface CopyTemplateOptions {
|
|
|
19
19
|
* `.../services` never excludes a sibling `.../services-extra`.
|
|
20
20
|
*/
|
|
21
21
|
excludePaths?: string[];
|
|
22
|
+
/**
|
|
23
|
+
* Called once per file actually written, with its `/`-separated
|
|
24
|
+
* destination path (relative to `targetDir`) and the exact bytes written
|
|
25
|
+
* (#8522 D2) — `create-ailk update`'s per-file record is built from these
|
|
26
|
+
* calls. No existing caller passes this, so their behaviour is unchanged.
|
|
27
|
+
*/
|
|
28
|
+
onWrite?: (relPath: string, bytes: Uint8Array) => void;
|
|
22
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Whether a walked template-relative path is inside (or equal to) an excluded
|
|
32
|
+
* directory. Compares `/`-normalized paths so the check holds on Windows, and
|
|
33
|
+
* only at a directory boundary — a prefix that stops mid-segment is no match.
|
|
34
|
+
* Exported (#8522 D3) so `create-ailk update` applies the same surface-gating
|
|
35
|
+
* boundary to a candidate new file.
|
|
36
|
+
*/
|
|
37
|
+
export declare function isExcluded(relativePath: string, excluded: readonly string[]): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* The pack-safe name `build-templates.mjs` ships each snapshot `.gitignore`
|
|
40
|
+
* under (#7366 D1) — npm and pnpm strip a file literally named `.gitignore`
|
|
41
|
+
* from a packed tarball, but keep one named `_gitignore`. One definition,
|
|
42
|
+
* imported by `build-templates.mjs` (from the compiled `dist/copy.js`) and
|
|
43
|
+
* by `copyTemplate` below, so the two can never spell it differently.
|
|
44
|
+
*/
|
|
45
|
+
export declare const PACKED_GITIGNORE_NAME = "_gitignore";
|
|
23
46
|
/**
|
|
24
47
|
* Whether a scaffolded path's content is text we run markers over. Exported
|
|
25
48
|
* because the template/base layer (`installBundleTree`) substitutes with the
|
|
26
49
|
* same classifier — one definition, not two (#6460).
|
|
27
50
|
*/
|
|
28
51
|
export declare function isTextFile(relativePath: string): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* The destination path a template-relative path (`/`-separated) copies to —
|
|
54
|
+
* `undefined` for a packaging-only `.gitkeep` or `.npmignore` (never a
|
|
55
|
+
* scaffold file), and a final `PACKED_GITIGNORE_NAME` restored to
|
|
56
|
+
* `.gitignore` in the same directory otherwise (#7366 D3) — so a scaffold
|
|
57
|
+
* never carries the packaging-only name. Exported (#8522 D3) as the one
|
|
58
|
+
* renderer of "the template's copy", shared by `copyTemplate` and by
|
|
59
|
+
* `create-ailk update`'s render of a running or an old template version.
|
|
60
|
+
*/
|
|
61
|
+
export declare function templateDestination(rel: string): string | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* The bytes a template-relative file copies as, at its destination path
|
|
64
|
+
* (#8522 D3): workspace versions plus markers for `package.json`, markers
|
|
65
|
+
* for `isTextFile`, raw bytes otherwise. Exported so `copyTemplate`'s walk
|
|
66
|
+
* and `create-ailk update`'s render of a running or an old template version
|
|
67
|
+
* share the one renderer — nothing about the copy itself changes.
|
|
68
|
+
*/
|
|
69
|
+
export declare function renderTemplateFile(bytes: Uint8Array, destRel: string, versions: WorkspaceVersionMap, markers: MarkerMap): Uint8Array;
|
|
29
70
|
export declare function copyTemplate(opts: CopyTemplateOptions): void;
|
package/dist/copy.js
CHANGED
|
@@ -17,8 +17,23 @@
|
|
|
17
17
|
* alike. Symlinks are skipped outright (never followed, never copied) —
|
|
18
18
|
* the snapshotted templates/ tree is built without symlinks; any symlink
|
|
19
19
|
* present at scaffold time is treated as a packaging accident and ignored.
|
|
20
|
+
*
|
|
21
|
+
* Packed ignore files (#7366):
|
|
22
|
+
* - npm and pnpm strip every file literally named `.gitignore` from a
|
|
23
|
+
* packed tarball, so `build-templates.mjs` ships each snapshot
|
|
24
|
+
* `.gitignore` under `PACKED_GITIGNORE_NAME` (`_gitignore`) instead. A
|
|
25
|
+
* file named `PACKED_GITIGNORE_NAME` is restored to `.gitignore` in the
|
|
26
|
+
* same directory on the way into a scaffold, classified and
|
|
27
|
+
* marker-substituted exactly as a `.gitignore` is today (its destination
|
|
28
|
+
* relative path, not its source name, drives `isTextFile`). A directory
|
|
29
|
+
* carrying both `.gitignore` and `PACKED_GITIGNORE_NAME` is refused
|
|
30
|
+
* outright, before either is written, rather than resolved by
|
|
31
|
+
* `readdir()` order. `.npmignore` — the packaging-only sibling
|
|
32
|
+
* `build-templates.mjs` writes beside each `PACKED_GITIGNORE_NAME` to
|
|
33
|
+
* keep pack-time exclusions working — is skipped like `.gitkeep`: it
|
|
34
|
+
* never belongs in a scaffold.
|
|
20
35
|
*/
|
|
21
|
-
import {
|
|
36
|
+
import { mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
22
37
|
import { extname, join, relative, resolve, sep } from "node:path";
|
|
23
38
|
import pkg from "../package.json" with { type: "json" };
|
|
24
39
|
import { substituteMarkers, substituteWorkspaceVersions, } from "./substitute.js";
|
|
@@ -57,8 +72,10 @@ export function buildScaffoldMarkers(projectName) {
|
|
|
57
72
|
* Whether a walked template-relative path is inside (or equal to) an excluded
|
|
58
73
|
* directory. Compares `/`-normalized paths so the check holds on Windows, and
|
|
59
74
|
* only at a directory boundary — a prefix that stops mid-segment is no match.
|
|
75
|
+
* Exported (#8522 D3) so `create-ailk update` applies the same surface-gating
|
|
76
|
+
* boundary to a candidate new file.
|
|
60
77
|
*/
|
|
61
|
-
function isExcluded(relativePath, excluded) {
|
|
78
|
+
export function isExcluded(relativePath, excluded) {
|
|
62
79
|
if (excluded.length === 0)
|
|
63
80
|
return false;
|
|
64
81
|
const rel = relativePath.split(sep).join("/");
|
|
@@ -71,6 +88,14 @@ function isExcluded(relativePath, excluded) {
|
|
|
71
88
|
}
|
|
72
89
|
return false;
|
|
73
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* The pack-safe name `build-templates.mjs` ships each snapshot `.gitignore`
|
|
93
|
+
* under (#7366 D1) — npm and pnpm strip a file literally named `.gitignore`
|
|
94
|
+
* from a packed tarball, but keep one named `_gitignore`. One definition,
|
|
95
|
+
* imported by `build-templates.mjs` (from the compiled `dist/copy.js`) and
|
|
96
|
+
* by `copyTemplate` below, so the two can never spell it differently.
|
|
97
|
+
*/
|
|
98
|
+
export const PACKED_GITIGNORE_NAME = "_gitignore";
|
|
74
99
|
/** File extensions whose content we treat as text (and run markers over). */
|
|
75
100
|
const TEXT_EXTS = new Set([
|
|
76
101
|
".md",
|
|
@@ -113,6 +138,46 @@ function isPackageJson(relativePath) {
|
|
|
113
138
|
const base = relativePath.split(/[\\/]/).pop() ?? "";
|
|
114
139
|
return base === "package.json";
|
|
115
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* The destination path a template-relative path (`/`-separated) copies to —
|
|
143
|
+
* `undefined` for a packaging-only `.gitkeep` or `.npmignore` (never a
|
|
144
|
+
* scaffold file), and a final `PACKED_GITIGNORE_NAME` restored to
|
|
145
|
+
* `.gitignore` in the same directory otherwise (#7366 D3) — so a scaffold
|
|
146
|
+
* never carries the packaging-only name. Exported (#8522 D3) as the one
|
|
147
|
+
* renderer of "the template's copy", shared by `copyTemplate` and by
|
|
148
|
+
* `create-ailk update`'s render of a running or an old template version.
|
|
149
|
+
*/
|
|
150
|
+
export function templateDestination(rel) {
|
|
151
|
+
const parts = rel.split("/");
|
|
152
|
+
const last = parts.length - 1;
|
|
153
|
+
if (last < 0)
|
|
154
|
+
return undefined;
|
|
155
|
+
if (parts[last] === ".gitkeep" || parts[last] === ".npmignore") {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
if (parts[last] === PACKED_GITIGNORE_NAME) {
|
|
159
|
+
parts[last] = ".gitignore";
|
|
160
|
+
}
|
|
161
|
+
return parts.join("/");
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* The bytes a template-relative file copies as, at its destination path
|
|
165
|
+
* (#8522 D3): workspace versions plus markers for `package.json`, markers
|
|
166
|
+
* for `isTextFile`, raw bytes otherwise. Exported so `copyTemplate`'s walk
|
|
167
|
+
* and `create-ailk update`'s render of a running or an old template version
|
|
168
|
+
* share the one renderer — nothing about the copy itself changes.
|
|
169
|
+
*/
|
|
170
|
+
export function renderTemplateFile(bytes, destRel, versions, markers) {
|
|
171
|
+
if (isPackageJson(destRel)) {
|
|
172
|
+
const text = Buffer.from(bytes).toString("utf8");
|
|
173
|
+
return Buffer.from(substituteMarkers(substituteWorkspaceVersions(text, versions), markers), "utf8");
|
|
174
|
+
}
|
|
175
|
+
if (isTextFile(destRel)) {
|
|
176
|
+
const text = Buffer.from(bytes).toString("utf8");
|
|
177
|
+
return Buffer.from(substituteMarkers(text, markers), "utf8");
|
|
178
|
+
}
|
|
179
|
+
return Buffer.from(bytes);
|
|
180
|
+
}
|
|
116
181
|
export function copyTemplate(opts) {
|
|
117
182
|
const templatesAbs = resolve(opts.templatesDir);
|
|
118
183
|
const targetAbs = resolve(opts.targetDir);
|
|
@@ -124,19 +189,30 @@ export function copyTemplate(opts) {
|
|
|
124
189
|
if (dir === undefined)
|
|
125
190
|
break;
|
|
126
191
|
const entries = readdirSync(dir, { withFileTypes: true });
|
|
192
|
+
// A directory carrying both the live name and the packed name would
|
|
193
|
+
// otherwise resolve by `readdir()` order — refuse outright, before
|
|
194
|
+
// writing either (#7366 D3).
|
|
195
|
+
const names = new Set(entries.map((entry) => entry.name));
|
|
196
|
+
if (names.has(".gitignore") && names.has(PACKED_GITIGNORE_NAME)) {
|
|
197
|
+
throw new Error(`templates directory carries both .gitignore and ${PACKED_GITIGNORE_NAME}: ${dir}`);
|
|
198
|
+
}
|
|
127
199
|
for (const entry of entries) {
|
|
128
|
-
// .gitkeep is a packaging artifact for the empty-templates state in git;
|
|
129
|
-
// never propagate it to scaffolder output.
|
|
130
|
-
if (entry.name === ".gitkeep")
|
|
131
|
-
continue;
|
|
132
200
|
const srcPath = join(dir, entry.name);
|
|
133
|
-
const rel = relative(templatesAbs, srcPath);
|
|
201
|
+
const rel = relative(templatesAbs, srcPath).split(sep).join("/");
|
|
134
202
|
// Surface gating (#4154): a route directory belonging to a surface the
|
|
135
203
|
// declared architecture does not carry is skipped outright — the route is
|
|
136
204
|
// ABSENT from the scaffold, not present-and-serving-a-placeholder.
|
|
137
205
|
if (isExcluded(rel, excluded))
|
|
138
206
|
continue;
|
|
139
|
-
|
|
207
|
+
// The destination path: `undefined` drops a packaging-only .gitkeep /
|
|
208
|
+
// .npmignore (#7366 D3); otherwise a packed .gitignore's name is
|
|
209
|
+
// restored. Everything below — the path-safety check, text
|
|
210
|
+
// classification, and marker substitution — runs on THIS path, never
|
|
211
|
+
// the source name.
|
|
212
|
+
const destRel = templateDestination(rel);
|
|
213
|
+
if (destRel === undefined)
|
|
214
|
+
continue;
|
|
215
|
+
const dstPath = join(targetAbs, destRel);
|
|
140
216
|
// Path-safety guard: dstPath MUST be within targetAbs. Use `path.sep`
|
|
141
217
|
// so the check holds on Windows (`\\`) as well as POSIX (`/`).
|
|
142
218
|
const resolvedDst = resolve(dstPath);
|
|
@@ -152,19 +228,9 @@ export function copyTemplate(opts) {
|
|
|
152
228
|
stack.push(srcPath);
|
|
153
229
|
continue;
|
|
154
230
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
writeFileSync(dstPath, transformed);
|
|
159
|
-
}
|
|
160
|
-
else if (isTextFile(rel)) {
|
|
161
|
-
const text = readFileSync(srcPath, "utf8");
|
|
162
|
-
writeFileSync(dstPath, substituteMarkers(text, opts.markers));
|
|
163
|
-
}
|
|
164
|
-
else {
|
|
165
|
-
// Binary or unknown — copy verbatim.
|
|
166
|
-
copyFileSync(srcPath, dstPath);
|
|
167
|
-
}
|
|
231
|
+
const rendered = renderTemplateFile(readFileSync(srcPath), destRel, opts.versions, opts.markers);
|
|
232
|
+
writeFileSync(dstPath, rendered);
|
|
233
|
+
opts.onWrite?.(destRel, rendered);
|
|
168
234
|
}
|
|
169
235
|
}
|
|
170
236
|
}
|
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;
|