@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,162 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { FragmentNotFoundError, PageNotFoundError } from '../content/provider.js';
|
|
3
|
+
import { normalizeRoute } from '../content/routes.js';
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// S3 content provider — reads compiled manifest + payloads from S3
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
export class S3ContentProvider {
|
|
8
|
+
config;
|
|
9
|
+
#manifest;
|
|
10
|
+
constructor(config = {}) {
|
|
11
|
+
this.config = resolveS3Config(config);
|
|
12
|
+
}
|
|
13
|
+
async listRoutes() {
|
|
14
|
+
const manifest = await this.manifest();
|
|
15
|
+
return Object.values(manifest.routes).sort((a, b) => a.path.localeCompare(b.path));
|
|
16
|
+
}
|
|
17
|
+
async getSiteConfig() {
|
|
18
|
+
const manifest = await this.manifest();
|
|
19
|
+
return await this.getJSON(manifest.siteConfigRef);
|
|
20
|
+
}
|
|
21
|
+
async getPage(route) {
|
|
22
|
+
const manifest = await this.manifest();
|
|
23
|
+
const normalized = normalizeRoute(route);
|
|
24
|
+
const entry = Object.values(manifest.routes).find((candidate) => normalizeRoute(candidate.path) === normalized);
|
|
25
|
+
if (!entry) {
|
|
26
|
+
throw new PageNotFoundError(route);
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
return await this.getJSON(entry.payloadRef);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
throw new PageNotFoundError(route, error);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async getFragment(id) {
|
|
36
|
+
const manifest = await this.manifest();
|
|
37
|
+
const entry = manifest.fragments[id];
|
|
38
|
+
if (!entry) {
|
|
39
|
+
throw new FragmentNotFoundError(id);
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
return await this.getJSON(entry.payloadRef);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
throw new FragmentNotFoundError(id, error);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async manifest() {
|
|
49
|
+
this.#manifest ??= this.getJSON('manifest.json');
|
|
50
|
+
return await this.#manifest;
|
|
51
|
+
}
|
|
52
|
+
async getJSON(key) {
|
|
53
|
+
return await getS3JSON(joinKey(this.config.keyPrefix, key), this.config);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// S3 fetch with AWS SigV4 signing
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
export async function getS3JSON(key, config) {
|
|
60
|
+
const url = s3ObjectURL(config, key);
|
|
61
|
+
const signedHeaders = signedS3Headers('GET', url, config);
|
|
62
|
+
const res = await fetch(url, {
|
|
63
|
+
method: 'GET',
|
|
64
|
+
headers: signedHeaders,
|
|
65
|
+
cache: 'no-store'
|
|
66
|
+
});
|
|
67
|
+
if (!res.ok) {
|
|
68
|
+
throw new Error(`S3 GET ${key} failed with status ${res.status}: ${await res.text()}`);
|
|
69
|
+
}
|
|
70
|
+
return (await res.json());
|
|
71
|
+
}
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Internal helpers
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
function resolveS3Config(config) {
|
|
76
|
+
const resolved = {
|
|
77
|
+
bucket: config.bucket || process.env.ACI_S3_BUCKET || '',
|
|
78
|
+
keyPrefix: config.keyPrefix || process.env.ACI_S3_KEY_PREFIX || '',
|
|
79
|
+
region: config.region || process.env.AWS_REGION || 'us-east-1',
|
|
80
|
+
accessKeyId: config.accessKeyId || process.env.AWS_ACCESS_KEY_ID || '',
|
|
81
|
+
secretAccessKey: config.secretAccessKey || process.env.AWS_SECRET_ACCESS_KEY || '',
|
|
82
|
+
sessionToken: config.sessionToken || process.env.AWS_SESSION_TOKEN || '',
|
|
83
|
+
endpoint: trimSlash(config.endpoint || process.env.ACI_S3_ENDPOINT || '')
|
|
84
|
+
};
|
|
85
|
+
for (const key of ['bucket', 'accessKeyId', 'secretAccessKey']) {
|
|
86
|
+
if (!resolved[key])
|
|
87
|
+
throw new Error(`S3 config missing ${key}`);
|
|
88
|
+
}
|
|
89
|
+
return resolved;
|
|
90
|
+
}
|
|
91
|
+
function s3ObjectURL(config, key) {
|
|
92
|
+
const encodedKey = encodeKey(key);
|
|
93
|
+
if (config.endpoint) {
|
|
94
|
+
return new URL(`${config.endpoint}/${encodeURIComponent(config.bucket)}${encodedKey}`);
|
|
95
|
+
}
|
|
96
|
+
return new URL(`https://${config.bucket}.s3.${config.region}.amazonaws.com${encodedKey}`);
|
|
97
|
+
}
|
|
98
|
+
function signedS3Headers(method, url, config) {
|
|
99
|
+
const now = new Date();
|
|
100
|
+
const amzDate = amzTimestamp(now);
|
|
101
|
+
const dateStamp = amzDate.slice(0, 8);
|
|
102
|
+
const payloadHash = sha256Hex('');
|
|
103
|
+
const headers = new Headers({
|
|
104
|
+
host: url.host,
|
|
105
|
+
'x-amz-content-sha256': payloadHash,
|
|
106
|
+
'x-amz-date': amzDate
|
|
107
|
+
});
|
|
108
|
+
if (config.sessionToken)
|
|
109
|
+
headers.set('x-amz-security-token', config.sessionToken);
|
|
110
|
+
const signedHeaderNames = Array.from(headers.keys()).sort();
|
|
111
|
+
const canonicalHeaders = signedHeaderNames.map((k) => `${k}:${headers.get(k)?.trim()}\n`).join('');
|
|
112
|
+
const canonicalRequest = [
|
|
113
|
+
method,
|
|
114
|
+
url.pathname,
|
|
115
|
+
url.searchParams.toString(),
|
|
116
|
+
canonicalHeaders,
|
|
117
|
+
signedHeaderNames.join(';'),
|
|
118
|
+
payloadHash
|
|
119
|
+
].join('\n');
|
|
120
|
+
const credentialScope = `${dateStamp}/${config.region}/s3/aws4_request`;
|
|
121
|
+
const stringToSign = [
|
|
122
|
+
'AWS4-HMAC-SHA256',
|
|
123
|
+
amzDate,
|
|
124
|
+
credentialScope,
|
|
125
|
+
sha256Hex(canonicalRequest)
|
|
126
|
+
].join('\n');
|
|
127
|
+
const signingKey = awsSigningKey(config.secretAccessKey, dateStamp, config.region, 's3');
|
|
128
|
+
const signature = hmacHex(signingKey, stringToSign);
|
|
129
|
+
headers.set('authorization', `AWS4-HMAC-SHA256 Credential=${config.accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaderNames.join(';')}, Signature=${signature}`);
|
|
130
|
+
return headers;
|
|
131
|
+
}
|
|
132
|
+
function awsSigningKey(secret, dateStamp, region, service) {
|
|
133
|
+
const kDate = hmac(Buffer.from(`AWS4${secret}`, 'utf8'), dateStamp);
|
|
134
|
+
const kRegion = hmac(kDate, region);
|
|
135
|
+
const kService = hmac(kRegion, service);
|
|
136
|
+
return hmac(kService, 'aws4_request');
|
|
137
|
+
}
|
|
138
|
+
function amzTimestamp(date) {
|
|
139
|
+
return date.toISOString().replace(/[:-]|\.\d{3}/g, '');
|
|
140
|
+
}
|
|
141
|
+
function hmac(key, value) {
|
|
142
|
+
return crypto.createHmac('sha256', key).update(value, 'utf8').digest();
|
|
143
|
+
}
|
|
144
|
+
function hmacHex(key, value) {
|
|
145
|
+
return crypto.createHmac('sha256', key).update(value, 'utf8').digest('hex');
|
|
146
|
+
}
|
|
147
|
+
function sha256Hex(value) {
|
|
148
|
+
return crypto.createHash('sha256').update(value, 'utf8').digest('hex');
|
|
149
|
+
}
|
|
150
|
+
function trimSlash(value) {
|
|
151
|
+
return value.replace(/\/+$/, '');
|
|
152
|
+
}
|
|
153
|
+
function encodeKey(key) {
|
|
154
|
+
return `/${key.split('/').map(encodeURIComponent).join('/')}`;
|
|
155
|
+
}
|
|
156
|
+
function joinKey(...parts) {
|
|
157
|
+
return parts
|
|
158
|
+
.flatMap((part) => part.split('/'))
|
|
159
|
+
.map((part) => part.trim())
|
|
160
|
+
.filter(Boolean)
|
|
161
|
+
.join('/');
|
|
162
|
+
}
|
|
@@ -1,2 +1,18 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
|
|
1
|
+
import { type GradialContentWatchOptions, type VitePluginLike } from '../dev/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generates prerender entries from the compiled content manifest for SvelteKit.
|
|
4
|
+
*
|
|
5
|
+
* Usage in svelte.config.js:
|
|
6
|
+
* ```js
|
|
7
|
+
* import { gradialEntries } from '@gradial/aci/sveltekit';
|
|
8
|
+
* export default { kit: { prerender: { entries: await gradialEntries() } } };
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* Or in a catch-all `+page.server.ts`:
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { gradialEntries } from '@gradial/aci/sveltekit';
|
|
14
|
+
* export const entries = gradialEntries;
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare function gradialEntries(): Promise<string[]>;
|
|
18
|
+
export declare function gradialSvelteKit(options?: GradialContentWatchOptions): VitePluginLike;
|
package/dist/sveltekit/index.js
CHANGED
|
@@ -1,9 +1,40 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { gradialContentWatchPlugin, devRefreshPort, devRefreshScriptTag, } from '../dev/index.js';
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// Entries helper — for SvelteKit SSG prerendering
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
/**
|
|
6
|
+
* Generates prerender entries from the compiled content manifest for SvelteKit.
|
|
7
|
+
*
|
|
8
|
+
* Usage in svelte.config.js:
|
|
9
|
+
* ```js
|
|
10
|
+
* import { gradialEntries } from '@gradial/aci/sveltekit';
|
|
11
|
+
* export default { kit: { prerender: { entries: await gradialEntries() } } };
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* Or in a catch-all `+page.server.ts`:
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { gradialEntries } from '@gradial/aci/sveltekit';
|
|
17
|
+
* export const entries = gradialEntries;
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export async function gradialEntries() {
|
|
21
|
+
const { FileContentProvider } = await import('../providers/file.js');
|
|
22
|
+
const { normalizeRoute } = await import('../content/routes.js');
|
|
23
|
+
const provider = new FileContentProvider();
|
|
24
|
+
const routes = await provider.listRoutes();
|
|
25
|
+
return routes.map((entry) => {
|
|
26
|
+
const normalized = normalizeRoute(entry.path);
|
|
27
|
+
return normalized === '/' ? '/' : `/${normalized.replace(/^\/|\/$/g, '')}/`;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// SvelteKit Vite plugin
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
export function gradialSvelteKit(options = {}) {
|
|
34
|
+
const watchPlugin = gradialContentWatchPlugin(options);
|
|
4
35
|
return {
|
|
5
36
|
...watchPlugin,
|
|
6
|
-
name: '
|
|
37
|
+
name: 'gradial-sveltekit',
|
|
7
38
|
transformIndexHtml(html) {
|
|
8
39
|
if (process.env.NODE_ENV === 'production') {
|
|
9
40
|
return html;
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
import { type ContentProvider, type RenderInputOptions } from '../content/provider.js';
|
|
1
|
+
import { type ContentProvider, type RouteEntry, type RenderInputOptions } from '../content/provider.js';
|
|
2
2
|
import type { KernelPage, KernelRouteMetadata, KernelSiteConfig, RenderInput } from '../content/types.js';
|
|
3
|
-
export interface FixtureContentProviderOptions
|
|
4
|
-
siteConfig?:
|
|
5
|
-
pages?: Record<string,
|
|
3
|
+
export interface FixtureContentProviderOptions {
|
|
4
|
+
siteConfig?: KernelSiteConfig;
|
|
5
|
+
pages?: Record<string, KernelPage>;
|
|
6
|
+
fragments?: Record<string, unknown>;
|
|
6
7
|
}
|
|
7
|
-
export declare class FixtureContentProvider
|
|
8
|
+
export declare class FixtureContentProvider implements ContentProvider {
|
|
8
9
|
private siteConfig;
|
|
9
10
|
private pages;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
private fragments;
|
|
12
|
+
constructor(options?: FixtureContentProviderOptions);
|
|
13
|
+
getSiteConfig<T = unknown>(): Promise<T>;
|
|
14
|
+
getPage<T = unknown>(route: string): Promise<T>;
|
|
15
|
+
getFragment<T = unknown>(id: string): Promise<T>;
|
|
16
|
+
listRoutes(): Promise<RouteEntry[]>;
|
|
17
|
+
loadRenderInput(route?: string, options?: RenderInputOptions): Promise<RenderInput>;
|
|
15
18
|
resolveRouteMetadata(route?: string): Promise<KernelRouteMetadata>;
|
|
16
|
-
loadRenderInput(route?: string, options?: RenderInputOptions): Promise<RenderInput<TPage, TSiteConfig>>;
|
|
17
19
|
}
|
|
18
|
-
export declare function createFixtureContentProvider
|
|
20
|
+
export declare function createFixtureContentProvider(options?: FixtureContentProviderOptions): FixtureContentProvider;
|
|
19
21
|
export declare function defaultFixtureSiteConfig(): KernelSiteConfig;
|
|
20
22
|
export declare function defaultFixturePage(): KernelPage;
|
package/dist/testing/index.js
CHANGED
|
@@ -1,51 +1,64 @@
|
|
|
1
|
-
import { loadRenderInput, routeMetadataForContent, } from '../content/provider.js';
|
|
1
|
+
import { PageNotFoundError, FragmentNotFoundError, loadRenderInput, routeMetadataForContent, } from '../content/provider.js';
|
|
2
2
|
import { normalizeRoute } from '../content/routes.js';
|
|
3
3
|
export class FixtureContentProvider {
|
|
4
4
|
siteConfig;
|
|
5
5
|
pages;
|
|
6
|
+
fragments;
|
|
6
7
|
constructor(options = {}) {
|
|
7
8
|
this.siteConfig = cloneFixture(options.siteConfig ?? defaultFixtureSiteConfig());
|
|
8
9
|
this.pages = new Map();
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
};
|
|
10
|
+
this.fragments = new Map();
|
|
11
|
+
const inputPages = options.pages ?? { '/': defaultFixturePage() };
|
|
12
12
|
for (const [route, page] of Object.entries(inputPages)) {
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
this.pages.set(normalizeRoute(route), cloneFixture(page));
|
|
14
|
+
}
|
|
15
|
+
if (options.fragments) {
|
|
16
|
+
for (const [id, fragment] of Object.entries(options.fragments)) {
|
|
17
|
+
this.fragments.set(id, cloneFixture(fragment));
|
|
15
18
|
}
|
|
16
19
|
}
|
|
17
20
|
}
|
|
18
|
-
async
|
|
21
|
+
async getSiteConfig() {
|
|
19
22
|
return cloneFixture(this.siteConfig);
|
|
20
23
|
}
|
|
21
|
-
async
|
|
24
|
+
async getPage(route) {
|
|
22
25
|
const page = this.pages.get(normalizeRoute(route));
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return
|
|
26
|
+
if (!page) {
|
|
27
|
+
throw new PageNotFoundError(route);
|
|
28
|
+
}
|
|
29
|
+
return cloneFixture(page);
|
|
27
30
|
}
|
|
28
|
-
async
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const page = await this.loadPage(route);
|
|
33
|
-
if (page?.status === 'published') {
|
|
34
|
-
published.push(route);
|
|
35
|
-
}
|
|
31
|
+
async getFragment(id) {
|
|
32
|
+
const fragment = this.fragments.get(id);
|
|
33
|
+
if (fragment === undefined) {
|
|
34
|
+
throw new FragmentNotFoundError(id);
|
|
36
35
|
}
|
|
37
|
-
return
|
|
36
|
+
return cloneFixture(fragment);
|
|
38
37
|
}
|
|
39
|
-
async
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
async listRoutes() {
|
|
39
|
+
return [...this.pages.entries()]
|
|
40
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
41
|
+
.map(([routePath, page]) => ({
|
|
42
|
+
slug: routePath === '/' ? 'home' : routePath.replace(/^\//, ''),
|
|
43
|
+
path: routePath,
|
|
44
|
+
payloadRef: `routes/${routePath === '/' ? 'home' : routePath.replace(/^\//, '')}.json`,
|
|
45
|
+
digest: '',
|
|
46
|
+
}));
|
|
45
47
|
}
|
|
46
48
|
async loadRenderInput(route = '/', options = {}) {
|
|
47
49
|
return loadRenderInput(this, route, options);
|
|
48
50
|
}
|
|
51
|
+
async resolveRouteMetadata(route = '/') {
|
|
52
|
+
const siteConfig = await this.getSiteConfig();
|
|
53
|
+
let page;
|
|
54
|
+
try {
|
|
55
|
+
page = await this.getPage(route);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
page = null;
|
|
59
|
+
}
|
|
60
|
+
return routeMetadataForContent(siteConfig, page);
|
|
61
|
+
}
|
|
49
62
|
}
|
|
50
63
|
export function createFixtureContentProvider(options = {}) {
|
|
51
64
|
return new FixtureContentProvider(options);
|
|
@@ -56,7 +69,7 @@ export function defaultFixtureSiteConfig() {
|
|
|
56
69
|
$type: 'site',
|
|
57
70
|
status: 'published',
|
|
58
71
|
title: 'Fixture Site',
|
|
59
|
-
domain: 'www.
|
|
72
|
+
domain: 'www.aci.local',
|
|
60
73
|
defaultLocale: 'en-us',
|
|
61
74
|
seo: {
|
|
62
75
|
title: 'Fixture Site',
|
|
@@ -1,26 +1,43 @@
|
|
|
1
1
|
import type { IslandMode, VaryDimension } from './render-mode.js';
|
|
2
|
+
import type { ImageSlotContract } from './image.js';
|
|
3
|
+
import type { z } from 'zod';
|
|
2
4
|
export interface ComponentRenderModes {
|
|
3
5
|
canStatic: boolean;
|
|
4
6
|
canSSR: boolean;
|
|
5
7
|
canClientIsland: boolean;
|
|
6
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* A CMS-registered component — sync or async (server components).
|
|
11
|
+
* Accepts content props derived from the Zod schema.
|
|
12
|
+
*/
|
|
13
|
+
type CmsComponentFn<TProps = any> = ((props: TProps) => import('react').ReactElement | null) | ((props: TProps) => Promise<import('react').ReactElement | null>);
|
|
14
|
+
/**
|
|
15
|
+
* Infers the props type from a schema.
|
|
16
|
+
* If TSchema is a Zod type, returns z.infer<TSchema>.
|
|
17
|
+
* Otherwise returns Record<string, unknown>.
|
|
18
|
+
*/
|
|
19
|
+
type InferContentProps<TSchema> = TSchema extends z.ZodType<infer T> ? T : Record<string, unknown>;
|
|
7
20
|
export interface ComponentDefinition<TSchema = unknown> {
|
|
8
21
|
name: string;
|
|
9
|
-
component
|
|
22
|
+
/** The React component that renders this CMS block. Typed against the schema. */
|
|
23
|
+
component?: CmsComponentFn<InferContentProps<TSchema>>;
|
|
10
24
|
schema: TSchema;
|
|
11
25
|
renderModes?: ComponentRenderModes;
|
|
12
26
|
render?: ComponentRenderModes;
|
|
13
27
|
defaultIslandMode?: IslandMode;
|
|
14
28
|
varyDimensions?: VaryDimension[];
|
|
15
29
|
vary?: VaryDimension[];
|
|
30
|
+
imageSlots?: Record<string, ImageSlotContract>;
|
|
16
31
|
}
|
|
17
32
|
export interface ComponentContract<TSchema = unknown> {
|
|
18
33
|
name: string;
|
|
19
|
-
component?:
|
|
34
|
+
component?: CmsComponentFn<InferContentProps<TSchema>>;
|
|
20
35
|
schema: TSchema;
|
|
21
36
|
renderModes: ComponentRenderModes;
|
|
22
37
|
render: ComponentRenderModes;
|
|
23
38
|
defaultIslandMode?: IslandMode;
|
|
24
39
|
varyDimensions?: VaryDimension[];
|
|
25
40
|
vary?: VaryDimension[];
|
|
41
|
+
imageSlots?: Record<string, ImageSlotContract>;
|
|
26
42
|
}
|
|
43
|
+
export type { CmsComponentFn, InferContentProps };
|
package/dist/types/config.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export interface BareMetalConfig {
|
|
|
12
12
|
rendererEntry: string;
|
|
13
13
|
capabilities: RenderCapabilities;
|
|
14
14
|
routes: RouteConfig;
|
|
15
|
+
dam?: DAMConfig;
|
|
15
16
|
externalDependencies?: ExternalDependency[];
|
|
16
17
|
rendererProtocol: 'http' | 'stdio-json';
|
|
17
18
|
}
|
|
@@ -26,3 +27,6 @@ export interface ExternalDependency {
|
|
|
26
27
|
description?: string;
|
|
27
28
|
cacheTTL?: number;
|
|
28
29
|
}
|
|
30
|
+
export interface DAMConfig {
|
|
31
|
+
autoApproveUploads?: boolean;
|
|
32
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export interface ImageSource {
|
|
3
|
+
src: string;
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
type: string;
|
|
7
|
+
}
|
|
8
|
+
export interface PictureSource {
|
|
9
|
+
media?: string;
|
|
10
|
+
type: string;
|
|
11
|
+
srcset: string;
|
|
12
|
+
sizes?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface GradialImage {
|
|
15
|
+
$type: 'gradial.image';
|
|
16
|
+
assetId: string;
|
|
17
|
+
versionId: string;
|
|
18
|
+
alt: string;
|
|
19
|
+
fallback: ImageSource;
|
|
20
|
+
sources: PictureSource[];
|
|
21
|
+
}
|
|
22
|
+
export interface ImageSlotContract {
|
|
23
|
+
outputs: SlotOutput[];
|
|
24
|
+
formats: string[];
|
|
25
|
+
sizes: string;
|
|
26
|
+
}
|
|
27
|
+
export interface SlotOutput {
|
|
28
|
+
aspectRatio: string;
|
|
29
|
+
widths: number[];
|
|
30
|
+
media?: string;
|
|
31
|
+
}
|
|
32
|
+
export declare const GradialImageSchema: z.ZodObject<{
|
|
33
|
+
$type: z.ZodLiteral<"gradial.image">;
|
|
34
|
+
assetId: z.ZodString;
|
|
35
|
+
versionId: z.ZodString;
|
|
36
|
+
alt: z.ZodString;
|
|
37
|
+
fallback: z.ZodObject<{
|
|
38
|
+
src: z.ZodString;
|
|
39
|
+
width: z.ZodNumber;
|
|
40
|
+
height: z.ZodNumber;
|
|
41
|
+
type: z.ZodString;
|
|
42
|
+
}, z.core.$strip>;
|
|
43
|
+
sources: z.ZodArray<z.ZodObject<{
|
|
44
|
+
media: z.ZodOptional<z.ZodString>;
|
|
45
|
+
type: z.ZodString;
|
|
46
|
+
srcset: z.ZodString;
|
|
47
|
+
sizes: z.ZodOptional<z.ZodString>;
|
|
48
|
+
}, z.core.$strip>>;
|
|
49
|
+
}, z.core.$strip>;
|
|
50
|
+
export type ImageHTMLAttributes = Record<string, string | number | boolean | undefined | null>;
|
|
51
|
+
export declare function renderImageHTML(image: GradialImage, attrs?: ImageHTMLAttributes): string;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const GradialImageSchema = z.object({
|
|
3
|
+
$type: z.literal('gradial.image'),
|
|
4
|
+
assetId: z.string().min(1),
|
|
5
|
+
versionId: z.string().min(1),
|
|
6
|
+
alt: z.string(),
|
|
7
|
+
fallback: z.object({
|
|
8
|
+
src: z.string().min(1),
|
|
9
|
+
width: z.number().int().nonnegative(),
|
|
10
|
+
height: z.number().int().nonnegative(),
|
|
11
|
+
type: z.string().min(1),
|
|
12
|
+
}),
|
|
13
|
+
sources: z.array(z.object({
|
|
14
|
+
media: z.string().optional(),
|
|
15
|
+
type: z.string().min(1),
|
|
16
|
+
srcset: z.string().min(1),
|
|
17
|
+
sizes: z.string().optional(),
|
|
18
|
+
})),
|
|
19
|
+
});
|
|
20
|
+
export function renderImageHTML(image, attrs = {}) {
|
|
21
|
+
const imgAttrs = renderAttrs({
|
|
22
|
+
...attrs,
|
|
23
|
+
src: image.fallback.src,
|
|
24
|
+
alt: image.alt,
|
|
25
|
+
width: image.fallback.width > 0 ? image.fallback.width : undefined,
|
|
26
|
+
height: image.fallback.height > 0 ? image.fallback.height : undefined,
|
|
27
|
+
});
|
|
28
|
+
const img = `<img${imgAttrs}>`;
|
|
29
|
+
if (!image.sources.length) {
|
|
30
|
+
return img;
|
|
31
|
+
}
|
|
32
|
+
const sources = image.sources.map((source) => `<source${renderAttrs({
|
|
33
|
+
media: source.media,
|
|
34
|
+
type: source.type,
|
|
35
|
+
srcset: source.srcset,
|
|
36
|
+
sizes: source.sizes,
|
|
37
|
+
})}>`).join('');
|
|
38
|
+
return `<picture>${sources}${img}</picture>`;
|
|
39
|
+
}
|
|
40
|
+
function renderAttrs(attrs) {
|
|
41
|
+
const rendered = Object.entries(attrs)
|
|
42
|
+
.filter(([, value]) => value !== undefined && value !== null && value !== false)
|
|
43
|
+
.map(([name, value]) => value === true ? escapeName(name) : `${escapeName(name)}="${escapeAttr(String(value))}"`);
|
|
44
|
+
if (!rendered.length) {
|
|
45
|
+
return '';
|
|
46
|
+
}
|
|
47
|
+
return ' ' + rendered.join(' ');
|
|
48
|
+
}
|
|
49
|
+
function escapeName(name) {
|
|
50
|
+
return name.replace(/[^A-Za-z0-9_:-]/g, '');
|
|
51
|
+
}
|
|
52
|
+
function escapeAttr(value) {
|
|
53
|
+
return value
|
|
54
|
+
.replace(/&/g, '&')
|
|
55
|
+
.replace(/"/g, '"')
|
|
56
|
+
.replace(/</g, '<')
|
|
57
|
+
.replace(/>/g, '>');
|
|
58
|
+
}
|
package/dist/types/index.d.ts
CHANGED
package/dist/types/index.js
CHANGED
package/dist/types/layout.d.ts
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
|
+
import type { FragmentRefNode } from './page.js';
|
|
1
2
|
export interface LayoutSlot {
|
|
2
3
|
name: string;
|
|
3
4
|
required: boolean;
|
|
4
5
|
}
|
|
6
|
+
/** Default content for non-required layout slots, keyed by slot name. */
|
|
7
|
+
export type LayoutDefaults = Record<string, FragmentRefNode[]>;
|
|
5
8
|
export interface LayoutDefinition {
|
|
6
9
|
name: string;
|
|
7
10
|
slots: LayoutSlot[];
|
|
11
|
+
/** Default fragment references for slots not provided by the page. */
|
|
12
|
+
defaults?: LayoutDefaults;
|
|
8
13
|
}
|
|
9
14
|
export interface LayoutContract {
|
|
10
15
|
name: string;
|
|
11
16
|
slots: LayoutSlot[];
|
|
17
|
+
defaults?: LayoutDefaults;
|
|
18
|
+
}
|
|
19
|
+
/** A compiled layout file as stored on disk. */
|
|
20
|
+
export interface CompiledLayout {
|
|
21
|
+
name: string;
|
|
22
|
+
slots: LayoutSlot[];
|
|
23
|
+
defaults: LayoutDefaults;
|
|
12
24
|
}
|
package/package.json
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gradial/aci",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
7
|
"files": [
|
|
8
8
|
"dist",
|
|
9
|
+
"bin/aci.js",
|
|
9
10
|
"src/cli/*.mjs",
|
|
10
11
|
"README.md"
|
|
11
12
|
],
|
|
13
|
+
"bin": {
|
|
14
|
+
"aci": "./bin/aci.js"
|
|
15
|
+
},
|
|
12
16
|
"publishConfig": {
|
|
13
|
-
"access": "
|
|
17
|
+
"access": "public"
|
|
14
18
|
},
|
|
15
19
|
"exports": {
|
|
16
20
|
".": {
|
|
@@ -41,14 +45,26 @@
|
|
|
41
45
|
"types": "./dist/providers/file.d.ts",
|
|
42
46
|
"import": "./dist/providers/file.js"
|
|
43
47
|
},
|
|
48
|
+
"./providers/s3": {
|
|
49
|
+
"types": "./dist/providers/s3.d.ts",
|
|
50
|
+
"import": "./dist/providers/s3.js"
|
|
51
|
+
},
|
|
44
52
|
"./dev": {
|
|
45
53
|
"types": "./dist/dev/index.d.ts",
|
|
46
54
|
"import": "./dist/dev/index.js"
|
|
47
55
|
},
|
|
56
|
+
"./assets": {
|
|
57
|
+
"types": "./dist/assets/index.d.ts",
|
|
58
|
+
"import": "./dist/assets/index.js"
|
|
59
|
+
},
|
|
48
60
|
"./astro": {
|
|
49
61
|
"types": "./dist/astro/index.d.ts",
|
|
50
62
|
"import": "./dist/astro/index.js"
|
|
51
63
|
},
|
|
64
|
+
"./next": {
|
|
65
|
+
"types": "./dist/next/index.d.ts",
|
|
66
|
+
"import": "./dist/next/index.js"
|
|
67
|
+
},
|
|
52
68
|
"./next/server": {
|
|
53
69
|
"types": "./dist/next/server.d.ts",
|
|
54
70
|
"import": "./dist/next/server.js",
|
|
@@ -72,6 +88,7 @@
|
|
|
72
88
|
"import": "./dist/testing/index.js"
|
|
73
89
|
},
|
|
74
90
|
"./cli/compile-registry": "./src/cli/compile-registry.mjs",
|
|
91
|
+
"./cli/validate-content": "./src/cli/validate-content.mjs",
|
|
75
92
|
"./cli/verify-renderer": "./src/cli/verify-renderer.mjs"
|
|
76
93
|
},
|
|
77
94
|
"scripts": {
|
|
@@ -99,6 +116,13 @@
|
|
|
99
116
|
"optional": true
|
|
100
117
|
}
|
|
101
118
|
},
|
|
119
|
+
"optionalDependencies": {
|
|
120
|
+
"@gradial/cli-darwin-arm64": "0.1.0",
|
|
121
|
+
"@gradial/cli-darwin-x64": "0.1.0",
|
|
122
|
+
"@gradial/cli-linux-x64": "0.1.0",
|
|
123
|
+
"@gradial/cli-linux-arm64": "0.1.0",
|
|
124
|
+
"@gradial/cli-win32-x64": "0.1.0"
|
|
125
|
+
},
|
|
102
126
|
"devDependencies": {
|
|
103
127
|
"@vercel/edge-config": "^1.4.3",
|
|
104
128
|
"@types/node": "^24.10.1",
|