@gradial/aci 0.1.20-preview.2 → 0.1.20-preview.3
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 +8 -10
- package/bin/aci +0 -0
- package/dist/astro/index.d.ts +2 -0
- package/dist/astro/index.js +4 -0
- package/dist/content/index.d.ts +1 -0
- package/dist/content/index.js +1 -0
- package/dist/content/provider.d.ts +19 -0
- package/dist/content/provider.js +7 -0
- package/dist/content/resolve-slots.d.ts +9 -0
- package/dist/content/resolve-slots.js +24 -0
- package/dist/content/types.d.ts +1 -1
- package/dist/content/validation.js +0 -1
- package/dist/define-component.js +0 -6
- package/dist/define-layout.d.ts +2 -0
- package/dist/define-layout.js +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/next/asset-route.d.ts +2 -0
- package/dist/next/asset-route.js +10 -2
- package/dist/next/config.js +1 -1
- package/dist/next/middleware.d.ts +6 -2
- package/dist/next/middleware.js +7 -2
- package/dist/next/page.d.ts +16 -15
- package/dist/next/page.js +27 -9
- package/dist/next/render.d.ts +5 -0
- package/dist/next/render.js +15 -0
- package/dist/next/root-layout.d.ts +4 -0
- package/dist/next/root-layout.js +5 -0
- package/dist/next/server.d.ts +3 -2
- package/dist/next/server.js +12 -2
- package/dist/providers/file.d.ts +2 -0
- package/dist/providers/file.js +61 -1
- package/dist/providers/s3.d.ts +1 -0
- package/dist/providers/s3.js +14 -1
- package/dist/registry.d.ts +15 -0
- package/dist/registry.js +10 -0
- package/dist/sveltekit/index.d.ts +2 -0
- package/dist/sveltekit/index.js +4 -0
- package/dist/testing/index.d.ts +3 -0
- package/dist/testing/index.js +15 -2
- package/dist/types/component.d.ts +0 -9
- package/dist/types/config.d.ts +0 -1
- package/dist/types/page.d.ts +1 -2
- package/dist/types/render-mode.d.ts +0 -1
- package/package.json +19 -13
- package/src/cli/compile-registry.mjs +132 -12
- package/src/types/component.ts +0 -10
- package/src/types/config.ts +0 -1
- package/src/types/page.ts +1 -2
- package/src/types/render-mode.ts +0 -8
- package/dist/content/cache.test.d.ts +0 -1
- package/dist/content/cache.test.js +0 -86
- package/dist/next/preview-banner.d.ts +0 -4
- package/dist/next/preview-banner.js +0 -51
- package/dist/next/preview.d.ts +0 -7
- package/dist/next/preview.js +0 -37
- package/src/cli/verify-renderer.mjs +0 -73
package/dist/providers/file.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { FragmentNotFoundError, PageNotFoundError } from '../content/provider.js';
|
|
3
|
+
import { FragmentNotFoundError, LayoutNotFoundError, PageNotFoundError } from '../content/provider.js';
|
|
4
4
|
import { normalizeRoute } from '../content/routes.js';
|
|
5
5
|
export class FileContentProvider {
|
|
6
6
|
root;
|
|
@@ -46,12 +46,72 @@ export class FileContentProvider {
|
|
|
46
46
|
throw new FragmentNotFoundError(id, error);
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
+
async getLayout(name) {
|
|
50
|
+
const manifest = await this.manifest();
|
|
51
|
+
const layouts = manifest.layouts;
|
|
52
|
+
if (!layouts || !layouts[name]) {
|
|
53
|
+
throw new LayoutNotFoundError(name);
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
return await this.readJSON(`layouts/${name}.json`);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
throw new LayoutNotFoundError(name, error);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
49
62
|
async manifest() {
|
|
50
63
|
this.#manifest ??= this.readJSON('manifest.json');
|
|
51
64
|
return await this.#manifest;
|
|
52
65
|
}
|
|
66
|
+
async fetchRaw(key) {
|
|
67
|
+
const relativeKey = key.replace(/^\/+/, '');
|
|
68
|
+
const filePath = path.resolve(this.root, relativeKey);
|
|
69
|
+
const rel = path.relative(this.root, filePath);
|
|
70
|
+
if (rel === '' || rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
71
|
+
return new Response('not found', { status: 404 });
|
|
72
|
+
}
|
|
73
|
+
let body;
|
|
74
|
+
try {
|
|
75
|
+
body = await fs.readFile(filePath);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return new Response('not found', { status: 404 });
|
|
79
|
+
}
|
|
80
|
+
return new Response(new Uint8Array(body), {
|
|
81
|
+
status: 200,
|
|
82
|
+
headers: {
|
|
83
|
+
'content-type': contentType(filePath),
|
|
84
|
+
'cache-control': 'public, max-age=31536000, immutable',
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
53
88
|
async readJSON(key) {
|
|
54
89
|
const raw = await fs.readFile(path.join(this.root, key), 'utf8');
|
|
55
90
|
return JSON.parse(raw);
|
|
56
91
|
}
|
|
57
92
|
}
|
|
93
|
+
function contentType(filePath) {
|
|
94
|
+
switch (path.extname(filePath).toLowerCase()) {
|
|
95
|
+
case '.webp':
|
|
96
|
+
return 'image/webp';
|
|
97
|
+
case '.svg':
|
|
98
|
+
return 'image/svg+xml';
|
|
99
|
+
case '.jpg':
|
|
100
|
+
case '.jpeg':
|
|
101
|
+
return 'image/jpeg';
|
|
102
|
+
case '.png':
|
|
103
|
+
return 'image/png';
|
|
104
|
+
case '.avif':
|
|
105
|
+
return 'image/avif';
|
|
106
|
+
case '.gif':
|
|
107
|
+
return 'image/gif';
|
|
108
|
+
case '.css':
|
|
109
|
+
return 'text/css';
|
|
110
|
+
case '.js':
|
|
111
|
+
return 'application/javascript';
|
|
112
|
+
case '.json':
|
|
113
|
+
return 'application/json';
|
|
114
|
+
default:
|
|
115
|
+
return 'application/octet-stream';
|
|
116
|
+
}
|
|
117
|
+
}
|
package/dist/providers/s3.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export declare class S3ContentProvider implements ContentProvider {
|
|
|
17
17
|
getSiteConfig<T = unknown>(): Promise<T>;
|
|
18
18
|
getPage<T = unknown>(route: string): Promise<T>;
|
|
19
19
|
getFragment<T = unknown>(id: string): Promise<T>;
|
|
20
|
+
getLayout<T = unknown>(name: string): Promise<T>;
|
|
20
21
|
manifest(): Promise<CompiledManifest>;
|
|
21
22
|
fetchRaw(key: string): Promise<Response>;
|
|
22
23
|
private getJSON;
|
package/dist/providers/s3.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
|
-
import { FragmentNotFoundError, PageNotFoundError } from '../content/provider.js';
|
|
2
|
+
import { FragmentNotFoundError, LayoutNotFoundError, PageNotFoundError } from '../content/provider.js';
|
|
3
3
|
import { normalizeRoute } from '../content/routes.js';
|
|
4
4
|
// ---------------------------------------------------------------------------
|
|
5
5
|
// S3 content provider — reads compiled content files from S3
|
|
@@ -45,6 +45,19 @@ export class S3ContentProvider {
|
|
|
45
45
|
throw new FragmentNotFoundError(id, error);
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
|
+
async getLayout(name) {
|
|
49
|
+
const manifest = await this.manifest();
|
|
50
|
+
const layouts = manifest.layouts;
|
|
51
|
+
if (!layouts || !layouts[name]) {
|
|
52
|
+
throw new LayoutNotFoundError(name);
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
return await this.getJSON(`layouts/${name}.json`);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
throw new LayoutNotFoundError(name, error);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
48
61
|
async manifest() {
|
|
49
62
|
this.#manifest ??= this.getJSON('manifest.json');
|
|
50
63
|
return await this.#manifest;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ComponentContract, InferComponentProps } from './types/component.js';
|
|
2
|
+
export type AnyComponent = (abstract new (...args: any[]) => any) | ((props: any) => any) | Record<string, any>;
|
|
3
|
+
export interface RegistryEntry {
|
|
4
|
+
name: string;
|
|
5
|
+
component: AnyComponent;
|
|
6
|
+
contract: ComponentContract;
|
|
7
|
+
}
|
|
8
|
+
export type Registry = readonly RegistryEntry[];
|
|
9
|
+
type ValidatedEntry<T> = T extends readonly [infer C extends ComponentContract, infer Comp] ? Comp extends (props: InferComponentProps<C>) => any ? T : Comp extends (props: InferComponentProps<C> & infer _) => any ? T : Comp extends new (props: InferComponentProps<C>, ...args: any[]) => any ? T : Comp extends Record<string, any> ? T : never : never;
|
|
10
|
+
type ValidatedEntries<T extends readonly (readonly [ComponentContract, AnyComponent])[]> = {
|
|
11
|
+
[K in keyof T]: ValidatedEntry<T[K]>;
|
|
12
|
+
};
|
|
13
|
+
export declare function createRegistry<const T extends readonly (readonly [ComponentContract, AnyComponent])[]>(entries: ValidatedEntries<T> & T): Registry;
|
|
14
|
+
export declare function registryLookup(registry: Registry, name: string): AnyComponent | undefined;
|
|
15
|
+
export {};
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function createRegistry(entries) {
|
|
2
|
+
return entries.map(([contract, component]) => ({
|
|
3
|
+
name: contract.name,
|
|
4
|
+
component,
|
|
5
|
+
contract,
|
|
6
|
+
}));
|
|
7
|
+
}
|
|
8
|
+
export function registryLookup(registry, name) {
|
|
9
|
+
return registry.find((r) => r.name === name)?.component;
|
|
10
|
+
}
|
|
@@ -19,5 +19,7 @@ export declare function gradialEntries(): Promise<string[]>;
|
|
|
19
19
|
export declare function getPageRuntimeRenderInput(event: {
|
|
20
20
|
request: Request;
|
|
21
21
|
}): RenderInput | null;
|
|
22
|
+
export { createRegistry, registryLookup } from '../registry.js';
|
|
23
|
+
export type { Registry, RegistryEntry, AnyComponent } from '../registry.js';
|
|
22
24
|
export { getPreviewMode, createPreviewBannerHTML, createPreviewCookies, type PreviewContext, type PreviewCookie, } from './preview.js';
|
|
23
25
|
export declare function withGradialAci(options?: GradialContentWatchOptions): VitePluginLike;
|
package/dist/sveltekit/index.js
CHANGED
|
@@ -32,6 +32,10 @@ export function getPageRuntimeRenderInput(event) {
|
|
|
32
32
|
return getPendingRenderInputFromHeaders(event.request.headers);
|
|
33
33
|
}
|
|
34
34
|
// ---------------------------------------------------------------------------
|
|
35
|
+
// Registry helpers
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
export { createRegistry, registryLookup } from '../registry.js';
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
35
39
|
// Preview support
|
|
36
40
|
// ---------------------------------------------------------------------------
|
|
37
41
|
export { getPreviewMode, createPreviewBannerHTML, createPreviewCookies, } from './preview.js';
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -4,15 +4,18 @@ export interface FixtureContentProviderOptions {
|
|
|
4
4
|
siteConfig?: KernelSiteConfig;
|
|
5
5
|
pages?: Record<string, KernelPage>;
|
|
6
6
|
fragments?: Record<string, unknown>;
|
|
7
|
+
layouts?: Record<string, unknown>;
|
|
7
8
|
}
|
|
8
9
|
export declare class FixtureContentProvider implements ContentProvider {
|
|
9
10
|
private siteConfig;
|
|
10
11
|
private pages;
|
|
11
12
|
private fragments;
|
|
13
|
+
private layouts;
|
|
12
14
|
constructor(options?: FixtureContentProviderOptions);
|
|
13
15
|
getSiteConfig<T = unknown>(): Promise<T>;
|
|
14
16
|
getPage<T = unknown>(route: string): Promise<T>;
|
|
15
17
|
getFragment<T = unknown>(id: string): Promise<T>;
|
|
18
|
+
getLayout<T = unknown>(name: string): Promise<T>;
|
|
16
19
|
listRoutes(): Promise<RouteEntry[]>;
|
|
17
20
|
loadRenderInput(route?: string, options?: RenderInputOptions): Promise<RenderInput>;
|
|
18
21
|
resolveRouteMetadata(route?: string): Promise<KernelRouteMetadata>;
|
package/dist/testing/index.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { PageNotFoundError, FragmentNotFoundError, loadRenderInput, routeMetadataForContent, } from '../content/provider.js';
|
|
1
|
+
import { PageNotFoundError, FragmentNotFoundError, LayoutNotFoundError, 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
6
|
fragments;
|
|
7
|
+
layouts;
|
|
7
8
|
constructor(options = {}) {
|
|
8
9
|
this.siteConfig = cloneFixture(options.siteConfig ?? defaultFixtureSiteConfig());
|
|
9
10
|
this.pages = new Map();
|
|
10
11
|
this.fragments = new Map();
|
|
12
|
+
this.layouts = new Map();
|
|
11
13
|
const inputPages = options.pages ?? { '/': defaultFixturePage() };
|
|
12
14
|
for (const [route, page] of Object.entries(inputPages)) {
|
|
13
15
|
this.pages.set(normalizeRoute(route), cloneFixture(page));
|
|
@@ -17,6 +19,11 @@ export class FixtureContentProvider {
|
|
|
17
19
|
this.fragments.set(id, cloneFixture(fragment));
|
|
18
20
|
}
|
|
19
21
|
}
|
|
22
|
+
if (options.layouts) {
|
|
23
|
+
for (const [name, layout] of Object.entries(options.layouts)) {
|
|
24
|
+
this.layouts.set(name, cloneFixture(layout));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
20
27
|
}
|
|
21
28
|
async getSiteConfig() {
|
|
22
29
|
return cloneFixture(this.siteConfig);
|
|
@@ -35,6 +42,13 @@ export class FixtureContentProvider {
|
|
|
35
42
|
}
|
|
36
43
|
return cloneFixture(fragment);
|
|
37
44
|
}
|
|
45
|
+
async getLayout(name) {
|
|
46
|
+
const layout = this.layouts.get(name);
|
|
47
|
+
if (layout === undefined) {
|
|
48
|
+
throw new LayoutNotFoundError(name);
|
|
49
|
+
}
|
|
50
|
+
return cloneFixture(layout);
|
|
51
|
+
}
|
|
38
52
|
async listRoutes() {
|
|
39
53
|
return [...this.pages.entries()]
|
|
40
54
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
@@ -84,7 +98,6 @@ export function defaultFixturePage() {
|
|
|
84
98
|
$type: 'page',
|
|
85
99
|
status: 'published',
|
|
86
100
|
layout: 'marketing',
|
|
87
|
-
renderMode: 'static',
|
|
88
101
|
metadata: {
|
|
89
102
|
title: 'Fixture Home',
|
|
90
103
|
description: 'Fixture home page.',
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import type { IslandMode, VaryDimension } from './render-mode.js';
|
|
2
2
|
import type { ImageSlotContract } from './image.js';
|
|
3
3
|
import type { z } from 'zod';
|
|
4
|
-
export interface ComponentRenderModes {
|
|
5
|
-
canStatic: boolean;
|
|
6
|
-
canSSR: boolean;
|
|
7
|
-
canClientIsland: boolean;
|
|
8
|
-
}
|
|
9
4
|
/**
|
|
10
5
|
* A CMS-registered component — sync or async (server components).
|
|
11
6
|
* Accepts content props derived from the Zod schema.
|
|
@@ -26,8 +21,6 @@ export interface ComponentDefinition<TSchema = unknown> {
|
|
|
26
21
|
*/
|
|
27
22
|
component?: CmsComponentFn<any>;
|
|
28
23
|
schema: TSchema;
|
|
29
|
-
renderModes?: ComponentRenderModes;
|
|
30
|
-
render?: ComponentRenderModes;
|
|
31
24
|
defaultIslandMode?: IslandMode;
|
|
32
25
|
varyDimensions?: VaryDimension[];
|
|
33
26
|
vary?: VaryDimension[];
|
|
@@ -37,8 +30,6 @@ export interface ComponentContract<TSchema = unknown> {
|
|
|
37
30
|
name: string;
|
|
38
31
|
component?: CmsComponentFn<any>;
|
|
39
32
|
schema: TSchema;
|
|
40
|
-
renderModes: ComponentRenderModes;
|
|
41
|
-
render: ComponentRenderModes;
|
|
42
33
|
defaultIslandMode?: IslandMode;
|
|
43
34
|
varyDimensions?: VaryDimension[];
|
|
44
35
|
vary?: VaryDimension[];
|
package/dist/types/config.d.ts
CHANGED
package/dist/types/page.d.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { VaryDimension } from './render-mode.js';
|
|
2
2
|
export interface PageDocument {
|
|
3
3
|
path: string;
|
|
4
4
|
layout: string;
|
|
5
5
|
locale: string;
|
|
6
6
|
metadata: PageMetadata;
|
|
7
7
|
regions: Record<string, RegionNode>;
|
|
8
|
-
renderMode: RenderMode;
|
|
9
8
|
}
|
|
10
9
|
export interface PageMetadata {
|
|
11
10
|
title: string;
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
export type RenderMode = 'static' | 'static-with-fragments' | 'prerender-on-demand' | 'ssr-page' | 'ssr-island' | 'client-island';
|
|
2
1
|
export type IslandMode = 'ssr' | 'client';
|
|
3
2
|
export type VaryDimension = 'geo:country' | 'geo:region' | 'geo:city' | `cookie:${string}` | `header:${string}` | 'locale' | `query:${string}`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gradial/aci",
|
|
3
|
-
"version": "0.1.20-preview.
|
|
3
|
+
"version": "0.1.20-preview.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -43,10 +43,6 @@
|
|
|
43
43
|
"types": "./dist/content/validation.d.ts",
|
|
44
44
|
"import": "./dist/content/validation.js"
|
|
45
45
|
},
|
|
46
|
-
"./content/cache": {
|
|
47
|
-
"types": "./dist/content/cache.d.ts",
|
|
48
|
-
"import": "./dist/content/cache.js"
|
|
49
|
-
},
|
|
50
46
|
"./content/tailwind-validator": {
|
|
51
47
|
"types": "./dist/content/tailwind-validator.d.ts",
|
|
52
48
|
"import": "./dist/content/tailwind-validator.js"
|
|
@@ -94,6 +90,16 @@
|
|
|
94
90
|
"import": "./dist/next/page.js",
|
|
95
91
|
"default": "./dist/next/page.js"
|
|
96
92
|
},
|
|
93
|
+
"./next/root-layout": {
|
|
94
|
+
"types": "./dist/next/root-layout.d.ts",
|
|
95
|
+
"import": "./dist/next/root-layout.js",
|
|
96
|
+
"default": "./dist/next/root-layout.js"
|
|
97
|
+
},
|
|
98
|
+
"./next/render": {
|
|
99
|
+
"types": "./dist/next/render.d.ts",
|
|
100
|
+
"import": "./dist/next/render.js",
|
|
101
|
+
"default": "./dist/next/render.js"
|
|
102
|
+
},
|
|
97
103
|
"./next/server": {
|
|
98
104
|
"types": "./dist/next/server.d.ts",
|
|
99
105
|
"import": "./dist/next/server.js",
|
|
@@ -127,19 +133,16 @@
|
|
|
127
133
|
"scripts": {
|
|
128
134
|
"build": "tsc -p tsconfig.json",
|
|
129
135
|
"build:cli": "cd ../.. && ./scripts/build-cli.sh",
|
|
130
|
-
"compile-registry": "tsx src/cli/compile-registry.mjs"
|
|
131
|
-
"prepack": "npm run build"
|
|
132
|
-
},
|
|
133
|
-
"overrides": {
|
|
134
|
-
"postcss": "^8.5.10"
|
|
136
|
+
"compile-registry": "tsx src/cli/compile-registry.mjs"
|
|
135
137
|
},
|
|
136
138
|
"dependencies": {
|
|
137
139
|
"ws": "^8.18.0"
|
|
138
140
|
},
|
|
139
141
|
"peerDependencies": {
|
|
140
142
|
"@vercel/edge-config": "^1.4.3",
|
|
141
|
-
"next": "^15.
|
|
142
|
-
"react": "^19.0.0",
|
|
143
|
+
"next": "^15.0.0",
|
|
144
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
145
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
143
146
|
"zod": "^4.0.0"
|
|
144
147
|
},
|
|
145
148
|
"peerDependenciesMeta": {
|
|
@@ -151,6 +154,9 @@
|
|
|
151
154
|
},
|
|
152
155
|
"react": {
|
|
153
156
|
"optional": true
|
|
157
|
+
},
|
|
158
|
+
"react-dom": {
|
|
159
|
+
"optional": true
|
|
154
160
|
}
|
|
155
161
|
},
|
|
156
162
|
"devDependencies": {
|
|
@@ -165,4 +171,4 @@
|
|
|
165
171
|
"ws": "^8.18.0",
|
|
166
172
|
"zod": "^4.0.0"
|
|
167
173
|
}
|
|
168
|
-
}
|
|
174
|
+
}
|
|
@@ -52,22 +52,22 @@ async function compileRegistry(options) {
|
|
|
52
52
|
const schemaPath = path.join(outDir, schemaFile);
|
|
53
53
|
await writeFile(schemaPath, JSON.stringify(schema, null, 2) + '\n');
|
|
54
54
|
schemaFiles.push(schemaPath);
|
|
55
|
-
const renderModes = component.renderModes ?? component.render;
|
|
56
55
|
const varyDimensions = component.varyDimensions ?? component.vary;
|
|
57
56
|
const imageSlots = normalizeImageSlots(component.imageSlots);
|
|
57
|
+
const renderModes = component.renderModes ?? component.render;
|
|
58
58
|
componentEntries.push({
|
|
59
59
|
name: component.name,
|
|
60
60
|
schemaFile,
|
|
61
|
-
render: {
|
|
61
|
+
render: renderModes ? {
|
|
62
62
|
static: Boolean(renderModes.canStatic ?? renderModes.static),
|
|
63
63
|
ssr: Boolean(renderModes.canSSR ?? renderModes.ssr),
|
|
64
64
|
clientIsland: Boolean(renderModes.canClientIsland ?? renderModes.clientIsland),
|
|
65
|
-
},
|
|
66
|
-
renderModes: {
|
|
65
|
+
} : { static: true, ssr: true, clientIsland: false },
|
|
66
|
+
renderModes: renderModes ? {
|
|
67
67
|
canStatic: Boolean(renderModes.canStatic ?? renderModes.static),
|
|
68
68
|
canSSR: Boolean(renderModes.canSSR ?? renderModes.ssr),
|
|
69
69
|
canClientIsland: Boolean(renderModes.canClientIsland ?? renderModes.clientIsland),
|
|
70
|
-
},
|
|
70
|
+
} : { canStatic: true, canSSR: true, canClientIsland: false },
|
|
71
71
|
...(component.defaultIslandMode ? { defaultIslandMode: component.defaultIslandMode } : {}),
|
|
72
72
|
...(varyDimensions?.length ? { vary: varyDimensions, varyDimensions } : {}),
|
|
73
73
|
...(Object.keys(imageSlots).length ? { imageSlots } : {}),
|
|
@@ -117,6 +117,10 @@ async function compileRegistry(options) {
|
|
|
117
117
|
};
|
|
118
118
|
const registryPath = path.join(outDir, 'registry.json');
|
|
119
119
|
await writeFile(registryPath, JSON.stringify(registry, null, 2) + '\n');
|
|
120
|
+
|
|
121
|
+
const pageSchemaPath = await generatePageSchema(schemaDir, componentEntries, outDir);
|
|
122
|
+
schemaFiles.push(pageSchemaPath);
|
|
123
|
+
|
|
120
124
|
return { registry: registryPath, schemas: schemaFiles };
|
|
121
125
|
}
|
|
122
126
|
|
|
@@ -150,12 +154,6 @@ function validateComponent(component, index, seen) {
|
|
|
150
154
|
if (!component.schema) throw new Error(`components[${index}].schema is required`);
|
|
151
155
|
validateZodSubset(component.schema, `components[${index}].schema`);
|
|
152
156
|
validateImageSlotSchemaMatch(component, index);
|
|
153
|
-
const renderModes = component.renderModes ?? component.render;
|
|
154
|
-
if (!renderModes) throw new Error(`components[${index}].renderModes is required`);
|
|
155
|
-
const enabled = Boolean(renderModes.canStatic ?? renderModes.static)
|
|
156
|
-
|| Boolean(renderModes.canSSR ?? renderModes.ssr)
|
|
157
|
-
|| Boolean(renderModes.canClientIsland ?? renderModes.clientIsland);
|
|
158
|
-
if (!enabled) throw new Error(`components[${index}].renderModes must enable at least one mode`);
|
|
159
157
|
}
|
|
160
158
|
|
|
161
159
|
function validateImageSlotSchemaMatch(component, index) {
|
|
@@ -349,7 +347,11 @@ function validateLayout(layout, index, seen) {
|
|
|
349
347
|
seenSlots.add(slot.name);
|
|
350
348
|
slots.push({ name: slot.name, required: Boolean(slot.required) });
|
|
351
349
|
}
|
|
352
|
-
|
|
350
|
+
const entry = { name: layout.name, slots };
|
|
351
|
+
if (layout.defaults && typeof layout.defaults === 'object') {
|
|
352
|
+
entry.defaults = layout.defaults;
|
|
353
|
+
}
|
|
354
|
+
return entry;
|
|
353
355
|
}
|
|
354
356
|
|
|
355
357
|
function compileSchema(schema, name) {
|
|
@@ -368,6 +370,124 @@ function compileSchema(schema, name) {
|
|
|
368
370
|
};
|
|
369
371
|
}
|
|
370
372
|
|
|
373
|
+
async function generatePageSchema(schemaDir, componentEntries, outDir) {
|
|
374
|
+
const { readFile } = await import('node:fs/promises');
|
|
375
|
+
const defs = {
|
|
376
|
+
baseBlock: {
|
|
377
|
+
type: 'object',
|
|
378
|
+
required: ['id', 'component', 'props'],
|
|
379
|
+
additionalProperties: true,
|
|
380
|
+
properties: {
|
|
381
|
+
id: { type: 'string', minLength: 1 },
|
|
382
|
+
component: { type: 'string' },
|
|
383
|
+
props: { type: 'object' },
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
damAssetRef: {
|
|
387
|
+
type: 'object',
|
|
388
|
+
required: ['$type', 'assetId'],
|
|
389
|
+
additionalProperties: true,
|
|
390
|
+
properties: {
|
|
391
|
+
$type: { const: 'dam.assetRef' },
|
|
392
|
+
assetId: { type: 'string', minLength: 1 },
|
|
393
|
+
version: { type: 'string', minLength: 1 },
|
|
394
|
+
alt: { type: 'string' },
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
};
|
|
398
|
+
const oneOf = [];
|
|
399
|
+
for (const entry of componentEntries) {
|
|
400
|
+
const schemaPath = path.join(outDir, entry.schemaFile);
|
|
401
|
+
const raw = await readFile(schemaPath, 'utf-8');
|
|
402
|
+
const componentSchema = JSON.parse(raw);
|
|
403
|
+
const { $schema: _s, title: _t, ...propsSchema } = componentSchema;
|
|
404
|
+
replaceGradialImagesWithDamAssetRef(propsSchema, defs.damAssetRef);
|
|
405
|
+
const defName = `${entry.name}Block`;
|
|
406
|
+
defs[defName] = {
|
|
407
|
+
allOf: [
|
|
408
|
+
{ $ref: '#/$defs/baseBlock' },
|
|
409
|
+
{
|
|
410
|
+
properties: {
|
|
411
|
+
component: { const: entry.name },
|
|
412
|
+
props: propsSchema,
|
|
413
|
+
},
|
|
414
|
+
},
|
|
415
|
+
],
|
|
416
|
+
};
|
|
417
|
+
oneOf.push({ $ref: `#/$defs/${defName}` });
|
|
418
|
+
}
|
|
419
|
+
const pageSchema = {
|
|
420
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
421
|
+
$id: 'https://baremetal.local/schemas/page.schema.json',
|
|
422
|
+
title: 'bare-metal Page',
|
|
423
|
+
type: 'object',
|
|
424
|
+
required: ['$type', 'id', 'status', 'layout', 'metadata', 'regions'],
|
|
425
|
+
additionalProperties: true,
|
|
426
|
+
properties: {
|
|
427
|
+
$type: { const: 'page' },
|
|
428
|
+
id: { type: 'string', minLength: 1 },
|
|
429
|
+
status: { type: 'string', minLength: 1 },
|
|
430
|
+
layout: { type: 'string', minLength: 1 },
|
|
431
|
+
metadata: {
|
|
432
|
+
type: 'object',
|
|
433
|
+
required: ['title'],
|
|
434
|
+
additionalProperties: true,
|
|
435
|
+
properties: {
|
|
436
|
+
title: { type: 'string', minLength: 1 },
|
|
437
|
+
description: { type: 'string' },
|
|
438
|
+
canonical: { type: 'string' },
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
regions: {
|
|
442
|
+
type: 'object',
|
|
443
|
+
additionalProperties: {
|
|
444
|
+
type: 'array',
|
|
445
|
+
items: { oneOf },
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
},
|
|
449
|
+
$defs: defs,
|
|
450
|
+
};
|
|
451
|
+
const pageSchemaPath = path.join(schemaDir, 'page.schema.json');
|
|
452
|
+
await writeFile(pageSchemaPath, JSON.stringify(pageSchema, null, 2) + '\n');
|
|
453
|
+
return pageSchemaPath;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function replaceGradialImagesWithDamAssetRef(schema, damAssetRef) {
|
|
457
|
+
if (!schema || typeof schema !== 'object') return schema;
|
|
458
|
+
if (schema.$ref) return;
|
|
459
|
+
if (Array.isArray(schema.anyOf)) {
|
|
460
|
+
for (const s of schema.anyOf) replaceGradialImagesWithDamAssetRef(s, damAssetRef);
|
|
461
|
+
}
|
|
462
|
+
if (Array.isArray(schema.allOf)) {
|
|
463
|
+
for (const s of schema.allOf) replaceGradialImagesWithDamAssetRef(s, damAssetRef);
|
|
464
|
+
}
|
|
465
|
+
if (Array.isArray(schema.oneOf)) {
|
|
466
|
+
for (const s of schema.oneOf) replaceGradialImagesWithDamAssetRef(s, damAssetRef);
|
|
467
|
+
}
|
|
468
|
+
if (schema.properties) {
|
|
469
|
+
for (const [key, value] of Object.entries(schema.properties)) {
|
|
470
|
+
if (isJsonGradialImage(value)) {
|
|
471
|
+
schema.properties[key] = { $ref: '#/$defs/damAssetRef' };
|
|
472
|
+
} else {
|
|
473
|
+
replaceGradialImagesWithDamAssetRef(value, damAssetRef);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (schema.items) {
|
|
478
|
+
if (isJsonGradialImage(schema.items)) {
|
|
479
|
+
schema.items = { $ref: '#/$defs/damAssetRef' };
|
|
480
|
+
} else {
|
|
481
|
+
replaceGradialImagesWithDamAssetRef(schema.items, damAssetRef);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function isJsonGradialImage(node) {
|
|
487
|
+
return node?.type === 'object'
|
|
488
|
+
&& node?.properties?.$type?.const === 'gradial.image';
|
|
489
|
+
}
|
|
490
|
+
|
|
371
491
|
function validateZodSubset(schema, pathLabel, seen = new Set()) {
|
|
372
492
|
if (seen.has(schema)) return;
|
|
373
493
|
seen.add(schema);
|
package/src/types/component.ts
CHANGED
|
@@ -2,12 +2,6 @@ import type { IslandMode, VaryDimension } from './render-mode.js';
|
|
|
2
2
|
import type { ImageSlotContract } from './image.js';
|
|
3
3
|
import type { z } from 'zod';
|
|
4
4
|
|
|
5
|
-
export interface ComponentRenderModes {
|
|
6
|
-
canStatic: boolean;
|
|
7
|
-
canSSR: boolean;
|
|
8
|
-
canClientIsland: boolean;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
5
|
/**
|
|
12
6
|
* A CMS-registered component — sync or async (server components).
|
|
13
7
|
* Accepts content props derived from the Zod schema.
|
|
@@ -32,8 +26,6 @@ export interface ComponentDefinition<TSchema = unknown> {
|
|
|
32
26
|
*/
|
|
33
27
|
component?: CmsComponentFn<any>;
|
|
34
28
|
schema: TSchema;
|
|
35
|
-
renderModes?: ComponentRenderModes;
|
|
36
|
-
render?: ComponentRenderModes;
|
|
37
29
|
defaultIslandMode?: IslandMode;
|
|
38
30
|
varyDimensions?: VaryDimension[];
|
|
39
31
|
vary?: VaryDimension[];
|
|
@@ -44,8 +36,6 @@ export interface ComponentContract<TSchema = unknown> {
|
|
|
44
36
|
name: string;
|
|
45
37
|
component?: CmsComponentFn<any>;
|
|
46
38
|
schema: TSchema;
|
|
47
|
-
renderModes: ComponentRenderModes;
|
|
48
|
-
render: ComponentRenderModes;
|
|
49
39
|
defaultIslandMode?: IslandMode;
|
|
50
40
|
varyDimensions?: VaryDimension[];
|
|
51
41
|
vary?: VaryDimension[];
|
package/src/types/config.ts
CHANGED
package/src/types/page.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { VaryDimension } from './render-mode.js';
|
|
2
2
|
|
|
3
3
|
export interface PageDocument {
|
|
4
4
|
path: string;
|
|
@@ -6,7 +6,6 @@ export interface PageDocument {
|
|
|
6
6
|
locale: string;
|
|
7
7
|
metadata: PageMetadata;
|
|
8
8
|
regions: Record<string, RegionNode>;
|
|
9
|
-
renderMode: RenderMode;
|
|
10
9
|
}
|
|
11
10
|
|
|
12
11
|
export interface PageMetadata {
|
package/src/types/render-mode.ts
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|