@gradial/aci 0.1.45 → 0.1.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audience-data-models.d.ts +142 -2
- package/dist/audience-data-models.js +2 -0
- package/dist/cli/compile-registry.d.mts +1 -5
- package/dist/cli/compile-registry.mjs +10 -25
- package/dist/cli/validate-content.d.mts +1 -31
- package/dist/cli/validate-content.mjs +12 -50
- package/dist/cli/verify-next-cdn-routes.mjs +1 -0
- package/dist/content/provider.d.ts +1 -1
- package/dist/content/resolve-slots.js +0 -1
- package/dist/content/types.d.ts +0 -2
- package/dist/internal/render-proof.d.ts +1 -0
- package/dist/internal/render-proof.js +197 -0
- package/dist/next/middleware.js +9 -54
- package/dist/next/page.js +9 -4
- package/dist/next/personalization-host.d.ts +5 -0
- package/dist/next/server.js +88 -75
- package/dist/next/vercel-collect-sdk-route.js +6 -30
- package/dist/next/vercel-edge-projection-v1.d.ts +24 -0
- package/dist/next/vercel-edge-projection-v1.js +880 -0
- package/dist/next/vercel-s3-personalization.js +7 -11
- package/dist/providers/s3.d.ts +2 -4
- package/dist/providers/s3.js +173 -19
- package/dist/types/block-ref.d.ts +0 -2
- package/dist/types/block-ref.js +0 -1
- package/package.json +2 -2
- package/src/cli/compile-registry.mjs +10 -24
- package/src/cli/validate-content.mjs +13 -52
- package/src/cli/verify-next-cdn-routes.mjs +6 -0
- package/src/types/block-ref.ts +0 -2
- package/templates/astro/starter-content/src/fixtures/atomicBlocks.ts +6 -7
- package/templates/astro/starter-content/src/fixtures/composition.ts +2 -3
- package/templates/astro/starter-content/src/fixtures/sections.ts +1 -2
- package/templates/common/.agents/skills/authoring/SKILL.md +3 -11
- package/templates/common/.agents/skills/authoring/references/block-slots.md +0 -3
- package/templates/common/.content/pages/home/_index.json +0 -10
- package/templates/common/starter-content/.content/pages/about/_index.json +6 -6
- package/templates/common/starter-content/.content/pages/home/_index.json +38 -22
- package/src/types/block-ref.test.ts +0 -45
- package/templates/nextjs/template/src/app/collect/route.ts +0 -18
package/dist/next/middleware.js
CHANGED
|
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server.js';
|
|
|
2
2
|
import { getUncachedEdgeConfigValue } from './edge-config.js';
|
|
3
3
|
import { PREVIEW_TOKEN_COOKIE, PREVIEW_RELEASE_COOKIE, extractPreviewToken, resolvePreviewContext, createPreviewCookies, previewSignKey, } from '../preview/core.js';
|
|
4
4
|
import { CONTENT_PATH_HEADER, CONTENT_PATH_CACHE_PARAM, resolvePersonalizationRequest, } from './personalization-host.js';
|
|
5
|
+
import { activeReleaseV1Key, clampDeploymentCompatibleReleaseV1, decodeActiveReleaseV1, decodeDeploymentCompatibilityV1, deploymentCompatibilityKey, } from './vercel-edge-projection-v1.js';
|
|
5
6
|
const RELEASE_SCOPED_PREFIX = '/__r';
|
|
6
7
|
const FRAMEWORK_ORIGIN_AUTH_HEADER = 'x-gradial-framework-origin-auth';
|
|
7
8
|
const FRAMEWORK_SITE_ID_HEADER = 'x-baremetal-site-id';
|
|
@@ -371,73 +372,27 @@ async function activeReleaseFromEdgeConfig(config) {
|
|
|
371
372
|
if (!edgeConfig || !siteId)
|
|
372
373
|
return '';
|
|
373
374
|
const active = await activeReleasePointer(edgeConfig, siteId);
|
|
374
|
-
if (
|
|
375
|
+
if (active == null)
|
|
375
376
|
return '';
|
|
376
377
|
const deploymentId = config.deploymentId || process.env.VERCEL_DEPLOYMENT_ID || '';
|
|
377
378
|
if (!deploymentId)
|
|
378
379
|
return active.releaseId;
|
|
379
|
-
const
|
|
380
|
-
if (
|
|
380
|
+
const compatibilityValue = await getEdgeConfigValue(edgeConfig, deploymentCompatibilityKey(siteId, deploymentId));
|
|
381
|
+
if (compatibilityValue == null)
|
|
381
382
|
return active.releaseId;
|
|
382
|
-
return
|
|
383
|
+
return clampDeploymentCompatibleReleaseV1(active, decodeDeploymentCompatibilityV1(compatibilityValue));
|
|
383
384
|
}
|
|
384
385
|
function siteIdForConfig(config) {
|
|
385
386
|
return config.siteId || process.env.ACI_SITE_ID || '';
|
|
386
387
|
}
|
|
387
|
-
async function getEdgeConfigJSON(edgeConfig, key) {
|
|
388
|
-
const value = await getEdgeConfigValue(edgeConfig, key);
|
|
389
|
-
if (!value)
|
|
390
|
-
return null;
|
|
391
|
-
if (typeof value === 'string') {
|
|
392
|
-
try {
|
|
393
|
-
return JSON.parse(value);
|
|
394
|
-
}
|
|
395
|
-
catch {
|
|
396
|
-
return null;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
return value;
|
|
400
|
-
}
|
|
401
388
|
async function getEdgeConfigValue(edgeConfig, key) {
|
|
402
389
|
return getUncachedEdgeConfigValue(edgeConfig, key);
|
|
403
390
|
}
|
|
404
|
-
function activeReleaseKey(siteId) {
|
|
405
|
-
return `activeRelease_${edgeKeySegment(siteId)}`;
|
|
406
|
-
}
|
|
407
|
-
function deploymentCompatibilityKey(siteId, deploymentId) {
|
|
408
|
-
return `deploymentCompatibility_${edgeKeySegment(siteId)}_${edgeKeySegment(deploymentId)}`;
|
|
409
|
-
}
|
|
410
391
|
async function activeReleasePointer(edgeConfig, siteId) {
|
|
411
|
-
const value = await getEdgeConfigValue(edgeConfig,
|
|
412
|
-
if (
|
|
413
|
-
return
|
|
414
|
-
|
|
415
|
-
try {
|
|
416
|
-
const parsed = JSON.parse(value);
|
|
417
|
-
if (parsed.releaseId)
|
|
418
|
-
return parsed;
|
|
419
|
-
}
|
|
420
|
-
catch {
|
|
421
|
-
return { releaseId: value };
|
|
422
|
-
}
|
|
423
|
-
return { releaseId: value };
|
|
424
|
-
}
|
|
425
|
-
const pointer = value;
|
|
426
|
-
return { releaseId: pointer.releaseId || '', sequence: pointer.sequence };
|
|
427
|
-
}
|
|
428
|
-
function clampRelease(activeReleaseId, activeSequence, compatibility) {
|
|
429
|
-
if (!activeSequence)
|
|
430
|
-
return activeReleaseId;
|
|
431
|
-
if (compatibility.minSequence && activeSequence < compatibility.minSequence && compatibility.minReleaseId) {
|
|
432
|
-
return compatibility.minReleaseId;
|
|
433
|
-
}
|
|
434
|
-
if (compatibility.maxSequence && activeSequence > compatibility.maxSequence && compatibility.maxReleaseId) {
|
|
435
|
-
return compatibility.maxReleaseId;
|
|
436
|
-
}
|
|
437
|
-
return activeReleaseId;
|
|
438
|
-
}
|
|
439
|
-
function edgeKeySegment(value) {
|
|
440
|
-
return value.replace(/[^A-Za-z0-9_-]/g, '_');
|
|
392
|
+
const value = await getEdgeConfigValue(edgeConfig, activeReleaseV1Key(siteId));
|
|
393
|
+
if (value == null)
|
|
394
|
+
return null;
|
|
395
|
+
return decodeActiveReleaseV1(value);
|
|
441
396
|
}
|
|
442
397
|
function isPassthroughRoute(pathname) {
|
|
443
398
|
return PASSTHROUGH_ROUTES.some((route) => routeMatches(pathname, route));
|
package/dist/next/page.js
CHANGED
|
@@ -6,6 +6,7 @@ import { routeMetadataForContent, resolveSlots, } from '../content/index.js';
|
|
|
6
6
|
import { renderBlocks } from './render.js';
|
|
7
7
|
import { AssignmentsScript } from './assignments-script.js';
|
|
8
8
|
import { CollectScript } from './collect-script.js';
|
|
9
|
+
import { renderProofContentFor } from '../internal/render-proof.js';
|
|
9
10
|
/**
|
|
10
11
|
* Creates the catch-all route handler for a Next.js ACI site.
|
|
11
12
|
*
|
|
@@ -64,12 +65,14 @@ export function createPage(registry, options) {
|
|
|
64
65
|
}
|
|
65
66
|
async function CatchAllPage({ params }) {
|
|
66
67
|
const { route, releaseId } = await resolveRouteAndRelease(params);
|
|
68
|
+
const fetchConfig = { releaseId };
|
|
67
69
|
try {
|
|
68
|
-
const input = await getRenderInput(route,
|
|
70
|
+
const input = await getRenderInput(route, fetchConfig);
|
|
69
71
|
if (!input.page)
|
|
70
72
|
notFound();
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
+
const renderProofContent = renderProofContentFor(input);
|
|
74
|
+
const layout = await getLayout(input.page.layout, fetchConfig);
|
|
75
|
+
const resolvedSlots = await resolveSlots(layout, input.page.regions || {}, (id) => getFragment(id, fetchConfig));
|
|
73
76
|
const renderedSlots = await Promise.all(layout.slots.map(async (slot) => {
|
|
74
77
|
const blocks = resolvedSlots[slot.name];
|
|
75
78
|
if (!blocks?.length)
|
|
@@ -81,7 +84,9 @@ export function createPage(registry, options) {
|
|
|
81
84
|
}
|
|
82
85
|
return (_jsx(Fragment, { children: renderedBlocks }, slot.name));
|
|
83
86
|
}));
|
|
84
|
-
return (_jsxs(_Fragment, { children: [
|
|
87
|
+
return (_jsxs(_Fragment, { children: [renderProofContent
|
|
88
|
+
? _jsx("meta", { name: "aci-render-proof", content: renderProofContent })
|
|
89
|
+
: null, renderedSlots.filter(Boolean), _jsx(AssignmentsScript, { page: input.page }), _jsx(CollectScript, { siteConfig: input.siteConfig })] }));
|
|
85
90
|
}
|
|
86
91
|
catch (error) {
|
|
87
92
|
if (error instanceof PageNotFoundError)
|
|
@@ -29,12 +29,17 @@ export interface EdgeRouteEntry {
|
|
|
29
29
|
dispatch: EdgeDispatch;
|
|
30
30
|
variants?: Record<string, EdgeRouteVariant>;
|
|
31
31
|
}
|
|
32
|
+
export interface CollectSDKEntry {
|
|
33
|
+
hash: string;
|
|
34
|
+
path: string;
|
|
35
|
+
}
|
|
32
36
|
export interface EdgeRouteManifest {
|
|
33
37
|
manifestVersion: string;
|
|
34
38
|
releaseId: string;
|
|
35
39
|
siteId: string;
|
|
36
40
|
routes: Record<string, EdgeRouteEntry>;
|
|
37
41
|
experiments?: Record<string, PersonalizationExperiment>;
|
|
42
|
+
collectSdk?: CollectSDKEntry;
|
|
38
43
|
notFoundPage?: string;
|
|
39
44
|
errorPage?: string;
|
|
40
45
|
}
|
package/dist/next/server.js
CHANGED
|
@@ -3,17 +3,19 @@ import { FragmentNotFoundError, LayoutNotFoundError, PageNotFoundError, } from '
|
|
|
3
3
|
import { getCachedManifest, getCachedSiteConfig } from '../content/cache.js';
|
|
4
4
|
import { S3ContentProvider } from '../providers/s3.js';
|
|
5
5
|
import { GRADIAL_RENDER_INPUT_HEADER, getPendingRenderInput } from '../runtime/page.js';
|
|
6
|
+
import { attachRenderProofReceipt, selectVerifiedPage, } from '../internal/render-proof.js';
|
|
6
7
|
import { getUncachedEdgeConfigValue } from './edge-config.js';
|
|
8
|
+
import { activeReleaseV1Key, clampDeploymentCompatibleReleaseV1, decodeActiveReleaseV1, decodeDeploymentCompatibilityV1, deploymentCompatibilityKey, } from './vercel-edge-projection-v1.js';
|
|
7
9
|
export { FragmentNotFoundError, PageNotFoundError, LayoutNotFoundError };
|
|
10
|
+
const MAX_S3_PROVIDER_CACHE_ENTRIES = 8;
|
|
11
|
+
const s3ProviderCache = [];
|
|
12
|
+
const s3ProviderReleaseIds = new WeakMap();
|
|
8
13
|
export async function getPage(route = '', config = {}) {
|
|
9
14
|
const runtimeInput = await getPageRuntimeRenderInput();
|
|
10
15
|
if (runtimeInput)
|
|
11
16
|
return runtimeInput.page;
|
|
12
17
|
const provider = await resolveProviderWithFallback(config);
|
|
13
|
-
|
|
14
|
-
if (variantPage !== null)
|
|
15
|
-
return variantPage;
|
|
16
|
-
return await provider.getPage(route);
|
|
18
|
+
return (await selectRenderPage(provider, route)).page;
|
|
17
19
|
}
|
|
18
20
|
export async function getSiteConfig(config = {}) {
|
|
19
21
|
const runtimeInput = await getPageRuntimeRenderInput();
|
|
@@ -55,17 +57,21 @@ export async function getRenderInput(route = '/', config = {}) {
|
|
|
55
57
|
const provider = await resolveProviderWithFallback(config);
|
|
56
58
|
// Cache shared resources (manifest, siteConfig) but not per-page content
|
|
57
59
|
// Page-level caching is handled by CDN
|
|
58
|
-
const [
|
|
59
|
-
|
|
60
|
+
const [selection, siteConfig] = await Promise.all([
|
|
61
|
+
selectRenderPage(provider, route),
|
|
60
62
|
getCachedSiteConfig(provider),
|
|
61
63
|
]);
|
|
62
|
-
|
|
64
|
+
const input = {
|
|
63
65
|
route,
|
|
64
66
|
domain: siteConfig.domain || 'www.aci.local',
|
|
65
67
|
locale: siteConfig.defaultLocale || 'en-us',
|
|
66
68
|
siteConfig,
|
|
67
|
-
page,
|
|
69
|
+
page: selection.page,
|
|
68
70
|
};
|
|
71
|
+
if (selection.receipt) {
|
|
72
|
+
attachRenderProofReceipt(input, selection.receipt);
|
|
73
|
+
}
|
|
74
|
+
return input;
|
|
69
75
|
}
|
|
70
76
|
export async function getPageRuntimeRenderInput() {
|
|
71
77
|
try {
|
|
@@ -83,20 +89,39 @@ export async function getPageRuntimeRenderInput() {
|
|
|
83
89
|
// is cosmetic. The S3 provider only serves refs the release manifest
|
|
84
90
|
// declares.
|
|
85
91
|
const ACI_CONTENT_PATH_HEADER = 'x-aci-content-path';
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
92
|
+
const ACI_VARIANT_KEY_HEADER = 'x-aci-variant-key';
|
|
93
|
+
async function selectRenderPage(provider, route) {
|
|
94
|
+
if (!(provider instanceof S3ContentProvider)) {
|
|
95
|
+
return { page: await provider.getPage(route) };
|
|
96
|
+
}
|
|
97
|
+
const releaseId = s3ProviderReleaseIds.get(provider);
|
|
98
|
+
if (!releaseId) {
|
|
99
|
+
throw new Error('ACI S3 page integrity error: provider release is unavailable');
|
|
100
|
+
}
|
|
101
|
+
let contentRef = '';
|
|
102
|
+
let contentRefPresent = false;
|
|
103
|
+
let variantKey = '';
|
|
104
|
+
let variantKeyPresent = false;
|
|
90
105
|
try {
|
|
91
106
|
const current = await headers();
|
|
92
|
-
|
|
107
|
+
const selectedContentRef = current.get(ACI_CONTENT_PATH_HEADER);
|
|
108
|
+
contentRefPresent = selectedContentRef !== null;
|
|
109
|
+
contentRef = selectedContentRef ?? '';
|
|
110
|
+
const selectedVariantKey = current.get(ACI_VARIANT_KEY_HEADER);
|
|
111
|
+
variantKeyPresent = selectedVariantKey !== null;
|
|
112
|
+
variantKey = selectedVariantKey ?? '';
|
|
93
113
|
}
|
|
94
114
|
catch {
|
|
95
|
-
|
|
115
|
+
// `headers()` has no request context in static generation and unit tests.
|
|
96
116
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
117
|
+
return await provider[selectVerifiedPage]({
|
|
118
|
+
releaseId,
|
|
119
|
+
route,
|
|
120
|
+
contentRef,
|
|
121
|
+
contentRefPresent,
|
|
122
|
+
variantKey,
|
|
123
|
+
variantKeyPresent,
|
|
124
|
+
});
|
|
100
125
|
}
|
|
101
126
|
export { isPreviewMode } from './preview-mode.js';
|
|
102
127
|
export async function routeFromNextParams(params) {
|
|
@@ -230,7 +255,11 @@ export async function resolveReleaseId(config = {}) {
|
|
|
230
255
|
}
|
|
231
256
|
async function resolveS3Provider(config) {
|
|
232
257
|
const releaseId = await resolveReleaseId(config);
|
|
233
|
-
|
|
258
|
+
if (releaseId.length > 128
|
|
259
|
+
|| !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(releaseId)) {
|
|
260
|
+
throw new Error(`ACI release ID is invalid: ${releaseId}`);
|
|
261
|
+
}
|
|
262
|
+
const candidate = new S3ContentProvider({
|
|
234
263
|
bucket: config.bucket,
|
|
235
264
|
keyPrefix: releaseKey(config, releaseId),
|
|
236
265
|
region: config.region,
|
|
@@ -240,9 +269,29 @@ async function resolveS3Provider(config) {
|
|
|
240
269
|
roleArn: config.roleArn,
|
|
241
270
|
endpoint: config.endpoint,
|
|
242
271
|
});
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
272
|
+
s3ProviderReleaseIds.set(candidate, releaseId);
|
|
273
|
+
const cachedIndex = s3ProviderCache.findIndex((entry) => sameS3ProviderConfig(entry.config, candidate.config));
|
|
274
|
+
if (cachedIndex >= 0) {
|
|
275
|
+
const [cached] = s3ProviderCache.splice(cachedIndex, 1);
|
|
276
|
+
s3ProviderCache.push(cached);
|
|
277
|
+
return await cached.provider;
|
|
278
|
+
}
|
|
279
|
+
const provider = getCachedManifest(candidate).then(() => candidate);
|
|
280
|
+
const entry = { config: candidate.config, provider };
|
|
281
|
+
s3ProviderCache.push(entry);
|
|
282
|
+
if (s3ProviderCache.length > MAX_S3_PROVIDER_CACHE_ENTRIES) {
|
|
283
|
+
s3ProviderCache.shift();
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
return await provider;
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
const rejectedIndex = s3ProviderCache.findIndex((cached) => cached.provider === provider);
|
|
290
|
+
if (rejectedIndex >= 0) {
|
|
291
|
+
s3ProviderCache.splice(rejectedIndex, 1);
|
|
292
|
+
}
|
|
293
|
+
throw error;
|
|
294
|
+
}
|
|
246
295
|
}
|
|
247
296
|
async function resolveProviderWithFallback(config) {
|
|
248
297
|
if (process.env.ACI_CONTENT_PROVIDER === 'file') {
|
|
@@ -251,35 +300,31 @@ async function resolveProviderWithFallback(config) {
|
|
|
251
300
|
}
|
|
252
301
|
return await resolveS3Provider(config);
|
|
253
302
|
}
|
|
303
|
+
function sameS3ProviderConfig(left, right) {
|
|
304
|
+
return left.bucket === right.bucket
|
|
305
|
+
&& left.keyPrefix === right.keyPrefix
|
|
306
|
+
&& left.region === right.region
|
|
307
|
+
&& left.accessKeyId === right.accessKeyId
|
|
308
|
+
&& left.secretAccessKey === right.secretAccessKey
|
|
309
|
+
&& left.sessionToken === right.sessionToken
|
|
310
|
+
&& left.roleArn === right.roleArn
|
|
311
|
+
&& left.endpoint === right.endpoint;
|
|
312
|
+
}
|
|
254
313
|
async function activeReleaseFromEdgeConfig(config) {
|
|
255
314
|
const edgeConfig = config.edgeConfig || process.env.EDGE_CONFIG || '';
|
|
256
315
|
const siteId = config.siteId || process.env.ACI_SITE_ID || '';
|
|
257
316
|
if (!edgeConfig || !siteId)
|
|
258
317
|
return '';
|
|
259
318
|
const active = await activeReleasePointer(edgeConfig, siteId);
|
|
260
|
-
if (
|
|
319
|
+
if (active == null)
|
|
261
320
|
return '';
|
|
262
321
|
const deploymentId = config.deploymentId || process.env.VERCEL_DEPLOYMENT_ID || '';
|
|
263
322
|
if (!deploymentId)
|
|
264
323
|
return active.releaseId;
|
|
265
|
-
const
|
|
266
|
-
if (
|
|
324
|
+
const compatibilityValue = await getEdgeConfigValue(edgeConfig, deploymentCompatibilityKey(siteId, deploymentId));
|
|
325
|
+
if (compatibilityValue == null)
|
|
267
326
|
return active.releaseId;
|
|
268
|
-
return
|
|
269
|
-
}
|
|
270
|
-
async function getEdgeConfigJSON(edgeConfig, key) {
|
|
271
|
-
const value = await getEdgeConfigValue(edgeConfig, key);
|
|
272
|
-
if (!value)
|
|
273
|
-
return null;
|
|
274
|
-
if (typeof value === 'string') {
|
|
275
|
-
try {
|
|
276
|
-
return JSON.parse(value);
|
|
277
|
-
}
|
|
278
|
-
catch {
|
|
279
|
-
return null;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
return value;
|
|
327
|
+
return clampDeploymentCompatibleReleaseV1(active, decodeDeploymentCompatibilityV1(compatibilityValue));
|
|
283
328
|
}
|
|
284
329
|
async function getEdgeConfigValue(edgeConfig, key) {
|
|
285
330
|
return getUncachedEdgeConfigValue(edgeConfig, key);
|
|
@@ -287,43 +332,11 @@ async function getEdgeConfigValue(edgeConfig, key) {
|
|
|
287
332
|
function releaseKey(config, releaseId) {
|
|
288
333
|
return joinKey(config.keyPrefix || process.env.ACI_S3_KEY_PREFIX || '', 'releases', releaseId);
|
|
289
334
|
}
|
|
290
|
-
function activeReleaseKey(siteId) {
|
|
291
|
-
return `activeRelease_${edgeKeySegment(siteId)}`;
|
|
292
|
-
}
|
|
293
|
-
function deploymentCompatibilityKey(siteId, deploymentId) {
|
|
294
|
-
return `deploymentCompatibility_${edgeKeySegment(siteId)}_${edgeKeySegment(deploymentId)}`;
|
|
295
|
-
}
|
|
296
335
|
async function activeReleasePointer(edgeConfig, siteId) {
|
|
297
|
-
const value = await getEdgeConfigValue(edgeConfig,
|
|
298
|
-
if (
|
|
299
|
-
return
|
|
300
|
-
|
|
301
|
-
try {
|
|
302
|
-
const parsed = JSON.parse(value);
|
|
303
|
-
if (parsed.releaseId)
|
|
304
|
-
return parsed;
|
|
305
|
-
}
|
|
306
|
-
catch {
|
|
307
|
-
return { releaseId: value };
|
|
308
|
-
}
|
|
309
|
-
return { releaseId: value };
|
|
310
|
-
}
|
|
311
|
-
const pointer = value;
|
|
312
|
-
return { releaseId: pointer.releaseId || '', sequence: pointer.sequence };
|
|
313
|
-
}
|
|
314
|
-
function clampRelease(activeReleaseId, activeSequence, compatibility) {
|
|
315
|
-
if (!activeSequence)
|
|
316
|
-
return activeReleaseId;
|
|
317
|
-
if (compatibility.minSequence && activeSequence < compatibility.minSequence && compatibility.minReleaseId) {
|
|
318
|
-
return compatibility.minReleaseId;
|
|
319
|
-
}
|
|
320
|
-
if (compatibility.maxSequence && activeSequence > compatibility.maxSequence && compatibility.maxReleaseId) {
|
|
321
|
-
return compatibility.maxReleaseId;
|
|
322
|
-
}
|
|
323
|
-
return activeReleaseId;
|
|
324
|
-
}
|
|
325
|
-
function edgeKeySegment(value) {
|
|
326
|
-
return value.replace(/[^A-Za-z0-9_-]/g, '_');
|
|
336
|
+
const value = await getEdgeConfigValue(edgeConfig, activeReleaseV1Key(siteId));
|
|
337
|
+
if (value == null)
|
|
338
|
+
return null;
|
|
339
|
+
return decodeActiveReleaseV1(value);
|
|
327
340
|
}
|
|
328
341
|
function joinKey(...parts) {
|
|
329
342
|
return parts
|
|
@@ -2,6 +2,7 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
|
3
3
|
import { awsCredentialsProvider } from '@vercel/oidc-aws-credentials-provider';
|
|
4
4
|
import { getUncachedEdgeConfigValue } from './edge-config.js';
|
|
5
|
+
import { activeReleaseV1Key, decodeActiveReleaseV1, decodeEdgeRouteProjectionV1, edgeRoutesV1Key, } from './vercel-edge-projection-v1.js';
|
|
5
6
|
const SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
6
7
|
/**
|
|
7
8
|
* Creates the Node-only stable collect-SDK route used by ACI's injected
|
|
@@ -34,12 +35,11 @@ export function createVercelCollectSDKRoute(input, dependencies = {}) {
|
|
|
34
35
|
});
|
|
35
36
|
}
|
|
36
37
|
try {
|
|
37
|
-
const active =
|
|
38
|
-
const projection =
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
38
|
+
const active = decodeActiveReleaseV1(await readEdgeConfig(config.edgeConfig, activeReleaseV1Key(config.siteId)));
|
|
39
|
+
const projection = decodeEdgeRouteProjectionV1(await readEdgeConfig(config.edgeConfig, edgeRoutesV1Key(config.siteId, active.releaseId)), {
|
|
40
|
+
siteId: config.siteId,
|
|
41
|
+
releaseId: active.releaseId,
|
|
42
|
+
});
|
|
43
43
|
if (!Object.prototype.hasOwnProperty.call(projection, 'collectSdk')) {
|
|
44
44
|
return new Response(method === 'HEAD' ? null : 'collect SDK is not enabled for this release', {
|
|
45
45
|
status: 404,
|
|
@@ -115,21 +115,6 @@ function validateConfig(input) {
|
|
|
115
115
|
hostingKeyPrefix: cleanKey(input.hostingKeyPrefix || ''),
|
|
116
116
|
};
|
|
117
117
|
}
|
|
118
|
-
function parseActiveReleasePointer(value) {
|
|
119
|
-
const parsed = parseRecord(value, 'active release pointer');
|
|
120
|
-
return { releaseId: requiredPathSegment(parsed.releaseId || '', 'active release ID') };
|
|
121
|
-
}
|
|
122
|
-
function parseProjection(value) {
|
|
123
|
-
const parsed = parseRecord(value, 'collect SDK route projection');
|
|
124
|
-
const projection = {
|
|
125
|
-
releaseId: requiredPathSegment(parsed.releaseId || '', 'projection release ID'),
|
|
126
|
-
siteId: requiredPathSegment(parsed.siteId || '', 'projection site ID'),
|
|
127
|
-
};
|
|
128
|
-
if (Object.prototype.hasOwnProperty.call(parsed, 'collectSdk')) {
|
|
129
|
-
projection.collectSdk = parsed.collectSdk;
|
|
130
|
-
}
|
|
131
|
-
return projection;
|
|
132
|
-
}
|
|
133
118
|
function parseRecord(value, label) {
|
|
134
119
|
const parsed = typeof value === 'string' ? JSON.parse(value) : value;
|
|
135
120
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
@@ -172,15 +157,6 @@ function ifNoneMatchSatisfied(value, etag) {
|
|
|
172
157
|
return normalized === etag;
|
|
173
158
|
});
|
|
174
159
|
}
|
|
175
|
-
function activeReleaseKey(siteId) {
|
|
176
|
-
return `activeRelease_${edgeKeySegment(siteId)}`;
|
|
177
|
-
}
|
|
178
|
-
function edgeRoutesKey(siteId, releaseId) {
|
|
179
|
-
return `edgeRoutes_${edgeKeySegment(siteId)}_${edgeKeySegment(releaseId)}`;
|
|
180
|
-
}
|
|
181
|
-
function edgeKeySegment(value) {
|
|
182
|
-
return value.replace(/[^A-Za-z0-9_-]/g, '_');
|
|
183
|
-
}
|
|
184
160
|
function requiredPathSegment(value, label) {
|
|
185
161
|
const clean = value.trim();
|
|
186
162
|
if (!clean || clean === '.' || clean === '..' || clean.includes('/') || clean.includes('\\')) {
|
|
@@ -0,0 +1,24 @@
|
|
|
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
|
+
export interface ActiveReleasePointerV1 {
|
|
6
|
+
releaseId: string;
|
|
7
|
+
sequence: bigint;
|
|
8
|
+
}
|
|
9
|
+
export interface DeploymentCompatibilityV1 {
|
|
10
|
+
minReleaseId?: string;
|
|
11
|
+
minSequence?: bigint;
|
|
12
|
+
maxReleaseId?: string;
|
|
13
|
+
maxSequence?: bigint;
|
|
14
|
+
}
|
|
15
|
+
export declare function activeReleaseV1Key(siteId: string): string;
|
|
16
|
+
export declare function edgeRoutesV1Key(siteId: string, releaseId: string): string;
|
|
17
|
+
export declare function deploymentCompatibilityKey(siteId: string, deploymentId: string): string;
|
|
18
|
+
export declare function decodeActiveReleaseV1(value: unknown): ActiveReleasePointerV1;
|
|
19
|
+
export declare function decodeDeploymentCompatibilityV1(value: unknown): DeploymentCompatibilityV1;
|
|
20
|
+
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;
|