@gradial/aci 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +63 -0
  2. package/dist/astro/index.d.ts +17 -0
  3. package/dist/astro/index.js +21 -0
  4. package/dist/compiler/index.d.ts +2 -0
  5. package/dist/compiler/index.js +2 -0
  6. package/dist/compiler/validate-subset.d.ts +2 -0
  7. package/dist/compiler/validate-subset.js +73 -0
  8. package/dist/compiler/zod-to-jsonschema.d.ts +2 -0
  9. package/dist/compiler/zod-to-jsonschema.js +13 -0
  10. package/dist/content/contract.d.ts +83 -0
  11. package/dist/content/contract.js +104 -0
  12. package/dist/content/index.d.ts +6 -0
  13. package/dist/content/index.js +6 -0
  14. package/dist/content/provider.d.ts +15 -0
  15. package/dist/content/provider.js +36 -0
  16. package/dist/content/routes.d.ts +16 -0
  17. package/dist/content/routes.js +69 -0
  18. package/dist/content/tailwind-validator.d.ts +6 -0
  19. package/dist/content/tailwind-validator.js +31 -0
  20. package/dist/content/types.d.ts +105 -0
  21. package/dist/content/types.js +1 -0
  22. package/dist/content/validation.d.ts +108 -0
  23. package/dist/content/validation.js +184 -0
  24. package/dist/define-component.d.ts +2 -0
  25. package/dist/define-component.js +13 -0
  26. package/dist/define-layout.d.ts +3 -0
  27. package/dist/define-layout.js +6 -0
  28. package/dist/dev/browser.d.ts +7 -0
  29. package/dist/dev/browser.js +2 -0
  30. package/dist/dev/index.d.ts +30 -0
  31. package/dist/dev/index.js +70 -0
  32. package/dist/index.d.ts +3 -0
  33. package/dist/index.js +3 -0
  34. package/dist/next/dev-refresh.d.ts +5 -0
  35. package/dist/next/dev-refresh.js +44 -0
  36. package/dist/next/middleware.d.ts +8 -0
  37. package/dist/next/middleware.js +132 -0
  38. package/dist/next/preview.d.ts +7 -0
  39. package/dist/next/preview.js +37 -0
  40. package/dist/next/server.d.ts +58 -0
  41. package/dist/next/server.js +269 -0
  42. package/dist/providers/file.d.ts +18 -0
  43. package/dist/providers/file.js +91 -0
  44. package/dist/sveltekit/index.d.ts +2 -0
  45. package/dist/sveltekit/index.js +18 -0
  46. package/dist/testing/index.d.ts +20 -0
  47. package/dist/testing/index.js +86 -0
  48. package/dist/types/component.d.ts +26 -0
  49. package/dist/types/component.js +1 -0
  50. package/dist/types/config.d.ts +28 -0
  51. package/dist/types/config.js +1 -0
  52. package/dist/types/index.d.ts +6 -0
  53. package/dist/types/index.js +6 -0
  54. package/dist/types/layout.d.ts +12 -0
  55. package/dist/types/layout.js +1 -0
  56. package/dist/types/page.d.ts +45 -0
  57. package/dist/types/page.js +1 -0
  58. package/dist/types/render-mode.d.ts +3 -0
  59. package/dist/types/render-mode.js +1 -0
  60. package/dist/types/renderer.d.ts +71 -0
  61. package/dist/types/renderer.js +1 -0
  62. package/package.json +112 -0
  63. package/src/cli/compile-registry.mjs +199 -0
  64. package/src/cli/verify-renderer.mjs +73 -0
@@ -0,0 +1,132 @@
1
+ import { createClient, get as getEdgeConfigItem } from '@vercel/edge-config';
2
+ import { NextResponse } from 'next/server';
3
+ import { validatePreviewToken } from './preview.js';
4
+ const RELEASE_HEADER = 'x-gradial-release-id';
5
+ const ROUTE_HEADER = 'x-gradial-route';
6
+ const DISPATCH_HEADER = 'x-gradial-dispatch';
7
+ export function createGradialMiddleware(config) {
8
+ return async function gradialMiddleware(request) {
9
+ const requestHeaders = gradialHeaders(request.headers);
10
+ const preview = await releaseFromPreviewToken(request, config);
11
+ if (preview.invalid) {
12
+ return new Response('invalid preview token', { status: 403 });
13
+ }
14
+ const releaseId = preview.releaseId || await activeReleaseFromEdgeConfig(config);
15
+ if (!releaseId) {
16
+ return NextResponse.next({ request: { headers: requestHeaders } });
17
+ }
18
+ requestHeaders.set(RELEASE_HEADER, releaseId);
19
+ requestHeaders.set(ROUTE_HEADER, request.nextUrl.pathname);
20
+ requestHeaders.set(DISPATCH_HEADER, 'ssr-page');
21
+ return NextResponse.next({
22
+ request: {
23
+ headers: requestHeaders,
24
+ },
25
+ });
26
+ };
27
+ }
28
+ function gradialHeaders(headers) {
29
+ const next = new Headers(headers);
30
+ next.delete(RELEASE_HEADER);
31
+ next.delete(ROUTE_HEADER);
32
+ next.delete(DISPATCH_HEADER);
33
+ return next;
34
+ }
35
+ async function activeReleaseFromEdgeConfig(config) {
36
+ const edgeConfig = config.edgeConfig || process.env.EDGE_CONFIG || '';
37
+ if (!edgeConfig || !config.siteId)
38
+ return '';
39
+ const active = await activeReleasePointer(edgeConfig, config.siteId);
40
+ if (!active.releaseId)
41
+ return '';
42
+ const deploymentId = config.deploymentId || process.env.VERCEL_DEPLOYMENT_ID || '';
43
+ if (!deploymentId)
44
+ return active.releaseId;
45
+ const compatibility = await getEdgeConfigJSON(edgeConfig, deploymentCompatibilityKey(config.siteId, deploymentId));
46
+ if (!compatibility)
47
+ return active.releaseId;
48
+ return clampRelease(active.releaseId, active.sequence || 0, compatibility);
49
+ }
50
+ async function getEdgeConfigJSON(edgeConfig, key) {
51
+ const value = await getEdgeConfigValue(edgeConfig, key);
52
+ if (!value)
53
+ return null;
54
+ if (typeof value === 'string') {
55
+ try {
56
+ return JSON.parse(value);
57
+ }
58
+ catch {
59
+ return null;
60
+ }
61
+ }
62
+ return value;
63
+ }
64
+ async function getEdgeConfigValue(edgeConfig, key) {
65
+ return edgeConfig === process.env.EDGE_CONFIG
66
+ ? await getEdgeConfigItem(key)
67
+ : await createClient(edgeConfig).get(key);
68
+ }
69
+ function activeReleaseKey(siteId) {
70
+ return `activeRelease_${edgeKeySegment(siteId)}`;
71
+ }
72
+ function deploymentCompatibilityKey(siteId, deploymentId) {
73
+ return `deploymentCompatibility_${edgeKeySegment(siteId)}_${edgeKeySegment(deploymentId)}`;
74
+ }
75
+ async function activeReleasePointer(edgeConfig, siteId) {
76
+ const value = await getEdgeConfigValue(edgeConfig, activeReleaseKey(siteId));
77
+ if (!value)
78
+ return { releaseId: '' };
79
+ if (typeof value === 'string') {
80
+ try {
81
+ const parsed = JSON.parse(value);
82
+ if (parsed.releaseId)
83
+ return parsed;
84
+ }
85
+ catch {
86
+ return { releaseId: value };
87
+ }
88
+ return { releaseId: value };
89
+ }
90
+ const pointer = value;
91
+ return { releaseId: pointer.releaseId || '', sequence: pointer.sequence };
92
+ }
93
+ function clampRelease(activeReleaseId, activeSequence, compatibility) {
94
+ if (!activeSequence)
95
+ return activeReleaseId;
96
+ if (compatibility.minSequence && activeSequence < compatibility.minSequence && compatibility.minReleaseId) {
97
+ return compatibility.minReleaseId;
98
+ }
99
+ if (compatibility.maxSequence && activeSequence > compatibility.maxSequence && compatibility.maxReleaseId) {
100
+ return compatibility.maxReleaseId;
101
+ }
102
+ return activeReleaseId;
103
+ }
104
+ function edgeKeySegment(value) {
105
+ return value.replace(/[^A-Za-z0-9_-]/g, '_');
106
+ }
107
+ async function releaseFromPreviewToken(request, config) {
108
+ const token = previewTokenFromRequest(request);
109
+ if (!token)
110
+ return { releaseId: '', invalid: false };
111
+ const signKey = config.previewSignKey || process.env.GRADIAL_PREVIEW_SIGN_KEY || process.env.HOSTING_PREVIEW_SIGN_KEY || '';
112
+ if (!signKey)
113
+ return { releaseId: '', invalid: true };
114
+ const claims = await validatePreviewToken(token, signKey);
115
+ if (!claims || claims.scope !== 'preview' || !claims.releaseId)
116
+ return { releaseId: '', invalid: true };
117
+ if (claims.siteId && config.siteId && claims.siteId !== config.siteId)
118
+ return { releaseId: '', invalid: true };
119
+ if (!claims.expiresAt || Date.parse(claims.expiresAt) <= Date.now())
120
+ return { releaseId: '', invalid: true };
121
+ return { releaseId: claims.releaseId, invalid: false };
122
+ }
123
+ function previewTokenFromRequest(request) {
124
+ const queryToken = request.nextUrl.searchParams.get('preview_token');
125
+ if (queryToken)
126
+ return queryToken;
127
+ const cookieToken = request.cookies.get('bm_preview')?.value || '';
128
+ if (cookieToken)
129
+ return cookieToken;
130
+ const auth = request.headers.get('authorization') || '';
131
+ return auth.startsWith('Bearer ') ? auth.slice('Bearer '.length) : '';
132
+ }
@@ -0,0 +1,7 @@
1
+ export interface PreviewClaims {
2
+ releaseId?: string;
3
+ siteId?: string;
4
+ expiresAt?: string;
5
+ scope?: string;
6
+ }
7
+ export declare function validatePreviewToken(token: string, signKey: string): Promise<PreviewClaims | null>;
@@ -0,0 +1,37 @@
1
+ export async function validatePreviewToken(token, signKey) {
2
+ try {
3
+ const parts = token.split('.');
4
+ if (parts.length !== 3)
5
+ return null;
6
+ const signingInput = `${parts[0]}.${parts[1]}`;
7
+ const signature = base64URLToBytes(parts[2]);
8
+ const key = await crypto.subtle.importKey('raw', new TextEncoder().encode(signKey), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
9
+ const expected = new Uint8Array(await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(signingInput)));
10
+ if (!constantTimeEqual(signature, expected))
11
+ return null;
12
+ const payload = new TextDecoder().decode(base64URLToBytes(parts[1]));
13
+ return JSON.parse(payload);
14
+ }
15
+ catch {
16
+ return null;
17
+ }
18
+ }
19
+ function base64URLToBytes(value) {
20
+ const base64 = value.replace(/-/g, '+').replace(/_/g, '/');
21
+ const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, '=');
22
+ const binary = atob(padded);
23
+ const bytes = new Uint8Array(binary.length);
24
+ for (let i = 0; i < binary.length; i++) {
25
+ bytes[i] = binary.charCodeAt(i);
26
+ }
27
+ return bytes;
28
+ }
29
+ function constantTimeEqual(left, right) {
30
+ if (left.length !== right.length)
31
+ return false;
32
+ let diff = 0;
33
+ for (let i = 0; i < left.length; i++) {
34
+ diff |= left[i] ^ right[i];
35
+ }
36
+ return diff === 0;
37
+ }
@@ -0,0 +1,58 @@
1
+ export declare class PageNotFoundError extends Error {
2
+ constructor(route: string, cause?: unknown);
3
+ }
4
+ export declare class FragmentNotFoundError extends Error {
5
+ constructor(name: string, cause?: unknown);
6
+ }
7
+ export interface GradialFetchConfig {
8
+ siteId?: string;
9
+ bucket?: string;
10
+ keyPrefix?: string;
11
+ region?: string;
12
+ accessKeyId?: string;
13
+ secretAccessKey?: string;
14
+ sessionToken?: string;
15
+ endpoint?: string;
16
+ releaseId?: string;
17
+ edgeConfig?: string;
18
+ deploymentId?: string;
19
+ }
20
+ export interface RouteManifest {
21
+ manifestVersion: string;
22
+ releaseId: string;
23
+ siteId: string;
24
+ routes: Record<string, RouteEntry>;
25
+ }
26
+ export interface RouteEntry {
27
+ path: string;
28
+ mode: string;
29
+ bodyArtifact?: string;
30
+ fragments?: Array<{
31
+ id: string;
32
+ artifactRef?: string;
33
+ }>;
34
+ cachePolicy?: {
35
+ cacheControl?: string;
36
+ };
37
+ }
38
+ export interface GradialRenderInput<TPage, TSite extends GradialSiteConfig = GradialSiteConfig> {
39
+ route: string;
40
+ domain: string;
41
+ locale: string;
42
+ siteConfig: TSite;
43
+ page: TPage;
44
+ }
45
+ export interface GradialSiteConfig {
46
+ domain?: string;
47
+ defaultLocale?: string;
48
+ }
49
+ export declare function getPage<T = unknown>(route?: string, config?: GradialFetchConfig): Promise<T>;
50
+ export declare function getFragment<T = unknown>(name: string, config?: GradialFetchConfig): Promise<T>;
51
+ export declare function getRoutes(config?: GradialFetchConfig): Promise<string[]>;
52
+ export declare function getRenderInput<TPage, TSite extends GradialSiteConfig = GradialSiteConfig>(route?: string, config?: GradialFetchConfig): Promise<GradialRenderInput<TPage, TSite>>;
53
+ export declare function routeFromNextParams(params?: Promise<{
54
+ slug?: string[];
55
+ }> | {
56
+ slug?: string[];
57
+ }): Promise<string>;
58
+ export declare function resolveReleaseId(config?: GradialFetchConfig): Promise<string>;
@@ -0,0 +1,269 @@
1
+ import crypto from 'node:crypto';
2
+ import { createClient, get as getEdgeConfigItem } from '@vercel/edge-config';
3
+ import { headers } from 'next/headers';
4
+ const RELEASE_HEADER = 'x-gradial-release-id';
5
+ export class PageNotFoundError extends Error {
6
+ constructor(route, cause) {
7
+ super(`Gradial page fragment not found for route ${route}`);
8
+ this.name = 'PageNotFoundError';
9
+ this.cause = cause;
10
+ }
11
+ }
12
+ export class FragmentNotFoundError extends Error {
13
+ constructor(name, cause) {
14
+ super(`Gradial fragment not found: ${name}`);
15
+ this.name = 'FragmentNotFoundError';
16
+ this.cause = cause;
17
+ }
18
+ }
19
+ export async function getPage(route = '', config = {}) {
20
+ const releaseId = await resolveReleaseId(config);
21
+ const key = releaseKey(config, releaseId, 'fragments', 'page', pageRouteSegment(route), 'index.json');
22
+ try {
23
+ return await getS3JSON(key, config);
24
+ }
25
+ catch (error) {
26
+ throw new PageNotFoundError(route, error);
27
+ }
28
+ }
29
+ export async function getFragment(name, config = {}) {
30
+ const releaseId = await resolveReleaseId(config);
31
+ const cleanName = cleanPath(name).replace(/\.[^.]+$/, '');
32
+ const key = releaseKey(config, releaseId, 'fragments', 'global', `${cleanName}.json`);
33
+ try {
34
+ return await getS3JSON(key, config);
35
+ }
36
+ catch (error) {
37
+ throw new FragmentNotFoundError(name, error);
38
+ }
39
+ }
40
+ export async function getRoutes(config = {}) {
41
+ const releaseId = await resolveReleaseId(config);
42
+ const manifest = await getS3JSON(releaseKey(config, releaseId, 'route-manifest.json'), config);
43
+ return Object.keys(manifest.routes || {}).sort();
44
+ }
45
+ export async function getRenderInput(route = '/', config = {}) {
46
+ const [page, siteConfig] = await Promise.all([
47
+ getPage(route, config),
48
+ getFragment('site', config),
49
+ ]);
50
+ return {
51
+ route,
52
+ domain: siteConfig.domain || 'www.baremetal.local',
53
+ locale: siteConfig.defaultLocale || 'en-us',
54
+ siteConfig,
55
+ page,
56
+ };
57
+ }
58
+ export async function routeFromNextParams(params) {
59
+ if (!params)
60
+ return '/';
61
+ const resolved = await params;
62
+ if (!resolved.slug || resolved.slug.length === 0)
63
+ return '/';
64
+ return `/${resolved.slug.join('/')}/`;
65
+ }
66
+ export async function resolveReleaseId(config = {}) {
67
+ if (config.releaseId)
68
+ return config.releaseId;
69
+ const headerReleaseId = await releaseIdFromHeaders();
70
+ if (headerReleaseId)
71
+ return headerReleaseId;
72
+ if (process.env.GRADIAL_RELEASE_ID)
73
+ return process.env.GRADIAL_RELEASE_ID;
74
+ const activeReleaseId = await activeReleaseFromEdgeConfig(config);
75
+ if (activeReleaseId)
76
+ return activeReleaseId;
77
+ throw new Error('Gradial release ID is not available from headers, GRADIAL_RELEASE_ID, or Edge Config');
78
+ }
79
+ async function releaseIdFromHeaders() {
80
+ try {
81
+ const current = await headers();
82
+ return current.get(RELEASE_HEADER) || '';
83
+ }
84
+ catch {
85
+ return '';
86
+ }
87
+ }
88
+ async function activeReleaseFromEdgeConfig(config) {
89
+ const edgeConfig = config.edgeConfig || process.env.EDGE_CONFIG || '';
90
+ const siteId = config.siteId || process.env.GRADIAL_SITE_ID || '';
91
+ if (!edgeConfig || !siteId)
92
+ return '';
93
+ const active = await activeReleasePointer(edgeConfig, siteId);
94
+ if (!active.releaseId)
95
+ return '';
96
+ const deploymentId = config.deploymentId || process.env.VERCEL_DEPLOYMENT_ID || '';
97
+ if (!deploymentId)
98
+ return active.releaseId;
99
+ const compatibility = await getEdgeConfigJSON(edgeConfig, deploymentCompatibilityKey(siteId, deploymentId));
100
+ if (!compatibility)
101
+ return active.releaseId;
102
+ return clampRelease(active.releaseId, active.sequence || 0, compatibility);
103
+ }
104
+ async function getEdgeConfigJSON(edgeConfig, key) {
105
+ const value = await getEdgeConfigValue(edgeConfig, key);
106
+ if (!value)
107
+ return null;
108
+ if (typeof value === 'string') {
109
+ try {
110
+ return JSON.parse(value);
111
+ }
112
+ catch {
113
+ return null;
114
+ }
115
+ }
116
+ return value;
117
+ }
118
+ async function getEdgeConfigValue(edgeConfig, key) {
119
+ return edgeConfig === process.env.EDGE_CONFIG
120
+ ? await getEdgeConfigItem(key)
121
+ : await createClient(edgeConfig).get(key);
122
+ }
123
+ async function getS3JSON(key, config) {
124
+ const resolved = resolveS3Config(config);
125
+ const url = s3ObjectURL(resolved, key);
126
+ const signedHeaders = signedS3Headers('GET', url, resolved);
127
+ const res = await fetch(url, {
128
+ method: 'GET',
129
+ headers: signedHeaders,
130
+ cache: 'no-store',
131
+ });
132
+ if (!res.ok) {
133
+ throw new Error(`S3 GET ${key} failed with status ${res.status}: ${await res.text()}`);
134
+ }
135
+ return (await res.json());
136
+ }
137
+ function resolveS3Config(config) {
138
+ const resolved = {
139
+ siteId: config.siteId || process.env.GRADIAL_SITE_ID || '',
140
+ bucket: config.bucket || process.env.GRADIAL_S3_BUCKET || '',
141
+ keyPrefix: config.keyPrefix || process.env.GRADIAL_S3_KEY_PREFIX || '',
142
+ region: config.region || process.env.AWS_REGION || 'us-east-1',
143
+ accessKeyId: config.accessKeyId || process.env.AWS_ACCESS_KEY_ID || '',
144
+ secretAccessKey: config.secretAccessKey || process.env.AWS_SECRET_ACCESS_KEY || '',
145
+ sessionToken: config.sessionToken || process.env.AWS_SESSION_TOKEN || '',
146
+ endpoint: trimSlash(config.endpoint || process.env.GRADIAL_S3_ENDPOINT || ''),
147
+ };
148
+ for (const key of ['bucket', 'accessKeyId', 'secretAccessKey']) {
149
+ if (!resolved[key])
150
+ throw new Error(`Gradial S3 config missing ${key}`);
151
+ }
152
+ return resolved;
153
+ }
154
+ function releaseKey(config, releaseId, ...parts) {
155
+ return joinKey(config.keyPrefix || process.env.GRADIAL_S3_KEY_PREFIX || '', 'releases', releaseId, ...parts);
156
+ }
157
+ function s3ObjectURL(config, key) {
158
+ const encodedKey = encodeKey(key);
159
+ if (config.endpoint) {
160
+ return new URL(`${config.endpoint}/${encodeURIComponent(config.bucket)}${encodedKey}`);
161
+ }
162
+ return new URL(`https://${config.bucket}.s3.${config.region}.amazonaws.com${encodedKey}`);
163
+ }
164
+ function signedS3Headers(method, url, config) {
165
+ const now = new Date();
166
+ const amzDate = amzTimestamp(now);
167
+ const dateStamp = amzDate.slice(0, 8);
168
+ const payloadHash = sha256Hex('');
169
+ const headers = new Headers({
170
+ host: url.host,
171
+ 'x-amz-content-sha256': payloadHash,
172
+ 'x-amz-date': amzDate,
173
+ });
174
+ if (config.sessionToken)
175
+ headers.set('x-amz-security-token', config.sessionToken);
176
+ const signedHeaderNames = Array.from(headers.keys()).sort();
177
+ const canonicalHeaders = signedHeaderNames.map((key) => `${key}:${headers.get(key)?.trim()}\n`).join('');
178
+ const canonicalRequest = [
179
+ method,
180
+ url.pathname,
181
+ url.searchParams.toString(),
182
+ canonicalHeaders,
183
+ signedHeaderNames.join(';'),
184
+ payloadHash,
185
+ ].join('\n');
186
+ const credentialScope = `${dateStamp}/${config.region}/s3/aws4_request`;
187
+ const stringToSign = [
188
+ 'AWS4-HMAC-SHA256',
189
+ amzDate,
190
+ credentialScope,
191
+ sha256Hex(canonicalRequest),
192
+ ].join('\n');
193
+ const signingKey = awsSigningKey(config.secretAccessKey, dateStamp, config.region, 's3');
194
+ const signature = hmacHex(signingKey, stringToSign);
195
+ headers.set('authorization', `AWS4-HMAC-SHA256 Credential=${config.accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaderNames.join(';')}, Signature=${signature}`);
196
+ return headers;
197
+ }
198
+ function awsSigningKey(secret, dateStamp, region, service) {
199
+ const kDate = hmac(Buffer.from(`AWS4${secret}`, 'utf8'), dateStamp);
200
+ const kRegion = hmac(kDate, region);
201
+ const kService = hmac(kRegion, service);
202
+ return hmac(kService, 'aws4_request');
203
+ }
204
+ function amzTimestamp(date) {
205
+ return date.toISOString().replace(/[:-]|\.\d{3}/g, '');
206
+ }
207
+ function hmac(key, value) {
208
+ return crypto.createHmac('sha256', key).update(value, 'utf8').digest();
209
+ }
210
+ function hmacHex(key, value) {
211
+ return crypto.createHmac('sha256', key).update(value, 'utf8').digest('hex');
212
+ }
213
+ function sha256Hex(value) {
214
+ return crypto.createHash('sha256').update(value, 'utf8').digest('hex');
215
+ }
216
+ function pageRouteSegment(route) {
217
+ const clean = cleanPath(route);
218
+ return clean === '' ? 'index' : clean;
219
+ }
220
+ function activeReleaseKey(siteId) {
221
+ return `activeRelease_${edgeKeySegment(siteId)}`;
222
+ }
223
+ function deploymentCompatibilityKey(siteId, deploymentId) {
224
+ return `deploymentCompatibility_${edgeKeySegment(siteId)}_${edgeKeySegment(deploymentId)}`;
225
+ }
226
+ async function activeReleasePointer(edgeConfig, siteId) {
227
+ const value = await getEdgeConfigValue(edgeConfig, activeReleaseKey(siteId));
228
+ if (!value)
229
+ return { releaseId: '' };
230
+ if (typeof value === 'string') {
231
+ try {
232
+ const parsed = JSON.parse(value);
233
+ if (parsed.releaseId)
234
+ return parsed;
235
+ }
236
+ catch {
237
+ return { releaseId: value };
238
+ }
239
+ return { releaseId: value };
240
+ }
241
+ const pointer = value;
242
+ return { releaseId: pointer.releaseId || '', sequence: pointer.sequence };
243
+ }
244
+ function clampRelease(activeReleaseId, activeSequence, compatibility) {
245
+ if (!activeSequence)
246
+ return activeReleaseId;
247
+ if (compatibility.minSequence && activeSequence < compatibility.minSequence && compatibility.minReleaseId) {
248
+ return compatibility.minReleaseId;
249
+ }
250
+ if (compatibility.maxSequence && activeSequence > compatibility.maxSequence && compatibility.maxReleaseId) {
251
+ return compatibility.maxReleaseId;
252
+ }
253
+ return activeReleaseId;
254
+ }
255
+ function edgeKeySegment(value) {
256
+ return value.replace(/[^A-Za-z0-9_-]/g, '_');
257
+ }
258
+ function cleanPath(value) {
259
+ return value.replace(/^\/+|\/+$/g, '').replace(/\/+/g, '/');
260
+ }
261
+ function joinKey(...parts) {
262
+ return parts.map(cleanPath).filter(Boolean).join('/');
263
+ }
264
+ function encodeKey(key) {
265
+ return `/${key.split('/').map(encodeURIComponent).join('/')}`;
266
+ }
267
+ function trimSlash(value) {
268
+ return value.replace(/\/+$/, '');
269
+ }
@@ -0,0 +1,18 @@
1
+ import { type ContentPathOptions } from '../content/routes.js';
2
+ import { type ContentProvider, type RenderInputOptions } from '../content/provider.js';
3
+ import type { KernelPage, KernelRouteMetadata, KernelSiteConfig, RenderInput } from '../content/types.js';
4
+ export interface FileContentProviderOptions extends ContentPathOptions {
5
+ parseSiteConfig?: (value: unknown, source: string) => KernelSiteConfig;
6
+ parsePage?: (value: unknown, source: string) => KernelPage;
7
+ }
8
+ export declare class FileContentProvider<TPage extends KernelPage = KernelPage, TSiteConfig extends KernelSiteConfig = KernelSiteConfig> implements ContentProvider<TPage, TSiteConfig> {
9
+ readonly options: FileContentProviderOptions;
10
+ constructor(options?: FileContentProviderOptions);
11
+ loadSiteConfig(): Promise<TSiteConfig>;
12
+ loadPage(route?: string): Promise<TPage | null>;
13
+ listRoutes(): Promise<string[]>;
14
+ listPublishedRoutes(): Promise<string[]>;
15
+ resolveRouteMetadata(route?: string): Promise<KernelRouteMetadata>;
16
+ loadRenderInput(route?: string, options?: RenderInputOptions): Promise<RenderInput<TPage, TSiteConfig>>;
17
+ }
18
+ export declare function createFileContentProvider<TPage extends KernelPage = KernelPage, TSiteConfig extends KernelSiteConfig = KernelSiteConfig>(options?: FileContentProviderOptions): FileContentProvider<TPage, TSiteConfig>;
@@ -0,0 +1,91 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { localeRoot, normalizeRoute, routeFromRelativePagePath, routeToPagePath, siteRoot } from '../content/routes.js';
4
+ import { loadRenderInput, routeMetadataForContent } from '../content/provider.js';
5
+ import { parseKernelPage, parseKernelSiteConfig } from '../content/validation.js';
6
+ export class FileContentProvider {
7
+ options;
8
+ constructor(options = {}) {
9
+ this.options = options;
10
+ }
11
+ async loadSiteConfig() {
12
+ const source = path.join(siteRoot(this.options), 'site.json');
13
+ const parsed = await readJSON(source);
14
+ const value = this.options.parseSiteConfig
15
+ ? this.options.parseSiteConfig(parsed, source)
16
+ : parseKernelSiteConfig(parsed, source);
17
+ return value;
18
+ }
19
+ async loadPage(route = '/') {
20
+ const source = routeToPagePath(route, this.options);
21
+ try {
22
+ const parsed = await readJSON(source);
23
+ const value = this.options.parsePage
24
+ ? this.options.parsePage(parsed, source)
25
+ : parseKernelPage(parsed, source);
26
+ return value;
27
+ }
28
+ catch (error) {
29
+ if (isNotFound(error)) {
30
+ return null;
31
+ }
32
+ throw error;
33
+ }
34
+ }
35
+ async listRoutes() {
36
+ const files = await walk(localeRoot(this.options));
37
+ return files
38
+ .filter((entry) => entry.endsWith('/_index.json'))
39
+ .map((entry) => routeFromRelativePagePath(entry))
40
+ .sort((left, right) => left.localeCompare(right));
41
+ }
42
+ async listPublishedRoutes() {
43
+ const routes = await this.listRoutes();
44
+ const pages = await Promise.all(routes.map(async (route) => ({
45
+ route,
46
+ page: await this.loadPage(route)
47
+ })));
48
+ return pages
49
+ .filter((entry) => entry.page?.status === 'published')
50
+ .map((entry) => entry.route);
51
+ }
52
+ async resolveRouteMetadata(route = '/') {
53
+ const normalizedRoute = normalizeRoute(route);
54
+ const [siteConfig, page] = await Promise.all([
55
+ this.loadSiteConfig(),
56
+ this.loadPage(normalizedRoute)
57
+ ]);
58
+ return routeMetadataForContent(siteConfig, page);
59
+ }
60
+ async loadRenderInput(route = '/', options = {}) {
61
+ return loadRenderInput(this, route, options);
62
+ }
63
+ }
64
+ export function createFileContentProvider(options = {}) {
65
+ return new FileContentProvider(options);
66
+ }
67
+ async function readJSON(source) {
68
+ const raw = await fs.readFile(source, 'utf8');
69
+ return JSON.parse(raw);
70
+ }
71
+ async function walk(directory, prefix = '') {
72
+ const entries = await fs.readdir(directory, { withFileTypes: true });
73
+ const files = [];
74
+ for (const entry of entries) {
75
+ const relative = prefix ? path.join(prefix, entry.name) : entry.name;
76
+ const absolute = path.join(directory, entry.name);
77
+ if (entry.isDirectory()) {
78
+ files.push(...await walk(absolute, relative));
79
+ }
80
+ else if (entry.isFile()) {
81
+ files.push(relative);
82
+ }
83
+ }
84
+ return files;
85
+ }
86
+ function isNotFound(error) {
87
+ return typeof error === 'object'
88
+ && error !== null
89
+ && 'code' in error
90
+ && error.code === 'ENOENT';
91
+ }
@@ -0,0 +1,2 @@
1
+ import { type BareMetalContentWatchOptions, type VitePluginLike } from '../dev/index.js';
2
+ export declare function baremetalSvelteKit(options?: BareMetalContentWatchOptions): VitePluginLike;
@@ -0,0 +1,18 @@
1
+ import { baremetalContentWatchPlugin, devRefreshPort, devRefreshScriptTag, } from '../dev/index.js';
2
+ export function baremetalSvelteKit(options = {}) {
3
+ const watchPlugin = baremetalContentWatchPlugin(options);
4
+ return {
5
+ ...watchPlugin,
6
+ name: 'baremetal-sveltekit',
7
+ transformIndexHtml(html) {
8
+ if (process.env.NODE_ENV === 'production') {
9
+ return html;
10
+ }
11
+ if (devRefreshPort(options) <= 0) {
12
+ return html;
13
+ }
14
+ const script = devRefreshScriptTag(options);
15
+ return html.includes('</body>') ? html.replace('</body>', `${script}</body>`) : `${html}${script}`;
16
+ },
17
+ };
18
+ }
@@ -0,0 +1,20 @@
1
+ import { type ContentProvider, type RenderInputOptions } from '../content/provider.js';
2
+ import type { KernelPage, KernelRouteMetadata, KernelSiteConfig, RenderInput } from '../content/types.js';
3
+ export interface FixtureContentProviderOptions<TPage extends KernelPage = KernelPage, TSiteConfig extends KernelSiteConfig = KernelSiteConfig> {
4
+ siteConfig?: TSiteConfig;
5
+ pages?: Record<string, TPage | null>;
6
+ }
7
+ export declare class FixtureContentProvider<TPage extends KernelPage = KernelPage, TSiteConfig extends KernelSiteConfig = KernelSiteConfig> implements ContentProvider<TPage, TSiteConfig> {
8
+ private siteConfig;
9
+ private pages;
10
+ constructor(options?: FixtureContentProviderOptions<TPage, TSiteConfig>);
11
+ loadSiteConfig(): Promise<TSiteConfig>;
12
+ loadPage(route?: string): Promise<TPage | null>;
13
+ listRoutes(): Promise<string[]>;
14
+ listPublishedRoutes(): Promise<string[]>;
15
+ resolveRouteMetadata(route?: string): Promise<KernelRouteMetadata>;
16
+ loadRenderInput(route?: string, options?: RenderInputOptions): Promise<RenderInput<TPage, TSiteConfig>>;
17
+ }
18
+ export declare function createFixtureContentProvider<TPage extends KernelPage = KernelPage, TSiteConfig extends KernelSiteConfig = KernelSiteConfig>(options?: FixtureContentProviderOptions<TPage, TSiteConfig>): FixtureContentProvider<TPage, TSiteConfig>;
19
+ export declare function defaultFixtureSiteConfig(): KernelSiteConfig;
20
+ export declare function defaultFixturePage(): KernelPage;