@gradial/aci 0.1.52-rc.3 → 0.1.52-rc.34-e2e.1

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.
Files changed (276) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +86 -5
  3. package/dist/astro/error-page.js +1 -0
  4. package/dist/astro/index.js +4 -0
  5. package/dist/astro/page.d.ts +5 -2
  6. package/dist/astro/page.js +35 -23
  7. package/dist/astro/preview.js +2 -17
  8. package/dist/audience-data-models.d.ts +308 -194
  9. package/dist/audience-data-models.js +314 -51
  10. package/dist/cli/compile-registry.d.mts +5 -1
  11. package/dist/cli/compile-registry.mjs +31 -46
  12. package/dist/cli/mintlify-render.mjs +8 -0
  13. package/dist/cli/validate-content.d.mts +31 -1
  14. package/dist/cli/validate-content.mjs +50 -12
  15. package/dist/collect/aci.js +1 -1
  16. package/dist/collect/aci.js.sha256 +1 -1
  17. package/dist/collect/api.d.ts +6 -7
  18. package/dist/collect/api.js +21 -8
  19. package/dist/collect/assignments.d.ts +7 -9
  20. package/dist/collect/assignments.js +23 -32
  21. package/dist/collect/batcher.d.ts +9 -7
  22. package/dist/collect/batcher.js +6 -5
  23. package/dist/collect/consent/envelope.d.ts +7 -1
  24. package/dist/collect/consent/envelope.js +3 -1
  25. package/dist/collect/consent/mapping.d.ts +2 -0
  26. package/dist/collect/consent/mapping.js +1 -0
  27. package/dist/collect/consenthold.d.ts +38 -0
  28. package/dist/collect/consenthold.js +82 -0
  29. package/dist/collect/declarative.js +7 -5
  30. package/dist/collect/gapbuffer.d.ts +13 -4
  31. package/dist/collect/gapbuffer.js +21 -10
  32. package/dist/collect/index.js +67 -20
  33. package/dist/collect/pageviews.d.ts +11 -11
  34. package/dist/collect/pageviews.js +27 -22
  35. package/dist/consent-banner/index.d.ts +17 -0
  36. package/dist/consent-banner/index.js +23 -0
  37. package/dist/content/index.d.ts +2 -1
  38. package/dist/content/index.js +4 -1
  39. package/dist/content/provider.d.ts +16 -2
  40. package/dist/content/provider.js +5 -1
  41. package/dist/content/resolve-slots.d.ts +33 -4
  42. package/dist/content/resolve-slots.js +127 -19
  43. package/dist/content/types.d.ts +7 -1
  44. package/dist/edge/expression-capabilities.v1.d.ts +106 -0
  45. package/dist/edge/expression-capabilities.v1.js +165 -0
  46. package/dist/index.d.ts +1 -2
  47. package/dist/index.js +0 -1
  48. package/dist/internal/domain/edge/compose.d.ts +24 -0
  49. package/dist/internal/domain/edge/compose.js +89 -0
  50. package/dist/internal/domain/edge/identity.d.ts +5 -0
  51. package/dist/internal/domain/edge/identity.js +50 -14
  52. package/dist/internal/domain/edge/index.d.ts +2 -0
  53. package/dist/internal/domain/edge/index.js +2 -0
  54. package/dist/internal/domain/edge/mimic_token.d.ts +14 -0
  55. package/dist/internal/domain/edge/mimic_token.js +125 -0
  56. package/dist/internal/domain/edge/personalization-core.d.ts +61 -22
  57. package/dist/internal/domain/edge/personalization-core.js +789 -198
  58. package/dist/internal/domain/edge/personalization_index.d.ts +17 -0
  59. package/dist/internal/domain/edge/personalization_index.js +56 -0
  60. package/dist/internal/domain/edge/record/codec.d.ts +6 -32
  61. package/dist/internal/domain/edge/record/codec.js +86 -168
  62. package/dist/internal/domain/edge/resolve.js +76 -39
  63. package/dist/internal/domain/edge/testdata/flat_only_validator_v1.d.ts +10 -0
  64. package/dist/internal/domain/edge/testdata/flat_only_validator_v1.js +253 -0
  65. package/dist/internal/domain/edge/types.d.ts +93 -41
  66. package/dist/internal/domain/edge/types.js +3 -3
  67. package/dist/internal/domain/edge/variant_key.d.ts +3 -0
  68. package/dist/internal/domain/edge/variant_key.js +44 -0
  69. package/dist/internal/platform/collectionstore/errors.d.ts +24 -0
  70. package/dist/internal/platform/collectionstore/errors.js +48 -0
  71. package/dist/mimic/core.d.ts +2 -0
  72. package/dist/mimic/core.js +31 -0
  73. package/dist/mintlify/blocks.d.ts +6 -0
  74. package/dist/mintlify/blocks.js +201 -0
  75. package/dist/mintlify/config.d.ts +3 -0
  76. package/dist/mintlify/config.js +77 -0
  77. package/dist/mintlify/index.d.ts +3 -0
  78. package/dist/mintlify/index.js +2 -0
  79. package/dist/mintlify/json.d.ts +8 -0
  80. package/dist/mintlify/json.js +37 -0
  81. package/dist/mintlify/materialize.d.ts +14 -0
  82. package/dist/mintlify/materialize.js +254 -0
  83. package/dist/mintlify/types.d.ts +74 -0
  84. package/dist/mintlify/types.js +1 -0
  85. package/dist/mintlify/validate.d.ts +7 -0
  86. package/dist/mintlify/validate.js +256 -0
  87. package/dist/next/assignments-navigation.d.ts +4 -0
  88. package/dist/next/assignments-navigation.js +18 -0
  89. package/dist/next/assignments-script.d.ts +3 -2
  90. package/dist/next/assignments-script.js +5 -5
  91. package/dist/next/error-pages.d.ts +4 -0
  92. package/dist/next/error-pages.js +4 -0
  93. package/dist/next/error-seat-payload.d.ts +2 -1
  94. package/dist/next/error-seat-payload.js +14 -24
  95. package/dist/next/error-seat.js +7 -2
  96. package/dist/next/hosted-page-target.d.ts +7 -0
  97. package/dist/next/hosted-page-target.js +39 -0
  98. package/dist/next/index.d.ts +1 -1
  99. package/dist/next/index.js +3 -1
  100. package/dist/next/page.d.ts +29 -6
  101. package/dist/next/page.js +47 -25
  102. package/dist/next/release-context.d.ts +29 -0
  103. package/dist/next/release-context.js +55 -0
  104. package/dist/next/render.d.ts +1 -1
  105. package/dist/next/render.js +15 -5
  106. package/dist/next/server.d.ts +8 -0
  107. package/dist/next/server.js +44 -3
  108. package/dist/next/vercel-edge-projection-v1.d.ts +0 -9
  109. package/dist/next/vercel-edge-projection-v1.js +9 -539
  110. package/dist/preview/core.d.ts +1 -0
  111. package/dist/preview/core.js +27 -0
  112. package/dist/react/VideoPlayer.d.ts +3 -3
  113. package/dist/react/VideoPlayer.js +2 -2
  114. package/dist/react/index.d.ts +4 -4
  115. package/dist/react/index.js +2 -3
  116. package/dist/registry.d.ts +4 -2
  117. package/dist/testing/conformance/checks.d.ts +2 -2
  118. package/dist/testing/conformance/checks.js +2 -0
  119. package/dist/testing/conformance/index.d.ts +1 -0
  120. package/dist/testing/conformance/index.js +1 -0
  121. package/dist/testing/conformance/next-stubs.d.ts +6 -0
  122. package/dist/testing/conformance/next-stubs.js +9 -0
  123. package/dist/testing/conformance/preset.d.ts +3 -2
  124. package/dist/testing/conformance/preset.js +15 -8
  125. package/dist/testing/conformance/root-identity.d.ts +31 -0
  126. package/dist/testing/conformance/root-identity.js +361 -0
  127. package/dist/testing/conformance/stubs/next-image.d.ts +7 -0
  128. package/dist/testing/conformance/stubs/next-image.js +5 -0
  129. package/dist/testing/conformance/stubs/next-link.d.ts +7 -0
  130. package/dist/testing/conformance/stubs/next-link.js +4 -0
  131. package/dist/testing/conformance/types.d.ts +5 -2
  132. package/dist/testing/index.d.ts +1 -1
  133. package/dist/testing/index.js +4 -3
  134. package/dist/types/block-ref.d.ts +2 -0
  135. package/dist/types/block-ref.js +3 -2
  136. package/dist/types/component.d.ts +9 -0
  137. package/dist/types/config.d.ts +0 -1
  138. package/dist/types/image.d.ts +54 -17
  139. package/dist/types/image.js +102 -18
  140. package/dist/types/index.d.ts +0 -1
  141. package/dist/types/index.js +0 -1
  142. package/dist/types/media.d.ts +14 -6
  143. package/dist/types/page.d.ts +0 -1
  144. package/dist/types/video.d.ts +7 -3
  145. package/migrations/conformance-registry.md +22 -0
  146. package/package.json +21 -7
  147. package/src/cli/compile-registry.mjs +30 -42
  148. package/src/cli/mintlify-render.mjs +9 -0
  149. package/src/cli/validate-content.mjs +52 -13
  150. package/src/types/block-ref.test.ts +60 -0
  151. package/src/types/block-ref.ts +4 -2
  152. package/src/types/component.ts +10 -0
  153. package/src/types/config.ts +0 -1
  154. package/src/types/image.ts +133 -29
  155. package/src/types/index.ts +0 -1
  156. package/src/types/page.ts +0 -1
  157. package/src/types/video.ts +1 -1
  158. package/templates/astro/starter-content/src/components/chrome/Footer.astro +5 -3
  159. package/templates/astro/starter-content/src/components/chrome/Header.astro +5 -3
  160. package/templates/astro/starter-content/src/components/elements/Button.astro +5 -2
  161. package/templates/astro/starter-content/src/components/elements/ButtonBlock.astro +5 -3
  162. package/templates/astro/starter-content/src/components/sections/ContainerSection.astro +4 -3
  163. package/templates/astro/starter-content/src/components/sections/FeatureCard.astro +4 -3
  164. package/templates/astro/starter-content/src/components/sections/FeatureGrid.astro +4 -3
  165. package/templates/astro/starter-content/src/components/sections/HomeHero.astro +5 -3
  166. package/templates/astro/starter-content/src/components/sections/PersonalizationChoice.astro +4 -3
  167. package/templates/astro/starter-content/src/components/sections/StatsBar.astro +4 -3
  168. package/templates/astro/starter-content/src/fixtures/atomicBlocks.ts +7 -6
  169. package/templates/astro/starter-content/src/fixtures/composition.ts +3 -2
  170. package/templates/astro/starter-content/src/fixtures/sections.ts +3 -3
  171. package/templates/astro/starter-content/src/stories/CompositionStoryFrame.astro +7 -2
  172. package/templates/astro/template/.aci.yaml.tmpl +0 -1
  173. package/templates/astro/template/package.json.tmpl +5 -5
  174. package/templates/astro/template/src/layouts/SiteLayout.astro +2 -2
  175. package/templates/common/.agents/skills/aci/SKILL.md +58 -1
  176. package/templates/common/.agents/skills/authoring/SKILL.md +203 -43
  177. package/templates/common/.agents/skills/authoring/references/block-registry.md +19 -3
  178. package/templates/common/.agents/skills/authoring/references/block-slots.md +3 -0
  179. package/templates/common/.agents/skills/authoring/references/contracts.md +9 -30
  180. package/templates/common/.agents/skills/authoring/references/repo-structure.md +5 -4
  181. package/templates/common/.agents/skills/authoring/references/sdk-server-functions.md +3 -3
  182. package/templates/common/.agents/skills/dam-delivery/SKILL.md +217 -0
  183. package/templates/common/.agents/skills/dam-delivery/references/permalink-grammar.md +167 -0
  184. package/templates/common/.agents/skills/migration/references/canon-migration.md +18 -67
  185. package/templates/common/.agents/skills/migration/references/selector-map.md +1 -1
  186. package/templates/common/.content/fragments/navbar/_index.json +24 -0
  187. package/templates/common/.content/fragments/site-footer/_index.json +61 -0
  188. package/templates/common/.content/pages/404/_index.json +4 -2
  189. package/templates/common/.content/pages/{home/_index.json → _index.json} +10 -1
  190. package/templates/common/.content/pages/error/_index.json +4 -2
  191. package/templates/common/.content/schemas/site.schema.json +11 -1
  192. package/templates/common/.content/templates/error.json +24 -0
  193. package/templates/common/.content/templates/home.json +28 -0
  194. package/templates/common/.content/templates/site.json +8 -0
  195. package/templates/common/.content/templates/standard.json +24 -0
  196. package/templates/common/.nvmrc +1 -1
  197. package/templates/common/starter-content/.content/.rules-baseline.json +8 -0
  198. package/templates/common/starter-content/.content/config/site.json +1 -1
  199. package/templates/common/starter-content/.content/content-rules/no-public-resource-paths.json +6 -0
  200. package/templates/common/starter-content/.content/dam/README.md +74 -0
  201. package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_home_hero → 000000000000000000000000101}/asset.json +1 -1
  202. package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_about_hero → 000000000000000000000000101}/governance.json +2 -1
  203. package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_about_hero → 000000000000000000000000102}/asset.json +1 -1
  204. package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_home_hero → 000000000000000000000000102}/governance.json +2 -1
  205. package/templates/common/starter-content/.content/fragments/navbar/_index.json +23 -20
  206. package/templates/common/starter-content/.content/fragments/site-footer/_index.json +59 -56
  207. package/templates/common/starter-content/.content/pages/404/_index.json +2 -2
  208. package/templates/common/starter-content/.content/pages/{home/_index.json → _index.json} +23 -3
  209. package/templates/common/starter-content/.content/pages/about/_index.json +5 -2
  210. package/templates/common/starter-content/.content/pages/error/_index.json +2 -2
  211. package/templates/common/starter-content/.content/patterns/components/button.json +55 -0
  212. package/templates/common/starter-content/.content/patterns/components/container.json +67 -0
  213. package/templates/common/starter-content/.content/patterns/components/feature_card.json +30 -0
  214. package/templates/common/starter-content/.content/patterns/components/feature_grid.json +59 -0
  215. package/templates/common/starter-content/.content/patterns/components/home_hero.json +151 -0
  216. package/templates/common/starter-content/.content/patterns/components/personalization_choice.json +42 -0
  217. package/templates/common/starter-content/.content/patterns/components/site-footer.json +203 -0
  218. package/templates/common/starter-content/.content/patterns/components/site-navigation.json +115 -0
  219. package/templates/common/starter-content/.content/patterns/components/stats_bar.json +45 -0
  220. package/templates/common/starter-content/.content/schemas/site.schema.json +11 -1
  221. package/templates/nextjs/starter-content/src/components/chrome/Footer.tsx +4 -3
  222. package/templates/nextjs/starter-content/src/components/chrome/Header.tsx +4 -3
  223. package/templates/nextjs/starter-content/src/components/elements/Button.tsx +4 -3
  224. package/templates/nextjs/starter-content/src/components/elements/ButtonBlock.tsx +4 -3
  225. package/templates/nextjs/starter-content/src/components/sections/ContainerSection.tsx +4 -2
  226. package/templates/nextjs/starter-content/src/components/sections/FeatureCard.tsx +4 -3
  227. package/templates/nextjs/starter-content/src/components/sections/FeatureGrid.tsx +4 -2
  228. package/templates/nextjs/starter-content/src/components/sections/HomeHero.tsx +4 -3
  229. package/templates/nextjs/starter-content/src/components/sections/PersonalizationChoice.tsx +4 -2
  230. package/templates/nextjs/starter-content/src/components/sections/StatsBar.tsx +4 -3
  231. package/templates/nextjs/template/.aci.yaml +9 -0
  232. package/templates/nextjs/template/.aci.yaml.tmpl +0 -1
  233. package/templates/nextjs/template/package.json.tmpl +4 -3
  234. package/templates/nextjs/template/src/app/_components/c15t-bridge.ts +30 -0
  235. package/templates/nextjs/template/src/app/_components/c15t-consent-ui.tsx +37 -0
  236. package/templates/nextjs/template/src/app/_components/consent-manager.css +63 -0
  237. package/templates/nextjs/template/src/app/_components/consent-manager.tsx +76 -0
  238. package/templates/nextjs/template/src/app/fonts/inter/OFL.txt +93 -0
  239. package/templates/nextjs/template/src/app/fonts/inter/inter-latin.woff2 +0 -0
  240. package/templates/nextjs/template/src/app/fonts/space-grotesk/OFL.txt +93 -0
  241. package/templates/nextjs/template/src/app/fonts/space-grotesk/space-grotesk-latin.woff2 +0 -0
  242. package/templates/nextjs/template/src/app/layout.tsx +12 -7
  243. package/templates/nextjs/template/tests/conformance.test.ts.tmpl +1 -0
  244. package/dist/define-layout.d.ts +0 -6
  245. package/dist/define-layout.js +0 -14
  246. package/dist/edge/personalization-core.d.ts +0 -11
  247. package/dist/edge/personalization-core.js +0 -269
  248. package/dist/next/collect-route.d.ts +0 -19
  249. package/dist/next/collect-route.js +0 -459
  250. package/dist/next/envelope-digest.d.ts +0 -21
  251. package/dist/next/envelope-digest.js +0 -58
  252. package/dist/next/middleware.d.ts +0 -40
  253. package/dist/next/middleware.js +0 -475
  254. package/dist/next/personalization-host.d.ts +0 -134
  255. package/dist/next/personalization-host.js +0 -270
  256. package/dist/next/root-layout.d.ts +0 -4
  257. package/dist/next/root-layout.js +0 -5
  258. package/dist/next/vercel-collect-sdk-route.d.ts +0 -27
  259. package/dist/next/vercel-collect-sdk-route.js +0 -181
  260. package/dist/next/vercel-personalization.d.ts +0 -4
  261. package/dist/next/vercel-personalization.js +0 -4
  262. package/dist/next/vercel-s3-personalization.d.ts +0 -79
  263. package/dist/next/vercel-s3-personalization.js +0 -570
  264. package/dist/types/layout.d.ts +0 -24
  265. package/src/types/layout.ts +0 -29
  266. package/templates/astro/starter-content/src/render/RenderPage.astro +0 -67
  267. package/templates/astro/template/src/cms/contracts/layouts/index.ts +0 -18
  268. package/templates/nextjs/starter-content/src/components/elements/AtomicBlock.tsx +0 -13
  269. package/templates/nextjs/template/src/cms/contracts/layouts/index.ts +0 -16
  270. /package/dist/{types/layout.js → cli/mintlify-render.d.mts} +0 -0
  271. /package/templates/common/.content/overlays/audience/preference-blue/pages/{home/_index.json → _index.json} +0 -0
  272. /package/templates/common/.content/overlays/audience/preference-red/pages/{home/_index.json → _index.json} +0 -0
  273. /package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_home_hero → 000000000000000000000000101}/editorial.json +0 -0
  274. /package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_home_hero → 000000000000000000000000101}/versions/v0001.json +0 -0
  275. /package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_about_hero → 000000000000000000000000102}/editorial.json +0 -0
  276. /package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_about_hero → 000000000000000000000000102}/versions/v0001.json +0 -0
@@ -1,9 +1,190 @@
1
+ // GENERATED by scripts/generate-data-models.go — DO NOT EDIT.
2
+ // Regenerate: make data-models
3
+ //
4
+ // Authoring data models for the audience-family and experimentation content
5
+ // documents, generated from the Go document readers' contracts (the readers
6
+ // stay the authority on what the API accepts; these give authors client-side
7
+ // validation that cannot drift from them). The canonical descriptor artifact
8
+ // is packages/sdk/schema/audience-data-models.v1.json, drift-gated in CI by
9
+ // make data-models-verify.
1
10
  import { z } from 'zod';
2
11
  import { defineDataModel } from './define-data-model.js';
3
- const resourceStatus = z.enum(['draft', 'enabled', 'disabled']);
4
- const experimentStatus = z.enum(['draft', 'running', 'paused', 'stopped', 'promoted', 'archived']);
5
- const experimentKind = z.enum(['ab', 'abn', 'xt']);
12
+ // Mirrors the ADR 0038 operator vocabulary for author-time DX; the expression
13
+ // kernel stays the authority, and the capability drift test pins this enum
14
+ // and the operand pairing to the generated artifact.
15
+ export const expressionOperators = ['equals', 'contains', 'startsWith', 'exists', 'greaterThan', 'greaterThanOrEqual', 'lessThan', 'lessThanOrEqual', 'before', 'after', 'between', 'in'];
6
16
  const scalarValue = z.union([z.string(), z.number(), z.boolean()]);
17
+ const maxInValues = 32;
18
+ const maxConditions = 64;
19
+ const maxExpressionDepth = 5;
20
+ const rangeBoundValue = z.union([z.number(), z.string()]);
21
+ const conditionValueSchema = z.union([
22
+ scalarValue,
23
+ z.array(scalarValue).min(1).max(maxInValues),
24
+ z.object({ min: rangeBoundValue, max: rangeBoundValue }).strict(),
25
+ ]);
26
+ const scalarOperandOperators = new Set(['equals', 'contains', 'startsWith']);
27
+ const numberOperandOperators = new Set(['greaterThan', 'greaterThanOrEqual', 'lessThan', 'lessThanOrEqual']);
28
+ const timestampOperandOperators = new Set(['before', 'after']);
29
+ // A condition is strict — the kernel's decoder retains unknown leaf keys so
30
+ // validation can reject them rather than silently dropping authored meaning.
31
+ const expressionConditionSchema = z.object({
32
+ field: z.string().min(1),
33
+ operator: z.enum(expressionOperators),
34
+ value: conditionValueSchema.optional(),
35
+ }).strict().superRefine((condition, context) => {
36
+ const { operator, value } = condition;
37
+ const fail = (message) => context.addIssue({ code: 'custom', path: ['value'], message });
38
+ if (operator === 'exists') {
39
+ if (value !== undefined && typeof value !== 'boolean')
40
+ fail('exists takes an optional boolean value');
41
+ return;
42
+ }
43
+ if (value === undefined) {
44
+ fail(`value is required for operator ${operator}`);
45
+ return;
46
+ }
47
+ if (scalarOperandOperators.has(operator) && (typeof value === 'object')) {
48
+ fail(`${operator} takes one scalar value`);
49
+ }
50
+ else if (numberOperandOperators.has(operator) && typeof value !== 'number') {
51
+ fail(`${operator} takes one finite JSON number`);
52
+ }
53
+ else if (timestampOperandOperators.has(operator) && typeof value !== 'string') {
54
+ fail(`${operator} takes one RFC 3339 timestamp string`);
55
+ }
56
+ else if (operator === 'between' && (typeof value !== 'object' || Array.isArray(value))) {
57
+ fail('between takes a {min, max} object');
58
+ }
59
+ else if (operator === 'in' && !Array.isArray(value)) {
60
+ fail(`in takes an array of 1 to ${maxInValues} scalars`);
61
+ }
62
+ });
63
+ // Groups are strict too: a member carrying all/any is a group, and any other
64
+ // key on it is an unsupported expression group the kernel refuses. An empty
65
+ // group is refused by validateAuthoredExpression below, mirroring the
66
+ // kernel's members-not-leaves emptiness rule.
67
+ const expressionGroupSchema = z.lazy(() => z.object({
68
+ all: z.array(expressionMemberSchema).optional(),
69
+ any: z.array(expressionMemberSchema).optional(),
70
+ }).strict());
71
+ const expressionMemberSchema = z.lazy(() => z.union([expressionConditionSchema, expressionGroupSchema]));
72
+ const requestScopeFields = [
73
+ { field: 'referrer' },
74
+ { field: 'query.', prefix: true },
75
+ { field: 'header.', prefix: true },
76
+ { field: 'cookie.', prefix: true },
77
+ { field: 'geo.', prefix: true },
78
+ { field: 'utm.', prefix: true },
79
+ { field: 'device.class', operators: ['equals', 'in', 'exists'], values: ['desktop', 'mobile', 'tablet', 'unknown'] },
80
+ { field: 'time.now', operators: ['before', 'after', 'between', 'exists'], instant: true },
81
+ { field: 'audience.memberships', operators: ['equals', 'in', 'exists'] },
82
+ ];
83
+ const eventScopeFields = [
84
+ { field: 'referrer' },
85
+ { field: 'page.path' },
86
+ { field: 'properties.', prefix: true },
87
+ { field: 'utm.', prefix: true },
88
+ ];
89
+ function expressionFieldRule(fields, field) {
90
+ for (const rule of fields) {
91
+ if (rule.prefix) {
92
+ // A prefix rule needs at least one character past the prefix, exactly
93
+ // like the kernel's fieldRuleFor.
94
+ if (field.length > rule.field.length && field.startsWith(rule.field))
95
+ return rule;
96
+ continue;
97
+ }
98
+ if (rule.field === field)
99
+ return rule;
100
+ }
101
+ return undefined;
102
+ }
103
+ // validateAuthoredExpression mirrors the kernel's validation walk
104
+ // (personalization/expression Validate): one diagnostic per broken node, the
105
+ // leaf budget counted with the same depth cutoff, emptiness judged on
106
+ // members not leaves.
107
+ function validateAuthoredExpression(expressionValue, fields, allowsEmpty, context) {
108
+ const fail = (path, message) => context.addIssue({ code: 'custom', path, message });
109
+ const walk = (members, path, depth) => {
110
+ (members ?? []).forEach((member, index) => {
111
+ const memberPath = [...path, index];
112
+ if (!('all' in member || 'any' in member)) {
113
+ validateScopedCondition(member, fields, memberPath, fail);
114
+ return;
115
+ }
116
+ const group = member;
117
+ // One diagnostic per broken node, and no descent past it: a too-deep
118
+ // branch reports its depth once rather than repeating for every
119
+ // condition buried inside it.
120
+ if (depth + 1 > maxExpressionDepth) {
121
+ fail(memberPath, `must not nest more than ${maxExpressionDepth} levels deep`);
122
+ return;
123
+ }
124
+ if ((group.all?.length ?? 0) + (group.any?.length ?? 0) === 0) {
125
+ fail(memberPath, 'must include at least one all or any condition');
126
+ return;
127
+ }
128
+ walk(group.all, [...memberPath, 'all'], depth + 1);
129
+ walk(group.any, [...memberPath, 'any'], depth + 1);
130
+ });
131
+ };
132
+ if ((expressionValue.all?.length ?? 0) + (expressionValue.any?.length ?? 0) === 0 && !allowsEmpty) {
133
+ fail([], 'must include at least one all or any condition');
134
+ }
135
+ if (countExpressionLeaves(expressionValue.all, 1) + countExpressionLeaves(expressionValue.any, 1) > maxConditions) {
136
+ fail([], `must contain at most ${maxConditions} conditions`);
137
+ }
138
+ walk(expressionValue.all, ['all'], 1);
139
+ walk(expressionValue.any, ['any'], 1);
140
+ }
141
+ // countExpressionLeaves counts leaf conditions across the tree with the same
142
+ // depth cutoff the validating walk applies, so the two limits never disagree
143
+ // about a tree.
144
+ function countExpressionLeaves(members, depth) {
145
+ let total = 0;
146
+ for (const member of members ?? []) {
147
+ if (!('all' in member || 'any' in member)) {
148
+ total++;
149
+ continue;
150
+ }
151
+ if (depth + 1 > maxExpressionDepth)
152
+ continue;
153
+ const group = member;
154
+ total += countExpressionLeaves(group.all, depth + 1) + countExpressionLeaves(group.any, depth + 1);
155
+ }
156
+ return total;
157
+ }
158
+ function validateScopedCondition(condition, fields, path, fail) {
159
+ const rule = expressionFieldRule(fields, condition.field);
160
+ if (!rule) {
161
+ fail([...path, 'field'], `field ${condition.field} is not supported in this scope`);
162
+ return;
163
+ }
164
+ if (rule.operators && !rule.operators.includes(condition.operator)) {
165
+ fail([...path, 'operator'], `operator ${condition.operator} is not supported for ${condition.field}`);
166
+ return;
167
+ }
168
+ if (rule.values && (condition.operator === 'equals' || condition.operator === 'in')) {
169
+ const members = condition.operator === 'in' && Array.isArray(condition.value) ? condition.value : [condition.value];
170
+ for (const memberValue of members) {
171
+ if (typeof memberValue === 'string' && !rule.values.includes(memberValue)) {
172
+ fail([...path, 'value'], `value ${JSON.stringify(memberValue)} is not supported for ${condition.field}`);
173
+ }
174
+ }
175
+ }
176
+ // An instant field narrows a dual-typed operand: between on time.now takes
177
+ // timestamp bounds, so a numeric range cannot validate and then sit
178
+ // silently inert at execution.
179
+ if (rule.instant && condition.operator === 'between') {
180
+ const range = condition.value;
181
+ if (typeof range?.min !== 'string' || typeof range?.max !== 'string') {
182
+ fail([...path, 'value'], `between takes timestamp bounds on ${condition.field}`);
183
+ }
184
+ }
185
+ }
186
+ const requestRuleExpressionSchema = expressionGroupSchema.superRefine((expressionValue, context) => validateAuthoredExpression(expressionValue, requestScopeFields, false, context));
187
+ const eventRuleExpressionSchema = expressionGroupSchema.superRefine((expressionValue, context) => validateAuthoredExpression(expressionValue, eventScopeFields, true, context));
7
188
  // Largest whole-day window whose nanoseconds fit in Go's time.Duration:
8
189
  // floor(MaxInt64 / (24 * time.Hour)) = 106751.
9
190
  const maxCriteriaWithinDays = 106751;
@@ -16,12 +197,20 @@ const criteriaExperimentSchema = z.object({
16
197
  experimentId: z.string().regex(/\S/, 'experimentId is required'),
17
198
  armId: z.string().optional(),
18
199
  }).strict();
200
+ // The server-stamped request geo filter (CollectGeo): normalized codes,
201
+ // exact match. geo requires country or region — the write gate owns that
202
+ // rule (a refinement would make this model uncompilable to JSON Schema).
203
+ const criteriaGeoSchema = z.object({
204
+ country: z.string().regex(/^[A-Z]{2}$/, 'geo.country must be an uppercase ISO 3166-1 alpha-2 code').optional(),
205
+ region: z.string().regex(/^[A-Z0-9]{1,3}$/, 'geo.region must be 1-3 uppercase alphanumerics').optional(),
206
+ }).strict();
19
207
  const criteriaFilterShape = {
20
208
  event: z.string().regex(/\S/, 'event is required'),
21
209
  path: z.string().regex(/^(?!\*$)/, 'path must not be a lone "*"').optional(),
22
210
  referrer: z.string().regex(/^(?!\*$)/, 'referrer must not be a lone "*"').optional(),
23
211
  withinDays: z.number().int().positive().max(maxCriteriaWithinDays).optional(),
24
212
  experiment: criteriaExperimentSchema.optional(),
213
+ geo: criteriaGeoSchema.optional(),
25
214
  properties: z.record(z.string(), criteriaFilterValue).optional(),
26
215
  };
27
216
  const segmentCriteriaConditionSchema = z.discriminatedUnion('type', [
@@ -51,93 +240,167 @@ const segmentCriteriaSchema = z.union([
51
240
  none: z.array(segmentCriteriaConditionSchema).optional(),
52
241
  }).strict(),
53
242
  ]);
54
- const requestRuleConditionSchema = z.object({
55
- field: z.string().min(1),
56
- operator: z.enum(['equals', 'contains', 'startsWith', 'exists']),
57
- value: scalarValue.optional(),
58
- });
59
- const requestRuleExpressionSchema = z.object({
60
- all: z.array(requestRuleConditionSchema).optional(),
61
- any: z.array(requestRuleConditionSchema).optional(),
62
- });
243
+ // The mapped-json extraction matrix (validateProviderMapping): mapping is
244
+ // forbidden for aep-batch-export and required for mapped-json, whose
245
+ // membership declares exactly one source. Pointer grammar and pattern
246
+ // compilation stay with the write gate.
247
+ function providerFormatMappingMatrix(props, context) {
248
+ const fail = (path, message) => context.addIssue({ code: 'custom', path, message });
249
+ if (props.format === "aep-batch-export") {
250
+ if (props.mapping !== undefined) {
251
+ fail(['mapping'], 'mapping is only valid for format "mapped-json"');
252
+ }
253
+ return;
254
+ }
255
+ if (props.mapping === undefined) {
256
+ fail(['mapping'], 'mapping is required for format "mapped-json"');
257
+ return;
258
+ }
259
+ const membership = props.mapping.membership;
260
+ if (membership === undefined) {
261
+ fail(['mapping', 'membership'], 'mapping.membership is required');
262
+ return;
263
+ }
264
+ const sources = [membership.segmentIdsPath, membership.objectNamePattern]
265
+ .filter((value) => value !== undefined && value !== '');
266
+ if (sources.length !== 1) {
267
+ fail(['mapping', 'membership'], 'mapping.membership must declare exactly one of segmentIdsPath or objectNamePattern');
268
+ }
269
+ }
270
+ // scopedID is the experiment scope's path-safe grammar (validateScope): ""
271
+ // is the homepage route; segments are identifiers; ".." is rejected.
272
+ const scopedID = z.string().refine((value) => {
273
+ const trimmed = value.replace(/^\/+|\/+$/g, '');
274
+ if (trimmed === '')
275
+ return true;
276
+ if (trimmed.includes('..'))
277
+ return false;
278
+ return trimmed.split('/').every((part) => /^[A-Za-z0-9_-]+$/.test(part));
279
+ }, { message: 'must be a path-safe scoped id' });
63
280
  export const segmentDataModel = defineDataModel({
64
281
  type: 'segment',
65
282
  schema: z.object({
66
- id: z.string().min(1),
67
- displayName: z.string().min(1),
68
- status: resourceStatus,
69
- priority: z.number().int().nonnegative().optional(),
283
+ id: z.string().regex(/^(?!base$)[A-Za-z0-9_-]+$/, 'must contain only letters, numbers, underscores, or hyphens; "base" is reserved (it names the base variant key and the no-leg-won selectedLeg sentinel, ADR 0050)'),
284
+ displayName: z.string().regex(/\S/, 'displayName is required'),
285
+ status: z.enum(['draft', 'enabled', 'disabled']),
286
+ priority: z.number().int().min(0).optional(),
70
287
  description: z.string().optional(),
288
+ // Absent = a static label the deriver never evaluates. The conditions budget (64) and geo's country-or-region requirement stay with the write gate: this model compiles to JSON Schema (compileSchema), which cannot represent refinements.
71
289
  criteria: segmentCriteriaSchema.optional(),
72
290
  }),
73
291
  });
74
292
  export const audienceDataModel = defineDataModel({
75
293
  type: 'audience',
76
294
  schema: z.object({
77
- id: z.string().min(1),
78
- displayName: z.string().min(1),
79
- status: resourceStatus,
80
- priority: z.number().int().nonnegative(),
295
+ id: z.string().regex(/^(?!base$)[A-Za-z0-9_-]+$/, 'must contain only letters, numbers, underscores, or hyphens; "base" is reserved (it names the base variant key and the no-leg-won selectedLeg sentinel, ADR 0050)'),
296
+ displayName: z.string().regex(/\S/, 'displayName is required'),
297
+ description: z.string().optional(),
298
+ status: z.enum(['draft', 'enabled', 'disabled']),
299
+ priority: z.number().int().min(0),
81
300
  membershipSource: z.enum(['rule', 'segment', 'manual', 'external', 'none']),
82
301
  ruleRef: z.string().optional(),
83
302
  segmentRef: z.string().optional(),
84
303
  providerRef: z.string().optional(),
304
+ externalSegmentId: z.string().min(1).max(256).refine((value) => value.trim() === value, { message: 'must not have leading or trailing whitespace' }).optional(),
85
305
  }),
86
306
  });
307
+ export const audienceProviderDataModel = defineDataModel({
308
+ type: 'audience_provider',
309
+ schema: z.object({
310
+ id: z.string().regex(/^(?!base$)[A-Za-z0-9_-]+$/, 'must contain only letters, numbers, underscores, or hyphens; "base" is reserved (it names the base variant key and the no-leg-won selectedLeg sentinel, ADR 0050)'),
311
+ displayName: z.string().regex(/\S/, 'displayName is required'),
312
+ status: z.enum(['draft', 'enabled', 'disabled']),
313
+ format: z.enum(['aep-batch-export', 'mapped-json']),
314
+ identity: z.object({
315
+ sourceNamespace: z.string().regex(/\S/, 'sourceNamespace is required').refine((value) => value.trim().toLowerCase() !== 'aci_id', { message: 'must be an external namespace, not the first-party aci_id namespace' }),
316
+ absencePolicy: z.enum(['unlink', 'retain']),
317
+ resolution: z.array(z.object({
318
+ field: z.string().regex(/^(cookie|query|header)\.[A-Za-z0-9_-]+$/),
319
+ }).strict()).optional(),
320
+ }).strict(),
321
+ membershipTtlDays: z.number().int().min(1),
322
+ // The write gate compiles it (CompileProviderDumpSchema); the schema only pins the object envelope.
323
+ dumpSchema: z.record(z.string(), z.unknown()).optional(),
324
+ // mapped-json extraction declarations (required for that format; the Go write gate owns the full matrix — valid RFC 6901 pointers, a compiling single-capture pattern).
325
+ mapping: z.object({
326
+ identity: z.object({
327
+ path: z.string().regex(/^\//),
328
+ }).strict(),
329
+ membership: z.object({
330
+ segmentIdsPath: z.string().regex(/^\//).optional(),
331
+ objectNamePattern: z.string().regex(/\S/, 'objectNamePattern must not be blank').optional(),
332
+ }).strict(),
333
+ qualifiedAtPath: z.string().regex(/^\//).optional(),
334
+ }).strict().optional(),
335
+ discovery: z.object({
336
+ mode: z.enum(['draft', 'report-only']),
337
+ maxPerImport: z.number().int().min(1).max(1000).optional(),
338
+ }).strict(),
339
+ limits: z.object({
340
+ maxDumpBytes: z.number().int().min(1).optional(),
341
+ maxRecords: z.number().int().min(1).optional(),
342
+ maxIdentitiesPerRecord: z.number().int().min(1).optional(),
343
+ }).strict().optional(),
344
+ }).strict().superRefine(providerFormatMappingMatrix),
345
+ });
87
346
  export const audienceRuleDataModel = defineDataModel({
88
347
  type: 'audience_rule',
89
348
  schema: z.object({
90
- id: z.string().min(1),
91
- status: resourceStatus,
349
+ id: z.string().regex(/^(?!base$)[A-Za-z0-9_-]+$/, 'must contain only letters, numbers, underscores, or hyphens; "base" is reserved (it names the base variant key and the no-leg-won selectedLeg sentinel, ADR 0050)'),
350
+ status: z.enum(['draft', 'enabled', 'disabled']),
92
351
  description: z.string().optional(),
93
352
  expression: requestRuleExpressionSchema,
94
353
  }),
95
354
  });
96
- export const visitorProfileDataModel = defineDataModel({
97
- type: 'visitor_profile',
98
- schema: z.object({
99
- visitorId: z.string().min(1),
100
- audienceIds: z.array(z.string().min(1)).optional(),
101
- segmentIds: z.array(z.string().min(1)).optional(),
102
- traits: z.record(z.string(), scalarValue).optional(),
103
- updatedAt: z.string().optional(),
104
- }),
105
- });
106
355
  export const goalDataModel = defineDataModel({
107
356
  type: 'goal',
108
357
  schema: z.object({
109
- id: z.string().min(1),
110
- displayName: z.string().min(1),
111
- status: resourceStatus,
112
- eventName: z.string().min(1),
113
- eventRule: requestRuleExpressionSchema.optional(),
358
+ id: z.string().regex(/^[A-Za-z0-9_-]+$/, 'must contain only letters, numbers, underscores, or hyphens'),
359
+ displayName: z.string().regex(/\S/, 'displayName is required'),
360
+ status: z.enum(['draft', 'enabled', 'disabled']),
361
+ eventName: z.string().regex(/^(?:rum\.page_view|rum\.link_click|rum\.variation_view|rum\.web_vital|experiment\.exposure|experiment\.conversion|experiment\.engagement|custom\.[^\s/\\]+)$/, 'must be one of the rum.* or experiment.* events, or use the custom.* namespace').max(128),
362
+ // Event scope (expression.ScopeEvent): referrer, page.path, properties.*, utm.* — request-only fields are rejected.
363
+ eventRule: eventRuleExpressionSchema.optional(),
114
364
  }),
115
365
  });
116
366
  export const experimentDataModel = defineDataModel({
117
367
  type: 'experiment',
118
368
  schema: z.object({
119
- id: z.string().min(1),
120
- displayName: z.string().min(1),
121
- status: experimentStatus,
122
- kind: experimentKind,
123
- priority: z.number().int().nonnegative(),
369
+ id: z.string().regex(/^[A-Za-z0-9_-]+$/, 'must contain only letters, numbers, underscores, or hyphens'),
370
+ displayName: z.string().regex(/\S/, 'displayName is required'),
371
+ status: z.enum(['draft', 'running', 'paused', 'stopped', 'promoted', 'archived']),
372
+ kind: z.enum(['ab', 'abn', 'xt']),
373
+ priority: z.number().int().min(0),
124
374
  allocationVersion: z.string().optional(),
125
375
  salt: z.string().optional(),
376
+ // At least one route or fragment (validateScope); "" is the homepage.
126
377
  scope: z.object({
127
- routes: z.array(z.string().min(1)).optional(),
128
- fragments: z.array(z.string().min(1)).optional(),
129
- }),
378
+ routes: z.array(scopedID).optional(),
379
+ fragments: z.array(scopedID).optional(),
380
+ }).refine((value) => Object.values(value).some((list) => Array.isArray(list) && list.length > 0), { message: 'must include at least one route or fragment' }),
381
+ eligibility: z.object({
382
+ // Top-level ref grammar: $ref:audiences/{id}, one path-safe segment (parseAudienceRef).
383
+ audienceRef: z.string().regex(/^\$ref:audiences\/[A-Za-z0-9_-]+$/),
384
+ mode: z.enum(['claims', 'selected']).optional(),
385
+ }).strict().optional(),
130
386
  primaryGoalRef: z.string().optional(),
131
387
  primaryGoalId: z.string().optional(),
132
- goalRefs: z.array(z.string().min(1)).optional(),
133
- goalIds: z.array(z.string().min(1)).optional(),
388
+ goalRefs: z.array(z.string().regex(/\S/, 'entries must be non-blank')).optional(),
389
+ goalIds: z.array(z.string().regex(/\S/, 'entries must be non-blank')).optional(),
390
+ promotedArmId: z.string().optional(),
391
+ promotedAt: z.string().optional(),
392
+ horizon: z.object({
393
+ uniqueExposedPerArm: z.number().int().min(1),
394
+ conversionWindowDays: z.number().int().min(1).max(90).optional(),
395
+ declaredAfterRun: z.literal(true).optional(),
396
+ }).strict().optional(),
134
397
  arms: z.array(z.object({
135
- id: z.string().min(1),
136
- displayName: z.string().min(1),
398
+ id: z.string().regex(/^[A-Za-z0-9_-]+$/, 'must contain only letters, numbers, underscores, or hyphens'),
399
+ displayName: z.string().regex(/\S/, 'displayName is required'),
137
400
  role: z.enum(['control', 'variant']),
138
401
  allocationBasisPoints: z.number().int().min(0).max(10000).optional(),
139
402
  rule: requestRuleExpressionSchema.optional(),
140
- })).min(1),
403
+ }).strict()).min(1),
141
404
  }),
142
405
  });
143
406
  export const experimentationDataModels = [
@@ -148,6 +411,6 @@ export const audienceDataModels = [
148
411
  segmentDataModel,
149
412
  audienceDataModel,
150
413
  audienceRuleDataModel,
151
- visitorProfileDataModel,
414
+ audienceProviderDataModel,
152
415
  ...experimentationDataModels,
153
416
  ];
@@ -1,2 +1,6 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ export function compileRegistry(options: any): Promise<{
3
+ registry: string;
4
+ schemas: string[];
5
+ }>;
6
+ export function generatePageSchema(schemaDir: any, componentEntries: any, outDir: any): Promise<string>;
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
+ import { realpathSync } from 'node:fs';
2
3
  import { mkdir, writeFile } from 'node:fs/promises';
3
4
  import path from 'node:path';
4
- import { pathToFileURL } from 'node:url';
5
+ import { fileURLToPath, pathToFileURL } from 'node:url';
5
6
  import { z } from 'zod';
6
7
  import { ImageSchema } from '../types/image.ts';
7
8
  import { VideoSchema } from '../types/video.ts';
@@ -18,23 +19,27 @@ try {
18
19
  globalThis.React = React;
19
20
  }
20
21
  catch { /* react not available — component files without JSX will still work */ }
21
- const args = parseArgs(process.argv.slice(2));
22
- try {
23
- const result = await compileRegistry(args);
24
- console.log(JSON.stringify({ success: true, ...result, errors: [] }, null, 2));
25
- }
26
- catch (error) {
27
- console.error(JSON.stringify({ success: false, errors: [String(error?.message ?? error)] }, null, 2));
28
- process.exitCode = 1;
22
+ if (isMainModule()) {
23
+ const args = parseArgs(process.argv.slice(2));
24
+ try {
25
+ const result = await compileRegistry(args);
26
+ console.log(JSON.stringify({ success: true, ...result, errors: [] }, null, 2));
27
+ }
28
+ catch (error) {
29
+ console.error(JSON.stringify({ success: false, errors: [String(error?.message ?? error)] }, null, 2));
30
+ process.exitCode = 1;
31
+ }
29
32
  }
30
33
  async function compileRegistry(options) {
31
- const required = ['components', 'layouts', 'output'];
34
+ if (options.layouts) {
35
+ throw new Error('--layouts is retired; author page composition under /templates instead');
36
+ }
37
+ const required = ['components', 'output'];
32
38
  for (const key of required) {
33
39
  if (!options[key])
34
40
  throw new Error(`missing --${key}`);
35
41
  }
36
42
  const components = await loadDefaultArray(options.components, 'component registry');
37
- const layouts = await loadDefaultArray(options.layouts, 'layout registry');
38
43
  const dataModels = options.data
39
44
  ? await loadDefaultArray(options.data, 'data model registry')
40
45
  : [];
@@ -62,8 +67,6 @@ async function compileRegistry(options) {
62
67
  ...(Object.keys(slots).length ? { slots } : {}),
63
68
  });
64
69
  }
65
- const seenLayouts = new Set();
66
- const layoutEntries = layouts.map((layout, index) => validateLayout(layout, index, seenLayouts));
67
70
  const seenDataModels = new Set();
68
71
  const dataModelEntries = [];
69
72
  if (dataModels.length > 0) {
@@ -95,7 +98,6 @@ async function compileRegistry(options) {
95
98
  manifestVersion: '1',
96
99
  generatedAt: new Date().toISOString(),
97
100
  components: componentEntries,
98
- layouts: layoutEntries,
99
101
  dataModels: dataModelEntries,
100
102
  };
101
103
  const registryPath = path.join(outDir, 'registry.json');
@@ -106,7 +108,7 @@ async function compileRegistry(options) {
106
108
  }
107
109
  async function loadDefaultArray(modulePath, label) {
108
110
  const mod = await import(pathToFileURL(path.resolve(modulePath)).href);
109
- const value = mod.default ?? mod.registry ?? mod.components ?? mod.layouts ?? mod.dataModels;
111
+ const value = mod.default ?? mod.registry ?? mod.components ?? mod.dataModels;
110
112
  if (!Array.isArray(value))
111
113
  throw new Error(`${label} must export an array`);
112
114
  return value;
@@ -350,12 +352,10 @@ function isImageSchema(schema) {
350
352
  return compiled?.type === 'object'
351
353
  && properties.$type?.const === 'gradial.image'
352
354
  && properties.assetId?.type === 'string'
353
- && properties.versionId?.type === 'string'
354
355
  && properties.alt?.type === 'string'
355
356
  && properties.sources?.type === 'array'
356
357
  && required.has('$type')
357
358
  && required.has('assetId')
358
- && required.has('versionId')
359
359
  && required.has('alt')
360
360
  && required.has('sources');
361
361
  }
@@ -394,32 +394,6 @@ function unwrapSchema(schema) {
394
394
  }
395
395
  return current;
396
396
  }
397
- function validateLayout(layout, index, seen) {
398
- if (!layout || typeof layout !== 'object')
399
- throw new Error(`layouts[${index}] must be an object`);
400
- if (!layout.name)
401
- throw new Error(`layouts[${index}].name is required`);
402
- if (seen.has(layout.name))
403
- throw new Error(`duplicate layout name ${layout.name}`);
404
- seen.add(layout.name);
405
- if (!Array.isArray(layout.slots))
406
- throw new Error(`layouts[${index}].slots must be an array`);
407
- const slots = [];
408
- const seenSlots = new Set();
409
- for (const [slotIndex, slot] of layout.slots.entries()) {
410
- if (!slot?.name)
411
- throw new Error(`layouts[${index}].slots[${slotIndex}].name is required`);
412
- if (seenSlots.has(slot.name))
413
- throw new Error(`duplicate slot name ${slot.name} in layout ${layout.name}`);
414
- seenSlots.add(slot.name);
415
- slots.push({ name: slot.name, required: Boolean(slot.required) });
416
- }
417
- const entry = { name: layout.name, slots };
418
- if (layout.defaults && typeof layout.defaults === 'object') {
419
- entry.defaults = layout.defaults;
420
- }
421
- return entry;
422
- }
423
397
  function compileSchema(schema, name) {
424
398
  const toJSONSchema = z.toJSONSchema;
425
399
  if (typeof toJSONSchema !== 'function') {
@@ -440,9 +414,10 @@ async function generatePageSchema(schemaDir, componentEntries, outDir) {
440
414
  const defs = {
441
415
  baseBlock: {
442
416
  type: 'object',
443
- required: ['id', 'component', 'props'],
417
+ required: ['$type', 'id', 'component', 'props'],
444
418
  additionalProperties: true,
445
419
  properties: {
420
+ $type: { const: 'component' },
446
421
  id: { type: 'string', minLength: 1 },
447
422
  component: { type: 'string' },
448
423
  props: { type: 'object' },
@@ -512,13 +487,12 @@ async function generatePageSchema(schemaDir, componentEntries, outDir) {
512
487
  $id: 'https://baremetal.local/schemas/page.schema.json',
513
488
  title: 'bare-metal Page',
514
489
  type: 'object',
515
- required: ['$type', 'id', 'status', 'layout', 'metadata', 'regions'],
490
+ required: ['$type', 'id', 'status', 'metadata', 'regions'],
516
491
  additionalProperties: true,
517
492
  properties: {
518
493
  $type: { const: 'page' },
519
494
  id: { type: 'string', minLength: 1 },
520
495
  status: { type: 'string', minLength: 1 },
521
- layout: { type: 'string', minLength: 1 },
522
496
  metadata: {
523
497
  type: 'object',
524
498
  required: ['title'],
@@ -757,3 +731,14 @@ function parseArgs(argv) {
757
731
  }
758
732
  return out;
759
733
  }
734
+ function isMainModule() {
735
+ if (!process.argv[1])
736
+ return false;
737
+ try {
738
+ return realpathSync(fileURLToPath(import.meta.url)) === realpathSync(path.resolve(process.argv[1]));
739
+ }
740
+ catch {
741
+ return false;
742
+ }
743
+ }
744
+ export { compileRegistry, generatePageSchema };
@@ -0,0 +1,8 @@
1
+ import path from 'node:path';
2
+ import { loadCompiledMintlifySource, materializeMintlifySite } from '../../dist/mintlify/index.js';
3
+ const [sourceArg, compiledArg, outputArg] = process.argv.slice(2);
4
+ if (!sourceArg || !compiledArg || !outputArg) {
5
+ throw new Error('Mintlify renderer requires content, compiled, and output directories');
6
+ }
7
+ const source = await loadCompiledMintlifySource(path.resolve(sourceArg), path.resolve(compiledArg));
8
+ await materializeMintlifySite({ ...source, outputDir: path.resolve(outputArg) });
@@ -1,2 +1,32 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ export function validateContent(options: any): Promise<{
3
+ warnings?: any[] | undefined;
4
+ success: boolean;
5
+ pagesValidated: number;
6
+ blocksValidated: number;
7
+ errors: ({
8
+ page: string;
9
+ message: string;
10
+ fragment?: undefined;
11
+ component?: undefined;
12
+ path?: undefined;
13
+ } | {
14
+ fragment: string;
15
+ message: string;
16
+ page?: undefined;
17
+ component?: undefined;
18
+ path?: undefined;
19
+ } | {
20
+ fragment: string;
21
+ component: any;
22
+ message: string;
23
+ page?: undefined;
24
+ path?: undefined;
25
+ } | {
26
+ fragment: string;
27
+ component: any;
28
+ path: any;
29
+ message: any;
30
+ page?: undefined;
31
+ })[];
32
+ }>;