@gradial/aci 0.1.52-rc.2 → 0.1.52-rc.22
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 +7 -0
- package/README.md +34 -4
- package/dist/astro/preview.js +2 -17
- package/dist/audience-data-models.d.ts +289 -16
- package/dist/audience-data-models.js +99 -2
- package/dist/cli/compile-registry.d.mts +5 -1
- package/dist/cli/compile-registry.mjs +25 -10
- package/dist/cli/mintlify-render.d.mts +1 -0
- package/dist/cli/mintlify-render.mjs +8 -0
- package/dist/cli/validate-content.d.mts +31 -1
- package/dist/cli/validate-content.mjs +50 -12
- package/dist/collect/aci.js +1 -1
- package/dist/collect/aci.js.sha256 +1 -1
- package/dist/collect/api.d.ts +3 -7
- package/dist/collect/api.js +2 -8
- package/dist/collect/batcher.d.ts +6 -5
- package/dist/collect/batcher.js +6 -5
- package/dist/collect/index.js +1 -1
- package/dist/content/provider.d.ts +1 -0
- package/dist/content/resolve-slots.d.ts +9 -2
- package/dist/content/resolve-slots.js +5 -3
- package/dist/content/types.d.ts +2 -0
- package/dist/edge/expression-capabilities.v1.d.ts +106 -0
- package/dist/edge/expression-capabilities.v1.js +165 -0
- package/dist/internal/domain/edge/compose.d.ts +24 -0
- package/dist/internal/domain/edge/compose.js +89 -0
- package/dist/internal/domain/edge/identity.js +1 -1
- package/dist/internal/domain/edge/index.d.ts +2 -0
- package/dist/internal/domain/edge/index.js +2 -0
- package/dist/internal/domain/edge/mimic_token.d.ts +13 -0
- package/dist/internal/domain/edge/mimic_token.js +58 -0
- package/dist/internal/domain/edge/personalization-core.d.ts +69 -5
- package/dist/internal/domain/edge/personalization-core.js +693 -58
- package/dist/internal/domain/edge/record/codec.d.ts +5 -2
- package/dist/internal/domain/edge/record/codec.js +85 -12
- package/dist/internal/domain/edge/resolve.js +38 -18
- 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 +52 -9
- package/dist/internal/domain/edge/types.js +3 -3
- package/dist/mimic/core.d.ts +2 -0
- package/dist/mimic/core.js +31 -0
- package/dist/mintlify/blocks.d.ts +6 -0
- package/dist/mintlify/blocks.js +198 -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 +12 -0
- package/dist/mintlify/materialize.js +159 -0
- package/dist/mintlify/types.d.ts +69 -0
- package/dist/mintlify/types.js +1 -0
- package/dist/mintlify/validate.d.ts +7 -0
- package/dist/mintlify/validate.js +252 -0
- package/dist/next/error-seat-payload.js +1 -1
- package/dist/next/hosted-page-target.d.ts +7 -0
- package/dist/next/hosted-page-target.js +39 -0
- package/dist/next/index.d.ts +0 -1
- package/dist/next/index.js +0 -1
- package/dist/next/page.d.ts +29 -6
- package/dist/next/page.js +47 -25
- package/dist/next/server.js +28 -1
- package/dist/next/vercel-edge-projection-v1.d.ts +0 -9
- package/dist/next/vercel-edge-projection-v1.js +9 -539
- package/dist/preview/core.d.ts +1 -0
- package/dist/preview/core.js +27 -0
- package/dist/providers/s3.js +4 -3
- package/dist/react/VideoPlayer.d.ts +2 -2
- package/dist/react/index.d.ts +2 -2
- package/dist/testing/index.js +2 -2
- package/dist/types/block-ref.d.ts +2 -0
- package/dist/types/block-ref.js +1 -0
- package/package.json +20 -7
- package/src/cli/compile-registry.mjs +24 -10
- package/src/cli/mintlify-render.mjs +9 -0
- package/src/cli/validate-content.mjs +52 -13
- package/src/types/block-ref.test.ts +45 -0
- package/src/types/block-ref.ts +2 -0
- package/templates/astro/starter-content/src/fixtures/atomicBlocks.ts +7 -6
- package/templates/astro/starter-content/src/fixtures/composition.ts +3 -2
- package/templates/astro/starter-content/src/fixtures/sections.ts +3 -2
- package/templates/astro/template/package.json.tmpl +1 -1
- package/templates/astro/template/src/layouts/SiteLayout.astro +2 -2
- package/templates/common/.agents/skills/aci/SKILL.md +56 -0
- package/templates/common/.agents/skills/authoring/SKILL.md +15 -6
- package/templates/common/.agents/skills/authoring/references/block-slots.md +3 -0
- package/templates/common/.agents/skills/dam-delivery/SKILL.md +228 -0
- package/templates/common/.agents/skills/dam-delivery/references/parameter-contract.md +107 -0
- package/templates/common/.agents/skills/dam-delivery/references/permalink-grammar.md +131 -0
- package/templates/common/.agents/skills/migration/references/selector-map.md +1 -1
- package/templates/common/.content/pages/{home/_index.json → _index.json} +10 -0
- package/templates/common/.content/schemas/site.schema.json +1 -1
- package/templates/common/.nvmrc +1 -1
- package/templates/common/starter-content/.content/.rules-baseline.json +8 -0
- package/templates/common/starter-content/.content/config/site.json +1 -1
- package/templates/common/starter-content/.content/content-rules/no-public-resource-paths.json +6 -0
- package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_home_hero → 000000000000000000000000101}/asset.json +1 -1
- package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_about_hero → 000000000000000000000000102}/asset.json +1 -1
- package/templates/common/starter-content/.content/fragments/navbar/_index.json +23 -20
- package/templates/common/starter-content/.content/fragments/site-footer/_index.json +59 -56
- package/templates/common/starter-content/.content/pages/404/_index.json +2 -1
- package/templates/common/starter-content/.content/pages/{home/_index.json → _index.json} +11 -2
- package/templates/common/starter-content/.content/pages/about/_index.json +4 -1
- package/templates/common/starter-content/.content/pages/error/_index.json +2 -1
- package/templates/common/starter-content/.content/patterns/components/button.json +55 -0
- package/templates/common/starter-content/.content/patterns/components/container.json +62 -0
- package/templates/common/starter-content/.content/patterns/components/feature_card.json +30 -0
- package/templates/common/starter-content/.content/patterns/components/feature_grid.json +57 -0
- package/templates/common/starter-content/.content/patterns/components/home_hero.json +142 -0
- package/templates/common/starter-content/.content/patterns/components/site-footer.json +203 -0
- package/templates/common/starter-content/.content/patterns/components/site-navigation.json +115 -0
- package/templates/common/starter-content/.content/patterns/components/stats_bar.json +45 -0
- package/templates/common/starter-content/.content/patterns/layouts/default.json +14 -0
- package/templates/common/starter-content/.content/schemas/site.schema.json +1 -1
- package/templates/nextjs/template/.aci.yaml +10 -0
- 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/layout.tsx +2 -0
- package/dist/edge/personalization-core.d.ts +0 -11
- package/dist/edge/personalization-core.js +0 -269
- package/dist/next/collect-route.d.ts +0 -19
- package/dist/next/collect-route.js +0 -459
- 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-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/templates/common/.content/overlays/audience/preference-blue/pages/{home/_index.json → _index.json} +0 -0
- /package/templates/common/.content/overlays/audience/preference-red/pages/{home/_index.json → _index.json} +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
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { validateBlock, validateSnippetReferences } from './blocks.js';
|
|
2
|
+
import { cleanDocumentPath, cleanFilePath, isExternalReference, isMintlifyJSONValue, isObject, nonEmptyString, } from './json.js';
|
|
3
|
+
const CONFIG_FRAGMENT_SCHEMA_VERSION = 1;
|
|
4
|
+
const API_SPEC_SCHEMA_VERSION = 1;
|
|
5
|
+
export function validateSource(site, documents, snippets, apiSpecs) {
|
|
6
|
+
const paths = new Set();
|
|
7
|
+
for (const document of documents) {
|
|
8
|
+
if (paths.has(document.path))
|
|
9
|
+
throw new Error(`ACI Mintlify document path is duplicated: ${document.path}`);
|
|
10
|
+
paths.add(document.path);
|
|
11
|
+
}
|
|
12
|
+
const navigated = new Set();
|
|
13
|
+
const navigation = isObject(site) ? site.navigation : undefined;
|
|
14
|
+
validateNavigationPartitions(navigation);
|
|
15
|
+
for (const page of navigationPages(navigation)) {
|
|
16
|
+
// Links are valid Mintlify navigation items but are not ACI documents.
|
|
17
|
+
if (isExternalReference(page))
|
|
18
|
+
continue;
|
|
19
|
+
const normalized = cleanDocumentPath(page);
|
|
20
|
+
if (!normalized || normalized !== page)
|
|
21
|
+
throw new Error(`ACI Mintlify navigation page is invalid: ${page}`);
|
|
22
|
+
if (!paths.has(page))
|
|
23
|
+
throw new Error(`ACI Mintlify navigation references missing document: ${page}`);
|
|
24
|
+
if (navigated.has(page))
|
|
25
|
+
throw new Error(`ACI Mintlify navigation references document more than once: ${page}`);
|
|
26
|
+
navigated.add(page);
|
|
27
|
+
}
|
|
28
|
+
validateRedirects(isObject(site) ? site.redirects : undefined, paths);
|
|
29
|
+
validateSnippetReferences(documents, snippets);
|
|
30
|
+
validateAPISpecReferences(site, apiSpecs);
|
|
31
|
+
}
|
|
32
|
+
export function validateSite(value, label) {
|
|
33
|
+
if (!isObject(value) || !isMintlifyJSONValue(value)) {
|
|
34
|
+
throw new Error(`ACI Mintlify site is invalid: ${label}`);
|
|
35
|
+
}
|
|
36
|
+
if ('$schema' in value && value.$schema !== 'https://mintlify.com/docs.json') {
|
|
37
|
+
throw new Error(`ACI Mintlify site has an unsupported docs schema: ${label}`);
|
|
38
|
+
}
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
export function validateDocument(value, label, sourcePath) {
|
|
42
|
+
const metadata = isObject(value) && isObject(value.metadata) ? value.metadata : undefined;
|
|
43
|
+
const regions = isObject(value) && isObject(value.regions) ? value.regions : undefined;
|
|
44
|
+
if (!isObject(value) || value.$type !== 'page' || !nonEmptyString(value.id) || !nonEmptyString(value.status) || !nonEmptyString(value.layout) || !nonEmptyString(sourcePath) || !metadata || !nonEmptyString(metadata.title) || !regions || !Array.isArray(regions.main)) {
|
|
45
|
+
throw new Error(`ACI Mintlify document is invalid: ${label}`);
|
|
46
|
+
}
|
|
47
|
+
const documentPath = cleanDocumentPath(sourcePath);
|
|
48
|
+
if (!documentPath || documentPath !== sourcePath)
|
|
49
|
+
throw new Error(`ACI Mintlify document path is invalid: ${sourcePath}`);
|
|
50
|
+
const ids = new Set();
|
|
51
|
+
const blocks = regions.main.map((block, index) => validateBlock(block, `${label}.regions.main[${index}]`, ids));
|
|
52
|
+
const renderer = isObject(metadata.mintlify) ? metadata.mintlify : {};
|
|
53
|
+
return {
|
|
54
|
+
$type: 'page', id: value.id, status: value.status, layout: value.layout, path: documentPath,
|
|
55
|
+
metadata: { title: metadata.title,
|
|
56
|
+
...(nonEmptyString(metadata.description) ? { description: metadata.description } : {}),
|
|
57
|
+
mintlify: {
|
|
58
|
+
...(nonEmptyString(renderer.sidebarTitle) ? { sidebarTitle: renderer.sidebarTitle } : {}),
|
|
59
|
+
...(nonEmptyString(renderer.icon) ? { icon: renderer.icon } : {}),
|
|
60
|
+
...(validFrontmatter(renderer.frontmatter) ? { frontmatter: renderer.frontmatter } : {}),
|
|
61
|
+
},
|
|
62
|
+
}, regions: { main: blocks },
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export function validateSnippet(value, label, sourcePath) {
|
|
66
|
+
if (!isObject(value) || value.$type !== 'fragment' || !nonEmptyString(value.id) || !nonEmptyString(sourcePath) || !isObject(value.block)) {
|
|
67
|
+
throw new Error(`ACI Mintlify snippet is invalid: ${label}`);
|
|
68
|
+
}
|
|
69
|
+
const snippetPath = cleanFilePath(sourcePath, '.mdx');
|
|
70
|
+
if (!snippetPath || snippetPath !== sourcePath)
|
|
71
|
+
throw new Error(`ACI Mintlify snippet path is invalid: ${sourcePath}`);
|
|
72
|
+
const ids = new Set();
|
|
73
|
+
return {
|
|
74
|
+
$type: 'fragment', id: value.id,
|
|
75
|
+
path: snippetPath, block: validateBlock(value.block, `${label}.block`, ids),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
export function validateConfigFragment(value, label) {
|
|
79
|
+
if (!isObject(value) || value.$type !== 'mintlify.config-fragment' || value.schemaVersion !== CONFIG_FRAGMENT_SCHEMA_VERSION || !nonEmptyString(value.path) || !isMintlifyJSONValue(value.value)) {
|
|
80
|
+
throw new Error(`ACI Mintlify config fragment is invalid: ${label}`);
|
|
81
|
+
}
|
|
82
|
+
const fragmentPath = cleanFilePath(value.path, '.json');
|
|
83
|
+
if (!fragmentPath || fragmentPath !== value.path || fragmentPath === 'docs.json')
|
|
84
|
+
throw new Error(`ACI Mintlify config fragment path is invalid: ${value.path}`);
|
|
85
|
+
return { $type: 'mintlify.config-fragment', schemaVersion: CONFIG_FRAGMENT_SCHEMA_VERSION, path: fragmentPath, value: value.value };
|
|
86
|
+
}
|
|
87
|
+
export function validateAPISpec(value, label) {
|
|
88
|
+
if (!isObject(value) || value.$type !== 'mintlify.api-spec' || value.schemaVersion !== API_SPEC_SCHEMA_VERSION || !nonEmptyString(value.path) || !['openapi', 'asyncapi', 'graphql'].includes(String(value.format)) || !nonEmptyString(value.source)) {
|
|
89
|
+
throw new Error(`ACI Mintlify API specification is invalid: ${label}`);
|
|
90
|
+
}
|
|
91
|
+
const sourcePath = value.path;
|
|
92
|
+
const extensions = value.format === 'graphql' ? ['.graphql', '.gql', '.json'] : ['.json', '.yaml', '.yml'];
|
|
93
|
+
const specPath = extensions.map((extension) => cleanFilePath(sourcePath, extension)).find(Boolean) ?? '';
|
|
94
|
+
if (!specPath || specPath !== sourcePath)
|
|
95
|
+
throw new Error(`ACI Mintlify API specification path is invalid: ${sourcePath}`);
|
|
96
|
+
return {
|
|
97
|
+
$type: 'mintlify.api-spec',
|
|
98
|
+
schemaVersion: API_SPEC_SCHEMA_VERSION,
|
|
99
|
+
path: specPath,
|
|
100
|
+
format: value.format,
|
|
101
|
+
source: value.source,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function validFrontmatter(value) {
|
|
105
|
+
if (value === undefined)
|
|
106
|
+
return false;
|
|
107
|
+
if (!isObject(value))
|
|
108
|
+
throw new Error('ACI Mintlify document frontmatter is invalid');
|
|
109
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
110
|
+
if (!/^[A-Za-z][A-Za-z0-9_-]*$/.test(key) || !isMintlifyJSONValue(entry)) {
|
|
111
|
+
throw new Error('ACI Mintlify document frontmatter is invalid');
|
|
112
|
+
}
|
|
113
|
+
if (['title', 'description', 'sidebarTitle', 'icon'].includes(key))
|
|
114
|
+
throw new Error(`ACI Mintlify document frontmatter key is reserved: ${key}`);
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
function validateRedirects(value, documentPaths) {
|
|
119
|
+
if (value === undefined)
|
|
120
|
+
return;
|
|
121
|
+
if (!Array.isArray(value))
|
|
122
|
+
throw new Error('ACI Mintlify redirects are invalid');
|
|
123
|
+
const redirects = new Map();
|
|
124
|
+
const routes = new Set([...documentPaths].map((documentPath) => `/${documentPath}`));
|
|
125
|
+
for (const entry of value) {
|
|
126
|
+
if (!isObject(entry) || !nonEmptyString(entry.source) || !nonEmptyString(entry.destination) || (entry.permanent !== undefined && typeof entry.permanent !== 'boolean')) {
|
|
127
|
+
throw new Error('ACI Mintlify redirect is invalid');
|
|
128
|
+
}
|
|
129
|
+
if (!entry.source.startsWith('/') || entry.source.includes('?') || entry.source.includes('#')) {
|
|
130
|
+
throw new Error(`ACI Mintlify redirect source is invalid: ${entry.source}`);
|
|
131
|
+
}
|
|
132
|
+
if (!entry.destination.startsWith('/') && !isExternalReference(entry.destination)) {
|
|
133
|
+
throw new Error(`ACI Mintlify redirect destination is invalid: ${entry.destination}`);
|
|
134
|
+
}
|
|
135
|
+
if (redirects.has(entry.source))
|
|
136
|
+
throw new Error(`ACI Mintlify redirect source is duplicated: ${entry.source}`);
|
|
137
|
+
if (!entry.source.includes('*') && !entry.source.includes(':') && routes.has(trimRouteSlash(entry.source))) {
|
|
138
|
+
throw new Error(`ACI Mintlify redirect source collides with a document: ${entry.source}`);
|
|
139
|
+
}
|
|
140
|
+
redirects.set(entry.source, entry.destination);
|
|
141
|
+
}
|
|
142
|
+
for (const [source, destination] of redirects) {
|
|
143
|
+
if (isExternalReference(destination) || destination.includes('*') || destination.includes(':'))
|
|
144
|
+
continue;
|
|
145
|
+
const destinationPath = trimRouteSlash(destination.split(/[?#]/, 1)[0]);
|
|
146
|
+
if (!routes.has(destinationPath) && !redirects.has(destinationPath)) {
|
|
147
|
+
throw new Error(`ACI Mintlify redirect destination is missing: ${destination}`);
|
|
148
|
+
}
|
|
149
|
+
const seen = new Set([source]);
|
|
150
|
+
let next = destinationPath;
|
|
151
|
+
while (redirects.has(next)) {
|
|
152
|
+
if (seen.has(next))
|
|
153
|
+
throw new Error(`ACI Mintlify redirect cycle includes: ${next}`);
|
|
154
|
+
seen.add(next);
|
|
155
|
+
const target = redirects.get(next);
|
|
156
|
+
if (isExternalReference(target) || target.includes('*') || target.includes(':'))
|
|
157
|
+
break;
|
|
158
|
+
next = trimRouteSlash(target.split(/[?#]/, 1)[0]);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function trimRouteSlash(value) {
|
|
163
|
+
return value.length > 1 ? value.replace(/\/+$/, '') : value;
|
|
164
|
+
}
|
|
165
|
+
function validateAPISpecReferences(site, specs) {
|
|
166
|
+
const byPath = new Map();
|
|
167
|
+
for (const spec of specs) {
|
|
168
|
+
if (byPath.has(spec.path))
|
|
169
|
+
throw new Error(`ACI Mintlify API specification path is duplicated: ${spec.path}`);
|
|
170
|
+
byPath.set(spec.path, spec);
|
|
171
|
+
}
|
|
172
|
+
const visit = (value) => {
|
|
173
|
+
if (Array.isArray(value)) {
|
|
174
|
+
for (const child of value)
|
|
175
|
+
visit(child);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (!isObject(value))
|
|
179
|
+
return;
|
|
180
|
+
for (const [key, child] of Object.entries(value)) {
|
|
181
|
+
if (['openapi', 'asyncapi', 'graphql'].includes(key)) {
|
|
182
|
+
const references = typeof child === 'string' ? [child] : Array.isArray(child) ? child.filter((entry) => typeof entry === 'string') : [];
|
|
183
|
+
for (const reference of references) {
|
|
184
|
+
if (isExternalReference(reference))
|
|
185
|
+
continue;
|
|
186
|
+
const spec = byPath.get(reference);
|
|
187
|
+
if (!spec || spec.format !== key)
|
|
188
|
+
throw new Error(`ACI Mintlify API specification reference is missing or has the wrong format: ${reference}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
visit(child);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
visit(site);
|
|
195
|
+
}
|
|
196
|
+
function navigationPages(value) {
|
|
197
|
+
if (!isObject(value))
|
|
198
|
+
return [];
|
|
199
|
+
const pages = [];
|
|
200
|
+
visitNavigation(value, pages);
|
|
201
|
+
return pages;
|
|
202
|
+
}
|
|
203
|
+
function visitNavigation(value, pages) {
|
|
204
|
+
if (Array.isArray(value)) {
|
|
205
|
+
for (const item of value)
|
|
206
|
+
visitNavigation(item, pages);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (!isObject(value))
|
|
210
|
+
return;
|
|
211
|
+
for (const [key, child] of Object.entries(value)) {
|
|
212
|
+
if (key === 'pages' && Array.isArray(child)) {
|
|
213
|
+
for (const item of child) {
|
|
214
|
+
if (typeof item === 'string')
|
|
215
|
+
pages.push(item);
|
|
216
|
+
else
|
|
217
|
+
visitNavigation(item, pages);
|
|
218
|
+
}
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
visitNavigation(child, pages);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function validateNavigationPartitions(value) {
|
|
225
|
+
if (Array.isArray(value)) {
|
|
226
|
+
for (const child of value)
|
|
227
|
+
validateNavigationPartitions(child);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (!isObject(value))
|
|
231
|
+
return;
|
|
232
|
+
for (const [key, child] of Object.entries(value)) {
|
|
233
|
+
if ((key === 'languages' || key === 'versions') && Array.isArray(child)) {
|
|
234
|
+
const identityKey = key === 'languages' ? 'language' : 'version';
|
|
235
|
+
const identities = new Set();
|
|
236
|
+
let defaults = 0;
|
|
237
|
+
for (const entry of child) {
|
|
238
|
+
if (!isObject(entry) || !nonEmptyString(entry[identityKey]))
|
|
239
|
+
throw new Error(`ACI Mintlify ${key} navigation is invalid`);
|
|
240
|
+
const identity = entry[identityKey];
|
|
241
|
+
if (identities.has(identity))
|
|
242
|
+
throw new Error(`ACI Mintlify ${key} navigation is duplicated: ${identity}`);
|
|
243
|
+
identities.add(identity);
|
|
244
|
+
if (entry.default === true)
|
|
245
|
+
defaults += 1;
|
|
246
|
+
}
|
|
247
|
+
if (defaults > 1)
|
|
248
|
+
throw new Error(`ACI Mintlify navigation has multiple default ${key}`);
|
|
249
|
+
}
|
|
250
|
+
validateNavigationPartitions(child);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
@@ -104,7 +104,7 @@ export function buildErrorSeatPayload(contentRoot, posture) {
|
|
|
104
104
|
if (!fragmentRef)
|
|
105
105
|
continue;
|
|
106
106
|
const fragment = readJSON(root, fragmentRef);
|
|
107
|
-
blocks.push({ id: fragment.id, component: fragment.component, props: fragment.props || {} });
|
|
107
|
+
blocks.push({ $type: 'component', id: fragment.id, component: fragment.component, props: fragment.props || {} });
|
|
108
108
|
}
|
|
109
109
|
if (blocks.length)
|
|
110
110
|
slots[slot.name] = blocks;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const VARIANT_NAMESPACE = '__variant';
|
|
2
|
+
export function parseHostedPageTarget(rawSegments) {
|
|
3
|
+
const segments = decodeSegments(rawSegments);
|
|
4
|
+
if (!segments)
|
|
5
|
+
return null;
|
|
6
|
+
const variantAt = segments.indexOf(VARIANT_NAMESPACE);
|
|
7
|
+
if (variantAt < 0)
|
|
8
|
+
return target('base', segments, segments);
|
|
9
|
+
if (variantAt !== segments.lastIndexOf(VARIANT_NAMESPACE) || variantAt !== segments.length - 2) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
...target('variant', segments.slice(0, variantAt), segments),
|
|
14
|
+
variantKey: segments[variantAt + 1],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function target(kind, routeSegments, targetSegments) {
|
|
18
|
+
return {
|
|
19
|
+
kind,
|
|
20
|
+
route: routeSegments.length === 0 ? '/' : `/${routeSegments.join('/')}/`,
|
|
21
|
+
pageRef: ['routes', ...targetSegments, 'index.json'].join('/'),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function decodeSegments(rawSegments) {
|
|
25
|
+
const segments = [];
|
|
26
|
+
for (const raw of rawSegments) {
|
|
27
|
+
let segment;
|
|
28
|
+
try {
|
|
29
|
+
segment = decodeURIComponent(raw);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
if (!segment || segment === '.' || segment === '..' || segment.includes('/'))
|
|
35
|
+
return null;
|
|
36
|
+
segments.push(segment);
|
|
37
|
+
}
|
|
38
|
+
return segments;
|
|
39
|
+
}
|
package/dist/next/index.d.ts
CHANGED
|
@@ -5,7 +5,6 @@ export { createPage, type CreatePageOptions, type SlotPropsFn } from './page.js'
|
|
|
5
5
|
export { getSiteConfig, getFragment, getLayout, getPage, getRenderInput, routeFromNextParams } from './server.js';
|
|
6
6
|
export { PageNotFoundError, FragmentNotFoundError, LayoutNotFoundError } from './server.js';
|
|
7
7
|
export { CollectScript, type CollectScriptProps } from './collect-script.js';
|
|
8
|
-
export { aciMiddlewareMatcher } from './middleware.js';
|
|
9
8
|
export { AssignmentsScript, type AssignmentsScriptProps } from './assignments-script.js';
|
|
10
9
|
export { createNotFound, renderPageForRoute, resolveErrorPage, type NextResolvedErrorPage, } from './error-pages.js';
|
|
11
10
|
export { ErrorPageInstrumentation } from './error-page-instrumentation.js';
|
package/dist/next/index.js
CHANGED
|
@@ -5,7 +5,6 @@ export { createPage } from './page.js';
|
|
|
5
5
|
export { getSiteConfig, getFragment, getLayout, getPage, getRenderInput, routeFromNextParams } from './server.js';
|
|
6
6
|
export { PageNotFoundError, FragmentNotFoundError, LayoutNotFoundError } from './server.js';
|
|
7
7
|
export { CollectScript } from './collect-script.js';
|
|
8
|
-
export { aciMiddlewareMatcher } from './middleware.js';
|
|
9
8
|
export { AssignmentsScript } from './assignments-script.js';
|
|
10
9
|
export { createNotFound, renderPageForRoute, resolveErrorPage, } from './error-pages.js';
|
|
11
10
|
export { ErrorPageInstrumentation } from './error-page-instrumentation.js';
|
package/dist/next/page.d.ts
CHANGED
|
@@ -31,12 +31,10 @@ export interface CreatePageOptions {
|
|
|
31
31
|
* the collect script tag exactly once per page (design/04 contract tag).
|
|
32
32
|
*
|
|
33
33
|
* Release-scoped routes (production): the middleware rewrites a public URL
|
|
34
|
-
* to `/__r/{releaseId}/
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* The `routes` namespace segment leaves room for future siblings under
|
|
39
|
-
* the release prefix (e.g. `/__r/{releaseId}/assets/*`).
|
|
34
|
+
* to `/__r/{releaseId}/{targetRef}`. The target ref selects the exact immutable
|
|
35
|
+
* base or variant page while its prefix identifies the logical public route.
|
|
36
|
+
* createPage passes both identities to the content loader, giving each target
|
|
37
|
+
* a distinct CDN cache key without sending selection headers into Next.
|
|
40
38
|
*
|
|
41
39
|
* Dev pass-through (ACI_CONTENT_PROVIDER=file): the middleware passes
|
|
42
40
|
* requests through unrewritten, so `/` arrives with an empty/undefined
|
|
@@ -54,4 +52,29 @@ export declare function createPage(registry: Registry, options?: CreatePageOptio
|
|
|
54
52
|
generateMetadata: ({ params }: NextPageProps) => Promise<Metadata>;
|
|
55
53
|
default: ({ params }: NextPageProps) => Promise<import("react").JSX.Element>;
|
|
56
54
|
};
|
|
55
|
+
/**
|
|
56
|
+
* Resolves the logical route and releaseId from Next.js catch-all params.
|
|
57
|
+
*
|
|
58
|
+
* Two shapes are supported:
|
|
59
|
+
*
|
|
60
|
+
* 1. **Rewritten (production):** middleware rewrites a public URL to the exact
|
|
61
|
+
* selected target under `/__r/{releaseId}`. A root variant therefore has
|
|
62
|
+
* `['__r', releaseId, '__variant', variantKey]`. The full suffix selects the
|
|
63
|
+
* immutable page object; the segments before the reserved `__variant`
|
|
64
|
+
* namespace form the logical public route.
|
|
65
|
+
*
|
|
66
|
+
* 2. **Dev pass-through (ACI_CONTENT_PROVIDER=file):** middleware passes
|
|
67
|
+
* requests through unrewritten, so the slug is the raw public path
|
|
68
|
+
* segments (possibly empty/undefined for `/`). No releaseId is present;
|
|
69
|
+
* the file provider serves content from disk.
|
|
70
|
+
*/
|
|
71
|
+
export declare function resolveRouteAndRelease(params: Promise<{
|
|
72
|
+
slug?: string[];
|
|
73
|
+
}> | {
|
|
74
|
+
slug?: string[];
|
|
75
|
+
}): Promise<{
|
|
76
|
+
route: string;
|
|
77
|
+
releaseId?: string;
|
|
78
|
+
pageRef?: string;
|
|
79
|
+
}>;
|
|
57
80
|
export {};
|
package/dist/next/page.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { notFound } from 'next/navigation.js';
|
|
3
|
+
import { headers } from 'next/headers.js';
|
|
3
4
|
import { Fragment } from 'react';
|
|
4
5
|
import { PageNotFoundError, getRenderInput, getLayout, getFragment, } from './server.js';
|
|
6
|
+
import { MIMIC_BANNER_HEADER, decodeMimicBanner } from '../mimic/core.js';
|
|
5
7
|
import { routeMetadataForContent, resolveSlots, } from '../content/index.js';
|
|
6
8
|
import { renderBlocks } from './render.js';
|
|
7
9
|
import { AssignmentsScript } from './assignments-script.js';
|
|
8
10
|
import { CollectScript } from './collect-script.js';
|
|
9
11
|
import { renderProofContentFor } from '../internal/render-proof.js';
|
|
12
|
+
import { parseHostedPageTarget } from './hosted-page-target.js';
|
|
10
13
|
/**
|
|
11
14
|
* Creates the catch-all route handler for a Next.js ACI site.
|
|
12
15
|
*
|
|
@@ -28,12 +31,10 @@ import { renderProofContentFor } from '../internal/render-proof.js';
|
|
|
28
31
|
* the collect script tag exactly once per page (design/04 contract tag).
|
|
29
32
|
*
|
|
30
33
|
* Release-scoped routes (production): the middleware rewrites a public URL
|
|
31
|
-
* to `/__r/{releaseId}/
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* The `routes` namespace segment leaves room for future siblings under
|
|
36
|
-
* the release prefix (e.g. `/__r/{releaseId}/assets/*`).
|
|
34
|
+
* to `/__r/{releaseId}/{targetRef}`. The target ref selects the exact immutable
|
|
35
|
+
* base or variant page while its prefix identifies the logical public route.
|
|
36
|
+
* createPage passes both identities to the content loader, giving each target
|
|
37
|
+
* a distinct CDN cache key without sending selection headers into Next.
|
|
37
38
|
*
|
|
38
39
|
* Dev pass-through (ACI_CONTENT_PROVIDER=file): the middleware passes
|
|
39
40
|
* requests through unrewritten, so `/` arrives with an empty/undefined
|
|
@@ -49,8 +50,9 @@ import { renderProofContentFor } from '../internal/render-proof.js';
|
|
|
49
50
|
export function createPage(registry, options) {
|
|
50
51
|
const dynamic = 'force-dynamic';
|
|
51
52
|
async function generateMetadata({ params }) {
|
|
52
|
-
const { route, releaseId } = await resolveRouteAndRelease(params);
|
|
53
|
-
const
|
|
53
|
+
const { route, releaseId, pageRef } = await resolveRouteAndRelease(params);
|
|
54
|
+
const fetchConfig = { releaseId, pageRef };
|
|
55
|
+
const input = await getRenderInput(route, fetchConfig);
|
|
54
56
|
const meta = routeMetadataForContent(input.siteConfig, input.page);
|
|
55
57
|
return {
|
|
56
58
|
title: meta.title,
|
|
@@ -64,8 +66,8 @@ export function createPage(registry, options) {
|
|
|
64
66
|
};
|
|
65
67
|
}
|
|
66
68
|
async function CatchAllPage({ params }) {
|
|
67
|
-
const { route, releaseId } = await resolveRouteAndRelease(params);
|
|
68
|
-
const fetchConfig = { releaseId };
|
|
69
|
+
const { route, releaseId, pageRef } = await resolveRouteAndRelease(params);
|
|
70
|
+
const fetchConfig = { releaseId, pageRef };
|
|
69
71
|
try {
|
|
70
72
|
const input = await getRenderInput(route, fetchConfig);
|
|
71
73
|
if (!input.page)
|
|
@@ -84,9 +86,9 @@ export function createPage(registry, options) {
|
|
|
84
86
|
}
|
|
85
87
|
return (_jsx(Fragment, { children: renderedBlocks }, slot.name));
|
|
86
88
|
}));
|
|
87
|
-
return (_jsxs(_Fragment, { children: [renderProofContent
|
|
89
|
+
return (_jsxs(_Fragment, { children: [renderedSlots.filter(Boolean), _jsx(AssignmentsScript, { page: input.page }), _jsx(CollectScript, { siteConfig: input.siteConfig }), _jsx(MimicBanner, { html: await readMimicBannerHTML() }), renderProofContent
|
|
88
90
|
? _jsx("meta", { name: "aci-render-proof", content: renderProofContent })
|
|
89
|
-
: null
|
|
91
|
+
: null] }));
|
|
90
92
|
}
|
|
91
93
|
catch (error) {
|
|
92
94
|
if (error instanceof PageNotFoundError)
|
|
@@ -105,19 +107,18 @@ export function createPage(registry, options) {
|
|
|
105
107
|
*
|
|
106
108
|
* Two shapes are supported:
|
|
107
109
|
*
|
|
108
|
-
* 1. **Rewritten (production):** middleware rewrites a public URL to
|
|
109
|
-
* `/__r/{releaseId}
|
|
110
|
-
* `['__r', releaseId, '
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* under the same release prefix).
|
|
110
|
+
* 1. **Rewritten (production):** middleware rewrites a public URL to the exact
|
|
111
|
+
* selected target under `/__r/{releaseId}`. A root variant therefore has
|
|
112
|
+
* `['__r', releaseId, '__variant', variantKey]`. The full suffix selects the
|
|
113
|
+
* immutable page object; the segments before the reserved `__variant`
|
|
114
|
+
* namespace form the logical public route.
|
|
114
115
|
*
|
|
115
116
|
* 2. **Dev pass-through (ACI_CONTENT_PROVIDER=file):** middleware passes
|
|
116
117
|
* requests through unrewritten, so the slug is the raw public path
|
|
117
118
|
* segments (possibly empty/undefined for `/`). No releaseId is present;
|
|
118
119
|
* the file provider serves content from disk.
|
|
119
120
|
*/
|
|
120
|
-
async function resolveRouteAndRelease(params) {
|
|
121
|
+
export async function resolveRouteAndRelease(params) {
|
|
121
122
|
const resolved = await params;
|
|
122
123
|
const slug = resolved.slug ?? [];
|
|
123
124
|
if (slug.length >= 2 && slug[0] === '__r') {
|
|
@@ -125,13 +126,14 @@ async function resolveRouteAndRelease(params) {
|
|
|
125
126
|
if (!releaseId || releaseId.includes('/') || releaseId === '.' || releaseId === '..') {
|
|
126
127
|
return { route: '/' };
|
|
127
128
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (rest.length === 0)
|
|
131
|
-
return { route: '/', releaseId };
|
|
132
|
-
if (rest.some((s) => s === '' || s === '.' || s === '..'))
|
|
129
|
+
const target = parseHostedPageTarget(slug.slice(2));
|
|
130
|
+
if (!target)
|
|
133
131
|
return { route: '/' };
|
|
134
|
-
return {
|
|
132
|
+
return {
|
|
133
|
+
route: target.route,
|
|
134
|
+
releaseId,
|
|
135
|
+
pageRef: target.pageRef,
|
|
136
|
+
};
|
|
135
137
|
}
|
|
136
138
|
const route = routeFromSlug(slug);
|
|
137
139
|
return { route };
|
|
@@ -141,3 +143,23 @@ function routeFromSlug(slug) {
|
|
|
141
143
|
return '/';
|
|
142
144
|
return `/${slug.join('/')}/`;
|
|
143
145
|
}
|
|
146
|
+
// The middleware emits the banner HTML in a reserved request header only
|
|
147
|
+
// after validating a signed mimic token; viewer-forged copies are stripped at
|
|
148
|
+
// the dispatch gate (specs/features/audience-mimic REQ-4). The header read
|
|
149
|
+
// happens here (the page already awaits request-scoped data) so the banner
|
|
150
|
+
// itself stays a synchronous leaf.
|
|
151
|
+
async function readMimicBannerHTML() {
|
|
152
|
+
try {
|
|
153
|
+
const current = await headers();
|
|
154
|
+
return decodeMimicBanner(current.get(MIMIC_BANNER_HEADER) || '');
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// headers() has no request context in static generation and unit tests.
|
|
158
|
+
return '';
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function MimicBanner({ html }) {
|
|
162
|
+
if (!html)
|
|
163
|
+
return null;
|
|
164
|
+
return _jsx("div", { dangerouslySetInnerHTML: { __html: html } });
|
|
165
|
+
}
|
package/dist/next/server.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { headers } from 'next/headers.js';
|
|
2
2
|
import { FragmentNotFoundError, LayoutNotFoundError, PageNotFoundError, } from '../content/provider.js';
|
|
3
|
+
import { ObjectContentProvider } from '../providers/object.js';
|
|
3
4
|
import { normalizeRoute } from '../content/routes.js';
|
|
4
5
|
import { getCachedManifest, getCachedSiteConfig } from '../content/cache.js';
|
|
5
6
|
import { S3ContentProvider } from '../providers/s3.js';
|
|
7
|
+
import { VercelReleaseObjectReader } from '../providers/vercel-object-reader.js';
|
|
6
8
|
import { GRADIAL_RENDER_INPUT_HEADER, getPendingRenderInput } from '../runtime/page.js';
|
|
7
9
|
import { attachRenderProofReceipt, selectVerifiedPage, } from '../internal/render-proof.js';
|
|
8
10
|
import { getUncachedEdgeConfigValue } from './edge-config.js';
|
|
9
11
|
import { activeReleaseV1Key, clampDeploymentCompatibleReleaseV1, decodeActiveReleaseV1, decodeDeploymentCompatibilityV1, deploymentCompatibilityKey, } from './vercel-edge-projection-v1.js';
|
|
10
12
|
export { FragmentNotFoundError, PageNotFoundError, LayoutNotFoundError };
|
|
11
|
-
const releaseObjectReaderSymbol = Symbol.for('aci.
|
|
13
|
+
const releaseObjectReaderSymbol = Symbol.for('@gradial/aci.release-object-reader');
|
|
14
|
+
const objectProviders = new WeakMap();
|
|
15
|
+
let vercelReleaseObjectReader;
|
|
12
16
|
export function setReleaseObjectReader(reader) {
|
|
13
17
|
const scope = globalThis;
|
|
14
18
|
if (reader)
|
|
@@ -339,6 +343,29 @@ async function resolveS3Provider(config) {
|
|
|
339
343
|
}
|
|
340
344
|
async function resolveProviderWithFallback(config) {
|
|
341
345
|
const injected = getInjectedReader();
|
|
346
|
+
const pageRef = config.pageRef;
|
|
347
|
+
const hostedReaderConfigured = injected || config.roleArn || process.env.AWS_ROLE_ARN;
|
|
348
|
+
if (pageRef && hostedReaderConfigured) {
|
|
349
|
+
const releaseId = config.releaseId || process.env.ACI_RELEASE_ID || 'test';
|
|
350
|
+
let provider = objectProviders.get(config);
|
|
351
|
+
if (!provider) {
|
|
352
|
+
if (!injected && !vercelReleaseObjectReader) {
|
|
353
|
+
vercelReleaseObjectReader = new VercelReleaseObjectReader({
|
|
354
|
+
bucket: config.bucket,
|
|
355
|
+
keyPrefix: config.keyPrefix,
|
|
356
|
+
region: config.region,
|
|
357
|
+
roleArn: config.roleArn,
|
|
358
|
+
endpoint: config.endpoint,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
const reader = injected || vercelReleaseObjectReader;
|
|
362
|
+
if (!reader)
|
|
363
|
+
throw new Error('ACI hosted page reader is unavailable');
|
|
364
|
+
provider = new ObjectContentProvider({ releaseId, pageRef, reader });
|
|
365
|
+
objectProviders.set(config, provider);
|
|
366
|
+
}
|
|
367
|
+
return provider;
|
|
368
|
+
}
|
|
342
369
|
if (injected) {
|
|
343
370
|
const releaseId = config.releaseId || process.env.ACI_RELEASE_ID || 'test';
|
|
344
371
|
return new InjectedReaderProvider(releaseId, injected);
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import type { EdgeRouteManifest } from './personalization-host.js';
|
|
2
|
-
export declare const VERCEL_EDGE_PROJECTION_SCHEMA_V1 = "gradial.vercel.edgeRoutes.v1";
|
|
3
|
-
export declare const VERCEL_EDGE_PROJECTION_ENCODING_V1 = "gzip+base64";
|
|
4
|
-
export declare const MAX_VERCEL_EDGE_MANIFEST_BYTES_V1: number;
|
|
5
1
|
export interface ActiveReleasePointerV1 {
|
|
6
2
|
releaseId: string;
|
|
7
3
|
sequence: bigint;
|
|
@@ -13,12 +9,7 @@ export interface DeploymentCompatibilityV1 {
|
|
|
13
9
|
maxSequence?: bigint;
|
|
14
10
|
}
|
|
15
11
|
export declare function activeReleaseV1Key(siteId: string): string;
|
|
16
|
-
export declare function edgeRoutesV1Key(siteId: string, releaseId: string): string;
|
|
17
12
|
export declare function deploymentCompatibilityKey(siteId: string, deploymentId: string): string;
|
|
18
13
|
export declare function decodeActiveReleaseV1(value: unknown): ActiveReleasePointerV1;
|
|
19
14
|
export declare function decodeDeploymentCompatibilityV1(value: unknown): DeploymentCompatibilityV1;
|
|
20
15
|
export declare function clampDeploymentCompatibleReleaseV1(active: ActiveReleasePointerV1, compatibility: DeploymentCompatibilityV1): string;
|
|
21
|
-
export declare function decodeEdgeRouteProjectionV1(value: unknown, expected: {
|
|
22
|
-
siteId: string;
|
|
23
|
-
releaseId: string;
|
|
24
|
-
}): EdgeRouteManifest;
|