@gradial/aci 0.1.52-rc.8 → 0.1.52
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/CHANGELOG.md +126 -0
- package/README.md +86 -5
- package/dist/astro/error-page.js +1 -0
- package/dist/astro/index.js +4 -0
- package/dist/astro/page.d.ts +5 -2
- package/dist/astro/page.js +35 -23
- package/dist/astro/preview.js +2 -17
- package/dist/audience-data-models.d.ts +308 -197
- package/dist/audience-data-models.js +313 -51
- package/dist/cli/compile-registry.mjs +6 -34
- package/dist/cli/mintlify-render.mjs +8 -0
- package/dist/collect/aci.js +1 -1
- package/dist/collect/aci.js.sha256 +1 -1
- package/dist/collect/api.d.ts +6 -7
- package/dist/collect/api.js +21 -8
- package/dist/collect/assignments.d.ts +7 -9
- package/dist/collect/assignments.js +23 -32
- package/dist/collect/batcher.d.ts +9 -7
- package/dist/collect/batcher.js +6 -5
- package/dist/collect/consent/envelope.d.ts +7 -1
- package/dist/collect/consent/envelope.js +3 -1
- package/dist/collect/consent/mapping.d.ts +2 -0
- package/dist/collect/consent/mapping.js +1 -0
- package/dist/collect/consenthold.d.ts +38 -0
- package/dist/collect/consenthold.js +82 -0
- package/dist/collect/declarative.js +7 -5
- package/dist/collect/gapbuffer.d.ts +13 -4
- package/dist/collect/gapbuffer.js +21 -10
- package/dist/collect/index.js +78 -21
- package/dist/collect/pageviews.d.ts +11 -11
- package/dist/collect/pageviews.js +27 -22
- package/dist/consent-banner/index.d.ts +17 -0
- package/dist/consent-banner/index.js +23 -0
- package/dist/content/index.d.ts +2 -1
- package/dist/content/index.js +4 -1
- package/dist/content/provider.d.ts +16 -2
- package/dist/content/provider.js +5 -1
- package/dist/content/resolve-slots.d.ts +24 -2
- package/dist/content/resolve-slots.js +127 -21
- package/dist/content/types.d.ts +5 -1
- package/dist/edge/expression-capabilities.v1.d.ts +106 -0
- package/dist/edge/expression-capabilities.v1.js +165 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.js +0 -1
- package/dist/internal/domain/edge/identity.d.ts +47 -4
- package/dist/internal/domain/edge/identity.js +130 -26
- package/dist/internal/domain/edge/index.d.ts +1 -1
- package/dist/internal/domain/edge/index.js +1 -1
- package/dist/internal/domain/edge/mimic_token.d.ts +14 -0
- package/dist/internal/domain/edge/mimic_token.js +125 -0
- package/dist/internal/domain/edge/personalization-core.d.ts +61 -22
- package/dist/internal/domain/edge/personalization-core.js +790 -200
- package/dist/internal/domain/edge/personalization_index.d.ts +17 -0
- package/dist/internal/domain/edge/personalization_index.js +55 -0
- package/dist/internal/domain/edge/resolve.js +76 -39
- package/dist/internal/domain/edge/testdata/flat_only_validator_v1.d.ts +10 -0
- package/dist/internal/domain/edge/testdata/flat_only_validator_v1.js +253 -0
- package/dist/internal/domain/edge/types.d.ts +82 -78
- package/dist/internal/domain/edge/types.js +3 -3
- package/dist/internal/domain/edge/variant_key.d.ts +4 -0
- package/dist/internal/domain/edge/variant_key.js +56 -0
- package/dist/internal/platform/collectionstore/errors.d.ts +24 -0
- package/dist/internal/platform/collectionstore/errors.js +48 -0
- package/dist/mintlify/blocks.d.ts +6 -0
- package/dist/mintlify/blocks.js +201 -0
- package/dist/mintlify/config.d.ts +3 -0
- package/dist/mintlify/config.js +77 -0
- package/dist/mintlify/index.d.ts +3 -0
- package/dist/mintlify/index.js +2 -0
- package/dist/mintlify/json.d.ts +8 -0
- package/dist/mintlify/json.js +37 -0
- package/dist/mintlify/materialize.d.ts +14 -0
- package/dist/mintlify/materialize.js +254 -0
- package/dist/mintlify/types.d.ts +74 -0
- package/dist/mintlify/types.js +1 -0
- package/dist/mintlify/validate.d.ts +7 -0
- package/dist/mintlify/validate.js +256 -0
- package/dist/next/assignments-navigation.d.ts +4 -0
- package/dist/next/assignments-navigation.js +18 -0
- package/dist/next/assignments-script.d.ts +3 -2
- package/dist/next/assignments-script.js +5 -5
- package/dist/next/error-pages.d.ts +4 -0
- package/dist/next/error-pages.js +4 -0
- package/dist/next/error-seat-payload.d.ts +2 -1
- package/dist/next/error-seat-payload.js +14 -24
- package/dist/next/error-seat.js +7 -2
- package/dist/next/index.d.ts +1 -1
- package/dist/next/index.js +3 -1
- package/dist/next/page.d.ts +2 -1
- package/dist/next/page.js +15 -4
- package/dist/next/release-context.d.ts +30 -0
- package/dist/next/release-context.js +56 -0
- package/dist/next/render.d.ts +1 -1
- package/dist/next/render.js +15 -5
- package/dist/next/server.d.ts +9 -3
- package/dist/next/server.js +17 -32
- package/dist/preview/core.d.ts +1 -0
- package/dist/preview/core.js +27 -0
- package/dist/react/VideoPlayer.d.ts +3 -3
- package/dist/react/index.d.ts +3 -3
- package/dist/registry.d.ts +4 -2
- package/dist/testing/conformance/checks.d.ts +2 -2
- package/dist/testing/conformance/checks.js +2 -0
- package/dist/testing/conformance/index.d.ts +1 -0
- package/dist/testing/conformance/index.js +1 -0
- package/dist/testing/conformance/next-stubs.d.ts +6 -0
- package/dist/testing/conformance/next-stubs.js +9 -0
- package/dist/testing/conformance/preset.d.ts +3 -2
- package/dist/testing/conformance/preset.js +15 -8
- package/dist/testing/conformance/root-identity.d.ts +31 -0
- package/dist/testing/conformance/root-identity.js +361 -0
- package/dist/testing/conformance/stubs/next-image.d.ts +7 -0
- package/dist/testing/conformance/stubs/next-image.js +5 -0
- package/dist/testing/conformance/stubs/next-link.d.ts +7 -0
- package/dist/testing/conformance/stubs/next-link.js +4 -0
- package/dist/testing/conformance/types.d.ts +5 -2
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +4 -3
- package/dist/types/block-ref.js +2 -2
- package/dist/types/component.d.ts +9 -0
- package/dist/types/config.d.ts +0 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.js +0 -1
- package/dist/types/page.d.ts +0 -1
- package/migrations/conformance-registry.md +22 -0
- package/migrations/experiment-goal-ids.md +43 -0
- package/package.json +21 -7
- package/src/cli/compile-registry.mjs +6 -30
- package/src/cli/mintlify-render.mjs +9 -0
- package/src/types/block-ref.test.ts +16 -1
- package/src/types/block-ref.ts +2 -2
- package/src/types/component.ts +10 -0
- package/src/types/config.ts +0 -1
- package/src/types/index.ts +0 -1
- package/src/types/page.ts +0 -1
- package/templates/astro/starter-content/src/components/chrome/Footer.astro +5 -3
- package/templates/astro/starter-content/src/components/chrome/Header.astro +5 -3
- package/templates/astro/starter-content/src/components/elements/Button.astro +5 -2
- package/templates/astro/starter-content/src/components/elements/ButtonBlock.astro +5 -3
- package/templates/astro/starter-content/src/components/sections/ContainerSection.astro +4 -3
- package/templates/astro/starter-content/src/components/sections/FeatureCard.astro +4 -3
- package/templates/astro/starter-content/src/components/sections/FeatureGrid.astro +4 -3
- package/templates/astro/starter-content/src/components/sections/HomeHero.astro +5 -3
- package/templates/astro/starter-content/src/components/sections/PersonalizationChoice.astro +4 -3
- package/templates/astro/starter-content/src/components/sections/StatsBar.astro +4 -3
- package/templates/astro/starter-content/src/fixtures/sections.ts +1 -1
- package/templates/astro/starter-content/src/stories/CompositionStoryFrame.astro +7 -2
- package/templates/astro/template/.aci.yaml.tmpl +0 -1
- package/templates/astro/template/package.json.tmpl +5 -5
- package/templates/astro/template/src/layouts/SiteLayout.astro +2 -2
- package/templates/common/.agents/skills/aci/SKILL.md +58 -1
- package/templates/common/.agents/skills/authoring/SKILL.md +194 -42
- package/templates/common/.agents/skills/authoring/references/block-registry.md +19 -3
- package/templates/common/.agents/skills/authoring/references/contracts.md +9 -30
- package/templates/common/.agents/skills/authoring/references/repo-structure.md +5 -4
- package/templates/common/.agents/skills/authoring/references/sdk-server-functions.md +3 -3
- package/templates/common/.agents/skills/dam-delivery/SKILL.md +216 -0
- package/templates/common/.agents/skills/dam-delivery/references/permalink-grammar.md +167 -0
- package/templates/common/.agents/skills/migration/references/canon-migration.md +18 -67
- package/templates/common/.agents/skills/migration/references/selector-map.md +1 -1
- package/templates/common/.content/fragments/navbar/_index.json +24 -0
- package/templates/common/.content/fragments/site-footer/_index.json +61 -0
- package/templates/common/.content/pages/404/_index.json +4 -2
- package/templates/common/.content/pages/_index.json +0 -1
- package/templates/common/.content/pages/error/_index.json +4 -2
- package/templates/common/.content/schemas/site.schema.json +11 -1
- package/templates/common/.content/templates/error.json +24 -0
- package/templates/common/.content/templates/home.json +28 -0
- package/templates/common/.content/templates/site.json +8 -0
- package/templates/common/.content/templates/standard.json +24 -0
- package/templates/common/.nvmrc +1 -1
- package/templates/common/starter-content/.content/config/site.json +1 -1
- package/templates/common/starter-content/.content/dam/README.md +74 -0
- package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_about_hero → 000000000000000000000000101}/asset.json +1 -1
- package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_home_hero → 000000000000000000000000102}/asset.json +1 -1
- package/templates/common/starter-content/.content/pages/404/_index.json +1 -2
- package/templates/common/starter-content/.content/pages/{home/_index.json → _index.json} +14 -3
- package/templates/common/starter-content/.content/pages/about/_index.json +2 -2
- package/templates/common/starter-content/.content/pages/error/_index.json +1 -2
- package/templates/common/starter-content/.content/patterns/components/button.json +2 -2
- package/templates/common/starter-content/.content/patterns/components/container.json +8 -3
- package/templates/common/starter-content/.content/patterns/components/feature_card.json +2 -2
- package/templates/common/starter-content/.content/patterns/components/feature_grid.json +5 -3
- package/templates/common/starter-content/.content/patterns/components/home_hero.json +2 -2
- package/templates/common/starter-content/.content/patterns/components/personalization_choice.json +2 -2
- package/templates/common/starter-content/.content/patterns/components/site-footer.json +2 -2
- package/templates/common/starter-content/.content/patterns/components/site-navigation.json +2 -2
- package/templates/common/starter-content/.content/patterns/components/stats_bar.json +2 -2
- package/templates/common/starter-content/.content/schemas/site.schema.json +11 -1
- package/templates/nextjs/starter-content/src/components/chrome/Footer.tsx +4 -3
- package/templates/nextjs/starter-content/src/components/chrome/Header.tsx +4 -3
- package/templates/nextjs/starter-content/src/components/elements/Button.tsx +4 -3
- package/templates/nextjs/starter-content/src/components/elements/ButtonBlock.tsx +4 -3
- package/templates/nextjs/starter-content/src/components/sections/ContainerSection.tsx +4 -2
- package/templates/nextjs/starter-content/src/components/sections/FeatureCard.tsx +4 -3
- package/templates/nextjs/starter-content/src/components/sections/FeatureGrid.tsx +4 -2
- package/templates/nextjs/starter-content/src/components/sections/HomeHero.tsx +4 -3
- package/templates/nextjs/starter-content/src/components/sections/PersonalizationChoice.tsx +4 -2
- package/templates/nextjs/starter-content/src/components/sections/StatsBar.tsx +4 -3
- package/templates/nextjs/template/.aci.yaml +0 -1
- package/templates/nextjs/template/.aci.yaml.tmpl +0 -1
- package/templates/nextjs/template/package.json.tmpl +4 -3
- package/templates/nextjs/template/src/app/_components/c15t-bridge.ts +30 -0
- package/templates/nextjs/template/src/app/_components/c15t-consent-ui.tsx +37 -0
- package/templates/nextjs/template/src/app/_components/consent-manager.css +63 -0
- package/templates/nextjs/template/src/app/_components/consent-manager.tsx +76 -0
- package/templates/nextjs/template/src/app/fonts/inter/OFL.txt +93 -0
- package/templates/nextjs/template/src/app/fonts/inter/inter-latin.woff2 +0 -0
- package/templates/nextjs/template/src/app/fonts/space-grotesk/OFL.txt +93 -0
- package/templates/nextjs/template/src/app/fonts/space-grotesk/space-grotesk-latin.woff2 +0 -0
- package/templates/nextjs/template/src/app/layout.tsx +12 -7
- package/templates/nextjs/template/tests/conformance.test.ts.tmpl +1 -0
- package/dist/define-layout.d.ts +0 -6
- package/dist/define-layout.js +0 -14
- package/dist/edge/personalization-core.d.ts +0 -11
- package/dist/edge/personalization-core.js +0 -269
- package/dist/internal/domain/edge/compose.d.ts +0 -24
- package/dist/internal/domain/edge/compose.js +0 -89
- package/dist/internal/domain/edge/record/codec.d.ts +0 -71
- package/dist/internal/domain/edge/record/codec.js +0 -401
- package/dist/next/collect-route.d.ts +0 -19
- package/dist/next/collect-route.js +0 -459
- package/dist/next/edge-config.d.ts +0 -1
- package/dist/next/edge-config.js +0 -92
- package/dist/next/envelope-digest.d.ts +0 -21
- package/dist/next/envelope-digest.js +0 -58
- package/dist/next/middleware.d.ts +0 -40
- package/dist/next/middleware.js +0 -475
- package/dist/next/personalization-host.d.ts +0 -134
- package/dist/next/personalization-host.js +0 -270
- package/dist/next/root-layout.d.ts +0 -4
- package/dist/next/root-layout.js +0 -5
- package/dist/next/vercel-collect-sdk-route.d.ts +0 -27
- package/dist/next/vercel-collect-sdk-route.js +0 -181
- package/dist/next/vercel-edge-projection-v1.d.ts +0 -24
- package/dist/next/vercel-edge-projection-v1.js +0 -880
- package/dist/next/vercel-personalization.d.ts +0 -4
- package/dist/next/vercel-personalization.js +0 -4
- package/dist/next/vercel-s3-personalization.d.ts +0 -79
- package/dist/next/vercel-s3-personalization.js +0 -570
- package/dist/types/layout.d.ts +0 -24
- package/src/types/layout.ts +0 -29
- package/templates/astro/starter-content/src/render/RenderPage.astro +0 -67
- package/templates/astro/template/src/cms/contracts/layouts/index.ts +0 -18
- package/templates/common/starter-content/.content/patterns/layouts/default.json +0 -14
- package/templates/nextjs/starter-content/src/components/elements/AtomicBlock.tsx +0 -13
- package/templates/nextjs/template/src/cms/contracts/layouts/index.ts +0 -16
- /package/dist/{types/layout.js → cli/mintlify-render.d.mts} +0 -0
- /package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_home_hero → 000000000000000000000000101}/editorial.json +0 -0
- /package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_about_hero → 000000000000000000000000101}/governance.json +0 -0
- /package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_home_hero → 000000000000000000000000101}/versions/v0001.json +0 -0
- /package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_about_hero → 000000000000000000000000102}/editorial.json +0 -0
- /package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_home_hero → 000000000000000000000000102}/governance.json +0 -0
- /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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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,166 @@ const segmentCriteriaSchema = z.union([
|
|
|
51
240
|
none: z.array(segmentCriteriaConditionSchema).optional(),
|
|
52
241
|
}).strict(),
|
|
53
242
|
]);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
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().
|
|
67
|
-
displayName: z.string().
|
|
68
|
-
status:
|
|
69
|
-
priority: z.number().int().
|
|
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().
|
|
78
|
-
displayName: z.string().
|
|
79
|
-
|
|
80
|
-
|
|
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().
|
|
91
|
-
status:
|
|
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().
|
|
110
|
-
displayName: z.string().
|
|
111
|
-
status:
|
|
112
|
-
eventName: z.string().
|
|
113
|
-
|
|
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().
|
|
120
|
-
displayName: z.string().
|
|
121
|
-
status:
|
|
122
|
-
kind:
|
|
123
|
-
priority: z.number().int().
|
|
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(
|
|
128
|
-
fragments: z.array(
|
|
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
|
-
|
|
133
|
-
|
|
388
|
+
goalIds: z.array(z.string().regex(/\S/, 'entries must be non-blank')).optional(),
|
|
389
|
+
promotedArmId: z.string().optional(),
|
|
390
|
+
promotedAt: z.string().optional(),
|
|
391
|
+
horizon: z.object({
|
|
392
|
+
uniqueExposedPerArm: z.number().int().min(1),
|
|
393
|
+
conversionWindowDays: z.number().int().min(1).max(90).optional(),
|
|
394
|
+
declaredAfterRun: z.literal(true).optional(),
|
|
395
|
+
}).strict().optional(),
|
|
134
396
|
arms: z.array(z.object({
|
|
135
|
-
id: z.string().
|
|
136
|
-
displayName: z.string().
|
|
397
|
+
id: z.string().regex(/^[A-Za-z0-9_-]+$/, 'must contain only letters, numbers, underscores, or hyphens'),
|
|
398
|
+
displayName: z.string().regex(/\S/, 'displayName is required'),
|
|
137
399
|
role: z.enum(['control', 'variant']),
|
|
138
400
|
allocationBasisPoints: z.number().int().min(0).max(10000).optional(),
|
|
139
401
|
rule: requestRuleExpressionSchema.optional(),
|
|
140
|
-
})).min(1),
|
|
402
|
+
}).strict()).min(1),
|
|
141
403
|
}),
|
|
142
404
|
});
|
|
143
405
|
export const experimentationDataModels = [
|
|
@@ -148,6 +410,6 @@ export const audienceDataModels = [
|
|
|
148
410
|
segmentDataModel,
|
|
149
411
|
audienceDataModel,
|
|
150
412
|
audienceRuleDataModel,
|
|
151
|
-
|
|
413
|
+
audienceProviderDataModel,
|
|
152
414
|
...experimentationDataModels,
|
|
153
415
|
];
|
|
@@ -31,13 +31,15 @@ if (isMainModule()) {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
async function compileRegistry(options) {
|
|
34
|
-
|
|
34
|
+
if (options.layouts) {
|
|
35
|
+
throw new Error('--layouts is retired; author page composition under /templates instead');
|
|
36
|
+
}
|
|
37
|
+
const required = ['components', 'output'];
|
|
35
38
|
for (const key of required) {
|
|
36
39
|
if (!options[key])
|
|
37
40
|
throw new Error(`missing --${key}`);
|
|
38
41
|
}
|
|
39
42
|
const components = await loadDefaultArray(options.components, 'component registry');
|
|
40
|
-
const layouts = await loadDefaultArray(options.layouts, 'layout registry');
|
|
41
43
|
const dataModels = options.data
|
|
42
44
|
? await loadDefaultArray(options.data, 'data model registry')
|
|
43
45
|
: [];
|
|
@@ -65,8 +67,6 @@ async function compileRegistry(options) {
|
|
|
65
67
|
...(Object.keys(slots).length ? { slots } : {}),
|
|
66
68
|
});
|
|
67
69
|
}
|
|
68
|
-
const seenLayouts = new Set();
|
|
69
|
-
const layoutEntries = layouts.map((layout, index) => validateLayout(layout, index, seenLayouts));
|
|
70
70
|
const seenDataModels = new Set();
|
|
71
71
|
const dataModelEntries = [];
|
|
72
72
|
if (dataModels.length > 0) {
|
|
@@ -98,7 +98,6 @@ async function compileRegistry(options) {
|
|
|
98
98
|
manifestVersion: '1',
|
|
99
99
|
generatedAt: new Date().toISOString(),
|
|
100
100
|
components: componentEntries,
|
|
101
|
-
layouts: layoutEntries,
|
|
102
101
|
dataModels: dataModelEntries,
|
|
103
102
|
};
|
|
104
103
|
const registryPath = path.join(outDir, 'registry.json');
|
|
@@ -109,7 +108,7 @@ async function compileRegistry(options) {
|
|
|
109
108
|
}
|
|
110
109
|
async function loadDefaultArray(modulePath, label) {
|
|
111
110
|
const mod = await import(pathToFileURL(path.resolve(modulePath)).href);
|
|
112
|
-
const value = mod.default ?? mod.registry ?? mod.components ?? mod.
|
|
111
|
+
const value = mod.default ?? mod.registry ?? mod.components ?? mod.dataModels;
|
|
113
112
|
if (!Array.isArray(value))
|
|
114
113
|
throw new Error(`${label} must export an array`);
|
|
115
114
|
return value;
|
|
@@ -397,32 +396,6 @@ function unwrapSchema(schema) {
|
|
|
397
396
|
}
|
|
398
397
|
return current;
|
|
399
398
|
}
|
|
400
|
-
function validateLayout(layout, index, seen) {
|
|
401
|
-
if (!layout || typeof layout !== 'object')
|
|
402
|
-
throw new Error(`layouts[${index}] must be an object`);
|
|
403
|
-
if (!layout.name)
|
|
404
|
-
throw new Error(`layouts[${index}].name is required`);
|
|
405
|
-
if (seen.has(layout.name))
|
|
406
|
-
throw new Error(`duplicate layout name ${layout.name}`);
|
|
407
|
-
seen.add(layout.name);
|
|
408
|
-
if (!Array.isArray(layout.slots))
|
|
409
|
-
throw new Error(`layouts[${index}].slots must be an array`);
|
|
410
|
-
const slots = [];
|
|
411
|
-
const seenSlots = new Set();
|
|
412
|
-
for (const [slotIndex, slot] of layout.slots.entries()) {
|
|
413
|
-
if (!slot?.name)
|
|
414
|
-
throw new Error(`layouts[${index}].slots[${slotIndex}].name is required`);
|
|
415
|
-
if (seenSlots.has(slot.name))
|
|
416
|
-
throw new Error(`duplicate slot name ${slot.name} in layout ${layout.name}`);
|
|
417
|
-
seenSlots.add(slot.name);
|
|
418
|
-
slots.push({ name: slot.name, required: Boolean(slot.required) });
|
|
419
|
-
}
|
|
420
|
-
const entry = { name: layout.name, slots };
|
|
421
|
-
if (layout.defaults && typeof layout.defaults === 'object') {
|
|
422
|
-
entry.defaults = layout.defaults;
|
|
423
|
-
}
|
|
424
|
-
return entry;
|
|
425
|
-
}
|
|
426
399
|
function compileSchema(schema, name) {
|
|
427
400
|
const toJSONSchema = z.toJSONSchema;
|
|
428
401
|
if (typeof toJSONSchema !== 'function') {
|
|
@@ -516,13 +489,12 @@ async function generatePageSchema(schemaDir, componentEntries, outDir) {
|
|
|
516
489
|
$id: 'https://baremetal.local/schemas/page.schema.json',
|
|
517
490
|
title: 'bare-metal Page',
|
|
518
491
|
type: 'object',
|
|
519
|
-
required: ['$type', 'id', 'status', '
|
|
492
|
+
required: ['$type', 'id', 'status', 'metadata', 'regions'],
|
|
520
493
|
additionalProperties: true,
|
|
521
494
|
properties: {
|
|
522
495
|
$type: { const: 'page' },
|
|
523
496
|
id: { type: 'string', minLength: 1 },
|
|
524
497
|
status: { type: 'string', minLength: 1 },
|
|
525
|
-
layout: { type: 'string', minLength: 1 },
|
|
526
498
|
metadata: {
|
|
527
499
|
type: 'object',
|
|
528
500
|
required: ['title'],
|
|
@@ -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) });
|