@gradial/aci 0.1.19 → 0.1.20-preview.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.
Files changed (82) hide show
  1. package/README.md +28 -8
  2. package/bin/aci +0 -0
  3. package/dist/astro/index.d.ts +4 -2
  4. package/dist/astro/index.js +9 -1
  5. package/dist/astro/preview.d.ts +5 -0
  6. package/dist/astro/preview.js +33 -0
  7. package/dist/content/index.d.ts +1 -0
  8. package/dist/content/index.js +1 -0
  9. package/dist/content/provider.d.ts +19 -0
  10. package/dist/content/provider.js +7 -0
  11. package/dist/content/resolve-slots.d.ts +9 -0
  12. package/dist/content/resolve-slots.js +24 -0
  13. package/dist/content/types.d.ts +1 -1
  14. package/dist/content/validation.js +0 -1
  15. package/dist/define-component.js +0 -6
  16. package/dist/define-data-model.d.ts +2 -0
  17. package/dist/define-data-model.js +3 -0
  18. package/dist/define-layout.d.ts +2 -0
  19. package/dist/define-layout.js +3 -0
  20. package/dist/dev/index.d.ts +12 -0
  21. package/dist/dev/index.js +84 -9
  22. package/dist/index.d.ts +2 -0
  23. package/dist/index.js +2 -0
  24. package/dist/next/asset-route.d.ts +2 -0
  25. package/dist/next/asset-route.js +10 -2
  26. package/dist/next/config.d.ts +1 -1
  27. package/dist/next/config.js +14 -2
  28. package/dist/next/content-watch.d.ts +11 -0
  29. package/dist/next/content-watch.js +106 -0
  30. package/dist/next/index.d.ts +2 -2
  31. package/dist/next/index.js +2 -2
  32. package/dist/next/middleware.d.ts +6 -2
  33. package/dist/next/middleware.js +40 -51
  34. package/dist/next/page.d.ts +41 -0
  35. package/dist/next/page.js +77 -0
  36. package/dist/next/preview-mode.js +4 -8
  37. package/dist/next/render.d.ts +5 -0
  38. package/dist/next/render.js +15 -0
  39. package/dist/next/root-layout.d.ts +4 -0
  40. package/dist/next/root-layout.js +5 -0
  41. package/dist/next/server.d.ts +3 -2
  42. package/dist/next/server.js +12 -8
  43. package/dist/preview/core.d.ts +41 -0
  44. package/dist/preview/core.js +116 -0
  45. package/dist/providers/file.d.ts +2 -0
  46. package/dist/providers/file.js +61 -1
  47. package/dist/providers/s3.d.ts +1 -0
  48. package/dist/providers/s3.js +14 -1
  49. package/dist/registry.d.ts +15 -0
  50. package/dist/registry.js +10 -0
  51. package/dist/sveltekit/index.d.ts +4 -1
  52. package/dist/sveltekit/index.js +9 -1
  53. package/dist/sveltekit/preview.d.ts +5 -0
  54. package/dist/sveltekit/preview.js +33 -0
  55. package/dist/testing/index.d.ts +3 -0
  56. package/dist/testing/index.js +15 -2
  57. package/dist/types/component.d.ts +0 -9
  58. package/dist/types/config.d.ts +1 -1
  59. package/dist/types/data.d.ts +19 -0
  60. package/dist/types/data.js +11 -0
  61. package/dist/types/image.d.ts +1 -1
  62. package/dist/types/image.js +1 -1
  63. package/dist/types/index.d.ts +1 -0
  64. package/dist/types/index.js +1 -0
  65. package/dist/types/page.d.ts +1 -2
  66. package/dist/types/render-mode.d.ts +0 -1
  67. package/package.json +40 -6
  68. package/src/cli/compile-registry.mjs +167 -19
  69. package/src/types/component.ts +0 -10
  70. package/src/types/config.ts +1 -1
  71. package/src/types/data.ts +47 -0
  72. package/src/types/image.ts +2 -2
  73. package/src/types/index.ts +1 -0
  74. package/src/types/page.ts +1 -2
  75. package/src/types/render-mode.ts +0 -8
  76. package/dist/next/preview-banner.d.ts +0 -4
  77. package/dist/next/preview-banner.js +0 -51
  78. package/dist/next/preview.d.ts +0 -7
  79. package/dist/next/preview.js +0 -37
  80. package/dist/render.d.ts +0 -14
  81. package/dist/render.js +0 -33
  82. package/src/cli/verify-renderer.mjs +0 -73
@@ -5,6 +5,8 @@ import { pathToFileURL } from 'node:url';
5
5
  import { z } from 'zod';
6
6
  import { GradialImageSchema } from '../types/image.ts';
7
7
 
8
+ const DATA_TYPE_PATTERN = /^[A-Za-z][A-Za-z0-9_-]*$/;
9
+
8
10
  // Shim React global so JSX-containing component files can be loaded.
9
11
  // The compile-registry only extracts contract metadata (schemas, imageSlots)
10
12
  // and never renders components, but importing .tsx files requires the JSX
@@ -33,6 +35,9 @@ async function compileRegistry(options) {
33
35
 
34
36
  const components = await loadDefaultArray(options.components, 'component registry');
35
37
  const layouts = await loadDefaultArray(options.layouts, 'layout registry');
38
+ const dataModels = options.data
39
+ ? await loadDefaultArray(options.data, 'data model registry')
40
+ : [];
36
41
  const outDir = path.resolve(options.output);
37
42
  const schemaDir = path.join(outDir, 'schemas');
38
43
  await mkdir(schemaDir, { recursive: true });
@@ -47,22 +52,11 @@ async function compileRegistry(options) {
47
52
  const schemaPath = path.join(outDir, schemaFile);
48
53
  await writeFile(schemaPath, JSON.stringify(schema, null, 2) + '\n');
49
54
  schemaFiles.push(schemaPath);
50
- const renderModes = component.renderModes ?? component.render;
51
55
  const varyDimensions = component.varyDimensions ?? component.vary;
52
56
  const imageSlots = normalizeImageSlots(component.imageSlots);
53
57
  componentEntries.push({
54
58
  name: component.name,
55
59
  schemaFile,
56
- render: {
57
- static: Boolean(renderModes.canStatic ?? renderModes.static),
58
- ssr: Boolean(renderModes.canSSR ?? renderModes.ssr),
59
- clientIsland: Boolean(renderModes.canClientIsland ?? renderModes.clientIsland),
60
- },
61
- renderModes: {
62
- canStatic: Boolean(renderModes.canStatic ?? renderModes.static),
63
- canSSR: Boolean(renderModes.canSSR ?? renderModes.ssr),
64
- canClientIsland: Boolean(renderModes.canClientIsland ?? renderModes.clientIsland),
65
- },
66
60
  ...(component.defaultIslandMode ? { defaultIslandMode: component.defaultIslandMode } : {}),
67
61
  ...(varyDimensions?.length ? { vary: varyDimensions, varyDimensions } : {}),
68
62
  ...(Object.keys(imageSlots).length ? { imageSlots } : {}),
@@ -72,6 +66,24 @@ async function compileRegistry(options) {
72
66
  const seenLayouts = new Set();
73
67
  const layoutEntries = layouts.map((layout, index) => validateLayout(layout, index, seenLayouts));
74
68
 
69
+ const seenDataModels = new Set();
70
+ const dataModelEntries = [];
71
+ if (dataModels.length > 0) {
72
+ await mkdir(path.join(schemaDir, 'data'), { recursive: true });
73
+ }
74
+ for (const [index, dataModel] of dataModels.entries()) {
75
+ validateDataModel(dataModel, index, seenDataModels);
76
+ const schema = compileSchema(dataModel.schema, dataModel.type);
77
+ const schemaFile = `schemas/data/${dataModel.type}.schema.json`;
78
+ const schemaPath = path.join(outDir, schemaFile);
79
+ await writeFile(schemaPath, JSON.stringify(schema, null, 2) + '\n');
80
+ schemaFiles.push(schemaPath);
81
+ dataModelEntries.push({
82
+ type: dataModel.type,
83
+ schemaFile,
84
+ });
85
+ }
86
+
75
87
  // Post-compilation: detect nested block patterns in JSON schemas and
76
88
  // validate that referenced component names exist in the registry.
77
89
  const knownNames = new Set(componentEntries.map((c) => c.name));
@@ -90,19 +102,39 @@ async function compileRegistry(options) {
90
102
  generatedAt: new Date().toISOString(),
91
103
  components: componentEntries,
92
104
  layouts: layoutEntries,
105
+ dataModels: dataModelEntries,
93
106
  };
94
107
  const registryPath = path.join(outDir, 'registry.json');
95
108
  await writeFile(registryPath, JSON.stringify(registry, null, 2) + '\n');
109
+
110
+ const pageSchemaPath = await generatePageSchema(schemaDir, componentEntries, outDir);
111
+ schemaFiles.push(pageSchemaPath);
112
+
96
113
  return { registry: registryPath, schemas: schemaFiles };
97
114
  }
98
115
 
99
116
  async function loadDefaultArray(modulePath, label) {
100
117
  const mod = await import(pathToFileURL(path.resolve(modulePath)).href);
101
- const value = mod.default ?? mod.registry ?? mod.components ?? mod.layouts;
118
+ const value = mod.default ?? mod.registry ?? mod.components ?? mod.layouts ?? mod.dataModels;
102
119
  if (!Array.isArray(value)) throw new Error(`${label} must export an array`);
103
120
  return value;
104
121
  }
105
122
 
123
+ function validateDataModel(dataModel, index, seen) {
124
+ if (!dataModel || typeof dataModel !== 'object') {
125
+ throw new Error(`dataModels[${index}] must be an object`);
126
+ }
127
+ if (typeof dataModel.type !== 'string' || !DATA_TYPE_PATTERN.test(dataModel.type)) {
128
+ throw new Error(`dataModels[${index}].type must match ${DATA_TYPE_PATTERN}`);
129
+ }
130
+ if (seen.has(dataModel.type)) {
131
+ throw new Error(`duplicate data model type ${dataModel.type}`);
132
+ }
133
+ seen.add(dataModel.type);
134
+ if (!dataModel.schema) throw new Error(`dataModels[${index}].schema is required`);
135
+ validateZodSubset(dataModel.schema, `dataModels[${index}].schema`);
136
+ }
137
+
106
138
  function validateComponent(component, index, seen) {
107
139
  if (!component || typeof component !== 'object') throw new Error(`components[${index}] must be an object`);
108
140
  if (!component.name) throw new Error(`components[${index}].name is required`);
@@ -111,12 +143,6 @@ function validateComponent(component, index, seen) {
111
143
  if (!component.schema) throw new Error(`components[${index}].schema is required`);
112
144
  validateZodSubset(component.schema, `components[${index}].schema`);
113
145
  validateImageSlotSchemaMatch(component, index);
114
- const renderModes = component.renderModes ?? component.render;
115
- if (!renderModes) throw new Error(`components[${index}].renderModes is required`);
116
- const enabled = Boolean(renderModes.canStatic ?? renderModes.static)
117
- || Boolean(renderModes.canSSR ?? renderModes.ssr)
118
- || Boolean(renderModes.canClientIsland ?? renderModes.clientIsland);
119
- if (!enabled) throw new Error(`components[${index}].renderModes must enable at least one mode`);
120
146
  }
121
147
 
122
148
  function validateImageSlotSchemaMatch(component, index) {
@@ -310,7 +336,11 @@ function validateLayout(layout, index, seen) {
310
336
  seenSlots.add(slot.name);
311
337
  slots.push({ name: slot.name, required: Boolean(slot.required) });
312
338
  }
313
- return { name: layout.name, slots };
339
+ const entry = { name: layout.name, slots };
340
+ if (layout.defaults && typeof layout.defaults === 'object') {
341
+ entry.defaults = layout.defaults;
342
+ }
343
+ return entry;
314
344
  }
315
345
 
316
346
  function compileSchema(schema, name) {
@@ -329,6 +359,124 @@ function compileSchema(schema, name) {
329
359
  };
330
360
  }
331
361
 
362
+ async function generatePageSchema(schemaDir, componentEntries, outDir) {
363
+ const { readFile } = await import('node:fs/promises');
364
+ const defs = {
365
+ baseBlock: {
366
+ type: 'object',
367
+ required: ['id', 'component', 'props'],
368
+ additionalProperties: true,
369
+ properties: {
370
+ id: { type: 'string', minLength: 1 },
371
+ component: { type: 'string' },
372
+ props: { type: 'object' },
373
+ },
374
+ },
375
+ damAssetRef: {
376
+ type: 'object',
377
+ required: ['$type', 'assetId'],
378
+ additionalProperties: true,
379
+ properties: {
380
+ $type: { const: 'dam.assetRef' },
381
+ assetId: { type: 'string', minLength: 1 },
382
+ version: { type: 'string', minLength: 1 },
383
+ alt: { type: 'string' },
384
+ },
385
+ },
386
+ };
387
+ const oneOf = [];
388
+ for (const entry of componentEntries) {
389
+ const schemaPath = path.join(outDir, entry.schemaFile);
390
+ const raw = await readFile(schemaPath, 'utf-8');
391
+ const componentSchema = JSON.parse(raw);
392
+ const { $schema: _s, title: _t, ...propsSchema } = componentSchema;
393
+ replaceGradialImagesWithDamAssetRef(propsSchema, defs.damAssetRef);
394
+ const defName = `${entry.name}Block`;
395
+ defs[defName] = {
396
+ allOf: [
397
+ { $ref: '#/$defs/baseBlock' },
398
+ {
399
+ properties: {
400
+ component: { const: entry.name },
401
+ props: propsSchema,
402
+ },
403
+ },
404
+ ],
405
+ };
406
+ oneOf.push({ $ref: `#/$defs/${defName}` });
407
+ }
408
+ const pageSchema = {
409
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
410
+ $id: 'https://baremetal.local/schemas/page.schema.json',
411
+ title: 'bare-metal Page',
412
+ type: 'object',
413
+ required: ['$type', 'id', 'status', 'layout', 'metadata', 'regions'],
414
+ additionalProperties: true,
415
+ properties: {
416
+ $type: { const: 'page' },
417
+ id: { type: 'string', minLength: 1 },
418
+ status: { type: 'string', minLength: 1 },
419
+ layout: { type: 'string', minLength: 1 },
420
+ metadata: {
421
+ type: 'object',
422
+ required: ['title'],
423
+ additionalProperties: true,
424
+ properties: {
425
+ title: { type: 'string', minLength: 1 },
426
+ description: { type: 'string' },
427
+ canonical: { type: 'string' },
428
+ },
429
+ },
430
+ regions: {
431
+ type: 'object',
432
+ additionalProperties: {
433
+ type: 'array',
434
+ items: { oneOf },
435
+ },
436
+ },
437
+ },
438
+ $defs: defs,
439
+ };
440
+ const pageSchemaPath = path.join(schemaDir, 'page.schema.json');
441
+ await writeFile(pageSchemaPath, JSON.stringify(pageSchema, null, 2) + '\n');
442
+ return pageSchemaPath;
443
+ }
444
+
445
+ function replaceGradialImagesWithDamAssetRef(schema, damAssetRef) {
446
+ if (!schema || typeof schema !== 'object') return schema;
447
+ if (schema.$ref) return;
448
+ if (Array.isArray(schema.anyOf)) {
449
+ for (const s of schema.anyOf) replaceGradialImagesWithDamAssetRef(s, damAssetRef);
450
+ }
451
+ if (Array.isArray(schema.allOf)) {
452
+ for (const s of schema.allOf) replaceGradialImagesWithDamAssetRef(s, damAssetRef);
453
+ }
454
+ if (Array.isArray(schema.oneOf)) {
455
+ for (const s of schema.oneOf) replaceGradialImagesWithDamAssetRef(s, damAssetRef);
456
+ }
457
+ if (schema.properties) {
458
+ for (const [key, value] of Object.entries(schema.properties)) {
459
+ if (isJsonGradialImage(value)) {
460
+ schema.properties[key] = { $ref: '#/$defs/damAssetRef' };
461
+ } else {
462
+ replaceGradialImagesWithDamAssetRef(value, damAssetRef);
463
+ }
464
+ }
465
+ }
466
+ if (schema.items) {
467
+ if (isJsonGradialImage(schema.items)) {
468
+ schema.items = { $ref: '#/$defs/damAssetRef' };
469
+ } else {
470
+ replaceGradialImagesWithDamAssetRef(schema.items, damAssetRef);
471
+ }
472
+ }
473
+ }
474
+
475
+ function isJsonGradialImage(node) {
476
+ return node?.type === 'object'
477
+ && node?.properties?.$type?.const === 'gradial.image';
478
+ }
479
+
332
480
  function validateZodSubset(schema, pathLabel, seen = new Set()) {
333
481
  if (seen.has(schema)) return;
334
482
  seen.add(schema);
@@ -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[];
@@ -10,8 +10,8 @@ export interface BareMetalConfig {
10
10
  publicDir?: string;
11
11
  };
12
12
  componentRegistry: string;
13
+ dataRegistry?: string;
13
14
  layoutRegistry: string;
14
- rendererEntry: string;
15
15
  capabilities: RenderCapabilities;
16
16
  routes: RouteConfig;
17
17
  dam?: DAMConfig;
@@ -0,0 +1,47 @@
1
+ import { z } from 'zod';
2
+
3
+ type InferDataProps<TSchema> = TSchema extends z.ZodType<infer T>
4
+ ? T
5
+ : Record<string, unknown>;
6
+
7
+ const dataRefPattern = /^\$ref:data\/[A-Za-z0-9][A-Za-z0-9_./-]*$/;
8
+
9
+ export interface DataModelDefinition<TSchema = unknown, TType extends string = string> {
10
+ type: TType;
11
+ schema: TSchema;
12
+ }
13
+
14
+ export interface DataModelContract<TSchema = unknown, TType extends string = string> {
15
+ type: TType;
16
+ schema: TSchema;
17
+ }
18
+
19
+ export interface DataEntry<TProps = Record<string, unknown>, TType extends string = string> {
20
+ $type: TType;
21
+ props: TProps;
22
+ }
23
+
24
+ export type InferDataModelProps<TContract> = TContract extends DataModelContract<infer TSchema, string>
25
+ ? InferDataProps<TSchema>
26
+ : Record<string, unknown>;
27
+
28
+ export type InferDataModelType<TContract> = TContract extends DataModelContract<unknown, infer TType>
29
+ ? TType
30
+ : string;
31
+
32
+ export type DataModelRef<TContract extends DataModelContract> = DataEntry<
33
+ InferDataModelProps<TContract>,
34
+ InferDataModelType<TContract>
35
+ >;
36
+
37
+ export function dataModelRef<TContract extends DataModelContract<z.ZodTypeAny>>(
38
+ contract: TContract,
39
+ ): z.ZodType<DataModelRef<TContract>> {
40
+ return z.union([
41
+ z.string().regex(dataRefPattern, `expected a $ref:data/... pointer for ${contract.type}`),
42
+ z.object({
43
+ $type: z.literal(contract.type),
44
+ props: contract.schema,
45
+ }),
46
+ ]).describe(`data model reference: ${contract.type}`) as unknown as z.ZodType<DataModelRef<TContract>>;
47
+ }
@@ -20,7 +20,7 @@ export interface GradialImage {
20
20
  versionId: string;
21
21
  alt: string;
22
22
  fallback: ImageSource;
23
- sources: PictureSource[];
23
+ sources: PictureSource[] | null;
24
24
  }
25
25
 
26
26
  export interface ImageSlotContract {
@@ -65,7 +65,7 @@ export function renderImageHTML(image: GradialImage, attrs: ImageHTMLAttributes
65
65
  height: image.fallback.height > 0 ? image.fallback.height : undefined,
66
66
  });
67
67
  const img = `<img${imgAttrs}>`;
68
- if (!image.sources.length) {
68
+ if (!image.sources?.length) {
69
69
  return img;
70
70
  }
71
71
  const sources = image.sources.map((source) => `<source${renderAttrs({
@@ -1,5 +1,6 @@
1
1
  export * from './config.js';
2
2
  export * from './component.js';
3
+ export * from './data.js';
3
4
  export * from './layout.js';
4
5
  export * from './page.js';
5
6
  export * from './renderer.js';
package/src/types/page.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { RenderMode, VaryDimension } from './render-mode.js';
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 {
@@ -1,11 +1,3 @@
1
- export type RenderMode =
2
- | 'static'
3
- | 'static-with-fragments'
4
- | 'prerender-on-demand'
5
- | 'ssr-page'
6
- | 'ssr-island'
7
- | 'client-island';
8
-
9
1
  export type IslandMode = 'ssr' | 'client';
10
2
 
11
3
  export type VaryDimension =
@@ -1,4 +0,0 @@
1
- export interface PreviewBannerProps {
2
- releaseId?: string;
3
- }
4
- export declare function PreviewBanner({ releaseId }: PreviewBannerProps): import("react").JSX.Element | null;
@@ -1,51 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- export function PreviewBanner({ releaseId }) {
3
- if (!releaseId)
4
- return null;
5
- const shortId = releaseId.length > 20
6
- ? `${releaseId.slice(0, 8)}\u2026${releaseId.slice(-6)}`
7
- : releaseId;
8
- return (_jsxs("div", { role: "alert", style: {
9
- position: 'fixed',
10
- top: 0,
11
- left: 0,
12
- right: 0,
13
- zIndex: 9999,
14
- display: 'flex',
15
- alignItems: 'center',
16
- justifyContent: 'center',
17
- gap: '12px',
18
- padding: '8px 16px',
19
- backgroundColor: '#1a1a2e',
20
- color: '#fff',
21
- fontSize: '13px',
22
- fontFamily: 'system-ui, -apple-system, sans-serif',
23
- boxShadow: '0 2px 8px rgba(0,0,0,0.3)',
24
- }, children: [_jsx("span", { "aria-hidden": "true", style: {
25
- display: 'inline-flex',
26
- alignItems: 'center',
27
- justifyContent: 'center',
28
- width: '18px',
29
- height: '18px',
30
- borderRadius: '50%',
31
- backgroundColor: '#f59e0b',
32
- color: '#1a1a2e',
33
- fontWeight: 700,
34
- fontSize: '11px',
35
- flexShrink: 0,
36
- }, children: "P" }), _jsx("span", { style: { fontWeight: 500 }, children: "Release Preview" }), _jsx("span", { style: {
37
- fontFamily: 'monospace',
38
- opacity: 0.7,
39
- fontSize: '12px',
40
- }, title: releaseId, children: shortId }), _jsx("a", { href: "?leave_preview=1", style: {
41
- marginLeft: 'auto',
42
- padding: '4px 12px',
43
- borderRadius: '6px',
44
- backgroundColor: '#dc2626',
45
- color: '#fff',
46
- textDecoration: 'none',
47
- fontSize: '12px',
48
- fontWeight: 600,
49
- whiteSpace: 'nowrap',
50
- }, children: "Exit Preview" })] }));
51
- }
@@ -1,7 +0,0 @@
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>;
@@ -1,37 +0,0 @@
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
- }
package/dist/render.d.ts DELETED
@@ -1,14 +0,0 @@
1
- import type { ComponentType, ReactNode } from 'react';
2
- export interface BlockData {
3
- id: string;
4
- component: string;
5
- props: Record<string, unknown>;
6
- }
7
- export type ComponentRegistry = Record<string, ComponentType<any>>;
8
- export interface RenderBlocksOptions {
9
- registry: ComponentRegistry;
10
- compositionParams?: Record<string, unknown>;
11
- slot?: string;
12
- }
13
- export declare function renderBlock(block: BlockData, registry: ComponentRegistry, compositionParams?: Record<string, unknown>): ReactNode;
14
- export declare function renderBlocks(blocks: readonly BlockData[], opts: RenderBlocksOptions): ReactNode[];
package/dist/render.js DELETED
@@ -1,33 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { PreviewBanner } from './next/preview-banner.js';
3
- function BlockError({ component, id }) {
4
- return (_jsxs("div", { style: {
5
- padding: '1rem 1.25rem',
6
- margin: '1rem 0',
7
- background: '#fef2f2',
8
- border: '2px solid #dc2626',
9
- borderRadius: '6px',
10
- fontFamily: 'ui-monospace, monospace',
11
- fontSize: '13px',
12
- lineHeight: 1.5,
13
- color: '#991b1b',
14
- }, children: [_jsx("strong", { children: "Unknown block:" }), " ", _jsx("code", { children: component }), _jsx("br", {}), _jsxs("span", { style: { color: '#b91c1c', opacity: 0.7 }, children: ["id: ", id] })] }));
15
- }
16
- export function renderBlock(block, registry, compositionParams) {
17
- const Component = registry[block.component];
18
- if (!Component) {
19
- if (process.env.NODE_ENV !== 'production') {
20
- return _jsx(BlockError, { component: block.component, id: block.id }, block.id);
21
- }
22
- return null;
23
- }
24
- return _jsx(Component, { ...block.props, ...compositionParams }, block.id);
25
- }
26
- export function renderBlocks(blocks, opts) {
27
- const { registry, compositionParams, slot } = opts;
28
- const rendered = blocks.map((block) => renderBlock(block, registry, compositionParams));
29
- if (slot === 'main') {
30
- return [_jsx(PreviewBanner, {}, "__gradial_preview"), ...rendered];
31
- }
32
- return rendered;
33
- }
@@ -1,73 +0,0 @@
1
- #!/usr/bin/env node
2
- import fs from 'node:fs';
3
- import path from 'node:path';
4
- import { pathToFileURL } from 'node:url';
5
-
6
- const args = parseArgs(process.argv.slice(2));
7
-
8
- try {
9
- if (!args.renderer) throw new Error('missing --renderer');
10
- await verifyRenderer(path.resolve(args.renderer));
11
- console.log(JSON.stringify({ success: true, errors: [] }, null, 2));
12
- } catch (error) {
13
- console.error(JSON.stringify({ success: false, errors: [String(error?.message ?? error)] }, null, 2));
14
- process.exitCode = 1;
15
- }
16
-
17
- async function verifyRenderer(rendererPath) {
18
- try {
19
- const mod = await import(pathToFileURL(rendererPath).href);
20
- const renderer = mod.default ?? mod.renderer;
21
- if (!renderer || typeof renderer.renderPage !== 'function') {
22
- throw new Error('renderer must export a GradialRenderer with renderPage');
23
- }
24
- return;
25
- } catch (error) {
26
- if (!shouldUseStaticFallback(error)) {
27
- throw error;
28
- }
29
- }
30
-
31
- const source = fs.readFileSync(rendererPath, 'utf8');
32
- if (!staticallyExportsRenderPage(source)) {
33
- throw new Error('renderer must export a GradialRenderer with renderPage');
34
- }
35
- }
36
-
37
- function shouldUseStaticFallback(error) {
38
- const message = String(error?.message ?? error);
39
- return message.includes('Unknown file extension ".astro"');
40
- }
41
-
42
- function staticallyExportsRenderPage(source) {
43
- if (!/\brenderPage\s*[:=]\s*(async\s*)?(\([^)]*\)|[A-Za-z_$][\w$]*)?\s*=>|\brenderPage\s*[:=]\s*(async\s*)?function\b|\basync\s+renderPage\s*\(|\brenderPage\s*\(/m.test(source)) {
44
- return false;
45
- }
46
- if (/export\s+default\s+{[\s\S]*\brenderPage\b[\s\S]*}/m.test(source)) {
47
- return true;
48
- }
49
- const defaultName = source.match(/export\s+default\s+([A-Za-z_$][\w$]*)\s*;?/m)?.[1];
50
- const namedExport = source.match(/export\s*{\s*([A-Za-z_$][\w$]*)\s+as\s+renderer\s*}/m)?.[1] ?? source.match(/export\s*{\s*([A-Za-z_$][\w$]*)\s*}/m)?.[1];
51
- const rendererName = defaultName ?? namedExport;
52
- if (!rendererName) {
53
- return false;
54
- }
55
- const declaration = new RegExp(`(?:const|let|var)\\s+${rendererName}\\b[\\s\\S]*?\\{[\\s\\S]*?\\brenderPage\\b`, 'm');
56
- return declaration.test(source);
57
- }
58
-
59
- function parseArgs(argv) {
60
- const out = {};
61
- for (let i = 0; i < argv.length; i++) {
62
- const arg = argv[i];
63
- if (!arg.startsWith('--')) continue;
64
- const body = arg.slice(2);
65
- const equals = body.indexOf('=');
66
- if (equals >= 0) {
67
- out[body.slice(0, equals)] = body.slice(equals + 1);
68
- continue;
69
- }
70
- out[body] = argv[++i];
71
- }
72
- return out;
73
- }