@gradial/aci 0.1.41 → 0.1.43
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 +15 -11
- package/README.md +4 -4
- package/dist/astro/index.d.ts +1 -1
- package/dist/astro/index.js +1 -1
- package/dist/astro/page.d.ts +6 -0
- package/dist/astro/page.js +3 -2
- package/dist/audience-data-models.d.ts +286 -0
- package/dist/audience-data-models.js +46 -0
- package/dist/cli/compile-registry.d.mts +6 -0
- package/dist/cli/compile-registry.mjs +774 -0
- package/dist/cli/materialize-template.d.mts +19 -0
- package/dist/cli/materialize-template.mjs +128 -0
- package/dist/cli/validate-content.d.mts +32 -0
- package/dist/cli/validate-content.mjs +464 -0
- package/dist/cli/verify-next-cdn-routes.d.mts +2 -0
- package/dist/cli/verify-next-cdn-routes.mjs +47 -0
- package/dist/collect/aci.js +1 -0
- package/dist/collect/aci.js.sha256 +1 -0
- package/dist/collect/assignments.d.ts +65 -0
- package/dist/collect/assignments.js +138 -0
- package/dist/collect/batcher.d.ts +23 -0
- package/dist/collect/batcher.js +28 -6
- package/dist/collect/consent/envelope.d.ts +9 -3
- package/dist/collect/consent/envelope.js +58 -40
- package/dist/collect/declarative.d.ts +1 -1
- package/dist/collect/declarative.js +7 -1
- package/dist/collect/index.js +39 -6
- package/dist/collect/linkclicks.js +4 -2
- package/dist/collect/pageviews.d.ts +15 -3
- package/dist/collect/pageviews.js +28 -13
- package/dist/collect/transport.d.ts +4 -0
- package/dist/collect/transport.js +37 -2
- package/dist/consent/portable-envelope.d.ts +9 -0
- package/dist/consent/portable-envelope.js +110 -0
- package/dist/consent-banner/index.d.ts +33 -0
- package/dist/consent-banner/index.js +100 -0
- package/dist/content/assets.d.ts +1 -0
- package/dist/content/assets.js +1 -0
- package/dist/content/personalization.d.ts +35 -0
- package/dist/content/personalization.js +57 -0
- package/dist/content/resolve-slots.js +1 -0
- package/dist/content/types.d.ts +9 -1
- package/dist/edge/personalization-core.d.ts +11 -0
- package/dist/edge/personalization-core.js +265 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/next/assignments-script.d.ts +14 -0
- package/dist/next/assignments-script.js +17 -0
- package/dist/next/collect-route.d.ts +19 -0
- package/dist/next/collect-route.js +422 -0
- package/dist/next/envelope-digest.d.ts +21 -0
- package/dist/next/envelope-digest.js +58 -0
- package/dist/next/index.d.ts +1 -0
- package/dist/next/index.js +1 -0
- package/dist/next/middleware.d.ts +31 -3
- package/dist/next/middleware.js +254 -30
- package/dist/next/page.js +2 -1
- package/dist/next/personalization-host.d.ts +127 -0
- package/dist/next/personalization-host.js +257 -0
- package/dist/next/server.d.ts +1 -0
- package/dist/next/server.js +9 -6
- package/dist/next/vercel-collect-sdk-route.d.ts +27 -0
- package/dist/next/vercel-collect-sdk-route.js +205 -0
- package/dist/next/vercel-personalization.d.ts +4 -0
- package/dist/next/vercel-personalization.js +4 -0
- package/dist/next/vercel-s3-personalization.d.ts +79 -0
- package/dist/next/vercel-s3-personalization.js +574 -0
- package/dist/providers/s3.d.ts +20 -5
- package/dist/providers/s3.js +93 -23
- package/dist/react/VideoPlayer.d.ts +6 -6
- package/dist/react/index.d.ts +6 -6
- package/dist/testing/conformance/checks.js +75 -4
- package/dist/types/block-ref.d.ts +2 -0
- package/dist/types/block-ref.js +1 -0
- package/dist/types/page.d.ts +1 -1
- package/package.json +44 -15
- package/src/astro/ConsentBanner.astro +62 -0
- package/src/cli/compile-registry.mjs +24 -10
- package/src/cli/validate-content.mjs +52 -13
- package/src/cli/verify-next-cdn-routes.mjs +5 -4
- package/src/consent-banner/styles.css +110 -0
- package/src/types/block-ref.test.ts +45 -0
- package/src/types/block-ref.ts +2 -0
- package/src/types/page.ts +1 -1
- package/templates/astro/starter-content/src/cms/contracts/components/index.ts +2 -0
- package/templates/astro/starter-content/src/cms/contracts/components/personalizationChoice.contract.ts +17 -0
- package/templates/astro/starter-content/src/cms/registry.ts +3 -0
- package/templates/astro/starter-content/src/components/sections/PersonalizationChoice.astro +41 -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 +2 -1
- package/templates/astro/starter-content/src/render/RenderPage.astro +3 -2
- package/templates/astro/template/src/layouts/SiteLayout.astro +3 -1
- package/templates/astro/template/src/pages/[...slug].astro +1 -0
- package/templates/common/.agents/skills/authoring/SKILL.md +11 -3
- package/templates/common/.agents/skills/authoring/references/block-slots.md +3 -0
- package/templates/common/.content/audiences/preference-blue/_index.json +11 -0
- package/templates/common/.content/audiences/preference-red/_index.json +11 -0
- package/templates/common/.content/overlays/audience/preference-blue/pages/home/_index.json +13 -0
- package/templates/common/.content/overlays/audience/preference-red/pages/home/_index.json +13 -0
- package/templates/common/.content/pages/home/_index.json +124 -4
- package/templates/common/.content/segments/preference-blue/_index.json +20 -0
- package/templates/common/.content/segments/preference-red/_index.json +20 -0
- package/templates/common/contracts/components/index.ts +21 -1
- package/templates/common/contracts/components/personalizationChoice.contract.ts +17 -0
- package/templates/common/starter-content/.content/pages/about/_index.json +3 -0
- package/templates/common/starter-content/.content/pages/home/_index.json +9 -0
- package/templates/nextjs/starter-content/src/cms/contracts/components/index.ts +3 -1
- package/templates/nextjs/starter-content/src/cms/contracts/components/personalizationChoice.contract.ts +17 -0
- package/templates/nextjs/starter-content/src/cms/registry.ts +3 -0
- package/templates/nextjs/starter-content/src/components/sections/PersonalizationChoice.tsx +53 -0
- package/templates/nextjs/starter-content/src/middleware.ts +41 -0
- package/templates/nextjs/template/src/app/collect/route.ts +18 -0
- package/templates/nextjs/template/src/middleware.ts +36 -2
- package/templates/nextjs/template/src/middleware.ts.tmpl +31 -1
- package/templates/astro/template/.aci.yaml +0 -28
- package/templates/common/.content/.gradial-dam/blobs/sha256/7f/bf/7fbfc7fe230ff12688c496f660119e41d9725c4504d82e310c15665c73c09292 +0 -0
- package/templates/common/.content/.gradial-dam/blobs/sha256/a0/f5/a0f506689de9b15a7055f70103a3f02cd3ba105771ae2de6754df2ede1210d3a +0 -0
- package/templates/common/.content/.gradial-dam/blobs/sha256/dd/b7/ddb7e9642be08317bcd4b2bd26b58ba8d0d34dd7239e03e684a4f3634fa3845b +0 -0
- package/templates/common/.content/.gradial-dam/blobs/sha256/fb/32/fb3260e8286ea8231ea1c77b9c0be92d6edd9026565731eb14cfdcfd6b1b75b3 +0 -0
- package/templates/common/.content/config/robots.txt +0 -2
- package/templates/common/.content/config/site.json +0 -197
- package/templates/common/.content/dam/folders/seed/ast_site_about_hero/asset.json +0 -9
- package/templates/common/.content/dam/folders/seed/ast_site_about_hero/editorial.json +0 -14
- package/templates/common/.content/dam/folders/seed/ast_site_about_hero/governance.json +0 -23
- package/templates/common/.content/dam/folders/seed/ast_site_about_hero/versions/v0001.json +0 -37
- package/templates/common/.content/dam/folders/seed/ast_site_home_hero/asset.json +0 -9
- package/templates/common/.content/dam/folders/seed/ast_site_home_hero/editorial.json +0 -14
- package/templates/common/.content/dam/folders/seed/ast_site_home_hero/governance.json +0 -23
- package/templates/common/.content/dam/folders/seed/ast_site_home_hero/versions/v0001.json +0 -37
- package/templates/common/.content/fragments/navbar/_index.json +0 -25
- package/templates/common/.content/fragments/site-footer/_index.json +0 -62
- package/templates/common/.content/pages/about/_index.json +0 -51
- package/templates/nextjs/template/.aci.yaml +0 -26
package/dist/providers/s3.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
|
+
import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
|
3
|
+
import { awsCredentialsProvider } from '@vercel/oidc-aws-credentials-provider';
|
|
2
4
|
import { FragmentNotFoundError, LayoutNotFoundError, PageNotFoundError } from '../content/provider.js';
|
|
3
5
|
import { normalizeRoute } from '../content/routes.js';
|
|
4
6
|
// ---------------------------------------------------------------------------
|
|
@@ -7,8 +9,10 @@ import { normalizeRoute } from '../content/routes.js';
|
|
|
7
9
|
export class S3ContentProvider {
|
|
8
10
|
config;
|
|
9
11
|
#manifest;
|
|
10
|
-
|
|
12
|
+
#client;
|
|
13
|
+
constructor(config = {}, client) {
|
|
11
14
|
this.config = resolveS3Config(config);
|
|
15
|
+
this.#client = client || oidcS3Client(this.config);
|
|
12
16
|
}
|
|
13
17
|
async listRoutes() {
|
|
14
18
|
const manifest = await this.manifest();
|
|
@@ -34,7 +38,7 @@ export class S3ContentProvider {
|
|
|
34
38
|
}
|
|
35
39
|
/**
|
|
36
40
|
* getPageByRef serves the exact payload the edge selected (the
|
|
37
|
-
* X-
|
|
41
|
+
* X-Aci-Content-Ref contract): base page or variant. Only refs the
|
|
38
42
|
* release manifest declares are served — the header names published
|
|
39
43
|
* content, it cannot address arbitrary keys.
|
|
40
44
|
*/
|
|
@@ -83,24 +87,18 @@ export class S3ContentProvider {
|
|
|
83
87
|
return await this.#manifest;
|
|
84
88
|
}
|
|
85
89
|
async fetchRaw(key) {
|
|
86
|
-
return await getS3Raw(key, this.config);
|
|
90
|
+
return await getS3Raw(key, this.config, this.#client);
|
|
87
91
|
}
|
|
88
92
|
async getJSON(key) {
|
|
89
|
-
return await getS3JSON(joinKey(this.config.keyPrefix, key), this.config);
|
|
93
|
+
return await getS3JSON(joinKey(this.config.keyPrefix, key), this.config, this.#client);
|
|
90
94
|
}
|
|
91
95
|
}
|
|
92
96
|
// ---------------------------------------------------------------------------
|
|
93
97
|
// S3 fetch with AWS SigV4 signing
|
|
94
98
|
// ---------------------------------------------------------------------------
|
|
95
|
-
export async function getS3JSON(key, config) {
|
|
99
|
+
export async function getS3JSON(key, config, client) {
|
|
96
100
|
const startTime = Date.now();
|
|
97
|
-
const
|
|
98
|
-
const signedHeaders = signedS3Headers('GET', url, config);
|
|
99
|
-
const res = await fetch(url, {
|
|
100
|
-
method: 'GET',
|
|
101
|
-
headers: signedHeaders,
|
|
102
|
-
cache: 'no-store'
|
|
103
|
-
});
|
|
101
|
+
const res = await getS3Raw(key, config, client);
|
|
104
102
|
const duration = Date.now() - startTime;
|
|
105
103
|
console.log(`[ACI S3] GET ${key} - ${res.status} in ${duration}ms`);
|
|
106
104
|
if (!res.ok) {
|
|
@@ -108,7 +106,9 @@ export async function getS3JSON(key, config) {
|
|
|
108
106
|
}
|
|
109
107
|
return (await res.json());
|
|
110
108
|
}
|
|
111
|
-
export async function getS3Raw(key, config) {
|
|
109
|
+
export async function getS3Raw(key, config, client) {
|
|
110
|
+
if (client)
|
|
111
|
+
return await getS3RawWithClient(key, config, client);
|
|
112
112
|
const url = s3ObjectURL(config, key);
|
|
113
113
|
const signedHeaders = signedS3Headers('GET', url, config);
|
|
114
114
|
return await fetch(url, {
|
|
@@ -122,20 +122,90 @@ export async function getS3Raw(key, config) {
|
|
|
122
122
|
// ---------------------------------------------------------------------------
|
|
123
123
|
function resolveS3Config(config) {
|
|
124
124
|
const resolved = {
|
|
125
|
-
bucket: config.bucket
|
|
126
|
-
keyPrefix: config.keyPrefix
|
|
127
|
-
region: config.region
|
|
128
|
-
accessKeyId: config.accessKeyId
|
|
129
|
-
secretAccessKey: config.secretAccessKey
|
|
130
|
-
sessionToken: config.sessionToken
|
|
131
|
-
|
|
125
|
+
bucket: configuredValue(config.bucket, process.env.ACI_S3_BUCKET),
|
|
126
|
+
keyPrefix: configuredValue(config.keyPrefix, process.env.ACI_S3_KEY_PREFIX),
|
|
127
|
+
region: configuredValue(config.region, process.env.AWS_REGION) || 'us-east-1',
|
|
128
|
+
accessKeyId: configuredValue(config.accessKeyId, process.env.AWS_ACCESS_KEY_ID),
|
|
129
|
+
secretAccessKey: configuredValue(config.secretAccessKey, process.env.AWS_SECRET_ACCESS_KEY),
|
|
130
|
+
sessionToken: configuredValue(config.sessionToken, process.env.AWS_SESSION_TOKEN),
|
|
131
|
+
roleArn: configuredValue(config.roleArn, process.env.AWS_ROLE_ARN),
|
|
132
|
+
endpoint: trimSlash(configuredValue(config.endpoint, process.env.ACI_S3_ENDPOINT))
|
|
132
133
|
};
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
if (!resolved.bucket)
|
|
135
|
+
throw new Error('S3 config missing bucket');
|
|
136
|
+
if (!resolved.roleArn) {
|
|
137
|
+
if (resolved.accessKeyId && !resolved.secretAccessKey) {
|
|
138
|
+
throw new Error('S3 config missing secretAccessKey');
|
|
139
|
+
}
|
|
140
|
+
if (!resolved.accessKeyId && resolved.secretAccessKey) {
|
|
141
|
+
throw new Error('S3 config missing accessKeyId');
|
|
142
|
+
}
|
|
143
|
+
if (!resolved.accessKeyId) {
|
|
144
|
+
throw new Error('S3 config missing credential mode: set AWS_ROLE_ARN or both AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY');
|
|
145
|
+
}
|
|
136
146
|
}
|
|
137
147
|
return resolved;
|
|
138
148
|
}
|
|
149
|
+
function oidcS3Client(config) {
|
|
150
|
+
if (!config.roleArn)
|
|
151
|
+
return undefined;
|
|
152
|
+
return new S3Client({
|
|
153
|
+
region: config.region,
|
|
154
|
+
...(config.endpoint ? {
|
|
155
|
+
endpoint: config.endpoint,
|
|
156
|
+
forcePathStyle: true,
|
|
157
|
+
} : {}),
|
|
158
|
+
credentials: awsCredentialsProvider({
|
|
159
|
+
roleArn: config.roleArn,
|
|
160
|
+
clientConfig: { region: config.region },
|
|
161
|
+
}),
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
async function getS3RawWithClient(key, config, client) {
|
|
165
|
+
try {
|
|
166
|
+
const output = await client.send(new GetObjectCommand({
|
|
167
|
+
Bucket: config.bucket,
|
|
168
|
+
Key: key,
|
|
169
|
+
}));
|
|
170
|
+
if (!output.Body?.transformToWebStream) {
|
|
171
|
+
throw new Error(`S3 GET ${key} returned an unsupported object body`);
|
|
172
|
+
}
|
|
173
|
+
const headers = new Headers();
|
|
174
|
+
if (output.ContentType)
|
|
175
|
+
headers.set('content-type', output.ContentType);
|
|
176
|
+
if (output.ContentLength !== undefined) {
|
|
177
|
+
headers.set('content-length', String(output.ContentLength));
|
|
178
|
+
}
|
|
179
|
+
if (output.ETag)
|
|
180
|
+
headers.set('etag', output.ETag);
|
|
181
|
+
if (output.LastModified) {
|
|
182
|
+
headers.set('last-modified', output.LastModified.toUTCString());
|
|
183
|
+
}
|
|
184
|
+
return new Response(output.Body.transformToWebStream(), {
|
|
185
|
+
status: 200,
|
|
186
|
+
headers,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
const status = s3ErrorStatus(error);
|
|
191
|
+
if (status) {
|
|
192
|
+
return new Response(error instanceof Error ? error.message : String(error), { status });
|
|
193
|
+
}
|
|
194
|
+
throw new Error(`S3 GET ${key} failed`, { cause: error });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function s3ErrorStatus(error) {
|
|
198
|
+
if (!error || typeof error !== 'object')
|
|
199
|
+
return undefined;
|
|
200
|
+
const status = error
|
|
201
|
+
.$metadata?.httpStatusCode;
|
|
202
|
+
return typeof status === 'number' && status >= 400 && status <= 599
|
|
203
|
+
? status
|
|
204
|
+
: undefined;
|
|
205
|
+
}
|
|
206
|
+
function configuredValue(configured, environment) {
|
|
207
|
+
return (configured === undefined ? environment : configured)?.trim() || '';
|
|
208
|
+
}
|
|
139
209
|
function s3ObjectURL(config, key) {
|
|
140
210
|
const encodedKey = encodeKey(key);
|
|
141
211
|
if (config.endpoint) {
|
|
@@ -16,12 +16,16 @@ export declare function VideoPlayer({ video, ...attrs }: VideoPlayerProps): impo
|
|
|
16
16
|
title: string;
|
|
17
17
|
rel?: string | undefined | undefined;
|
|
18
18
|
content?: string | undefined | undefined;
|
|
19
|
-
dir?: string | undefined | undefined;
|
|
20
|
-
hidden?: boolean | undefined | undefined;
|
|
21
19
|
slot?: string | undefined | undefined;
|
|
22
20
|
style?: import("react").CSSProperties | undefined;
|
|
21
|
+
dir?: string | undefined | undefined;
|
|
22
|
+
hidden?: boolean | undefined | undefined;
|
|
23
|
+
dangerouslySetInnerHTML?: {
|
|
24
|
+
__html: string | TrustedHTML;
|
|
25
|
+
} | undefined | undefined;
|
|
23
26
|
children?: import("react").ReactNode;
|
|
24
27
|
className?: string | undefined | undefined;
|
|
28
|
+
lang?: string | undefined | undefined;
|
|
25
29
|
crossOrigin?: "" | "anonymous" | "use-credentials" | undefined;
|
|
26
30
|
defaultChecked?: boolean | undefined | undefined;
|
|
27
31
|
defaultValue?: string | number | readonly string[] | undefined;
|
|
@@ -34,7 +38,6 @@ export declare function VideoPlayer({ video, ...attrs }: VideoPlayerProps): impo
|
|
|
34
38
|
contextMenu?: string | undefined | undefined;
|
|
35
39
|
draggable?: (boolean | "true" | "false") | undefined;
|
|
36
40
|
enterKeyHint?: "enter" | "done" | "go" | "next" | "previous" | "search" | "send" | undefined | undefined;
|
|
37
|
-
lang?: string | undefined | undefined;
|
|
38
41
|
nonce?: string | undefined | undefined;
|
|
39
42
|
spellCheck?: (boolean | "true" | "false") | undefined;
|
|
40
43
|
tabIndex?: number | undefined | undefined;
|
|
@@ -121,9 +124,6 @@ export declare function VideoPlayer({ video, ...attrs }: VideoPlayerProps): impo
|
|
|
121
124
|
"aria-valuemin"?: number | undefined | undefined;
|
|
122
125
|
"aria-valuenow"?: number | undefined | undefined;
|
|
123
126
|
"aria-valuetext"?: string | undefined | undefined;
|
|
124
|
-
dangerouslySetInnerHTML?: {
|
|
125
|
-
__html: string | TrustedHTML;
|
|
126
|
-
} | undefined | undefined;
|
|
127
127
|
onCopy?: import("react").ClipboardEventHandler<HTMLVideoElement> | undefined;
|
|
128
128
|
onCopyCapture?: import("react").ClipboardEventHandler<HTMLVideoElement> | undefined;
|
|
129
129
|
onCut?: import("react").ClipboardEventHandler<HTMLVideoElement> | undefined;
|
package/dist/react/index.d.ts
CHANGED
|
@@ -25,12 +25,16 @@ export declare function Image({ image, value, ...attrs }: ImageProps): import("r
|
|
|
25
25
|
rel?: string | undefined | undefined;
|
|
26
26
|
content?: string | undefined | undefined;
|
|
27
27
|
referrerPolicy?: import("react").HTMLAttributeReferrerPolicy | undefined;
|
|
28
|
-
dir?: string | undefined | undefined;
|
|
29
|
-
hidden?: boolean | undefined | undefined;
|
|
30
28
|
slot?: string | undefined | undefined;
|
|
31
29
|
style?: import("react").CSSProperties | undefined;
|
|
30
|
+
dir?: string | undefined | undefined;
|
|
31
|
+
hidden?: boolean | undefined | undefined;
|
|
32
|
+
dangerouslySetInnerHTML?: {
|
|
33
|
+
__html: string | TrustedHTML;
|
|
34
|
+
} | undefined | undefined;
|
|
32
35
|
children?: ReactNode;
|
|
33
36
|
className?: string | undefined | undefined;
|
|
37
|
+
lang?: string | undefined | undefined;
|
|
34
38
|
crossOrigin?: "" | "anonymous" | "use-credentials" | undefined;
|
|
35
39
|
fetchPriority?: "high" | "low" | "auto" | undefined | undefined;
|
|
36
40
|
useMap?: string | undefined | undefined;
|
|
@@ -45,7 +49,6 @@ export declare function Image({ image, value, ...attrs }: ImageProps): import("r
|
|
|
45
49
|
contextMenu?: string | undefined | undefined;
|
|
46
50
|
draggable?: (boolean | "true" | "false") | undefined;
|
|
47
51
|
enterKeyHint?: "enter" | "done" | "go" | "next" | "previous" | "search" | "send" | undefined | undefined;
|
|
48
|
-
lang?: string | undefined | undefined;
|
|
49
52
|
nonce?: string | undefined | undefined;
|
|
50
53
|
spellCheck?: (boolean | "true" | "false") | undefined;
|
|
51
54
|
tabIndex?: number | undefined | undefined;
|
|
@@ -132,9 +135,6 @@ export declare function Image({ image, value, ...attrs }: ImageProps): import("r
|
|
|
132
135
|
"aria-valuemin"?: number | undefined | undefined;
|
|
133
136
|
"aria-valuenow"?: number | undefined | undefined;
|
|
134
137
|
"aria-valuetext"?: string | undefined | undefined;
|
|
135
|
-
dangerouslySetInnerHTML?: {
|
|
136
|
-
__html: string | TrustedHTML;
|
|
137
|
-
} | undefined | undefined;
|
|
138
138
|
onCopy?: import("react").ClipboardEventHandler<HTMLImageElement> | undefined;
|
|
139
139
|
onCopyCapture?: import("react").ClipboardEventHandler<HTMLImageElement> | undefined;
|
|
140
140
|
onCut?: import("react").ClipboardEventHandler<HTMLImageElement> | undefined;
|
|
@@ -278,7 +278,37 @@ export function checkMiddleware(ctx) {
|
|
|
278
278
|
// the SDK's canonical value. Next.js requires config.matcher to be
|
|
279
279
|
// statically analyzable, so importing the matcher from the SDK breaks the
|
|
280
280
|
// build. Instead the template inlines the value and this check detects drift.
|
|
281
|
-
const canonicalMatcher =
|
|
281
|
+
const canonicalMatcher = String.raw `[
|
|
282
|
+
'/((?!_next(?:/|$)|api(?:/|$)|collect(?:/|$)|gradial/assets(?:/|$)|favicon\\.ico$).*)',
|
|
283
|
+
{
|
|
284
|
+
source: '/:path*',
|
|
285
|
+
has: [{ type: 'header', key: 'x-gradial-framework-origin-auth' }],
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
source: '/:path*',
|
|
289
|
+
has: [{ type: 'header', key: 'x-baremetal-site-id' }],
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
source: '/:path*',
|
|
293
|
+
has: [{ type: 'header', key: 'x-baremetal-release-id' }],
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
source: '/:path*',
|
|
297
|
+
has: [{ type: 'header', key: 'x-aci-content-ref' }],
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
source: '/:path*',
|
|
301
|
+
has: [{ type: 'header', key: 'x-baremetal-content-base-url' }],
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
source: '/:path*',
|
|
305
|
+
has: [{ type: 'header', key: 'x-aci-content-path' }],
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
source: '/:path*',
|
|
309
|
+
has: [{ type: 'query', key: '__aci_content_path' }],
|
|
310
|
+
},
|
|
311
|
+
]`;
|
|
282
312
|
// Check for actual import/usage of aciMiddlewareMatcher (not just a mention
|
|
283
313
|
// in a comment). Look for `matcher: aciMiddlewareMatcher` or
|
|
284
314
|
// `import { ... aciMiddlewareMatcher ... }`.
|
|
@@ -289,9 +319,9 @@ export function checkMiddleware(ctx) {
|
|
|
289
319
|
}
|
|
290
320
|
else {
|
|
291
321
|
// Verify the inlined value matches the SDK canonical
|
|
292
|
-
const
|
|
293
|
-
if (
|
|
294
|
-
const normalizedInlined =
|
|
322
|
+
const matcherLiteral = extractArrayPropertyLiteral(middlewareFile.content, 'matcher');
|
|
323
|
+
if (matcherLiteral) {
|
|
324
|
+
const normalizedInlined = matcherLiteral.replace(/\s+/g, '');
|
|
295
325
|
const normalizedCanonical = canonicalMatcher.replace(/\s+/g, '');
|
|
296
326
|
results.push(normalizedInlined === normalizedCanonical
|
|
297
327
|
? ok('middleware', 'middleware matcher matches SDK canonical', rel)
|
|
@@ -303,6 +333,47 @@ export function checkMiddleware(ctx) {
|
|
|
303
333
|
}
|
|
304
334
|
return results;
|
|
305
335
|
}
|
|
336
|
+
function extractArrayPropertyLiteral(content, propertyName) {
|
|
337
|
+
const property = new RegExp(`\\b${propertyName}\\s*:`).exec(content);
|
|
338
|
+
if (!property)
|
|
339
|
+
return null;
|
|
340
|
+
const start = content.indexOf('[', property.index + property[0].length);
|
|
341
|
+
if (start < 0)
|
|
342
|
+
return null;
|
|
343
|
+
let depth = 0;
|
|
344
|
+
let quote = '';
|
|
345
|
+
let escaped = false;
|
|
346
|
+
for (let index = start; index < content.length; index += 1) {
|
|
347
|
+
const character = content[index];
|
|
348
|
+
if (quote) {
|
|
349
|
+
if (escaped) {
|
|
350
|
+
escaped = false;
|
|
351
|
+
}
|
|
352
|
+
else if (character === '\\') {
|
|
353
|
+
escaped = true;
|
|
354
|
+
}
|
|
355
|
+
else if (character === quote) {
|
|
356
|
+
quote = '';
|
|
357
|
+
}
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
if (character === "'" || character === '"' || character === '`') {
|
|
361
|
+
quote = character;
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
if (character === '[') {
|
|
365
|
+
depth += 1;
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
if (character === ']') {
|
|
369
|
+
depth -= 1;
|
|
370
|
+
if (depth === 0) {
|
|
371
|
+
return content.slice(start, index + 1);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
306
377
|
// ---------------------------------------------------------------------------
|
|
307
378
|
// Check: noFileContentProvider — no direct FileContentProvider imports in app code
|
|
308
379
|
// ---------------------------------------------------------------------------
|
|
@@ -5,6 +5,7 @@ export interface BlockRefMeta {
|
|
|
5
5
|
multiple: boolean;
|
|
6
6
|
}
|
|
7
7
|
declare const blockRefBase: z.ZodObject<{
|
|
8
|
+
$type: z.ZodLiteral<"component">;
|
|
8
9
|
id: z.ZodString;
|
|
9
10
|
component: z.ZodString;
|
|
10
11
|
props: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
@@ -12,6 +13,7 @@ declare const blockRefBase: z.ZodObject<{
|
|
|
12
13
|
type InferContractProps<T> = T extends ComponentContract<string, infer Schema> ? Schema extends z.ZodType<infer P> ? P : Record<string, unknown> : Record<string, unknown>;
|
|
13
14
|
type InferContractId<T> = T extends ComponentContract<infer Id, unknown> ? Id : never;
|
|
14
15
|
export type BlockRefEntry<T extends ComponentContract> = {
|
|
16
|
+
$type: 'component';
|
|
15
17
|
id: string;
|
|
16
18
|
component: InferContractId<T>;
|
|
17
19
|
props: InferContractProps<T>;
|
package/dist/types/block-ref.js
CHANGED
package/dist/types/page.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gradial/aci",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.43",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "https://github.com/gradial/bare-metal-go.git",
|
|
7
|
+
"directory": "packages/sdk"
|
|
8
|
+
},
|
|
4
9
|
"type": "module",
|
|
5
10
|
"main": "./dist/index.js",
|
|
6
11
|
"types": "./dist/index.d.ts",
|
|
@@ -9,6 +14,8 @@
|
|
|
9
14
|
"bin/aci.js",
|
|
10
15
|
"src/cli/*.mjs",
|
|
11
16
|
"src/types/*.ts",
|
|
17
|
+
"src/astro/*.astro",
|
|
18
|
+
"src/consent-banner/*.css",
|
|
12
19
|
"templates/common",
|
|
13
20
|
"templates/nextjs",
|
|
14
21
|
"templates/astro",
|
|
@@ -65,6 +72,16 @@
|
|
|
65
72
|
"import": "./dist/next/middleware.js",
|
|
66
73
|
"default": "./dist/next/middleware.js"
|
|
67
74
|
},
|
|
75
|
+
"./next/vercel-personalization": {
|
|
76
|
+
"types": "./dist/next/vercel-personalization.d.ts",
|
|
77
|
+
"import": "./dist/next/vercel-personalization.js",
|
|
78
|
+
"default": "./dist/next/vercel-personalization.js"
|
|
79
|
+
},
|
|
80
|
+
"./next/vercel-collect-sdk-route": {
|
|
81
|
+
"types": "./dist/next/vercel-collect-sdk-route.d.ts",
|
|
82
|
+
"import": "./dist/next/vercel-collect-sdk-route.js",
|
|
83
|
+
"default": "./dist/next/vercel-collect-sdk-route.js"
|
|
84
|
+
},
|
|
68
85
|
"./next/asset-route": {
|
|
69
86
|
"types": "./dist/next/asset-route.d.ts",
|
|
70
87
|
"import": "./dist/next/asset-route.js",
|
|
@@ -84,12 +101,30 @@
|
|
|
84
101
|
"import": "./dist/astro/index.js",
|
|
85
102
|
"default": "./dist/astro/index.js"
|
|
86
103
|
},
|
|
104
|
+
"./astro/ConsentBanner": {
|
|
105
|
+
"import": "./src/astro/ConsentBanner.astro"
|
|
106
|
+
},
|
|
107
|
+
"./consent-banner": {
|
|
108
|
+
"types": "./dist/consent-banner/index.d.ts",
|
|
109
|
+
"import": "./dist/consent-banner/index.js"
|
|
110
|
+
},
|
|
87
111
|
"./testing": {
|
|
88
112
|
"types": "./dist/testing/conformance/index.d.ts",
|
|
89
113
|
"import": "./dist/testing/conformance/index.js"
|
|
90
114
|
}
|
|
91
115
|
},
|
|
116
|
+
"scripts": {
|
|
117
|
+
"build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.json && node scripts/build-collect.mjs",
|
|
118
|
+
"test": "node --import tsx --test \"src/**/*.test.ts\"",
|
|
119
|
+
"build:collect": "node scripts/build-collect.mjs",
|
|
120
|
+
"build:cli": "cd ../.. && ./scripts/build-cli.sh",
|
|
121
|
+
"generate-starters": "cd ../.. && tsx scripts/generate-starters.ts",
|
|
122
|
+
"compile-registry": "tsx src/cli/compile-registry.mjs",
|
|
123
|
+
"prepack": "npm run generate-starters && npm run build && node scripts/ensure-bin-modes.mjs"
|
|
124
|
+
},
|
|
92
125
|
"dependencies": {
|
|
126
|
+
"@aws-sdk/client-s3": "^3.883.0",
|
|
127
|
+
"@vercel/oidc-aws-credentials-provider": "^3.3.0",
|
|
93
128
|
"markdown-it": "^14.1.0",
|
|
94
129
|
"sanitize-html": "^2.13.1",
|
|
95
130
|
"ws": "^8.18.0"
|
|
@@ -121,6 +156,7 @@
|
|
|
121
156
|
"@types/react": "^19.0.0",
|
|
122
157
|
"@types/sanitize-html": "^2.16.0",
|
|
123
158
|
"@types/ws": "^8.18.0",
|
|
159
|
+
"esbuild": "0.28.1",
|
|
124
160
|
"jsdom": "^29.1.1",
|
|
125
161
|
"next": "^15.5.19",
|
|
126
162
|
"tsx": "^4.20.0",
|
|
@@ -128,18 +164,11 @@
|
|
|
128
164
|
"zod": "^4.0.0"
|
|
129
165
|
},
|
|
130
166
|
"optionalDependencies": {
|
|
131
|
-
"@gradial/aci-cli-darwin-arm64": "0.1.
|
|
132
|
-
"@gradial/aci-cli-darwin-x64": "0.1.
|
|
133
|
-
"@gradial/aci-cli-linux-arm64": "0.1.
|
|
134
|
-
"@gradial/aci-cli-linux-x64": "0.1.
|
|
135
|
-
"@gradial/aci-cli-win32-arm64": "0.1.
|
|
136
|
-
"@gradial/aci-cli-win32-x64": "0.1.
|
|
137
|
-
},
|
|
138
|
-
"scripts": {
|
|
139
|
-
"build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.json",
|
|
140
|
-
"test": "node --import tsx --test \"src/**/*.test.ts\"",
|
|
141
|
-
"build:cli": "cd ../.. && ./scripts/build-cli.sh",
|
|
142
|
-
"generate-starters": "cd ../.. && tsx scripts/generate-starters.ts",
|
|
143
|
-
"compile-registry": "tsx src/cli/compile-registry.mjs"
|
|
167
|
+
"@gradial/aci-cli-darwin-arm64": "0.1.43",
|
|
168
|
+
"@gradial/aci-cli-darwin-x64": "0.1.43",
|
|
169
|
+
"@gradial/aci-cli-linux-arm64": "0.1.43",
|
|
170
|
+
"@gradial/aci-cli-linux-x64": "0.1.43",
|
|
171
|
+
"@gradial/aci-cli-win32-arm64": "0.1.43",
|
|
172
|
+
"@gradial/aci-cli-win32-x64": "0.1.43"
|
|
144
173
|
}
|
|
145
|
-
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
// ConsentBanner.astro — Astro wrapper around the SDK's first-party consent
|
|
3
|
+
// banner. Import this into your layout and pass site-specific text/styles.
|
|
4
|
+
//
|
|
5
|
+
// Usage:
|
|
6
|
+
// import ConsentBanner from '@gradial/aci/astro/ConsentBanner.astro';
|
|
7
|
+
// <ConsentBanner
|
|
8
|
+
// heading="Cookie Preferences"
|
|
9
|
+
// message="We use cookies to personalize your experience."
|
|
10
|
+
// acceptLabel="Accept all"
|
|
11
|
+
// rejectLabel="Reject"
|
|
12
|
+
// privacyPolicyHref="/privacy"
|
|
13
|
+
// privacyPolicyLabel="Privacy Policy"
|
|
14
|
+
// />
|
|
15
|
+
|
|
16
|
+
export interface Props {
|
|
17
|
+
heading?: string;
|
|
18
|
+
message?: string;
|
|
19
|
+
acceptLabel?: string;
|
|
20
|
+
rejectLabel?: string;
|
|
21
|
+
privacyPolicyLabel?: string;
|
|
22
|
+
privacyPolicyHref?: string;
|
|
23
|
+
source?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const {
|
|
27
|
+
heading,
|
|
28
|
+
message,
|
|
29
|
+
acceptLabel,
|
|
30
|
+
rejectLabel,
|
|
31
|
+
privacyPolicyLabel,
|
|
32
|
+
privacyPolicyHref,
|
|
33
|
+
source,
|
|
34
|
+
} = Astro.props as Props;
|
|
35
|
+
|
|
36
|
+
const opts = JSON.stringify({
|
|
37
|
+
heading,
|
|
38
|
+
message,
|
|
39
|
+
acceptLabel,
|
|
40
|
+
rejectLabel,
|
|
41
|
+
privacyPolicyLabel,
|
|
42
|
+
privacyPolicyHref,
|
|
43
|
+
source,
|
|
44
|
+
});
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
<div
|
|
48
|
+
id="aci-consent-banner-mount"
|
|
49
|
+
data-aci-consent-banner-mount
|
|
50
|
+
data-opts={opts}
|
|
51
|
+
>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<script>
|
|
55
|
+
import { mountConsentBanner } from '../consent-banner/index.js';
|
|
56
|
+
|
|
57
|
+
const mount = document.getElementById('aci-consent-banner-mount');
|
|
58
|
+
if (mount) {
|
|
59
|
+
const opts = JSON.parse(mount.getAttribute('data-opts') || '{}');
|
|
60
|
+
mountConsentBanner(document, opts);
|
|
61
|
+
}
|
|
62
|
+
</script>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
2
3
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
3
4
|
import path from 'node:path';
|
|
4
|
-
import { pathToFileURL } from 'node:url';
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
6
|
import { z } from 'zod';
|
|
6
7
|
import { ImageSchema } from '../types/image.ts';
|
|
7
8
|
import { VideoSchema } from '../types/video.ts';
|
|
@@ -19,14 +20,15 @@ try {
|
|
|
19
20
|
if (!globalThis.React) globalThis.React = React;
|
|
20
21
|
} catch { /* react not available — component files without JSX will still work */ }
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
} catch (error) {
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
if (isMainModule()) {
|
|
24
|
+
const args = parseArgs(process.argv.slice(2));
|
|
25
|
+
try {
|
|
26
|
+
const result = await compileRegistry(args);
|
|
27
|
+
console.log(JSON.stringify({ success: true, ...result, errors: [] }, null, 2));
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error(JSON.stringify({ success: false, errors: [String(error?.message ?? error)] }, null, 2));
|
|
30
|
+
process.exitCode = 1;
|
|
31
|
+
}
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
async function compileRegistry(options) {
|
|
@@ -430,9 +432,10 @@ async function generatePageSchema(schemaDir, componentEntries, outDir) {
|
|
|
430
432
|
const defs = {
|
|
431
433
|
baseBlock: {
|
|
432
434
|
type: 'object',
|
|
433
|
-
required: ['id', 'component', 'props'],
|
|
435
|
+
required: ['$type', 'id', 'component', 'props'],
|
|
434
436
|
additionalProperties: true,
|
|
435
437
|
properties: {
|
|
438
|
+
$type: { const: 'component' },
|
|
436
439
|
id: { type: 'string', minLength: 1 },
|
|
437
440
|
component: { type: 'string' },
|
|
438
441
|
props: { type: 'object' },
|
|
@@ -741,3 +744,14 @@ function parseArgs(argv) {
|
|
|
741
744
|
}
|
|
742
745
|
return out;
|
|
743
746
|
}
|
|
747
|
+
|
|
748
|
+
function isMainModule() {
|
|
749
|
+
if (!process.argv[1]) return false;
|
|
750
|
+
try {
|
|
751
|
+
return realpathSync(fileURLToPath(import.meta.url)) === realpathSync(path.resolve(process.argv[1]));
|
|
752
|
+
} catch {
|
|
753
|
+
return false;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
export { compileRegistry, generatePageSchema };
|