@gradial/aci 0.1.0 → 0.1.1
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/README.md +1 -1
- package/bin/aci.js +45 -0
- package/dist/assets/index.d.ts +1 -0
- package/dist/assets/index.js +1 -0
- package/dist/astro/index.d.ts +24 -2
- package/dist/astro/index.js +42 -4
- package/dist/content/index.d.ts +0 -3
- package/dist/content/index.js +0 -3
- package/dist/content/provider.d.ts +32 -8
- package/dist/content/provider.js +26 -16
- package/dist/content/routes.d.ts +6 -12
- package/dist/content/routes.js +9 -55
- package/dist/content/validation.js +1 -1
- package/dist/define-layout.js +5 -1
- package/dist/dev/browser.d.ts +1 -1
- package/dist/dev/browser.js +1 -1
- package/dist/dev/index.d.ts +3 -3
- package/dist/dev/index.js +8 -8
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/next/config.d.ts +14 -0
- package/dist/next/config.js +22 -0
- package/dist/next/dev-refresh.js +4 -4
- package/dist/next/edge-config.d.ts +1 -0
- package/dist/next/edge-config.js +92 -0
- package/dist/next/index.d.ts +2 -0
- package/dist/next/index.js +2 -0
- package/dist/next/middleware.js +4 -6
- package/dist/next/server.d.ts +5 -24
- package/dist/next/server.js +47 -152
- package/dist/providers/file.d.ts +11 -17
- package/dist/providers/file.js +44 -78
- package/dist/providers/s3.d.ts +24 -0
- package/dist/providers/s3.js +162 -0
- package/dist/sveltekit/index.d.ts +18 -2
- package/dist/sveltekit/index.js +35 -4
- package/dist/testing/index.d.ts +14 -12
- package/dist/testing/index.js +41 -28
- package/dist/types/component.d.ts +19 -2
- package/dist/types/config.d.ts +4 -0
- package/dist/types/image.d.ts +51 -0
- package/dist/types/image.js +58 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/layout.d.ts +12 -0
- package/package.json +26 -2
- package/src/cli/compile-registry.mjs +162 -0
- package/src/cli/css-stub-loader.mjs +27 -0
- package/src/cli/generate-registry.mjs +72 -0
- package/src/cli/validate-content.mjs +391 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
export async function getUncachedEdgeConfigValue(edgeConfig, key) {
|
|
2
|
+
const connection = parseEdgeConfigConnection(edgeConfig);
|
|
3
|
+
if (!connection) {
|
|
4
|
+
throw new Error('Invalid Edge Config connection string');
|
|
5
|
+
}
|
|
6
|
+
const headers = new Headers({
|
|
7
|
+
authorization: `Bearer ${connection.token}`,
|
|
8
|
+
'x-edge-config-min-updated-at': `${Number.MAX_SAFE_INTEGER}`,
|
|
9
|
+
});
|
|
10
|
+
if (process.env.VERCEL_ENV) {
|
|
11
|
+
headers.set('x-edge-config-vercel-env', process.env.VERCEL_ENV);
|
|
12
|
+
}
|
|
13
|
+
const url = `${connection.baseUrl}/item/${encodeURIComponent(key)}?version=${encodeURIComponent(connection.version)}`;
|
|
14
|
+
const response = await fetch(url, {
|
|
15
|
+
headers,
|
|
16
|
+
cache: 'no-store',
|
|
17
|
+
});
|
|
18
|
+
if (response.ok) {
|
|
19
|
+
return response.json();
|
|
20
|
+
}
|
|
21
|
+
if (response.status === 404 && response.headers.has('x-edge-config-digest')) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
const body = await response.text().catch(() => '');
|
|
25
|
+
throw new Error(`Edge Config read ${key} returned ${response.status}: ${body.slice(0, 500)}`);
|
|
26
|
+
}
|
|
27
|
+
function parseEdgeConfigConnection(value) {
|
|
28
|
+
const text = value.trim();
|
|
29
|
+
if (!text)
|
|
30
|
+
return null;
|
|
31
|
+
return parseEdgeConfigQueryConnection(text)
|
|
32
|
+
|| parseVercelEdgeConfigURL(text)
|
|
33
|
+
|| parseExternalEdgeConfigURL(text);
|
|
34
|
+
}
|
|
35
|
+
function parseEdgeConfigQueryConnection(value) {
|
|
36
|
+
if (!value.startsWith('edge-config:'))
|
|
37
|
+
return null;
|
|
38
|
+
const params = new URLSearchParams(value.slice('edge-config:'.length));
|
|
39
|
+
const id = params.get('id') || '';
|
|
40
|
+
const token = params.get('token') || '';
|
|
41
|
+
if (!id || !token)
|
|
42
|
+
return null;
|
|
43
|
+
return {
|
|
44
|
+
baseUrl: `https://edge-config.vercel.com/${id}`,
|
|
45
|
+
token,
|
|
46
|
+
version: params.get('version') || '1',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function parseVercelEdgeConfigURL(value) {
|
|
50
|
+
let url;
|
|
51
|
+
try {
|
|
52
|
+
url = new URL(value);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
if (url.protocol !== 'https:' || url.host !== 'edge-config.vercel.com')
|
|
58
|
+
return null;
|
|
59
|
+
const id = url.pathname.split('/').filter(Boolean)[0] || '';
|
|
60
|
+
const token = url.searchParams.get('token') || '';
|
|
61
|
+
if (!id || !id.startsWith('ecfg') || !token)
|
|
62
|
+
return null;
|
|
63
|
+
return {
|
|
64
|
+
baseUrl: `https://edge-config.vercel.com/${id}`,
|
|
65
|
+
token,
|
|
66
|
+
version: url.searchParams.get('version') || '1',
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function parseExternalEdgeConfigURL(value) {
|
|
70
|
+
let url;
|
|
71
|
+
try {
|
|
72
|
+
url = new URL(value);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
let id = url.searchParams.get('id') || '';
|
|
78
|
+
const token = url.searchParams.get('token') || '';
|
|
79
|
+
const version = url.searchParams.get('version') || '1';
|
|
80
|
+
if (!id || url.pathname.startsWith('/ecfg_')) {
|
|
81
|
+
id = url.pathname.split('/').filter(Boolean)[0] || '';
|
|
82
|
+
}
|
|
83
|
+
if (!id || !token)
|
|
84
|
+
return null;
|
|
85
|
+
url.search = '';
|
|
86
|
+
const base = url.toString().replace(/\/+$/, '');
|
|
87
|
+
return {
|
|
88
|
+
baseUrl: base,
|
|
89
|
+
token,
|
|
90
|
+
version,
|
|
91
|
+
};
|
|
92
|
+
}
|
package/dist/next/middleware.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createClient, get as getEdgeConfigItem } from '@vercel/edge-config';
|
|
2
1
|
import { NextResponse } from 'next/server';
|
|
2
|
+
import { getUncachedEdgeConfigValue } from './edge-config.js';
|
|
3
3
|
import { validatePreviewToken } from './preview.js';
|
|
4
4
|
const RELEASE_HEADER = 'x-gradial-release-id';
|
|
5
5
|
const ROUTE_HEADER = 'x-gradial-route';
|
|
@@ -62,9 +62,7 @@ async function getEdgeConfigJSON(edgeConfig, key) {
|
|
|
62
62
|
return value;
|
|
63
63
|
}
|
|
64
64
|
async function getEdgeConfigValue(edgeConfig, key) {
|
|
65
|
-
return edgeConfig
|
|
66
|
-
? await getEdgeConfigItem(key)
|
|
67
|
-
: await createClient(edgeConfig).get(key);
|
|
65
|
+
return getUncachedEdgeConfigValue(edgeConfig, key);
|
|
68
66
|
}
|
|
69
67
|
function activeReleaseKey(siteId) {
|
|
70
68
|
return `activeRelease_${edgeKeySegment(siteId)}`;
|
|
@@ -108,7 +106,7 @@ async function releaseFromPreviewToken(request, config) {
|
|
|
108
106
|
const token = previewTokenFromRequest(request);
|
|
109
107
|
if (!token)
|
|
110
108
|
return { releaseId: '', invalid: false };
|
|
111
|
-
const signKey = config.previewSignKey || process.env.
|
|
109
|
+
const signKey = config.previewSignKey || process.env.ACI_PREVIEW_SIGN_KEY || '';
|
|
112
110
|
if (!signKey)
|
|
113
111
|
return { releaseId: '', invalid: true };
|
|
114
112
|
const claims = await validatePreviewToken(token, signKey);
|
|
@@ -124,7 +122,7 @@ function previewTokenFromRequest(request) {
|
|
|
124
122
|
const queryToken = request.nextUrl.searchParams.get('preview_token');
|
|
125
123
|
if (queryToken)
|
|
126
124
|
return queryToken;
|
|
127
|
-
const cookieToken = request.cookies.get('
|
|
125
|
+
const cookieToken = request.cookies.get('aci_preview')?.value || '';
|
|
128
126
|
if (cookieToken)
|
|
129
127
|
return cookieToken;
|
|
130
128
|
const auth = request.headers.get('authorization') || '';
|
package/dist/next/server.d.ts
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
}
|
|
4
|
-
export declare class FragmentNotFoundError extends Error {
|
|
5
|
-
constructor(name: string, cause?: unknown);
|
|
6
|
-
}
|
|
1
|
+
import { FragmentNotFoundError, PageNotFoundError } from '../content/provider.js';
|
|
2
|
+
export { FragmentNotFoundError, PageNotFoundError };
|
|
7
3
|
export interface GradialFetchConfig {
|
|
8
4
|
siteId?: string;
|
|
9
5
|
bucket?: string;
|
|
@@ -17,24 +13,6 @@ export interface GradialFetchConfig {
|
|
|
17
13
|
edgeConfig?: string;
|
|
18
14
|
deploymentId?: string;
|
|
19
15
|
}
|
|
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
16
|
export interface GradialRenderInput<TPage, TSite extends GradialSiteConfig = GradialSiteConfig> {
|
|
39
17
|
route: string;
|
|
40
18
|
domain: string;
|
|
@@ -55,4 +33,7 @@ export declare function routeFromNextParams(params?: Promise<{
|
|
|
55
33
|
}> | {
|
|
56
34
|
slug?: string[];
|
|
57
35
|
}): Promise<string>;
|
|
36
|
+
export declare function generateGradialStaticParams(): Promise<Array<{
|
|
37
|
+
slug?: string[];
|
|
38
|
+
}>>;
|
|
58
39
|
export declare function resolveReleaseId(config?: GradialFetchConfig): Promise<string>;
|
package/dist/next/server.js
CHANGED
|
@@ -1,55 +1,31 @@
|
|
|
1
|
-
import crypto from 'node:crypto';
|
|
2
|
-
import { createClient, get as getEdgeConfigItem } from '@vercel/edge-config';
|
|
3
1
|
import { headers } from 'next/headers';
|
|
2
|
+
import { FragmentNotFoundError, PageNotFoundError, } from '../content/provider.js';
|
|
3
|
+
import { S3ContentProvider } from '../providers/s3.js';
|
|
4
|
+
import { getUncachedEdgeConfigValue } from './edge-config.js';
|
|
4
5
|
const RELEASE_HEADER = 'x-gradial-release-id';
|
|
5
|
-
export
|
|
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
|
-
}
|
|
6
|
+
export { FragmentNotFoundError, PageNotFoundError };
|
|
19
7
|
export async function getPage(route = '', config = {}) {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
return await getS3JSON(key, config);
|
|
24
|
-
}
|
|
25
|
-
catch (error) {
|
|
26
|
-
throw new PageNotFoundError(route, error);
|
|
27
|
-
}
|
|
8
|
+
const provider = await resolveProvider(config);
|
|
9
|
+
return await provider.getPage(route);
|
|
28
10
|
}
|
|
29
11
|
export async function getFragment(name, config = {}) {
|
|
30
|
-
const
|
|
31
|
-
|
|
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
|
-
}
|
|
12
|
+
const provider = await resolveProvider(config);
|
|
13
|
+
return await provider.getFragment(name);
|
|
39
14
|
}
|
|
40
15
|
export async function getRoutes(config = {}) {
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
return
|
|
16
|
+
const provider = await resolveProvider(config);
|
|
17
|
+
const routes = await provider.listRoutes();
|
|
18
|
+
return routes.map((route) => route.path).sort();
|
|
44
19
|
}
|
|
45
20
|
export async function getRenderInput(route = '/', config = {}) {
|
|
21
|
+
const provider = await resolveProvider(config);
|
|
46
22
|
const [page, siteConfig] = await Promise.all([
|
|
47
|
-
getPage(route
|
|
48
|
-
|
|
23
|
+
provider.getPage(route),
|
|
24
|
+
provider.getSiteConfig(),
|
|
49
25
|
]);
|
|
50
26
|
return {
|
|
51
27
|
route,
|
|
52
|
-
domain: siteConfig.domain || 'www.
|
|
28
|
+
domain: siteConfig.domain || 'www.aci.local',
|
|
53
29
|
locale: siteConfig.defaultLocale || 'en-us',
|
|
54
30
|
siteConfig,
|
|
55
31
|
page,
|
|
@@ -63,18 +39,38 @@ export async function routeFromNextParams(params) {
|
|
|
63
39
|
return '/';
|
|
64
40
|
return `/${resolved.slug.join('/')}/`;
|
|
65
41
|
}
|
|
42
|
+
export async function generateGradialStaticParams() {
|
|
43
|
+
const { FileContentProvider } = await import('../providers/file.js');
|
|
44
|
+
const provider = new FileContentProvider();
|
|
45
|
+
const routes = await provider.listRoutes();
|
|
46
|
+
return routes.map((entry) => ({
|
|
47
|
+
slug: entry.path === '/' ? undefined : entry.path.replace(/^\/|\/$/g, '').split('/'),
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
66
50
|
export async function resolveReleaseId(config = {}) {
|
|
67
51
|
if (config.releaseId)
|
|
68
52
|
return config.releaseId;
|
|
69
53
|
const headerReleaseId = await releaseIdFromHeaders();
|
|
70
54
|
if (headerReleaseId)
|
|
71
55
|
return headerReleaseId;
|
|
72
|
-
if (process.env.
|
|
73
|
-
return process.env.
|
|
56
|
+
if (process.env.ACI_RELEASE_ID)
|
|
57
|
+
return process.env.ACI_RELEASE_ID;
|
|
74
58
|
const activeReleaseId = await activeReleaseFromEdgeConfig(config);
|
|
75
59
|
if (activeReleaseId)
|
|
76
60
|
return activeReleaseId;
|
|
77
|
-
throw new Error('
|
|
61
|
+
throw new Error('ACI release ID is not available from headers, ACI_RELEASE_ID, or Edge Config');
|
|
62
|
+
}
|
|
63
|
+
async function resolveProvider(config) {
|
|
64
|
+
const releaseId = await resolveReleaseId(config);
|
|
65
|
+
return new S3ContentProvider({
|
|
66
|
+
bucket: config.bucket,
|
|
67
|
+
keyPrefix: releaseKey(config, releaseId),
|
|
68
|
+
region: config.region,
|
|
69
|
+
accessKeyId: config.accessKeyId,
|
|
70
|
+
secretAccessKey: config.secretAccessKey,
|
|
71
|
+
sessionToken: config.sessionToken,
|
|
72
|
+
endpoint: config.endpoint,
|
|
73
|
+
});
|
|
78
74
|
}
|
|
79
75
|
async function releaseIdFromHeaders() {
|
|
80
76
|
try {
|
|
@@ -87,7 +83,7 @@ async function releaseIdFromHeaders() {
|
|
|
87
83
|
}
|
|
88
84
|
async function activeReleaseFromEdgeConfig(config) {
|
|
89
85
|
const edgeConfig = config.edgeConfig || process.env.EDGE_CONFIG || '';
|
|
90
|
-
const siteId = config.siteId || process.env.
|
|
86
|
+
const siteId = config.siteId || process.env.ACI_SITE_ID || '';
|
|
91
87
|
if (!edgeConfig || !siteId)
|
|
92
88
|
return '';
|
|
93
89
|
const active = await activeReleasePointer(edgeConfig, siteId);
|
|
@@ -116,106 +112,10 @@ async function getEdgeConfigJSON(edgeConfig, key) {
|
|
|
116
112
|
return value;
|
|
117
113
|
}
|
|
118
114
|
async function getEdgeConfigValue(edgeConfig, key) {
|
|
119
|
-
return edgeConfig
|
|
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');
|
|
115
|
+
return getUncachedEdgeConfigValue(edgeConfig, key);
|
|
212
116
|
}
|
|
213
|
-
function
|
|
214
|
-
return
|
|
215
|
-
}
|
|
216
|
-
function pageRouteSegment(route) {
|
|
217
|
-
const clean = cleanPath(route);
|
|
218
|
-
return clean === '' ? 'index' : clean;
|
|
117
|
+
function releaseKey(config, releaseId) {
|
|
118
|
+
return joinKey(config.keyPrefix || process.env.ACI_S3_KEY_PREFIX || '', 'releases', releaseId);
|
|
219
119
|
}
|
|
220
120
|
function activeReleaseKey(siteId) {
|
|
221
121
|
return `activeRelease_${edgeKeySegment(siteId)}`;
|
|
@@ -255,15 +155,10 @@ function clampRelease(activeReleaseId, activeSequence, compatibility) {
|
|
|
255
155
|
function edgeKeySegment(value) {
|
|
256
156
|
return value.replace(/[^A-Za-z0-9_-]/g, '_');
|
|
257
157
|
}
|
|
258
|
-
function cleanPath(value) {
|
|
259
|
-
return value.replace(/^\/+|\/+$/g, '').replace(/\/+/g, '/');
|
|
260
|
-
}
|
|
261
158
|
function joinKey(...parts) {
|
|
262
|
-
return parts
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
function trimSlash(value) {
|
|
268
|
-
return value.replace(/\/+$/, '');
|
|
159
|
+
return parts
|
|
160
|
+
.flatMap((part) => part.split('/'))
|
|
161
|
+
.map((part) => part.trim())
|
|
162
|
+
.filter(Boolean)
|
|
163
|
+
.join('/');
|
|
269
164
|
}
|
package/dist/providers/file.d.ts
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { type CompiledManifest, type ContentProvider, type RouteEntry } from '../content/provider.js';
|
|
2
|
+
export declare class FileContentProvider implements ContentProvider {
|
|
3
|
+
#private;
|
|
4
|
+
readonly root: string;
|
|
5
|
+
constructor(root?: string);
|
|
6
|
+
listRoutes(): Promise<RouteEntry[]>;
|
|
7
|
+
getSiteConfig<T = unknown>(): Promise<T>;
|
|
8
|
+
getPage<T = unknown>(route: string): Promise<T>;
|
|
9
|
+
getFragment<T = unknown>(id: string): Promise<T>;
|
|
10
|
+
manifest(): Promise<CompiledManifest>;
|
|
11
|
+
private readJSON;
|
|
7
12
|
}
|
|
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>;
|
package/dist/providers/file.js
CHANGED
|
@@ -1,91 +1,57 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { parseKernelPage, parseKernelSiteConfig } from '../content/validation.js';
|
|
3
|
+
import { FragmentNotFoundError, PageNotFoundError } from '../content/provider.js';
|
|
4
|
+
import { normalizeRoute } from '../content/routes.js';
|
|
6
5
|
export class FileContentProvider {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
root;
|
|
7
|
+
#manifest;
|
|
8
|
+
constructor(root) {
|
|
9
|
+
const resolved = root
|
|
10
|
+
|| process.env.ACI_CONTENT_ROOT
|
|
11
|
+
|| '.aci/compiled';
|
|
12
|
+
this.root = path.resolve(process.cwd(), resolved);
|
|
10
13
|
}
|
|
11
|
-
async
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return
|
|
18
|
-
}
|
|
19
|
-
async
|
|
20
|
-
const
|
|
14
|
+
async listRoutes() {
|
|
15
|
+
const manifest = await this.manifest();
|
|
16
|
+
return Object.values(manifest.routes).sort((a, b) => a.path.localeCompare(b.path));
|
|
17
|
+
}
|
|
18
|
+
async getSiteConfig() {
|
|
19
|
+
const manifest = await this.manifest();
|
|
20
|
+
return await this.readJSON(manifest.siteConfigRef);
|
|
21
|
+
}
|
|
22
|
+
async getPage(route) {
|
|
23
|
+
const manifest = await this.manifest();
|
|
24
|
+
const normalized = normalizeRoute(route);
|
|
25
|
+
const entry = Object.values(manifest.routes).find((candidate) => normalizeRoute(candidate.path) === normalized);
|
|
26
|
+
if (!entry) {
|
|
27
|
+
throw new PageNotFoundError(route);
|
|
28
|
+
}
|
|
21
29
|
try {
|
|
22
|
-
|
|
23
|
-
const value = this.options.parsePage
|
|
24
|
-
? this.options.parsePage(parsed, source)
|
|
25
|
-
: parseKernelPage(parsed, source);
|
|
26
|
-
return value;
|
|
30
|
+
return await this.readJSON(entry.payloadRef);
|
|
27
31
|
}
|
|
28
32
|
catch (error) {
|
|
29
|
-
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
throw error;
|
|
33
|
+
throw new PageNotFoundError(route, error);
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
|
-
async
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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));
|
|
36
|
+
async getFragment(id) {
|
|
37
|
+
const manifest = await this.manifest();
|
|
38
|
+
const entry = manifest.fragments[id];
|
|
39
|
+
if (!entry) {
|
|
40
|
+
throw new FragmentNotFoundError(id);
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
return await this.readJSON(entry.payloadRef);
|
|
79
44
|
}
|
|
80
|
-
|
|
81
|
-
|
|
45
|
+
catch (error) {
|
|
46
|
+
throw new FragmentNotFoundError(id, error);
|
|
82
47
|
}
|
|
83
48
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
49
|
+
async manifest() {
|
|
50
|
+
this.#manifest ??= this.readJSON('manifest.json');
|
|
51
|
+
return await this.#manifest;
|
|
52
|
+
}
|
|
53
|
+
async readJSON(key) {
|
|
54
|
+
const raw = await fs.readFile(path.join(this.root, key), 'utf8');
|
|
55
|
+
return JSON.parse(raw);
|
|
56
|
+
}
|
|
91
57
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type CompiledManifest, type ContentProvider, type RouteEntry } from '../content/provider.js';
|
|
2
|
+
export interface S3ContentProviderConfig {
|
|
3
|
+
bucket?: string;
|
|
4
|
+
keyPrefix?: string;
|
|
5
|
+
region?: string;
|
|
6
|
+
accessKeyId?: string;
|
|
7
|
+
secretAccessKey?: string;
|
|
8
|
+
sessionToken?: string;
|
|
9
|
+
endpoint?: string;
|
|
10
|
+
}
|
|
11
|
+
type ResolvedS3Config = Required<S3ContentProviderConfig>;
|
|
12
|
+
export declare class S3ContentProvider implements ContentProvider {
|
|
13
|
+
#private;
|
|
14
|
+
readonly config: ResolvedS3Config;
|
|
15
|
+
constructor(config?: S3ContentProviderConfig);
|
|
16
|
+
listRoutes(): Promise<RouteEntry[]>;
|
|
17
|
+
getSiteConfig<T = unknown>(): Promise<T>;
|
|
18
|
+
getPage<T = unknown>(route: string): Promise<T>;
|
|
19
|
+
getFragment<T = unknown>(id: string): Promise<T>;
|
|
20
|
+
manifest(): Promise<CompiledManifest>;
|
|
21
|
+
private getJSON;
|
|
22
|
+
}
|
|
23
|
+
export declare function getS3JSON<T>(key: string, config: ResolvedS3Config): Promise<T>;
|
|
24
|
+
export {};
|