@wildo-ai/saas-website 1.1.8 → 1.1.9
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/dist/esm/astro/assert-website-pages-have-sections.d.ts +28 -0
- package/dist/esm/astro/assert-website-pages-have-sections.d.ts.map +1 -0
- package/dist/esm/astro/assert-website-pages-have-sections.js +38 -0
- package/dist/esm/astro/assert-website-pages-have-sections.js.map +1 -0
- package/dist/esm/astro/label-pack-loader.d.ts +6 -0
- package/dist/esm/astro/label-pack-loader.d.ts.map +1 -1
- package/dist/esm/astro/label-pack-loader.js +10 -0
- package/dist/esm/astro/label-pack-loader.js.map +1 -1
- package/dist/esm/astro.d.ts +1 -0
- package/dist/esm/astro.d.ts.map +1 -1
- package/dist/esm/astro.js +1 -0
- package/dist/esm/astro.js.map +1 -1
- package/dist/esm/core/factories/define-website-section.d.ts +2 -0
- package/dist/esm/core/factories/define-website-section.d.ts.map +1 -1
- package/dist/esm/core/factories/define-website-section.js +2 -0
- package/dist/esm/core/factories/define-website-section.js.map +1 -1
- package/dist/esm/core/layouts/WebsitePageSections.d.ts +43 -0
- package/dist/esm/core/layouts/WebsitePageSections.d.ts.map +1 -0
- package/dist/esm/core/layouts/WebsitePageSections.js +13 -0
- package/dist/esm/core/layouts/WebsitePageSections.js.map +1 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.d.ts +2 -0
- package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.js +12 -3
- package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.js.map +1 -1
- package/dist/esm/schemas/sections/website-section-definition.shared.schemas.d.ts +9 -0
- package/dist/esm/schemas/sections/website-section-definition.shared.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/sections/website-section-definition.shared.schemas.js +11 -0
- package/dist/esm/schemas/sections/website-section-definition.shared.schemas.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/src/astro/__tests__/assert-website-pages-have-sections.test.ts +75 -0
- package/src/astro/assert-website-pages-have-sections.ts +41 -0
- package/src/astro/label-pack-loader.ts +10 -0
- package/src/astro.ts +1 -0
- package/src/core/__tests__/WebsitePageSections.test.tsx +57 -0
- package/src/core/factories/define-website-section.ts +4 -0
- package/src/core/layouts/WebsitePageSections.tsx +68 -0
- package/src/index.ts +1 -0
- package/src/schemas/__tests__/page-manifest.test.ts +4 -7
- package/src/schemas/manifests/website-page-manifest.shared.schemas.ts +12 -3
- package/src/schemas/sections/website-section-definition.shared.schemas.ts +11 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { WebsiteRootConfig } from '../schemas/manifests/website-root-config.shared.schemas';
|
|
2
|
+
/**
|
|
3
|
+
* The refs of every page that declares no section, in manifest order — the population
|
|
4
|
+
* {@link assertWebsitePagesHaveSections} refuses to build. Exported on its own so a report
|
|
5
|
+
* (`wildo website audit`) answers from the same predicate the build applies, without throwing.
|
|
6
|
+
*/
|
|
7
|
+
export declare function listWebsitePagesWithoutSections(rootConfig: Pick<WebsiteRootConfig, 'pageManifests'>): readonly string[];
|
|
8
|
+
/**
|
|
9
|
+
* Refuse to BUILD a website while any page has no section (#2205).
|
|
10
|
+
*
|
|
11
|
+
* A page manifest may declare an empty `sectionRefs` list: that is a page whose sections are still
|
|
12
|
+
* being authored, and the application-creation build lane creates exactly that state (the page task
|
|
13
|
+
* writes the empty list, then each section task appends itself). A dev server must be able to serve
|
|
14
|
+
* it, so the manifest schema accepts it.
|
|
15
|
+
*
|
|
16
|
+
* Building is where the consequence is. A built site is what a real reader receives, and a page
|
|
17
|
+
* with nothing between its header and footer is not unfinished there, it is wrong. So the refusal
|
|
18
|
+
* that used to sit in the manifest schema, where it crashed `astro dev` at module load, sits here.
|
|
19
|
+
*
|
|
20
|
+
* It runs from `assertLabelPacksValid`, the `astro:build:start` hook every website already wires,
|
|
21
|
+
* so no application has to edit its Astro config for the gate to arm. It is exported on its own for
|
|
22
|
+
* any other build entry (an audit command, a custom integration).
|
|
23
|
+
*
|
|
24
|
+
* Every empty page is named in ONE error rather than the first only, for the reason the label
|
|
25
|
+
* validator aggregates its locales: one build run should surface everything that blocks it.
|
|
26
|
+
*/
|
|
27
|
+
export declare function assertWebsitePagesHaveSections(rootConfig: Pick<WebsiteRootConfig, 'pageManifests'>): void;
|
|
28
|
+
//# sourceMappingURL=assert-website-pages-have-sections.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assert-website-pages-have-sections.d.ts","sourceRoot":"","sources":["../../../../src/astro/assert-website-pages-have-sections.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yDAAyD,CAAC;AAEjG;;;;GAIG;AACH,wBAAgB,+BAA+B,CAAC,UAAU,EAAE,IAAI,CAAC,iBAAiB,EAAE,eAAe,CAAC,GAAG,SAAS,MAAM,EAAE,CAIvH;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,8BAA8B,CAAC,UAAU,EAAE,IAAI,CAAC,iBAAiB,EAAE,eAAe,CAAC,GAAG,IAAI,CAQzG"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The refs of every page that declares no section, in manifest order — the population
|
|
3
|
+
* {@link assertWebsitePagesHaveSections} refuses to build. Exported on its own so a report
|
|
4
|
+
* (`wildo website audit`) answers from the same predicate the build applies, without throwing.
|
|
5
|
+
*/
|
|
6
|
+
export function listWebsitePagesWithoutSections(rootConfig) {
|
|
7
|
+
return rootConfig.pageManifests
|
|
8
|
+
.filter((manifest) => manifest.sectionRefs.length === 0)
|
|
9
|
+
.map((manifest) => String(manifest.ref));
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Refuse to BUILD a website while any page has no section (#2205).
|
|
13
|
+
*
|
|
14
|
+
* A page manifest may declare an empty `sectionRefs` list: that is a page whose sections are still
|
|
15
|
+
* being authored, and the application-creation build lane creates exactly that state (the page task
|
|
16
|
+
* writes the empty list, then each section task appends itself). A dev server must be able to serve
|
|
17
|
+
* it, so the manifest schema accepts it.
|
|
18
|
+
*
|
|
19
|
+
* Building is where the consequence is. A built site is what a real reader receives, and a page
|
|
20
|
+
* with nothing between its header and footer is not unfinished there, it is wrong. So the refusal
|
|
21
|
+
* that used to sit in the manifest schema, where it crashed `astro dev` at module load, sits here.
|
|
22
|
+
*
|
|
23
|
+
* It runs from `assertLabelPacksValid`, the `astro:build:start` hook every website already wires,
|
|
24
|
+
* so no application has to edit its Astro config for the gate to arm. It is exported on its own for
|
|
25
|
+
* any other build entry (an audit command, a custom integration).
|
|
26
|
+
*
|
|
27
|
+
* Every empty page is named in ONE error rather than the first only, for the reason the label
|
|
28
|
+
* validator aggregates its locales: one build run should surface everything that blocks it.
|
|
29
|
+
*/
|
|
30
|
+
export function assertWebsitePagesHaveSections(rootConfig) {
|
|
31
|
+
const emptyPageRefs = listWebsitePagesWithoutSections(rootConfig);
|
|
32
|
+
if (emptyPageRefs.length === 0)
|
|
33
|
+
return;
|
|
34
|
+
throw new Error(`[website-build] ${emptyPageRefs.length} page(s) declare no section and cannot be built: ${emptyPageRefs.map((ref) => `"${ref}"`).join(', ')}. `
|
|
35
|
+
+ 'A page with no section is a valid state while it is being authored (a dev server serves it), never one to publish. '
|
|
36
|
+
+ 'Add at least one section to each page\'s sections list, or remove the page from the root config\'s page manifests.');
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=assert-website-pages-have-sections.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assert-website-pages-have-sections.js","sourceRoot":"","sources":["../../../../src/astro/assert-website-pages-have-sections.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,UAAU,+BAA+B,CAAC,UAAoD;IAClG,OAAO,UAAU,CAAC,aAAa;SAC5B,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,CAAC;SACvD,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;AAC7C,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,8BAA8B,CAAC,UAAoD;IACjG,MAAM,aAAa,GAAG,+BAA+B,CAAC,UAAU,CAAC,CAAC;IAClE,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IACvC,MAAM,IAAI,KAAK,CACb,mBAAmB,aAAa,CAAC,MAAM,oDAAoD,aAAa,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;UAC5I,qHAAqH;UACrH,oHAAoH,CACzH,CAAC;AACJ,CAAC","sourcesContent":["import type { WebsiteRootConfig } from '../schemas/manifests/website-root-config.shared.schemas';\n\n/**\n * The refs of every page that declares no section, in manifest order — the population\n * {@link assertWebsitePagesHaveSections} refuses to build. Exported on its own so a report\n * (`wildo website audit`) answers from the same predicate the build applies, without throwing.\n */\nexport function listWebsitePagesWithoutSections(rootConfig: Pick<WebsiteRootConfig, 'pageManifests'>): readonly string[] {\n return rootConfig.pageManifests\n .filter((manifest) => manifest.sectionRefs.length === 0)\n .map((manifest) => String(manifest.ref));\n}\n\n/**\n * Refuse to BUILD a website while any page has no section (#2205).\n *\n * A page manifest may declare an empty `sectionRefs` list: that is a page whose sections are still\n * being authored, and the application-creation build lane creates exactly that state (the page task\n * writes the empty list, then each section task appends itself). A dev server must be able to serve\n * it, so the manifest schema accepts it.\n *\n * Building is where the consequence is. A built site is what a real reader receives, and a page\n * with nothing between its header and footer is not unfinished there, it is wrong. So the refusal\n * that used to sit in the manifest schema, where it crashed `astro dev` at module load, sits here.\n *\n * It runs from `assertLabelPacksValid`, the `astro:build:start` hook every website already wires,\n * so no application has to edit its Astro config for the gate to arm. It is exported on its own for\n * any other build entry (an audit command, a custom integration).\n *\n * Every empty page is named in ONE error rather than the first only, for the reason the label\n * validator aggregates its locales: one build run should surface everything that blocks it.\n */\nexport function assertWebsitePagesHaveSections(rootConfig: Pick<WebsiteRootConfig, 'pageManifests'>): void {\n const emptyPageRefs = listWebsitePagesWithoutSections(rootConfig);\n if (emptyPageRefs.length === 0) return;\n throw new Error(\n `[website-build] ${emptyPageRefs.length} page(s) declare no section and cannot be built: ${emptyPageRefs.map((ref) => `\"${ref}\"`).join(', ')}. `\n + 'A page with no section is a valid state while it is being authored (a dev server serves it), never one to publish. '\n + 'Add at least one section to each page\\'s sections list, or remove the page from the root config\\'s page manifests.',\n );\n}\n"]}
|
|
@@ -124,6 +124,12 @@ export declare function loadAndValidateAllLabelPacks(packsDir: string, siteConte
|
|
|
124
124
|
* `loadAndValidateAllLabelPacks` is what `wildo website audit` calls
|
|
125
125
|
* when it wants programmatic access to the per-locale results.
|
|
126
126
|
*
|
|
127
|
+
* **Empty pages**: it first refuses a page whose `sectionRefs` is empty
|
|
128
|
+
* (`assertWebsitePagesHaveSections`, #2205). That refusal used to live in
|
|
129
|
+
* the manifest schema, where it crashed `astro dev` too; this hook runs
|
|
130
|
+
* only at `astro:build:start`, which is where a page with nothing on it
|
|
131
|
+
* stops being unfinished and starts being wrong.
|
|
132
|
+
*
|
|
127
133
|
* **Key-origin annotation**: every `missing` key is tagged with the
|
|
128
134
|
* page (and, if section-scoped, the section) that declared it, so the
|
|
129
135
|
* author can grep + open the right `defineWebsiteSection` /
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"label-pack-loader.d.ts","sourceRoot":"","sources":["../../../../src/astro/label-pack-loader.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AAO9E,OAAO,EAEL,KAAK,6BAA6B,EAClC,KAAK,SAAS,EACd,KAAK,yBAAyB,EAC/B,MAAM,4CAA4C,CAAC;AAEpD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"label-pack-loader.d.ts","sourceRoot":"","sources":["../../../../src/astro/label-pack-loader.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AAO9E,OAAO,EAEL,KAAK,6BAA6B,EAClC,KAAK,SAAS,EACd,KAAK,yBAAyB,EAC/B,MAAM,4CAA4C,CAAC;AAEpD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAGjE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AAEH;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,GAAG,SAAS,CA4BpF;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,kBAAkB,GAC9B,GAAG,CAAC,iBAAiB,EAAE,SAAS,CAAC,CAMnC;AAED;;;GAGG;AACH,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,EAAE,yBAAyB,CAAC;CACnC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,kBAAkB,EAC/B,wBAAwB,EAAE,6BAA6B,GACtD,SAAS,yBAAyB,EAAE,CAmBtC;AAsCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,kBAAkB,EAC/B,wBAAwB,EAAE,6BAA6B,GACtD,IAAI,CAgEN;AA+ED,6DAA6D"}
|
|
@@ -3,6 +3,7 @@ import { resolve } from 'node:path';
|
|
|
3
3
|
import { WEBSITE_CONSENT_LABEL_KEYS } from '../schemas/label-keys/website-consent-label-keys.schemas.js';
|
|
4
4
|
import { WEBSITE_PROVIDER_COMPONENT_LABEL_KEYS } from '../schemas/label-keys/website-provider-component-label-keys.schemas.js';
|
|
5
5
|
import { validateLabelPackAgainstManifest, } from '../schemas/validators/label-pack.validator.js';
|
|
6
|
+
import { assertWebsitePagesHaveSections } from './assert-website-pages-have-sections.js';
|
|
6
7
|
/**
|
|
7
8
|
* @wildo_source:part:start saas.website.astro.label-loader facet:layer:astro facet:family:website
|
|
8
9
|
*
|
|
@@ -193,6 +194,12 @@ function composeEffectiveChromeLabelKeys(rootConfig) {
|
|
|
193
194
|
* `loadAndValidateAllLabelPacks` is what `wildo website audit` calls
|
|
194
195
|
* when it wants programmatic access to the per-locale results.
|
|
195
196
|
*
|
|
197
|
+
* **Empty pages**: it first refuses a page whose `sectionRefs` is empty
|
|
198
|
+
* (`assertWebsitePagesHaveSections`, #2205). That refusal used to live in
|
|
199
|
+
* the manifest schema, where it crashed `astro dev` too; this hook runs
|
|
200
|
+
* only at `astro:build:start`, which is where a page with nothing on it
|
|
201
|
+
* stops being unfinished and starts being wrong.
|
|
202
|
+
*
|
|
196
203
|
* **Key-origin annotation**: every `missing` key is tagged with the
|
|
197
204
|
* page (and, if section-scoped, the section) that declared it, so the
|
|
198
205
|
* author can grep + open the right `defineWebsiteSection` /
|
|
@@ -200,6 +207,9 @@ function composeEffectiveChromeLabelKeys(rootConfig) {
|
|
|
200
207
|
*/
|
|
201
208
|
export function assertLabelPacksValid(packsDir, siteContext, expectedKeysBySectionRef) {
|
|
202
209
|
const { rootConfig } = siteContext;
|
|
210
|
+
// The build-start hook every website wires, so the empty-page refusal arms with no config edit
|
|
211
|
+
// (#2205). First, because a page with no section has no label commitments to validate.
|
|
212
|
+
assertWebsitePagesHaveSections(rootConfig);
|
|
203
213
|
const validations = loadAndValidateAllLabelPacks(packsDir, siteContext, expectedKeysBySectionRef);
|
|
204
214
|
/**
|
|
205
215
|
* Build a key → origin map ONCE per assert call (cost is bounded by
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"label-pack-loader.js","sourceRoot":"","sources":["../../../../src/astro/label-pack-loader.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nimport type { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';\n\nimport type { WebsiteLabelKey } from '../schemas/label-keys/website-label-key.schemas';\nimport { WEBSITE_CONSENT_LABEL_KEYS } from '../schemas/label-keys/website-consent-label-keys.schemas';\nimport { WEBSITE_PROVIDER_COMPONENT_LABEL_KEYS } from '../schemas/label-keys/website-provider-component-label-keys.schemas';\nimport type { WebsitePageManifest } from '../schemas/manifests/website-page-manifest.shared.schemas';\nimport type { WebsiteRootConfig } from '../schemas/manifests/website-root-config.shared.schemas';\nimport {\n validateLabelPackAgainstManifest,\n type ExpectedLabelKeysBySectionRef,\n type LabelPack,\n type LabelPackValidationResult,\n} from '../schemas/validators/label-pack.validator';\n\nimport type { WebsiteSiteContext } from './website-site-context';\n\n/**\n * @wildo_source:part:start saas.website.astro.label-loader facet:layer:astro facet:family:website\n *\n * `label-pack-loader.ts` — synchronous, fs-backed loader for\n * marketing-site label packs. Consumed by:\n * - each page's `.astro` frontmatter, through `loadWebsitePageRuntime`,\n * which reads the active locale's pack during SSR / pre-render,\n * - `wildo website audit`, which validates every locale pack against\n * every manifest and section through `loadAndValidateAllLabelPacks`,\n * - any build-time hook that wants to fail fast on a missing or\n * malformed pack.\n *\n * **Convention** (matches the slice plan + the validator's contract):\n *\n * - One file per locale: `<packsDir>/<locale>.json`\n * - JSON object: flat key-value map, `Record<string, string>`\n * - Keys are fully-composed `WebsiteLabelKey`s (`website.<page>.<section>.<slot>`)\n * - Values are the localized strings\n *\n * **Why synchronous I/O**:\n * - The Astro frontmatter executes at build / SSR time in a Node\n * process (no event loop pressure to relieve), and the bridge\n * wants the pack resolved before it emits any HTML — async would\n * force the consumer to `await` inside an Astro frontmatter,\n * which is supported but adds a needless yield-point on a single\n * small JSON file.\n * - The validator is pure and synchronous; making the loader async\n * would add a coloring boundary for no benefit.\n *\n * **Failure modes — all surfaced as thrown errors with actionable\n * messages**:\n * - File missing — wrapped in a `[label-pack-loader] Could not read\n * pack at <path>` error so a typo in the dir / locale is obvious.\n * - JSON parse failure — error includes the file path and the\n * parser's own message.\n * - Top-level shape is not an object, or a value is not a string —\n * error names the offending key + observed type.\n *\n * **Why three exports** (loader, multi-loader, validator-runner):\n * - `loadLabelPack(packsDir, locale)` is the atomic primitive\n * (one file → one validated `LabelPack`). Used by the bridge at\n * request time.\n * - `loadAllLabelPacks(packsDir, siteContext)` is the build-time\n * batch — every locale pack at once. Used by the CLI auditor\n * and by the build-start hook.\n * - `validateAllLabelPacks(packsDir, siteContext, expectedKeysBySectionRef)`\n * is the highest-level entry point: load every pack, run the\n * validator on each, and either return the diagnostic results\n * or throw on the first hard error. The returned shape carries\n * per-locale diagnostics so the caller can format a CLI report.\n *\n * **`siteContext` parameter** — the consumer's composed\n * `WebsiteSiteContext` (see `website-site-context.ts`). The loader\n * reads `siteContext.websiteConfig.locales` for \"which locales does\n * the site ship?\" and `siteContext.rootConfig.{pageManifests,\n * chromeLabelKeys, skipToMainContentLabelKey}` for the manifest\n * registry / chrome-key set the validator runs against.\n */\n\n/**\n * Load and validate a single locale's label pack. Synchronous —\n * suitable for Astro frontmatter execution.\n *\n * @param packsDir Absolute or cwd-relative path to the directory\n * containing per-locale `<locale>.json` files.\n * @param locale The locale to load. Must be a member of\n * `WildoMarketingWebsiteConfig.locales`.\n * @returns A flat `LabelPack` (`Record<string, string>`) ready to\n * feed into `WebsiteRuntimeContextValue.labelPack`.\n * @throws When the file is missing, malformed JSON, not an object,\n * or contains a non-string value.\n */\nexport function loadLabelPack(packsDir: string, locale: AvailableLanguage): LabelPack {\n const filePath = resolve(packsDir, `${locale}.json`);\n\n let raw: string;\n try {\n raw = readFileSync(filePath, 'utf-8');\n } catch (cause) {\n throw new Error(\n `[label-pack-loader] Could not read label pack at ${filePath} ` +\n `(locale=\"${locale}\"). ` +\n `Make sure the directory exists and contains a \"${locale}.json\" file. ` +\n `Underlying error: ${(cause as Error).message}`,\n { cause },\n );\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (cause) {\n throw new Error(\n `[label-pack-loader] Failed to parse JSON in ${filePath} ` +\n `(locale=\"${locale}\"). Underlying parser error: ${(cause as Error).message}`,\n { cause },\n );\n }\n\n return assertLabelPackShape(parsed, filePath, locale);\n}\n\n/**\n * Batch loader — every locale declared on the site at once. The map\n * iteration order matches the declaration order of\n * `siteContext.websiteConfig.locales` so CLI reports come out in a\n * deterministic, authored sequence.\n */\nexport function loadAllLabelPacks(\n packsDir: string,\n siteContext: WebsiteSiteContext,\n): Map<AvailableLanguage, LabelPack> {\n const packs = new Map<AvailableLanguage, LabelPack>();\n for (const entry of siteContext.websiteConfig.locales) {\n packs.set(entry.locale, loadLabelPack(packsDir, entry.locale));\n }\n return packs;\n}\n\n/**\n * Per-locale validation result — the validator's flat output extended\n * with the locale tag so a multi-locale report can flatten + group.\n */\nexport interface LocaleLabelPackValidation {\n locale: AvailableLanguage;\n result: LabelPackValidationResult;\n}\n\n/**\n * Highest-level entry point: load every locale's pack and run the\n * label-pack validator against the manifest registry.\n *\n * **Return shape decision** — this function does NOT throw on\n * `missing` keys; it returns the per-locale results so the caller\n * (the build-start hook OR the CLI auditor OR a debugging\n * subprocess) can decide how to surface the failure (process exit\n * code, formatted CLI output, JSON report, …). A thin `throwIfAny…`\n * helper sits next to it for the common \"fail the build on first\n * missing key\" path.\n */\nexport function loadAndValidateAllLabelPacks(\n packsDir: string,\n siteContext: WebsiteSiteContext,\n expectedKeysBySectionRef: ExpectedLabelKeysBySectionRef,\n): readonly LocaleLabelPackValidation[] {\n const { rootConfig } = siteContext;\n const packs = loadAllLabelPacks(packsDir, siteContext);\n const manifests: readonly WebsitePageManifest[] = rootConfig.pageManifests;\n const effectiveChromeKeys = composeEffectiveChromeLabelKeys(rootConfig);\n\n const validations: LocaleLabelPackValidation[] = [];\n for (const [locale, pack] of packs) {\n validations.push({\n locale,\n result: validateLabelPackAgainstManifest(\n manifests,\n expectedKeysBySectionRef,\n pack,\n effectiveChromeKeys,\n ),\n });\n }\n return validations;\n}\n\n/**\n * Compose the chrome-label-key set the validator should treat as\n * \"expected\" — `WebsiteRootConfig.chromeLabelKeys` (consumer-authored)\n * UNION the framework-required `skipToMainContentLabelKey`.\n *\n * Centralized here because:\n * - Both `loadAndValidateAllLabelPacks` (used at build time) and\n * `buildKeyOriginMap` (used to annotate missing-key errors) need\n * the same composed list, and forking the union into two places\n * would be a drift hazard.\n * - The framework owns the skip-link key (it is rendered by\n * `WebsitePageLayout`, not the consumer's chrome), so the\n * consumer should NOT have to remember to also list it in\n * `chromeLabelKeys`. Auto-injection here keeps the contract\n * minimal at the consumer's call site (just declare the key in\n * `WebsiteRootConfig.skipToMainContentLabelKey` once + ship the\n * translation in every locale pack) while still surfacing the key\n * through the validator's \"missing\"/\"orphaned\" diagnostics.\n * - De-duplication is via `Set` semantics so a consumer who\n * defensively lists the key in both places (no-op but harmless)\n * does not produce a duplicate-key warning downstream.\n */\nfunction composeEffectiveChromeLabelKeys(\n rootConfig: WebsiteRootConfig,\n): readonly WebsiteLabelKey[] {\n const set = new Set<WebsiteLabelKey>(rootConfig.chromeLabelKeys);\n set.add(rootConfig.skipToMainContentLabelKey);\n // The visitor consent panel is framework-owned in the same way (#534): required in every pack, so a site\n // that turns on analytics can never ship it with an unlabelled way to refuse.\n for (const key of WEBSITE_CONSENT_LABEL_KEYS) set.add(key);\n // So is the provider-component slot's refusal wording (#1703): a site placing an address lookup must not\n // ship its consent-required or unavailable state as a raw key.\n for (const key of WEBSITE_PROVIDER_COMPONENT_LABEL_KEYS) set.add(key);\n return Array.from(set);\n}\n\n/**\n * Convenience wrapper for the build-start hook: validates every\n * declared locale's pack and throws a SINGLE aggregated error if any\n * locale has a hard failure (`missing` keys or `unmappedSectionRefs`).\n * Orphaned keys are logged via `console.warn` (soft warning per the\n * validator's contract) but do NOT throw.\n *\n * **Why aggregate (not first-fail)**: a multi-locale marketing site\n * with `en` and `fr` packs both missing keys would, under a\n * first-fail policy, force the author to fix `en`, re-run the build,\n * THEN see `fr`'s problem. Aggregating means one build run surfaces\n * every locale's hard errors together — material UX win for the\n * authoring loop. The aggregated message keeps each locale's failure\n * in its own block so the diagnostic stays scannable.\n *\n * **Why throw vs. return**: the build-start hook lives in\n * `astro.config.mjs` (or a `vite-plugin`-style integration) and\n * benefits from the shortest possible code path — a thrown error\n * naturally aborts `astro build` with a non-zero exit code and\n * surfaces the message in the build log. The richer\n * `loadAndValidateAllLabelPacks` is what `wildo website audit` calls\n * when it wants programmatic access to the per-locale results.\n *\n * **Key-origin annotation**: every `missing` key is tagged with the\n * page (and, if section-scoped, the section) that declared it, so the\n * author can grep + open the right `defineWebsiteSection` /\n * `defineWebsitePageManifest` call site without leaving the build log.\n */\nexport function assertLabelPacksValid(\n packsDir: string,\n siteContext: WebsiteSiteContext,\n expectedKeysBySectionRef: ExpectedLabelKeysBySectionRef,\n): void {\n const { rootConfig } = siteContext;\n const validations = loadAndValidateAllLabelPacks(\n packsDir,\n siteContext,\n expectedKeysBySectionRef,\n );\n\n /**\n * Build a key → origin map ONCE per assert call (cost is bounded by\n * the total number of declared keys across the whole site, which is\n * typically small for a marketing surface). Enriches the missing-key\n * error with the page/section that committed to each key — the\n * author can grep the ref to find the site authoring call site.\n */\n const keyOrigins = buildKeyOriginMap(\n rootConfig.pageManifests,\n expectedKeysBySectionRef,\n composeEffectiveChromeLabelKeys(rootConfig),\n );\n\n const localeErrorBlocks: string[] = [];\n\n for (const { locale, result } of validations) {\n const blocks: string[] = [];\n if (result.unmappedSectionRefs.length > 0) {\n blocks.push(\n ` unmappedSectionRefs (build-pipeline bug — sections referenced by a ` +\n `manifest but missing from the expected-keys collector): ` +\n `[${(result.unmappedSectionRefs as unknown as readonly string[]).join(', ')}]. ` +\n `Add every referenced section to the array passed to ` +\n `collectExpectedLabelKeysFromSections().`,\n );\n }\n if (result.missing.length > 0) {\n const annotated = (result.missing as unknown as readonly string[]).map(\n (key) => ` \"${key}\" ← ${keyOrigins.get(key) ?? 'unknown origin'}`,\n );\n blocks.push(\n ` missing ${result.missing.length} required key(s) in ${packsDir}/${locale}.json:\\n` +\n annotated.join('\\n'),\n );\n }\n if (blocks.length > 0) {\n localeErrorBlocks.push(`Locale \"${locale}\":\\n${blocks.join('\\n')}`);\n }\n if (result.orphaned.length > 0) {\n console.warn(\n `[label-pack-loader] Locale \"${locale}\": ${result.orphaned.length} ` +\n `orphaned key(s) in ${locale}.json (declared in pack but not consumed ` +\n `by any section or page manifest):\\n ${result.orphaned.join('\\n ')}`,\n );\n }\n }\n\n if (localeErrorBlocks.length > 0) {\n throw new Error(\n `[label-pack-loader] Label pack validation failed for ` +\n `${localeErrorBlocks.length} locale(s):\\n\\n${localeErrorBlocks.join('\\n\\n')}`,\n );\n }\n}\n\n/**\n * Build a `keyString -> \"page <pageRef> meta titleKey\" | \"section <sectionRef> in page <pageRef>\"`\n * lookup so the assert message can name the call site that committed\n * to each missing key. Pure and small (cost is O(totalKeys) per build\n * — bounded by the marketing surface).\n */\nfunction buildKeyOriginMap(\n manifests: readonly WebsitePageManifest[],\n expectedKeysBySectionRef: ExpectedLabelKeysBySectionRef,\n chromeLabelKeys: readonly WebsiteLabelKey[],\n): Map<string, string> {\n const origins = new Map<string, string>();\n for (const manifest of manifests) {\n const pageRefStr = manifest.ref as unknown as string;\n const titleKey = manifest.metaLabelKeys.titleKey as unknown as string;\n const descriptionKey = manifest.metaLabelKeys.descriptionKey as unknown as string;\n if (!origins.has(titleKey)) {\n origins.set(titleKey, `page \"${pageRefStr}\" metaLabelKeys.titleKey`);\n }\n if (!origins.has(descriptionKey)) {\n origins.set(descriptionKey, `page \"${pageRefStr}\" metaLabelKeys.descriptionKey`);\n }\n for (const sectionRef of manifest.sectionRefs) {\n const sectionRefStr = sectionRef as unknown as string;\n const sectionKeys = expectedKeysBySectionRef.get(sectionRef);\n if (sectionKeys === undefined) continue;\n for (const sectionKey of sectionKeys) {\n const keyStr = sectionKey as unknown as string;\n if (!origins.has(keyStr)) {\n origins.set(keyStr, `section \"${sectionRefStr}\" in page \"${pageRefStr}\"`);\n }\n }\n }\n }\n for (const chromeKey of chromeLabelKeys) {\n const keyStr = chromeKey as unknown as string;\n if (!origins.has(keyStr)) {\n origins.set(keyStr, `WebsiteRootConfig.chromeLabelKeys`);\n }\n }\n return origins;\n}\n\n/**\n * Defensive shape guard. Surface the offending key + observed type\n * so the consumer can find the malformed entry quickly. Pure\n * validation — no transformation; the returned value is the same\n * object reference passed in (cast to `LabelPack`) so the caller\n * does not pay an unnecessary copy.\n */\nfunction assertLabelPackShape(\n parsed: unknown,\n filePath: string,\n locale: AvailableLanguage,\n): LabelPack {\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw new Error(\n `[label-pack-loader] ${filePath} (locale=\"${locale}\") must contain a ` +\n `JSON object at the top level (got ${describeJson(parsed)}).`,\n );\n }\n for (const [key, value] of Object.entries(parsed)) {\n if (typeof value !== 'string') {\n throw new Error(\n `[label-pack-loader] ${filePath} (locale=\"${locale}\"): key \"${key}\" ` +\n `must map to a string (got ${describeJson(value)}).`,\n );\n }\n }\n return parsed as LabelPack;\n}\n\nfunction describeJson(value: unknown): string {\n if (value === null) return 'null';\n if (Array.isArray(value)) return 'array';\n return typeof value;\n}\n/** @wildo_source:part:end saas.website.astro.label-loader */\n"]}
|
|
1
|
+
{"version":3,"file":"label-pack-loader.js","sourceRoot":"","sources":["../../../../src/astro/label-pack-loader.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nimport type { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';\n\nimport type { WebsiteLabelKey } from '../schemas/label-keys/website-label-key.schemas';\nimport { WEBSITE_CONSENT_LABEL_KEYS } from '../schemas/label-keys/website-consent-label-keys.schemas';\nimport { WEBSITE_PROVIDER_COMPONENT_LABEL_KEYS } from '../schemas/label-keys/website-provider-component-label-keys.schemas';\nimport type { WebsitePageManifest } from '../schemas/manifests/website-page-manifest.shared.schemas';\nimport type { WebsiteRootConfig } from '../schemas/manifests/website-root-config.shared.schemas';\nimport {\n validateLabelPackAgainstManifest,\n type ExpectedLabelKeysBySectionRef,\n type LabelPack,\n type LabelPackValidationResult,\n} from '../schemas/validators/label-pack.validator';\n\nimport type { WebsiteSiteContext } from './website-site-context';\nimport { assertWebsitePagesHaveSections } from './assert-website-pages-have-sections';\n\n/**\n * @wildo_source:part:start saas.website.astro.label-loader facet:layer:astro facet:family:website\n *\n * `label-pack-loader.ts` — synchronous, fs-backed loader for\n * marketing-site label packs. Consumed by:\n * - each page's `.astro` frontmatter, through `loadWebsitePageRuntime`,\n * which reads the active locale's pack during SSR / pre-render,\n * - `wildo website audit`, which validates every locale pack against\n * every manifest and section through `loadAndValidateAllLabelPacks`,\n * - any build-time hook that wants to fail fast on a missing or\n * malformed pack.\n *\n * **Convention** (matches the slice plan + the validator's contract):\n *\n * - One file per locale: `<packsDir>/<locale>.json`\n * - JSON object: flat key-value map, `Record<string, string>`\n * - Keys are fully-composed `WebsiteLabelKey`s (`website.<page>.<section>.<slot>`)\n * - Values are the localized strings\n *\n * **Why synchronous I/O**:\n * - The Astro frontmatter executes at build / SSR time in a Node\n * process (no event loop pressure to relieve), and the bridge\n * wants the pack resolved before it emits any HTML — async would\n * force the consumer to `await` inside an Astro frontmatter,\n * which is supported but adds a needless yield-point on a single\n * small JSON file.\n * - The validator is pure and synchronous; making the loader async\n * would add a coloring boundary for no benefit.\n *\n * **Failure modes — all surfaced as thrown errors with actionable\n * messages**:\n * - File missing — wrapped in a `[label-pack-loader] Could not read\n * pack at <path>` error so a typo in the dir / locale is obvious.\n * - JSON parse failure — error includes the file path and the\n * parser's own message.\n * - Top-level shape is not an object, or a value is not a string —\n * error names the offending key + observed type.\n *\n * **Why three exports** (loader, multi-loader, validator-runner):\n * - `loadLabelPack(packsDir, locale)` is the atomic primitive\n * (one file → one validated `LabelPack`). Used by the bridge at\n * request time.\n * - `loadAllLabelPacks(packsDir, siteContext)` is the build-time\n * batch — every locale pack at once. Used by the CLI auditor\n * and by the build-start hook.\n * - `validateAllLabelPacks(packsDir, siteContext, expectedKeysBySectionRef)`\n * is the highest-level entry point: load every pack, run the\n * validator on each, and either return the diagnostic results\n * or throw on the first hard error. The returned shape carries\n * per-locale diagnostics so the caller can format a CLI report.\n *\n * **`siteContext` parameter** — the consumer's composed\n * `WebsiteSiteContext` (see `website-site-context.ts`). The loader\n * reads `siteContext.websiteConfig.locales` for \"which locales does\n * the site ship?\" and `siteContext.rootConfig.{pageManifests,\n * chromeLabelKeys, skipToMainContentLabelKey}` for the manifest\n * registry / chrome-key set the validator runs against.\n */\n\n/**\n * Load and validate a single locale's label pack. Synchronous —\n * suitable for Astro frontmatter execution.\n *\n * @param packsDir Absolute or cwd-relative path to the directory\n * containing per-locale `<locale>.json` files.\n * @param locale The locale to load. Must be a member of\n * `WildoMarketingWebsiteConfig.locales`.\n * @returns A flat `LabelPack` (`Record<string, string>`) ready to\n * feed into `WebsiteRuntimeContextValue.labelPack`.\n * @throws When the file is missing, malformed JSON, not an object,\n * or contains a non-string value.\n */\nexport function loadLabelPack(packsDir: string, locale: AvailableLanguage): LabelPack {\n const filePath = resolve(packsDir, `${locale}.json`);\n\n let raw: string;\n try {\n raw = readFileSync(filePath, 'utf-8');\n } catch (cause) {\n throw new Error(\n `[label-pack-loader] Could not read label pack at ${filePath} ` +\n `(locale=\"${locale}\"). ` +\n `Make sure the directory exists and contains a \"${locale}.json\" file. ` +\n `Underlying error: ${(cause as Error).message}`,\n { cause },\n );\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (cause) {\n throw new Error(\n `[label-pack-loader] Failed to parse JSON in ${filePath} ` +\n `(locale=\"${locale}\"). Underlying parser error: ${(cause as Error).message}`,\n { cause },\n );\n }\n\n return assertLabelPackShape(parsed, filePath, locale);\n}\n\n/**\n * Batch loader — every locale declared on the site at once. The map\n * iteration order matches the declaration order of\n * `siteContext.websiteConfig.locales` so CLI reports come out in a\n * deterministic, authored sequence.\n */\nexport function loadAllLabelPacks(\n packsDir: string,\n siteContext: WebsiteSiteContext,\n): Map<AvailableLanguage, LabelPack> {\n const packs = new Map<AvailableLanguage, LabelPack>();\n for (const entry of siteContext.websiteConfig.locales) {\n packs.set(entry.locale, loadLabelPack(packsDir, entry.locale));\n }\n return packs;\n}\n\n/**\n * Per-locale validation result — the validator's flat output extended\n * with the locale tag so a multi-locale report can flatten + group.\n */\nexport interface LocaleLabelPackValidation {\n locale: AvailableLanguage;\n result: LabelPackValidationResult;\n}\n\n/**\n * Highest-level entry point: load every locale's pack and run the\n * label-pack validator against the manifest registry.\n *\n * **Return shape decision** — this function does NOT throw on\n * `missing` keys; it returns the per-locale results so the caller\n * (the build-start hook OR the CLI auditor OR a debugging\n * subprocess) can decide how to surface the failure (process exit\n * code, formatted CLI output, JSON report, …). A thin `throwIfAny…`\n * helper sits next to it for the common \"fail the build on first\n * missing key\" path.\n */\nexport function loadAndValidateAllLabelPacks(\n packsDir: string,\n siteContext: WebsiteSiteContext,\n expectedKeysBySectionRef: ExpectedLabelKeysBySectionRef,\n): readonly LocaleLabelPackValidation[] {\n const { rootConfig } = siteContext;\n const packs = loadAllLabelPacks(packsDir, siteContext);\n const manifests: readonly WebsitePageManifest[] = rootConfig.pageManifests;\n const effectiveChromeKeys = composeEffectiveChromeLabelKeys(rootConfig);\n\n const validations: LocaleLabelPackValidation[] = [];\n for (const [locale, pack] of packs) {\n validations.push({\n locale,\n result: validateLabelPackAgainstManifest(\n manifests,\n expectedKeysBySectionRef,\n pack,\n effectiveChromeKeys,\n ),\n });\n }\n return validations;\n}\n\n/**\n * Compose the chrome-label-key set the validator should treat as\n * \"expected\" — `WebsiteRootConfig.chromeLabelKeys` (consumer-authored)\n * UNION the framework-required `skipToMainContentLabelKey`.\n *\n * Centralized here because:\n * - Both `loadAndValidateAllLabelPacks` (used at build time) and\n * `buildKeyOriginMap` (used to annotate missing-key errors) need\n * the same composed list, and forking the union into two places\n * would be a drift hazard.\n * - The framework owns the skip-link key (it is rendered by\n * `WebsitePageLayout`, not the consumer's chrome), so the\n * consumer should NOT have to remember to also list it in\n * `chromeLabelKeys`. Auto-injection here keeps the contract\n * minimal at the consumer's call site (just declare the key in\n * `WebsiteRootConfig.skipToMainContentLabelKey` once + ship the\n * translation in every locale pack) while still surfacing the key\n * through the validator's \"missing\"/\"orphaned\" diagnostics.\n * - De-duplication is via `Set` semantics so a consumer who\n * defensively lists the key in both places (no-op but harmless)\n * does not produce a duplicate-key warning downstream.\n */\nfunction composeEffectiveChromeLabelKeys(\n rootConfig: WebsiteRootConfig,\n): readonly WebsiteLabelKey[] {\n const set = new Set<WebsiteLabelKey>(rootConfig.chromeLabelKeys);\n set.add(rootConfig.skipToMainContentLabelKey);\n // The visitor consent panel is framework-owned in the same way (#534): required in every pack, so a site\n // that turns on analytics can never ship it with an unlabelled way to refuse.\n for (const key of WEBSITE_CONSENT_LABEL_KEYS) set.add(key);\n // So is the provider-component slot's refusal wording (#1703): a site placing an address lookup must not\n // ship its consent-required or unavailable state as a raw key.\n for (const key of WEBSITE_PROVIDER_COMPONENT_LABEL_KEYS) set.add(key);\n return Array.from(set);\n}\n\n/**\n * Convenience wrapper for the build-start hook: validates every\n * declared locale's pack and throws a SINGLE aggregated error if any\n * locale has a hard failure (`missing` keys or `unmappedSectionRefs`).\n * Orphaned keys are logged via `console.warn` (soft warning per the\n * validator's contract) but do NOT throw.\n *\n * **Why aggregate (not first-fail)**: a multi-locale marketing site\n * with `en` and `fr` packs both missing keys would, under a\n * first-fail policy, force the author to fix `en`, re-run the build,\n * THEN see `fr`'s problem. Aggregating means one build run surfaces\n * every locale's hard errors together — material UX win for the\n * authoring loop. The aggregated message keeps each locale's failure\n * in its own block so the diagnostic stays scannable.\n *\n * **Why throw vs. return**: the build-start hook lives in\n * `astro.config.mjs` (or a `vite-plugin`-style integration) and\n * benefits from the shortest possible code path — a thrown error\n * naturally aborts `astro build` with a non-zero exit code and\n * surfaces the message in the build log. The richer\n * `loadAndValidateAllLabelPacks` is what `wildo website audit` calls\n * when it wants programmatic access to the per-locale results.\n *\n * **Empty pages**: it first refuses a page whose `sectionRefs` is empty\n * (`assertWebsitePagesHaveSections`, #2205). That refusal used to live in\n * the manifest schema, where it crashed `astro dev` too; this hook runs\n * only at `astro:build:start`, which is where a page with nothing on it\n * stops being unfinished and starts being wrong.\n *\n * **Key-origin annotation**: every `missing` key is tagged with the\n * page (and, if section-scoped, the section) that declared it, so the\n * author can grep + open the right `defineWebsiteSection` /\n * `defineWebsitePageManifest` call site without leaving the build log.\n */\nexport function assertLabelPacksValid(\n packsDir: string,\n siteContext: WebsiteSiteContext,\n expectedKeysBySectionRef: ExpectedLabelKeysBySectionRef,\n): void {\n const { rootConfig } = siteContext;\n // The build-start hook every website wires, so the empty-page refusal arms with no config edit\n // (#2205). First, because a page with no section has no label commitments to validate.\n assertWebsitePagesHaveSections(rootConfig);\n const validations = loadAndValidateAllLabelPacks(\n packsDir,\n siteContext,\n expectedKeysBySectionRef,\n );\n\n /**\n * Build a key → origin map ONCE per assert call (cost is bounded by\n * the total number of declared keys across the whole site, which is\n * typically small for a marketing surface). Enriches the missing-key\n * error with the page/section that committed to each key — the\n * author can grep the ref to find the site authoring call site.\n */\n const keyOrigins = buildKeyOriginMap(\n rootConfig.pageManifests,\n expectedKeysBySectionRef,\n composeEffectiveChromeLabelKeys(rootConfig),\n );\n\n const localeErrorBlocks: string[] = [];\n\n for (const { locale, result } of validations) {\n const blocks: string[] = [];\n if (result.unmappedSectionRefs.length > 0) {\n blocks.push(\n ` unmappedSectionRefs (build-pipeline bug — sections referenced by a ` +\n `manifest but missing from the expected-keys collector): ` +\n `[${(result.unmappedSectionRefs as unknown as readonly string[]).join(', ')}]. ` +\n `Add every referenced section to the array passed to ` +\n `collectExpectedLabelKeysFromSections().`,\n );\n }\n if (result.missing.length > 0) {\n const annotated = (result.missing as unknown as readonly string[]).map(\n (key) => ` \"${key}\" ← ${keyOrigins.get(key) ?? 'unknown origin'}`,\n );\n blocks.push(\n ` missing ${result.missing.length} required key(s) in ${packsDir}/${locale}.json:\\n` +\n annotated.join('\\n'),\n );\n }\n if (blocks.length > 0) {\n localeErrorBlocks.push(`Locale \"${locale}\":\\n${blocks.join('\\n')}`);\n }\n if (result.orphaned.length > 0) {\n console.warn(\n `[label-pack-loader] Locale \"${locale}\": ${result.orphaned.length} ` +\n `orphaned key(s) in ${locale}.json (declared in pack but not consumed ` +\n `by any section or page manifest):\\n ${result.orphaned.join('\\n ')}`,\n );\n }\n }\n\n if (localeErrorBlocks.length > 0) {\n throw new Error(\n `[label-pack-loader] Label pack validation failed for ` +\n `${localeErrorBlocks.length} locale(s):\\n\\n${localeErrorBlocks.join('\\n\\n')}`,\n );\n }\n}\n\n/**\n * Build a `keyString -> \"page <pageRef> meta titleKey\" | \"section <sectionRef> in page <pageRef>\"`\n * lookup so the assert message can name the call site that committed\n * to each missing key. Pure and small (cost is O(totalKeys) per build\n * — bounded by the marketing surface).\n */\nfunction buildKeyOriginMap(\n manifests: readonly WebsitePageManifest[],\n expectedKeysBySectionRef: ExpectedLabelKeysBySectionRef,\n chromeLabelKeys: readonly WebsiteLabelKey[],\n): Map<string, string> {\n const origins = new Map<string, string>();\n for (const manifest of manifests) {\n const pageRefStr = manifest.ref as unknown as string;\n const titleKey = manifest.metaLabelKeys.titleKey as unknown as string;\n const descriptionKey = manifest.metaLabelKeys.descriptionKey as unknown as string;\n if (!origins.has(titleKey)) {\n origins.set(titleKey, `page \"${pageRefStr}\" metaLabelKeys.titleKey`);\n }\n if (!origins.has(descriptionKey)) {\n origins.set(descriptionKey, `page \"${pageRefStr}\" metaLabelKeys.descriptionKey`);\n }\n for (const sectionRef of manifest.sectionRefs) {\n const sectionRefStr = sectionRef as unknown as string;\n const sectionKeys = expectedKeysBySectionRef.get(sectionRef);\n if (sectionKeys === undefined) continue;\n for (const sectionKey of sectionKeys) {\n const keyStr = sectionKey as unknown as string;\n if (!origins.has(keyStr)) {\n origins.set(keyStr, `section \"${sectionRefStr}\" in page \"${pageRefStr}\"`);\n }\n }\n }\n }\n for (const chromeKey of chromeLabelKeys) {\n const keyStr = chromeKey as unknown as string;\n if (!origins.has(keyStr)) {\n origins.set(keyStr, `WebsiteRootConfig.chromeLabelKeys`);\n }\n }\n return origins;\n}\n\n/**\n * Defensive shape guard. Surface the offending key + observed type\n * so the consumer can find the malformed entry quickly. Pure\n * validation — no transformation; the returned value is the same\n * object reference passed in (cast to `LabelPack`) so the caller\n * does not pay an unnecessary copy.\n */\nfunction assertLabelPackShape(\n parsed: unknown,\n filePath: string,\n locale: AvailableLanguage,\n): LabelPack {\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw new Error(\n `[label-pack-loader] ${filePath} (locale=\"${locale}\") must contain a ` +\n `JSON object at the top level (got ${describeJson(parsed)}).`,\n );\n }\n for (const [key, value] of Object.entries(parsed)) {\n if (typeof value !== 'string') {\n throw new Error(\n `[label-pack-loader] ${filePath} (locale=\"${locale}\"): key \"${key}\" ` +\n `must map to a string (got ${describeJson(value)}).`,\n );\n }\n }\n return parsed as LabelPack;\n}\n\nfunction describeJson(value: unknown): string {\n if (value === null) return 'null';\n if (Array.isArray(value)) return 'array';\n return typeof value;\n}\n/** @wildo_source:part:end saas.website.astro.label-loader */\n"]}
|
package/dist/esm/astro.d.ts
CHANGED
|
@@ -69,6 +69,7 @@ export * from './astro/website-site-context';
|
|
|
69
69
|
export * from './astro/i18n-routing.helper';
|
|
70
70
|
export * from './astro/collect-expected-label-keys';
|
|
71
71
|
export * from './astro/label-pack-loader';
|
|
72
|
+
export * from './astro/assert-website-pages-have-sections';
|
|
72
73
|
/**
|
|
73
74
|
* `pick-labels-for-page` is INTENTIONALLY NOT re-exported here.
|
|
74
75
|
* It is an internal helper consumed only by `loadWebsitePageRuntime`
|
package/dist/esm/astro.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"astro.d.ts","sourceRoot":"","sources":["../../../src/astro.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;AAEH,cAAc,8BAA8B,CAAC;AAC7C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qCAAqC,CAAC;AACpD,cAAc,2BAA2B,CAAC;AAC1C;;;;;;;;;;;GAWG;AACH,cAAc,qCAAqC,CAAC;AACpD,cAAc,oCAAoC,CAAC;AACnD,cAAc,kCAAkC,CAAC;AACjD;;;;;;;;GAQG;AACH,cAAc,iCAAiC,CAAC;AAChD;;;;;;;;GAQG;AACH,cAAc,yBAAyB,CAAC;AACxC;;;;;;;;;;;;;;;;;;GAkBG;AACH,cAAc,0BAA0B,CAAC;AACzC;;;;;;;;;;GAUG;AACH,cAAc,kCAAkC,CAAC;AACjD,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC;;;;;;;;GAQG;AACH,cAAc,kEAAkE,CAAC"}
|
|
1
|
+
{"version":3,"file":"astro.d.ts","sourceRoot":"","sources":["../../../src/astro.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;AAEH,cAAc,8BAA8B,CAAC;AAC7C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qCAAqC,CAAC;AACpD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4CAA4C,CAAC;AAC3D;;;;;;;;;;;GAWG;AACH,cAAc,qCAAqC,CAAC;AACpD,cAAc,oCAAoC,CAAC;AACnD,cAAc,kCAAkC,CAAC;AACjD;;;;;;;;GAQG;AACH,cAAc,iCAAiC,CAAC;AAChD;;;;;;;;GAQG;AACH,cAAc,yBAAyB,CAAC;AACxC;;;;;;;;;;;;;;;;;;GAkBG;AACH,cAAc,0BAA0B,CAAC;AACzC;;;;;;;;;;GAUG;AACH,cAAc,kCAAkC,CAAC;AACjD,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC;;;;;;;;GAQG;AACH,cAAc,kEAAkE,CAAC"}
|
package/dist/esm/astro.js
CHANGED
|
@@ -69,6 +69,7 @@ export * from './astro/website-site-context.js';
|
|
|
69
69
|
export * from './astro/i18n-routing.helper.js';
|
|
70
70
|
export * from './astro/collect-expected-label-keys.js';
|
|
71
71
|
export * from './astro/label-pack-loader.js';
|
|
72
|
+
export * from './astro/assert-website-pages-have-sections.js';
|
|
72
73
|
/**
|
|
73
74
|
* `pick-labels-for-page` is INTENTIONALLY NOT re-exported here.
|
|
74
75
|
* It is an internal helper consumed only by `loadWebsitePageRuntime`
|
package/dist/esm/astro.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"astro.js","sourceRoot":"","sources":["../../../src/astro.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;AAEH,cAAc,8BAA8B,CAAC;AAC7C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qCAAqC,CAAC;AACpD,cAAc,2BAA2B,CAAC;AAC1C;;;;;;;;;;;GAWG;AACH,cAAc,qCAAqC,CAAC;AACpD,cAAc,oCAAoC,CAAC;AACnD,cAAc,kCAAkC,CAAC;AACjD;;;;;;;;GAQG;AACH,cAAc,iCAAiC,CAAC;AAChD;;;;;;;;GAQG;AACH,cAAc,yBAAyB,CAAC;AACxC;;;;;;;;;;;;;;;;;;GAkBG;AACH,cAAc,0BAA0B,CAAC;AACzC;;;;;;;;;;GAUG;AACH,cAAc,kCAAkC,CAAC;AACjD,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC;;;;;;;;GAQG;AACH,cAAc,kEAAkE,CAAC","sourcesContent":["/**\n * @wildo-package @wildo-ai/saas-website (astro adapter SSR/frontmatter entry)\n *\n * Astro **frontmatter / SSR** sub-entry. Re-exports the TypeScript\n * helpers a consumer's per-page `.astro` files consume INSIDE\n * frontmatter to render a marketing-site page (locale resolution,\n * label-pack loading, `<head>` HTML rendering, design-token CSS\n * rendering, i18n routing config, expected-label-key collection).\n *\n * **The `createWebsitePageBridgeIsland` factory lives in a SEPARATE\n * subpath — `@wildo-ai/saas-website/astro-island`** — and is NOT\n * re-exported here. The split exists because the consumer's per-page\n * `*.bridge.tsx` module is bundled into BOTH the SSR graph (via the\n * `.astro` page that statically imports it) AND the client graph\n * (Astro's hydration pipeline). Re-exporting the bridge factory\n * alongside the Node-using helpers here would force Vite to resolve\n * `node:fs` / `node:path` for the client bundle even though\n * tree-shaking would later drop them — resolution happens BEFORE\n * tree-shaking, so the build hard-fails with\n * `\"resolve\" is not exported by \"__vite-browser-external\"`. The\n * dedicated `astro-island` subpath keeps the client import graph\n * 100% bundle-clean.\n *\n * **No `.astro` shell shipped.** The Phase-5 redesign deliberately\n * removed the previously-planned `WebsitePageBridge.astro` shell\n * because:\n * 1. A `.astro` file cannot be re-exported from a TS subpath barrel\n * and `tsc` does not copy non-TS files to `dist/`, so the shell\n * was unreachable from `@wildo-ai/saas-website/astro`.\n * 2. The shell would have had to pass `pageComponent` /\n * `headerComponent` / `footerComponent` (`ComponentType` refs)\n * as React-island props — Astro's `devalue`-style island prop\n * serialization cannot transport function values, so hydration\n * would have mismatched on first interaction.\n * Both failure modes are sidestepped by giving consumers two\n * primitive-string-only TS helpers (`renderWebsitePageHeadHtml`,\n * `renderWebsiteDesignTokensCss`) for the Astro-side `<head>` work\n * and a closure-based island factory (`createWebsitePageBridgeIsland`,\n * exported from `@wildo-ai/saas-website/astro-island`) for the\n * React-island side. The consumer authors a thin per-page `.astro`\n * file that statically imports the per-page bridge island — Astro's\n * compiler then resolves the `client:*` directive against a static\n * module export, which is the only pattern that makes Astro's\n * hydration bundling work correctly. See `bridge-runtime.tsx`'s\n * file-level JSDoc for the full rationale.\n *\n * @wildo-boundary\n * This entry is on the SSR / Astro-build-time side of the bundle\n * boundary (D12 second loosening). It MAY import:\n * - `node:fs`, `node:path` (label-pack-loader, runtime-helper)\n * It MUST NOT import:\n * - the bridge-runtime module — that module ships into the\n * client island bundle and is exposed via the\n * `@wildo-ai/saas-website/astro-island` subpath instead.\n * - the Astro runtime (`astro` package, `astro:*` virtual modules)\n * — those live exclusively inside `.astro` files. Adding\n * `astro` here would make the engine package unimportable\n * from any context that does not boot the Astro compiler\n * (Vitest tests, the `wildo website audit` CLI, companion\n * subprocesses).\n *\n * Enforced by `engine/saas-website/src/__tests__/bundle-isolation.test.ts`\n * which walks `astro.ts` as one of `ENTRY_FILES` AND runs a targeted\n * negative test that `node:fs` / `node:path` are reachable from\n * `astro.ts` ONLY — never from `index.ts`, `companion-exports.ts`,\n * or `astro-island.ts`.\n */\n\nexport * from './astro/website-site-context';\nexport * from './astro/i18n-routing.helper';\nexport * from './astro/collect-expected-label-keys';\nexport * from './astro/label-pack-loader';\n/**\n * `pick-labels-for-page` is INTENTIONALLY NOT re-exported here.\n * It is an internal helper consumed only by `loadWebsitePageRuntime`\n * (which IS exported via the runtime helper below). Exposing it as a\n * public sub-entry surface would (a) invite consumers to call it\n * directly from `.astro` frontmatter — which they should never do\n * (the runtime helper already wires the slicing transparently), and\n * (b) lock the engine into an extra public API the audit step would\n * have to keep stable. Tests import the helper via its relative\n * path, which keeps the behavior covered without leaking the surface.\n * (Phase 6 audit fix M-B.)\n */\nexport * from './astro/website-page-runtime.helper';\nexport * from './astro/website-page-head.renderer';\nexport * from './astro/structured-data.renderer';\n/**\n * Phase 7 — sibling head renderer for blog posts. Reads from a\n * validated `BlogPostFrontmatter` instead of the page manifest's\n * `metaLabelKeys`; emits `og:type=article` plus the\n * `article:published_time` / `article:author` / `article:tag` lines\n * the OG \"article\" vertical requires. See the renderer file's\n * preamble for why this is a sibling rather than a flag on\n * `renderWebsitePageHeadHtml`.\n */\nexport * from './astro/blog-post-head.renderer';\n/**\n * Phase 8 close-out — `renderRobotsTxt(siteContext)` pure string\n * builder for the marketing site's `robots.txt` body. Sibling of\n * `renderWebsitePageHeadHtml` (same Astro-frontmatter consumption\n * shape: pure TypeScript, no Astro runtime dependency, vitest-\n * friendly). See `engine/saas-website/src/astro/robots-renderer.ts`\n * for the field-by-field rationale and\n * `.cursor/plans/saas-website-seo-sitemap.md` for the slice context.\n */\nexport * from './astro/robots-renderer';\n/**\n * Phase 8 close-out — `enumerateMarketingPageSitemapUrls(siteContext)`\n * pure helper that returns every (page-manifest × locale) absolute\n * URL the marketing site's sitemap MUST advertise, paired with its\n * hreflang alternates. Two consumption surfaces:\n *\n * 1. Vitest enforcement — a regression that drops a manifest-\n * registered route fails fast in unit tests, well before\n * `astro build` would surface it.\n * 2. `@astrojs/sitemap` `customPages` integration — when the\n * consumer needs to surface routes Astro's file-based router\n * cannot auto-discover (data-driven landing variants, etc.),\n * this helper computes the canonical URL list to spread into\n * the plugin config.\n *\n * See `engine/saas-website/src/astro/sitemap-coverage.ts` and\n * `.cursor/plans/saas-website-seo-sitemap.md` for the full\n * rationale + scope boundary.\n */\nexport * from './astro/sitemap-coverage';\n/**\n * AEO — `renderPageStructuredData(opts)` pure string builder that\n * collects every section's `getStructuredData()` (when present),\n * dedupes by `@id`, and emits one\n * `<script type=\"application/ld+json\">` block per surviving payload.\n * Sibling of `renderWebsitePageHeadHtml` (same pure-TS, vitest-\n * friendly contract). See\n * `engine/saas-website/src/astro/structured-data.renderer.ts`\n * for the safety + dedupe semantics and\n * `.cursor/plans/saas-website-aeo-geo.md` for the slice context.\n */\nexport * from './astro/structured-data.renderer';\nexport * from './astro/headers-renderer';\nexport * from './astro/llms-txt-server';\n/**\n * AEO — runtime payload type (`WebsiteStructuredDataPayload`) and\n * convenience return-shape union (`WebsiteStructuredDataReturn`)\n * the section author imports to type their `getStructuredData()`.\n * Defined locally in `saas-website` (not in `saas-specifications`)\n * because the runtime engine cannot depend on the spec layer; the\n * companion catalog at `_catalogs/schema-org/` describes the same\n * vocabulary at the COMPANION layer.\n */\nexport * from './schemas/structured-data/website-structured-data.shared.schemas';\n"]}
|
|
1
|
+
{"version":3,"file":"astro.js","sourceRoot":"","sources":["../../../src/astro.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;AAEH,cAAc,8BAA8B,CAAC;AAC7C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qCAAqC,CAAC;AACpD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4CAA4C,CAAC;AAC3D;;;;;;;;;;;GAWG;AACH,cAAc,qCAAqC,CAAC;AACpD,cAAc,oCAAoC,CAAC;AACnD,cAAc,kCAAkC,CAAC;AACjD;;;;;;;;GAQG;AACH,cAAc,iCAAiC,CAAC;AAChD;;;;;;;;GAQG;AACH,cAAc,yBAAyB,CAAC;AACxC;;;;;;;;;;;;;;;;;;GAkBG;AACH,cAAc,0BAA0B,CAAC;AACzC;;;;;;;;;;GAUG;AACH,cAAc,kCAAkC,CAAC;AACjD,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC;;;;;;;;GAQG;AACH,cAAc,kEAAkE,CAAC","sourcesContent":["/**\n * @wildo-package @wildo-ai/saas-website (astro adapter SSR/frontmatter entry)\n *\n * Astro **frontmatter / SSR** sub-entry. Re-exports the TypeScript\n * helpers a consumer's per-page `.astro` files consume INSIDE\n * frontmatter to render a marketing-site page (locale resolution,\n * label-pack loading, `<head>` HTML rendering, design-token CSS\n * rendering, i18n routing config, expected-label-key collection).\n *\n * **The `createWebsitePageBridgeIsland` factory lives in a SEPARATE\n * subpath — `@wildo-ai/saas-website/astro-island`** — and is NOT\n * re-exported here. The split exists because the consumer's per-page\n * `*.bridge.tsx` module is bundled into BOTH the SSR graph (via the\n * `.astro` page that statically imports it) AND the client graph\n * (Astro's hydration pipeline). Re-exporting the bridge factory\n * alongside the Node-using helpers here would force Vite to resolve\n * `node:fs` / `node:path` for the client bundle even though\n * tree-shaking would later drop them — resolution happens BEFORE\n * tree-shaking, so the build hard-fails with\n * `\"resolve\" is not exported by \"__vite-browser-external\"`. The\n * dedicated `astro-island` subpath keeps the client import graph\n * 100% bundle-clean.\n *\n * **No `.astro` shell shipped.** The Phase-5 redesign deliberately\n * removed the previously-planned `WebsitePageBridge.astro` shell\n * because:\n * 1. A `.astro` file cannot be re-exported from a TS subpath barrel\n * and `tsc` does not copy non-TS files to `dist/`, so the shell\n * was unreachable from `@wildo-ai/saas-website/astro`.\n * 2. The shell would have had to pass `pageComponent` /\n * `headerComponent` / `footerComponent` (`ComponentType` refs)\n * as React-island props — Astro's `devalue`-style island prop\n * serialization cannot transport function values, so hydration\n * would have mismatched on first interaction.\n * Both failure modes are sidestepped by giving consumers two\n * primitive-string-only TS helpers (`renderWebsitePageHeadHtml`,\n * `renderWebsiteDesignTokensCss`) for the Astro-side `<head>` work\n * and a closure-based island factory (`createWebsitePageBridgeIsland`,\n * exported from `@wildo-ai/saas-website/astro-island`) for the\n * React-island side. The consumer authors a thin per-page `.astro`\n * file that statically imports the per-page bridge island — Astro's\n * compiler then resolves the `client:*` directive against a static\n * module export, which is the only pattern that makes Astro's\n * hydration bundling work correctly. See `bridge-runtime.tsx`'s\n * file-level JSDoc for the full rationale.\n *\n * @wildo-boundary\n * This entry is on the SSR / Astro-build-time side of the bundle\n * boundary (D12 second loosening). It MAY import:\n * - `node:fs`, `node:path` (label-pack-loader, runtime-helper)\n * It MUST NOT import:\n * - the bridge-runtime module — that module ships into the\n * client island bundle and is exposed via the\n * `@wildo-ai/saas-website/astro-island` subpath instead.\n * - the Astro runtime (`astro` package, `astro:*` virtual modules)\n * — those live exclusively inside `.astro` files. Adding\n * `astro` here would make the engine package unimportable\n * from any context that does not boot the Astro compiler\n * (Vitest tests, the `wildo website audit` CLI, companion\n * subprocesses).\n *\n * Enforced by `engine/saas-website/src/__tests__/bundle-isolation.test.ts`\n * which walks `astro.ts` as one of `ENTRY_FILES` AND runs a targeted\n * negative test that `node:fs` / `node:path` are reachable from\n * `astro.ts` ONLY — never from `index.ts`, `companion-exports.ts`,\n * or `astro-island.ts`.\n */\n\nexport * from './astro/website-site-context';\nexport * from './astro/i18n-routing.helper';\nexport * from './astro/collect-expected-label-keys';\nexport * from './astro/label-pack-loader';\nexport * from './astro/assert-website-pages-have-sections';\n/**\n * `pick-labels-for-page` is INTENTIONALLY NOT re-exported here.\n * It is an internal helper consumed only by `loadWebsitePageRuntime`\n * (which IS exported via the runtime helper below). Exposing it as a\n * public sub-entry surface would (a) invite consumers to call it\n * directly from `.astro` frontmatter — which they should never do\n * (the runtime helper already wires the slicing transparently), and\n * (b) lock the engine into an extra public API the audit step would\n * have to keep stable. Tests import the helper via its relative\n * path, which keeps the behavior covered without leaking the surface.\n * (Phase 6 audit fix M-B.)\n */\nexport * from './astro/website-page-runtime.helper';\nexport * from './astro/website-page-head.renderer';\nexport * from './astro/structured-data.renderer';\n/**\n * Phase 7 — sibling head renderer for blog posts. Reads from a\n * validated `BlogPostFrontmatter` instead of the page manifest's\n * `metaLabelKeys`; emits `og:type=article` plus the\n * `article:published_time` / `article:author` / `article:tag` lines\n * the OG \"article\" vertical requires. See the renderer file's\n * preamble for why this is a sibling rather than a flag on\n * `renderWebsitePageHeadHtml`.\n */\nexport * from './astro/blog-post-head.renderer';\n/**\n * Phase 8 close-out — `renderRobotsTxt(siteContext)` pure string\n * builder for the marketing site's `robots.txt` body. Sibling of\n * `renderWebsitePageHeadHtml` (same Astro-frontmatter consumption\n * shape: pure TypeScript, no Astro runtime dependency, vitest-\n * friendly). See `engine/saas-website/src/astro/robots-renderer.ts`\n * for the field-by-field rationale and\n * `.cursor/plans/saas-website-seo-sitemap.md` for the slice context.\n */\nexport * from './astro/robots-renderer';\n/**\n * Phase 8 close-out — `enumerateMarketingPageSitemapUrls(siteContext)`\n * pure helper that returns every (page-manifest × locale) absolute\n * URL the marketing site's sitemap MUST advertise, paired with its\n * hreflang alternates. Two consumption surfaces:\n *\n * 1. Vitest enforcement — a regression that drops a manifest-\n * registered route fails fast in unit tests, well before\n * `astro build` would surface it.\n * 2. `@astrojs/sitemap` `customPages` integration — when the\n * consumer needs to surface routes Astro's file-based router\n * cannot auto-discover (data-driven landing variants, etc.),\n * this helper computes the canonical URL list to spread into\n * the plugin config.\n *\n * See `engine/saas-website/src/astro/sitemap-coverage.ts` and\n * `.cursor/plans/saas-website-seo-sitemap.md` for the full\n * rationale + scope boundary.\n */\nexport * from './astro/sitemap-coverage';\n/**\n * AEO — `renderPageStructuredData(opts)` pure string builder that\n * collects every section's `getStructuredData()` (when present),\n * dedupes by `@id`, and emits one\n * `<script type=\"application/ld+json\">` block per surviving payload.\n * Sibling of `renderWebsitePageHeadHtml` (same pure-TS, vitest-\n * friendly contract). See\n * `engine/saas-website/src/astro/structured-data.renderer.ts`\n * for the safety + dedupe semantics and\n * `.cursor/plans/saas-website-aeo-geo.md` for the slice context.\n */\nexport * from './astro/structured-data.renderer';\nexport * from './astro/headers-renderer';\nexport * from './astro/llms-txt-server';\n/**\n * AEO — runtime payload type (`WebsiteStructuredDataPayload`) and\n * convenience return-shape union (`WebsiteStructuredDataReturn`)\n * the section author imports to type their `getStructuredData()`.\n * Defined locally in `saas-website` (not in `saas-specifications`)\n * because the runtime engine cannot depend on the spec layer; the\n * companion catalog at `_catalogs/schema-org/` describes the same\n * vocabulary at the COMPANION layer.\n */\nexport * from './schemas/structured-data/website-structured-data.shared.schemas';\n"]}
|
|
@@ -24,6 +24,8 @@ export interface DefineWebsiteSectionInput<TProps> {
|
|
|
24
24
|
sectionRef: WebsiteSectionRef | string;
|
|
25
25
|
category: WebsiteSectionCategory;
|
|
26
26
|
expectedLabelKeys: readonly (WebsiteLabelKey | string)[];
|
|
27
|
+
/** The section's DOM id, so a link can target it (`<a href="#tiers">`). See `WebsiteSectionDefinition.anchorId`. */
|
|
28
|
+
anchorId?: string;
|
|
27
29
|
Component: ComponentType<TProps>;
|
|
28
30
|
/**
|
|
29
31
|
* See `WebsiteSectionDefinition.getStructuredData?` JSDoc for the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"define-website-section.d.ts","sourceRoot":"","sources":["../../../../../src/core/factories/define-website-section.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAG3C,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,kEAAkE,CAAC;AACjH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wDAAwD,CAAC;AACrG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAChF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oDAAoD,CAAC;AAC1F,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,sEAAsE,CAAC;AAExH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,yBAAyB,CAAC,MAAM;IAC/C,UAAU,EAAE,iBAAiB,GAAG,MAAM,CAAC;IACvC,QAAQ,EAAE,sBAAsB,CAAC;IACjC,iBAAiB,EAAE,SAAS,CAAC,eAAe,GAAG,MAAM,CAAC,EAAE,CAAC;IACzD,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACjC;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,2BAA2B,CAAC;CACvD;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,6BAA6B,CAAC,MAAM,CACnD,SAAQ,IAAI,CAAC,wBAAwB,EAAE,WAAW,GAAG,mBAAmB,CAAC;IACzE,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACjC,iBAAiB,CAAC,EAAE,MAAM,2BAA2B,CAAC;CACvD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EACzC,KAAK,EAAE,yBAAyB,CAAC,MAAM,CAAC,GACvC,6BAA6B,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"define-website-section.d.ts","sourceRoot":"","sources":["../../../../../src/core/factories/define-website-section.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAG3C,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,kEAAkE,CAAC;AACjH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wDAAwD,CAAC;AACrG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAChF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oDAAoD,CAAC;AAC1F,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,sEAAsE,CAAC;AAExH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,yBAAyB,CAAC,MAAM;IAC/C,UAAU,EAAE,iBAAiB,GAAG,MAAM,CAAC;IACvC,QAAQ,EAAE,sBAAsB,CAAC;IACjC,iBAAiB,EAAE,SAAS,CAAC,eAAe,GAAG,MAAM,CAAC,EAAE,CAAC;IACzD,oHAAoH;IACpH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACjC;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,2BAA2B,CAAC;CACvD;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,6BAA6B,CAAC,MAAM,CACnD,SAAQ,IAAI,CAAC,wBAAwB,EAAE,WAAW,GAAG,mBAAmB,CAAC;IACzE,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACjC,iBAAiB,CAAC,EAAE,MAAM,2BAA2B,CAAC;CACvD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EACzC,KAAK,EAAE,yBAAyB,CAAC,MAAM,CAAC,GACvC,6BAA6B,CAAC,MAAM,CAAC,CAsBvC;AACD,mEAAmE"}
|
|
@@ -51,6 +51,7 @@ export function defineWebsiteSection(input) {
|
|
|
51
51
|
sectionRef: input.sectionRef,
|
|
52
52
|
category: input.category,
|
|
53
53
|
expectedLabelKeys: input.expectedLabelKeys,
|
|
54
|
+
anchorId: input.anchorId,
|
|
54
55
|
Component: input.Component,
|
|
55
56
|
getStructuredData: input.getStructuredData,
|
|
56
57
|
});
|
|
@@ -63,6 +64,7 @@ export function defineWebsiteSection(input) {
|
|
|
63
64
|
sectionRef: parsed.sectionRef,
|
|
64
65
|
category: parsed.category,
|
|
65
66
|
expectedLabelKeys: parsed.expectedLabelKeys,
|
|
67
|
+
...(parsed.anchorId !== undefined ? { anchorId: parsed.anchorId } : {}),
|
|
66
68
|
Component: input.Component,
|
|
67
69
|
...(input.getStructuredData ? { getStructuredData: input.getStructuredData } : {}),
|
|
68
70
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"define-website-section.js","sourceRoot":"","sources":["../../../../../src/core/factories/define-website-section.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,8BAA8B,EAAE,MAAM,kEAAkE,CAAC;
|
|
1
|
+
{"version":3,"file":"define-website-section.js","sourceRoot":"","sources":["../../../../../src/core/factories/define-website-section.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,8BAA8B,EAAE,MAAM,kEAAkE,CAAC;AAyDlH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,MAAM,UAAU,oBAAoB,CAClC,KAAwC;IAExC,MAAM,MAAM,GAAG,8BAA8B,CAAC,KAAK,CAAC;QAClD,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,iBAAiB,EAAE,KAAK,CAAC,iBAAiB;QAC1C,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,iBAAiB,EAAE,KAAK,CAAC,iBAAiB;KAC3C,CAAC,CAAC;IACH;;;;OAIG;IACH,OAAO;QACL,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,iBAAiB,EAAE,MAAM,CAAC,iBAAiB;QAC3C,GAAG,CAAC,MAAM,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvE,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,GAAG,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,KAAK,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACnF,CAAC;AACJ,CAAC;AACD,mEAAmE","sourcesContent":["import type { ComponentType } from 'react';\n\nimport { WebsiteSectionDefinitionSchema } from '../../schemas/sections/website-section-definition.shared.schemas';\nimport type { WebsiteSectionDefinition } from '../../schemas/sections/website-section-definition.shared.schemas';\nimport type { WebsiteSectionCategory } from '../../schemas/sections/website-section-category.shared';\nimport type { WebsiteSectionRef } from '../../schemas/refs/section-ref.schemas';\nimport type { WebsiteLabelKey } from '../../schemas/label-keys/website-label-key.schemas';\nimport type { WebsiteStructuredDataReturn } from '../../schemas/structured-data/website-structured-data.shared.schemas';\n\n/**\n * @wildo_source:part:start saas.website.factories.define-section facet:layer:core facet:family:website\n *\n * Input shape consumed by `defineWebsiteSection<TProps>`. Mirrors\n * `WebsiteSectionDefinition` but keeps `Component` typed against the\n * caller-supplied generic so the precise prop shape survives end-to-end\n * (the consumer's `<HeroSection />` JSX can keep its strongly-typed\n * props after going through the factory).\n *\n * **String inputs vs. branded inputs**: `sectionRef` and\n * `expectedLabelKeys` accept raw strings as well as already-branded\n * values. The factory parses them through the underlying schemas, so\n * either path is safe — accepting strings directly removes the\n * ergonomic drag of forcing every section author to remember to call\n * `WebsiteSectionRefSchema.parse(...)` in their definition.\n */\nexport interface DefineWebsiteSectionInput<TProps> {\n sectionRef: WebsiteSectionRef | string;\n category: WebsiteSectionCategory;\n expectedLabelKeys: readonly (WebsiteLabelKey | string)[];\n /** The section's DOM id, so a link can target it (`<a href=\"#tiers\">`). See `WebsiteSectionDefinition.anchorId`. */\n anchorId?: string;\n Component: ComponentType<TProps>;\n /**\n * See `WebsiteSectionDefinition.getStructuredData?` JSDoc for the\n * full authoring contract. The factory passes the function through\n * unchanged; the framework invokes it at static-build time and\n * forwards the return value to `renderPageStructuredData()`.\n */\n getStructuredData?: () => WebsiteStructuredDataReturn;\n}\n\n/**\n * Return shape of `defineWebsiteSection<TProps>`. Identical to\n * `WebsiteSectionDefinition` except `Component` retains the\n * caller-supplied generic prop shape rather than the schema's\n * lowest-common-denominator `ComponentType<unknown>`.\n *\n * **Why this matters**: a consumer's `pageComponent` JSX composes\n * sections like `<HERO_SECTION.Component {...heroProps} />`. Without\n * preserving `TProps`, the spread would be untyped and a renamed prop\n * would silently miss the type system. With `TProps` preserved, the\n * call site fails to compile the moment the prop contract drifts.\n */\nexport interface TypedWebsiteSectionDefinition<TProps>\n extends Omit<WebsiteSectionDefinition, 'Component' | 'getStructuredData'> {\n Component: ComponentType<TProps>;\n getStructuredData?: () => WebsiteStructuredDataReturn;\n}\n\n/**\n * `defineWebsiteSection<TProps>` — factory that materializes a\n * `WebsiteSectionDefinition` from authored input.\n *\n * **Two responsibilities**, one entry point:\n * 1. Runtime validation — parses the input through\n * `WebsiteSectionDefinitionSchema` so brand checks, regex\n * validations, and array refinements run at module load time\n * (not at render time, not at build-output time).\n * 2. Type preservation — returns an object whose `Component` field\n * keeps the caller-supplied `TProps`, so JSX usage of the\n * returned section retains compile-time prop checking.\n *\n * **Why a factory instead of constructing the literal directly**:\n * - Consumers don't have to remember to parse refs / keys\n * themselves before assembling the literal.\n * - The schema parse provides a single audited validation chokepoint,\n * and the parsed `expectedLabelKeys` are what `wildo website audit`\n * checks every locale pack against, without re-entering the React graph.\n * - The factory is the documented authoring API the\n * `website_factory_authoring` skill targets — keeping the\n * authoring surface narrow lets the framework evolve the\n * internal schema (add fields, refinements) without churning\n * every section definition in every consumer app.\n *\n * @param input section authoring input (see `DefineWebsiteSectionInput`).\n * @returns a `TypedWebsiteSectionDefinition<TProps>` with `Component`\n * preserved at the precise prop type.\n *\n * @example\n * interface HeroProps {\n * headlineKey: WebsiteLabelKey;\n * subheadlineKey: WebsiteLabelKey;\n * }\n * function HeroSection({ headlineKey, subheadlineKey }: HeroProps) {\n * // …\n * }\n * export const HERO_SECTION = defineWebsiteSection<HeroProps>({\n * sectionRef: 'hero',\n * category: WebsiteSectionCategory.HERO,\n * expectedLabelKeys: [\n * 'website.landing.hero.headline',\n * 'website.landing.hero.subheadline',\n * ],\n * Component: HeroSection,\n * });\n */\nexport function defineWebsiteSection<TProps>(\n input: DefineWebsiteSectionInput<TProps>,\n): TypedWebsiteSectionDefinition<TProps> {\n const parsed = WebsiteSectionDefinitionSchema.parse({\n sectionRef: input.sectionRef,\n category: input.category,\n expectedLabelKeys: input.expectedLabelKeys,\n anchorId: input.anchorId,\n Component: input.Component,\n getStructuredData: input.getStructuredData,\n });\n /**\n * Re-attach `Component` and `getStructuredData` from the original\n * input so the precise generic types survive — `parsed.Component`\n * is `unknown` per the schema's `z.unknown()` contract.\n */\n return {\n sectionRef: parsed.sectionRef,\n category: parsed.category,\n expectedLabelKeys: parsed.expectedLabelKeys,\n ...(parsed.anchorId !== undefined ? { anchorId: parsed.anchorId } : {}),\n Component: input.Component,\n ...(input.getStructuredData ? { getStructuredData: input.getStructuredData } : {}),\n };\n}\n/** @wildo_source:part:end saas.website.factories.define-section */\n"]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { WebsiteSectionDefinition } from '../../schemas/sections/website-section-definition.shared.schemas';
|
|
3
|
+
/**
|
|
4
|
+
* @wildo_source:part:start saas.website.page-sections.component facet:layer:core facet:family:website
|
|
5
|
+
*
|
|
6
|
+
* `WebsitePageSections` — renders a page's ordered sections list, each inside its `WebsiteSection`
|
|
7
|
+
* wrapper, and a visible PENDING marker while the list is still empty.
|
|
8
|
+
*
|
|
9
|
+
* A page component should render its sections through this rather than mapping the list by hand:
|
|
10
|
+
*
|
|
11
|
+
* ```tsx
|
|
12
|
+
* export function PricingPage(): ReactNode {
|
|
13
|
+
* return <WebsitePageSections pageRef="pricing" sections={PRICING_SECTIONS} />;
|
|
14
|
+
* }
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* **Why the empty state is the point.** A page's sections list starts EMPTY when its page
|
|
18
|
+
* is authored before its sections — the application-creation build lane does exactly that, one task
|
|
19
|
+
* per page and one per section. An empty page is a valid manifest and a dev server serves it; the
|
|
20
|
+
* production build refuses it (`assertWebsitePagesHaveSections`). Mapped by hand, an empty list
|
|
21
|
+
* renders nothing at all, which is indistinguishable from a page that failed. This renders a
|
|
22
|
+
* marked block instead, so the page visibly says its sections are still to come.
|
|
23
|
+
*
|
|
24
|
+
* A section's `anchorId`, declared on its definition, becomes the wrapper's DOM `id`, so in-page links
|
|
25
|
+
* still have a target when no page places its sections by hand.
|
|
26
|
+
*
|
|
27
|
+
* **The marker carries identifiers, not prose.** It shows the page ref and the empty list as code,
|
|
28
|
+
* deliberately: words a person reads come from the label packs, and a label for a state no built
|
|
29
|
+
* page can ever reach (the build refuses an empty page) would be a key every locale must carry for
|
|
30
|
+
* nothing. A missing section must LOOK missing, which a raw identifier does.
|
|
31
|
+
*/
|
|
32
|
+
export interface WebsitePageSectionsProps {
|
|
33
|
+
/** The page's ref, as its manifest declares it. Shown in the pending marker. */
|
|
34
|
+
pageRef: string;
|
|
35
|
+
/** The page's ordered sections list — the same array its manifest's `sectionRefs` derive from. */
|
|
36
|
+
sections: readonly WebsiteSectionDefinition[];
|
|
37
|
+
}
|
|
38
|
+
export declare function WebsitePageSections({ pageRef, sections }: WebsitePageSectionsProps): ReactNode;
|
|
39
|
+
export declare namespace WebsitePageSections {
|
|
40
|
+
var displayName: string;
|
|
41
|
+
}
|
|
42
|
+
/** @wildo_source:part:end saas.website.page-sections.component */
|
|
43
|
+
//# sourceMappingURL=WebsitePageSections.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebsitePageSections.d.ts","sourceRoot":"","sources":["../../../../../src/core/layouts/WebsitePageSections.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAE9C,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,kEAAkE,CAAC;AAGjH;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,WAAW,wBAAwB;IACvC,gFAAgF;IAChF,OAAO,EAAE,MAAM,CAAC;IAChB,kGAAkG;IAClG,QAAQ,EAAE,SAAS,wBAAwB,EAAE,CAAC;CAC/C;AAED,wBAAgB,mBAAmB,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,wBAAwB,GAAG,SAAS,CAwB9F;yBAxBe,mBAAmB;;;AA0BnC,kEAAkE"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { WebsiteSection } from './WebsiteSection.js';
|
|
3
|
+
export function WebsitePageSections({ pageRef, sections }) {
|
|
4
|
+
if (sections.length === 0) {
|
|
5
|
+
return (_jsx("section", { "data-website-pending-page": pageRef, "aria-busy": "true", style: { margin: '2rem auto', maxWidth: '48rem', padding: '2rem', border: '2px dashed currentColor', opacity: 0.6, textAlign: 'center' }, children: _jsx("code", { children: `${pageRef}: sections = []` }) }));
|
|
6
|
+
}
|
|
7
|
+
return (_jsx(_Fragment, { children: sections.map((section) => {
|
|
8
|
+
const SectionComponent = section.Component;
|
|
9
|
+
return (_jsx(WebsiteSection, { sectionRef: section.sectionRef, category: section.category, id: section.anchorId, children: _jsx(SectionComponent, {}) }, String(section.sectionRef)));
|
|
10
|
+
}) }));
|
|
11
|
+
}
|
|
12
|
+
WebsitePageSections.displayName = 'WebsitePageSections';
|
|
13
|
+
//# sourceMappingURL=WebsitePageSections.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebsitePageSections.js","sourceRoot":"","sources":["../../../../../src/core/layouts/WebsitePageSections.tsx"],"names":[],"mappings":";AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAsClD,MAAM,UAAU,mBAAmB,CAAC,EAAE,OAAO,EAAE,QAAQ,EAA4B;IACjF,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,CACL,+CAC6B,OAAO,eACxB,MAAM,EAChB,KAAK,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,yBAAyB,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,QAAQ,EAAE,YAExI,yBAAO,GAAG,OAAO,iBAAiB,GAAQ,GAClC,CACX,CAAC;IACJ,CAAC;IACD,OAAO,CACL,4BACG,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;YACxB,MAAM,gBAAgB,GAAG,OAAO,CAAC,SAAgC,CAAC;YAClE,OAAO,CACL,KAAC,cAAc,IAAkC,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,EAAE,EAAE,OAAO,CAAC,QAAQ,YAC/H,KAAC,gBAAgB,KAAG,IADD,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAE9B,CAClB,CAAC;QACJ,CAAC,CAAC,GACD,CACJ,CAAC;AACJ,CAAC;AACD,mBAAmB,CAAC,WAAW,GAAG,qBAAqB,CAAC","sourcesContent":["import React, { type ReactNode } from 'react';\n\nimport type { WebsiteSectionDefinition } from '../../schemas/sections/website-section-definition.shared.schemas';\nimport { WebsiteSection } from './WebsiteSection';\n\n/**\n * @wildo_source:part:start saas.website.page-sections.component facet:layer:core facet:family:website\n *\n * `WebsitePageSections` — renders a page's ordered sections list, each inside its `WebsiteSection`\n * wrapper, and a visible PENDING marker while the list is still empty.\n *\n * A page component should render its sections through this rather than mapping the list by hand:\n *\n * ```tsx\n * export function PricingPage(): ReactNode {\n * return <WebsitePageSections pageRef=\"pricing\" sections={PRICING_SECTIONS} />;\n * }\n * ```\n *\n * **Why the empty state is the point.** A page's sections list starts EMPTY when its page\n * is authored before its sections — the application-creation build lane does exactly that, one task\n * per page and one per section. An empty page is a valid manifest and a dev server serves it; the\n * production build refuses it (`assertWebsitePagesHaveSections`). Mapped by hand, an empty list\n * renders nothing at all, which is indistinguishable from a page that failed. This renders a\n * marked block instead, so the page visibly says its sections are still to come.\n *\n * A section's `anchorId`, declared on its definition, becomes the wrapper's DOM `id`, so in-page links\n * still have a target when no page places its sections by hand.\n *\n * **The marker carries identifiers, not prose.** It shows the page ref and the empty list as code,\n * deliberately: words a person reads come from the label packs, and a label for a state no built\n * page can ever reach (the build refuses an empty page) would be a key every locale must carry for\n * nothing. A missing section must LOOK missing, which a raw identifier does.\n */\nexport interface WebsitePageSectionsProps {\n /** The page's ref, as its manifest declares it. Shown in the pending marker. */\n pageRef: string;\n /** The page's ordered sections list — the same array its manifest's `sectionRefs` derive from. */\n sections: readonly WebsiteSectionDefinition[];\n}\n\nexport function WebsitePageSections({ pageRef, sections }: WebsitePageSectionsProps): ReactNode {\n if (sections.length === 0) {\n return (\n <section\n data-website-pending-page={pageRef}\n aria-busy=\"true\"\n style={{ margin: '2rem auto', maxWidth: '48rem', padding: '2rem', border: '2px dashed currentColor', opacity: 0.6, textAlign: 'center' }}\n >\n <code>{`${pageRef}: sections = []`}</code>\n </section>\n );\n }\n return (\n <>\n {sections.map((section) => {\n const SectionComponent = section.Component as React.ComponentType;\n return (\n <WebsiteSection key={String(section.sectionRef)} sectionRef={section.sectionRef} category={section.category} id={section.anchorId}>\n <SectionComponent />\n </WebsiteSection>\n );\n })}\n </>\n );\n}\nWebsitePageSections.displayName = 'WebsitePageSections';\n/** @wildo_source:part:end saas.website.page-sections.component */\n"]}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -76,6 +76,7 @@ export * from './core/factories/define-website-section';
|
|
|
76
76
|
export * from './core/factories/define-website-page-manifest';
|
|
77
77
|
export * from './core/layouts/WebsitePageLayout';
|
|
78
78
|
export * from './core/layouts/WebsiteSection';
|
|
79
|
+
export * from './core/layouts/WebsitePageSections';
|
|
79
80
|
export * from './core/consent/website-consent-storage';
|
|
80
81
|
export * from './core/consent/WebsiteConsentContext';
|
|
81
82
|
export * from './core/consent/useWebsiteConsent';
|
package/dist/esm/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAEH,cAAc,iCAAiC,CAAC;AAChD,cAAc,oCAAoC,CAAC;AACnD,cAAc,gDAAgD,CAAC;AAC/D,cAAc,yDAAyD,CAAC;AACxE,cAAc,oEAAoE,CAAC;AACnF,cAAc,oDAAoD,CAAC;AACnE,cAAc,8DAA8D,CAAC;AAC7E,cAAc,kEAAkE,CAAC;AACjF,cAAc,iEAAiE,CAAC;AAChF,cAAc,gEAAgE,CAAC;AAC/E,cAAc,8DAA8D,CAAC;AAC7E,cAAc,0DAA0D,CAAC;AACzE,cAAc,wDAAwD,CAAC;AACvE,cAAc,qDAAqD,CAAC;AACpE,cAAc,2CAA2C,CAAC;AAE1D,cAAc,uCAAuC,CAAC;AACtD,cAAc,mCAAmC,CAAC;AAClD,cAAc,oCAAoC,CAAC;AACnD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,uCAAuC,CAAC;AACtD,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,qCAAqC,CAAC;AACpD,cAAc,yCAAyC,CAAC;AACxD,cAAc,+CAA+C,CAAC;AAC9D,cAAc,kCAAkC,CAAC;AACjD,cAAc,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAEH,cAAc,iCAAiC,CAAC;AAChD,cAAc,oCAAoC,CAAC;AACnD,cAAc,gDAAgD,CAAC;AAC/D,cAAc,yDAAyD,CAAC;AACxE,cAAc,oEAAoE,CAAC;AACnF,cAAc,oDAAoD,CAAC;AACnE,cAAc,8DAA8D,CAAC;AAC7E,cAAc,kEAAkE,CAAC;AACjF,cAAc,iEAAiE,CAAC;AAChF,cAAc,gEAAgE,CAAC;AAC/E,cAAc,8DAA8D,CAAC;AAC7E,cAAc,0DAA0D,CAAC;AACzE,cAAc,wDAAwD,CAAC;AACvE,cAAc,qDAAqD,CAAC;AACpE,cAAc,2CAA2C,CAAC;AAE1D,cAAc,uCAAuC,CAAC;AACtD,cAAc,mCAAmC,CAAC;AAClD,cAAc,oCAAoC,CAAC;AACnD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,uCAAuC,CAAC;AACtD,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,qCAAqC,CAAC;AACpD,cAAc,yCAAyC,CAAC;AACxD,cAAc,+CAA+C,CAAC;AAC9D,cAAc,kCAAkC,CAAC;AACjD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,oCAAoC,CAAC;AAEnD,cAAc,wCAAwC,CAAC;AACvD,cAAc,sCAAsC,CAAC;AACrD,cAAc,kCAAkC,CAAC;AACjD,cAAc,mCAAmC,CAAC;AAClD,cAAc,qCAAqC,CAAC;AAEpD,cAAc,+DAA+D,CAAC;AAC9E,cAAc,uDAAuD,CAAC;AACtE,cAAc,oDAAoD,CAAC;AACnE,cAAc,4DAA4D,CAAC;AAE3E,cAAc,2DAA2D,CAAC;AAC1E,cAAc,sDAAsD,CAAC;AACrE,cAAc,6CAA6C,CAAC;AAC5D,cAAc,uDAAuD,CAAC;AAEtE,cAAc,uCAAuC,CAAC;AACtD,cAAc,oCAAoC,CAAC;AACnD,cAAc,oCAAoC,CAAC;AACnD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,uCAAuC,CAAC;AACtD,cAAc,sCAAsC,CAAC;AACrD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,qCAAqC,CAAC"}
|
package/dist/esm/index.js
CHANGED
|
@@ -76,6 +76,7 @@ export * from './core/factories/define-website-section.js';
|
|
|
76
76
|
export * from './core/factories/define-website-page-manifest.js';
|
|
77
77
|
export * from './core/layouts/WebsitePageLayout.js';
|
|
78
78
|
export * from './core/layouts/WebsiteSection.js';
|
|
79
|
+
export * from './core/layouts/WebsitePageSections.js';
|
|
79
80
|
export * from './core/consent/website-consent-storage.js';
|
|
80
81
|
export * from './core/consent/WebsiteConsentContext.js';
|
|
81
82
|
export * from './core/consent/useWebsiteConsent.js';
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAEH,cAAc,iCAAiC,CAAC;AAChD,cAAc,oCAAoC,CAAC;AACnD,cAAc,gDAAgD,CAAC;AAC/D,cAAc,yDAAyD,CAAC;AACxE,cAAc,oEAAoE,CAAC;AACnF,cAAc,oDAAoD,CAAC;AACnE,cAAc,8DAA8D,CAAC;AAC7E,cAAc,kEAAkE,CAAC;AACjF,cAAc,iEAAiE,CAAC;AAChF,cAAc,gEAAgE,CAAC;AAC/E,cAAc,8DAA8D,CAAC;AAC7E,cAAc,0DAA0D,CAAC;AACzE,cAAc,wDAAwD,CAAC;AACvE,cAAc,qDAAqD,CAAC;AACpE,cAAc,2CAA2C,CAAC;AAE1D,cAAc,uCAAuC,CAAC;AACtD,cAAc,mCAAmC,CAAC;AAClD,cAAc,oCAAoC,CAAC;AACnD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,uCAAuC,CAAC;AACtD,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,qCAAqC,CAAC;AACpD,cAAc,yCAAyC,CAAC;AACxD,cAAc,+CAA+C,CAAC;AAC9D,cAAc,kCAAkC,CAAC;AACjD,cAAc,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAEH,cAAc,iCAAiC,CAAC;AAChD,cAAc,oCAAoC,CAAC;AACnD,cAAc,gDAAgD,CAAC;AAC/D,cAAc,yDAAyD,CAAC;AACxE,cAAc,oEAAoE,CAAC;AACnF,cAAc,oDAAoD,CAAC;AACnE,cAAc,8DAA8D,CAAC;AAC7E,cAAc,kEAAkE,CAAC;AACjF,cAAc,iEAAiE,CAAC;AAChF,cAAc,gEAAgE,CAAC;AAC/E,cAAc,8DAA8D,CAAC;AAC7E,cAAc,0DAA0D,CAAC;AACzE,cAAc,wDAAwD,CAAC;AACvE,cAAc,qDAAqD,CAAC;AACpE,cAAc,2CAA2C,CAAC;AAE1D,cAAc,uCAAuC,CAAC;AACtD,cAAc,mCAAmC,CAAC;AAClD,cAAc,oCAAoC,CAAC;AACnD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,uCAAuC,CAAC;AACtD,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,qCAAqC,CAAC;AACpD,cAAc,yCAAyC,CAAC;AACxD,cAAc,+CAA+C,CAAC;AAC9D,cAAc,kCAAkC,CAAC;AACjD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,oCAAoC,CAAC;AAEnD,cAAc,wCAAwC,CAAC;AACvD,cAAc,sCAAsC,CAAC;AACrD,cAAc,kCAAkC,CAAC;AACjD,cAAc,mCAAmC,CAAC;AAClD,cAAc,qCAAqC,CAAC;AAEpD,cAAc,+DAA+D,CAAC;AAC9E,cAAc,uDAAuD,CAAC;AACtE,cAAc,oDAAoD,CAAC;AACnE,cAAc,4DAA4D,CAAC;AAE3E,cAAc,2DAA2D,CAAC;AAC1E,cAAc,sDAAsD,CAAC;AACrE,cAAc,6CAA6C,CAAC;AAC5D,cAAc,uDAAuD,CAAC;AAEtE,cAAc,uCAAuC,CAAC;AACtD,cAAc,oCAAoC,CAAC;AACnD,cAAc,oCAAoC,CAAC;AACnD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,uCAAuC,CAAC;AACtD,cAAc,sCAAsC,CAAC;AACrD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,qCAAqC,CAAC","sourcesContent":["/**\n * @wildo-package @wildo-ai/saas-website\n *\n * SaaS marketing/landing website engine — schemas + helpers + layout\n * placeholders + Astro/MDX adapters + SEO/AEO/GEO builder library.\n *\n * Per `saas-website-slice.md` D6/D15: this engine ships **NO concrete\n * business sections** (no Hero, no CTA, no Pricing). It ships the\n * structural and helper machinery; consumers author every business\n * section themselves via the `defineWebsiteSection` factory and the\n * accompanying SKILLs in `framework-knowledge`.\n *\n * @wildo-boundary\n * This barrel re-exports the React-side surface: `schemas/`, `core/`,\n * `components/`. The other entries are separate subpaths with their own\n * audiences — `/astro` (Node, Astro frontmatter), `/astro-island` (the\n * bridge factory), `/config` and `/config-loader` (Node), `/mdx`,\n * `/companion` (React-free). Per directory:\n * - `schemas/` : NO React, NO Astro, NO `~wildo-frontend-lib/**`\n * - `core/`, `components/`: MAY use React; NO `~wildo-frontend-lib/**` (Q17=B)\n * - `astro/` : MAY use Astro and Node; NO React component runtime imports\n * - `config/` : Node-side loaders; never reachable from a React-island entry\n * - `mdx/` : MAY use `@mdx-js/*`\n *\n * D12 (client-bundle import discipline): everything reachable from a\n * React-island entry ships to the browser, so every shared model atom\n * comes from a side-effect-free `/public-runtime` subpath —\n * `@wildo-ai/saas-models/public-runtime` first,\n * `@wildo-ai/presets-components-models/public-runtime` for symbols it does\n * not re-export yet — and `@wildo-ai/zod-decorators` is allowed because\n * only an explicit `initZodDecorators()` mutates Zod's prototype.\n *\n * The ROOT barrels of `@wildo-ai/saas-models` and\n * `@wildo-ai/presets-components-models` are forbidden everywhere in this\n * package (they boot the decorator framework and pull the full schema\n * graph), as are the saas-app DI runtime (`inversify`,\n * `@wildo-ai/saas-frontend-lib`, `@wildo-ai/saas-backend-lib`) and\n * pure-backend libs (`mongoose`, `express`, `pino`, `helmet`, `argon2`,\n * `bcrypt`). Enforced by `src/__tests__/bundle-isolation.test.ts`, which\n * walks every entry's import graph against an allow-list.\n *\n * D18 (L1 boundary, Phase 2): the L1 primitives (`WebsiteHeading`,\n * `WebsiteText`, `WebsiteButton`, `WebsiteLink`, `WebsiteImage`) are\n * self-contained placeholders — they do NOT consume\n * `ComponentRegistryService` from `saas-frontend-lib` and do NOT\n * re-export `saas-frontend-lib` primitives. Consumers override at the\n * L1 boundary by barrel-re-exporting their preferred styled variant\n * from their app's `app/website/primitives/<Primitive>.tsx`. Phase 6\n * dogfood will revisit and decide whether to promote a registry-based\n * override mechanism.\n */\n\nexport * from './schemas/refs/page-ref.schemas';\nexport * from './schemas/refs/section-ref.schemas';\nexport * from './schemas/label-keys/website-label-key.schemas';\nexport * from './schemas/label-keys/website-consent-label-keys.schemas';\nexport * from './schemas/label-keys/website-provider-component-label-keys.schemas';\nexport * from './schemas/sections/website-section-category.shared';\nexport * from './schemas/sections/website-section-definition.shared.schemas';\nexport * from './schemas/structured-data/website-structured-data.shared.schemas';\nexport * from './schemas/structured-data/structured-data-reconciliation.shared';\nexport * from './schemas/structured-data/pricing-offer-structured-data.shared';\nexport * from './schemas/design-tokens/website-design-tokens.shared.schemas';\nexport * from './schemas/manifests/website-page-manifest.shared.schemas';\nexport * from './schemas/manifests/website-root-config.shared.schemas';\nexport * from './schemas/blog/blog-post-frontmatter.shared.schemas';\nexport * from './schemas/validators/label-pack.validator';\n\nexport * from './core/contexts/WebsiteRuntimeContext';\nexport * from './core/contexts/useWebsiteRuntime';\nexport * from './core/contexts/WebsitePageContext';\nexport * from './core/contexts/useWebsitePage';\nexport * from './core/contexts/WebsiteSectionContext';\nexport * from './core/contexts/useWebsiteSection';\nexport * from './core/hooks/useWebsiteLabel';\nexport * from './core/hooks/useWebsiteDesignTokens';\nexport * from './core/factories/define-website-section';\nexport * from './core/factories/define-website-page-manifest';\nexport * from './core/layouts/WebsitePageLayout';\nexport * from './core/layouts/WebsiteSection';\nexport * from './core/layouts/WebsitePageSections';\n\nexport * from './core/consent/website-consent-storage';\nexport * from './core/consent/WebsiteConsentContext';\nexport * from './core/consent/useWebsiteConsent';\nexport * from './core/consent/WebsiteConsentGate';\nexport * from './core/consent/WebsiteConsentBanner';\n\nexport * from './core/external-providers/provider-component-registry.website';\nexport * from './core/external-providers/useWebsiteProviderComponent';\nexport * from './core/external-providers/WebsiteProviderComponent';\nexport * from './core/external-providers/website-provider-sdk-activations';\n\nexport * from './core/anonymous-session/website-anonymous-session-client';\nexport * from './core/anonymous-session/inbound-contact-form.schema';\nexport * from './core/anonymous-session/InboundContactForm';\nexport * from './core/anonymous-session/website-submission-challenge';\n\nexport * from './components/low-level/WebsiteHeading';\nexport * from './components/low-level/WebsiteText';\nexport * from './components/low-level/WebsiteLink';\nexport * from './components/low-level/WebsiteInternalLink';\nexport * from './core/routing/internal-routing.utils';\nexport * from './components/low-level/WebsiteButton';\nexport * from './components/low-level/WebsiteInternalButton';\nexport * from './components/low-level/WebsiteImage';\n"]}
|
|
@@ -117,6 +117,8 @@ export type WebsitePageRoutePath = z.infer<typeof _WebsitePageRoutePathSchema>;
|
|
|
117
117
|
* not match the directive in its `.astro` file.
|
|
118
118
|
*
|
|
119
119
|
* **Refinements**:
|
|
120
|
+
* - `sectionRefs` MAY be empty — a page whose sections are still being authored. It may be
|
|
121
|
+
* served by a dev server and may not be built: see `assertWebsitePagesHaveSections`.
|
|
120
122
|
* - `sectionRefs` MUST contain unique values. Duplicates indicate
|
|
121
123
|
* either a copy-paste bug (two sections pointing at the same
|
|
122
124
|
* label-key root) or an intent to share a label-key root that
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"website-page-manifest.shared.schemas.d.ts","sourceRoot":"","sources":["../../../../../src/schemas/manifests/website-page-manifest.shared.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAC3C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,QAAA,MAAM,+BAA+B;;;;iBAInC,CAAC;AAEH,eAAO,MAAM,8BAA8B,EAAE,OAAO,+BACnB,CAAC;AAElC,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAC;AACvF,wEAAwE;AAExE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,QAAA,MAAM,2BAA2B,gEAWC,CAAC;AAEnC,eAAO,MAAM,0BAA0B,EAAE,OAAO,2BACnB,CAAC;AAE9B,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAC/E,mEAAmE;AAEnE
|
|
1
|
+
{"version":3,"file":"website-page-manifest.shared.schemas.d.ts","sourceRoot":"","sources":["../../../../../src/schemas/manifests/website-page-manifest.shared.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAC3C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,QAAA,MAAM,+BAA+B;;;;iBAInC,CAAC;AAEH,eAAO,MAAM,8BAA8B,EAAE,OAAO,+BACnB,CAAC;AAElC,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAC;AACvF,wEAAwE;AAExE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,QAAA,MAAM,2BAA2B,gEAWC,CAAC;AAEnC,eAAO,MAAM,0BAA0B,EAAE,OAAO,2BACnB,CAAC;AAE9B,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAC/E,mEAAmE;AAEnE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;AACH,oBAAY,4BAA4B;IACtC,+DAA+D;IAC/D,IAAI,SAAS;IACb,gEAAgE;IAChE,KAAK,UAAU;CAChB;AAED,eAAO,MAAM,kCAAkC,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,4BAA4B,CACxD,CAAC;AAEvC;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,QAAA,MAAM,2BAA2B;;iBAE/B,CAAC;AAEH,eAAO,MAAM,0BAA0B,EAAE,OAAO,2BACnB,CAAC;AAE9B,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAC/E,4DAA4D;AAE5D,QAAA,MAAM,0BAA0B;;;;;;;;;;;;;;iBA8C5B,CAAC;AAEL,eAAO,MAAM,yBAAyB,EAAE,OAAO,0BACnB,CAAC;AAE7B;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,mBAAoB,SAAQ,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC;IACrF,aAAa,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC;CACnC;;AACD,+DAA+D"}
|
|
@@ -124,6 +124,8 @@ export const WebsitePageRoutePathSchema = _WebsitePageRoutePathSchema;
|
|
|
124
124
|
* not match the directive in its `.astro` file.
|
|
125
125
|
*
|
|
126
126
|
* **Refinements**:
|
|
127
|
+
* - `sectionRefs` MAY be empty — a page whose sections are still being authored. It may be
|
|
128
|
+
* served by a dev server and may not be built: see `assertWebsitePagesHaveSections`.
|
|
127
129
|
* - `sectionRefs` MUST contain unique values. Duplicates indicate
|
|
128
130
|
* either a copy-paste bug (two sections pointing at the same
|
|
129
131
|
* label-key root) or an intent to share a label-key root that
|
|
@@ -179,9 +181,16 @@ const _WebsitePageManifestSchema = z
|
|
|
179
181
|
ref: WebsitePageRefSchema,
|
|
180
182
|
routePath: _WebsitePageRoutePathSchema,
|
|
181
183
|
pageComponent: z.unknown(),
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
184
|
+
/*
|
|
185
|
+
* EMPTY is a valid manifest (#2205): a page whose sections are not authored YET. The build lane
|
|
186
|
+
* creates that state on purpose (the page task writes the empty list, then each section task
|
|
187
|
+
* appends itself), and refusing it here made `astro dev` crash at module load until every page
|
|
188
|
+
* of the site had a section. An empty page is unfinished, not false, so it is representable and
|
|
189
|
+
* a dev server renders it (`WebsitePageSections` shows the pending state). What is refused is
|
|
190
|
+
* BUILDING one: `assertWebsitePagesHaveSections`, run from the build-start hook, because a
|
|
191
|
+
* published page with nothing on it is a statement to a real reader.
|
|
192
|
+
*/
|
|
193
|
+
sectionRefs: z.array(WebsiteSectionRefSchema),
|
|
185
194
|
metaLabelKeys: _WebsitePageMetaLabelKeysSchema,
|
|
186
195
|
hydration: WebsitePageHydrationStrategySchema.optional(),
|
|
187
196
|
seo: _WebsitePageSeoConfigSchema.optional(),
|