@gradial/aci 0.1.9 → 0.1.11

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/bin/aci ADDED
Binary file
package/dist/index.d.ts CHANGED
@@ -2,3 +2,4 @@ export * from './block-ref.js';
2
2
  export * from './define-component.js';
3
3
  export * from './define-layout.js';
4
4
  export * from './types/index.js';
5
+ export * from './render.js';
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ export * from './block-ref.js';
2
2
  export * from './define-component.js';
3
3
  export * from './define-layout.js';
4
4
  export * from './types/index.js';
5
+ export * from './render.js';
5
6
  // Note: content/ is NOT re-exported here because it imports node:path,
6
7
  // making it incompatible with client-side bundlers. Import server-only
7
8
  // content utilities from '@gradial/aci/content' explicitly.
@@ -1,2 +1,3 @@
1
1
  export { DevRefresh, type DevRefreshProps } from './dev-refresh.js';
2
2
  export { withGradial, type WithGradialOptions } from './config.js';
3
+ export { PreviewBanner } from './preview-banner.js';
@@ -1,2 +1,3 @@
1
1
  export { DevRefresh } from './dev-refresh.js';
2
2
  export { withGradial } from './config.js';
3
+ export { PreviewBanner } from './preview-banner.js';
@@ -6,6 +6,14 @@ const ROUTE_HEADER = 'x-gradial-route';
6
6
  const DISPATCH_HEADER = 'x-gradial-dispatch';
7
7
  export function createGradialMiddleware(config) {
8
8
  return async function gradialMiddleware(request) {
9
+ const url = request.nextUrl;
10
+ if (url.searchParams.get('leave_preview') === '1') {
11
+ const cleanUrl = new URL(url.pathname, url.origin);
12
+ const response = NextResponse.redirect(cleanUrl);
13
+ response.cookies.delete('aci_preview');
14
+ response.cookies.delete('aci_preview_release');
15
+ return response;
16
+ }
9
17
  const requestHeaders = gradialHeaders(request.headers);
10
18
  const preview = await releaseFromPreviewToken(request, config);
11
19
  if (preview.invalid) {
@@ -16,13 +24,28 @@ export function createGradialMiddleware(config) {
16
24
  return NextResponse.next({ request: { headers: requestHeaders } });
17
25
  }
18
26
  requestHeaders.set(RELEASE_HEADER, releaseId);
19
- requestHeaders.set(ROUTE_HEADER, request.nextUrl.pathname);
27
+ requestHeaders.set(ROUTE_HEADER, url.pathname);
20
28
  requestHeaders.set(DISPATCH_HEADER, 'ssr-page');
21
- return NextResponse.next({
22
- request: {
23
- headers: requestHeaders,
24
- },
25
- });
29
+ if (preview.releaseId) {
30
+ requestHeaders.set('x-gradial-preview', '1');
31
+ }
32
+ const response = NextResponse.next({ request: { headers: requestHeaders } });
33
+ const queryToken = url.searchParams.get('preview_token');
34
+ if (queryToken && preview.releaseId) {
35
+ response.cookies.set('aci_preview', queryToken, {
36
+ httpOnly: true,
37
+ sameSite: 'lax',
38
+ maxAge: 60 * 60,
39
+ path: '/',
40
+ });
41
+ response.cookies.set('aci_preview_release', preview.releaseId, {
42
+ httpOnly: false,
43
+ sameSite: 'lax',
44
+ maxAge: 60 * 60,
45
+ path: '/',
46
+ });
47
+ }
48
+ return response;
26
49
  };
27
50
  }
28
51
  function gradialHeaders(headers) {
@@ -0,0 +1 @@
1
+ export declare function PreviewBanner(): Promise<import("react").JSX.Element | null>;
@@ -0,0 +1,53 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { isPreviewMode } from './server.js';
3
+ export async function PreviewBanner() {
4
+ const releaseId = await isPreviewMode();
5
+ if (!releaseId)
6
+ return null;
7
+ const shortId = releaseId.length > 20
8
+ ? `${releaseId.slice(0, 8)}\u2026${releaseId.slice(-6)}`
9
+ : releaseId;
10
+ return (_jsxs("div", { role: "alert", style: {
11
+ position: 'fixed',
12
+ top: 0,
13
+ left: 0,
14
+ right: 0,
15
+ zIndex: 9999,
16
+ display: 'flex',
17
+ alignItems: 'center',
18
+ justifyContent: 'center',
19
+ gap: '12px',
20
+ padding: '8px 16px',
21
+ backgroundColor: '#1a1a2e',
22
+ color: '#fff',
23
+ fontSize: '13px',
24
+ fontFamily: 'system-ui, -apple-system, sans-serif',
25
+ boxShadow: '0 2px 8px rgba(0,0,0,0.3)',
26
+ }, children: [_jsx("span", { "aria-hidden": "true", style: {
27
+ display: 'inline-flex',
28
+ alignItems: 'center',
29
+ justifyContent: 'center',
30
+ width: '18px',
31
+ height: '18px',
32
+ borderRadius: '50%',
33
+ backgroundColor: '#f59e0b',
34
+ color: '#1a1a2e',
35
+ fontWeight: 700,
36
+ fontSize: '11px',
37
+ flexShrink: 0,
38
+ }, children: "P" }), _jsx("span", { style: { fontWeight: 500 }, children: "Release Preview" }), _jsx("span", { style: {
39
+ fontFamily: 'monospace',
40
+ opacity: 0.7,
41
+ fontSize: '12px',
42
+ }, title: releaseId, children: shortId }), _jsx("a", { href: "?leave_preview=1", style: {
43
+ marginLeft: 'auto',
44
+ padding: '4px 12px',
45
+ borderRadius: '6px',
46
+ backgroundColor: '#dc2626',
47
+ color: '#fff',
48
+ textDecoration: 'none',
49
+ fontSize: '12px',
50
+ fontWeight: 600,
51
+ whiteSpace: 'nowrap',
52
+ }, children: "Exit Preview" })] }));
53
+ }
@@ -32,6 +32,7 @@ export declare function getFragment<T = unknown>(name: string, config?: GradialF
32
32
  export declare function getRoutes(config?: GradialFetchConfig): Promise<string[]>;
33
33
  export declare function getRenderInput<TPage, TSite extends GradialSiteConfig = GradialSiteConfig>(route?: string, config?: GradialFetchConfig): Promise<GradialRenderInput<TPage, TSite>>;
34
34
  export declare function getPageRuntimeRenderInput(): Promise<RenderInput | null>;
35
+ export declare function isPreviewMode(): Promise<string>;
35
36
  export declare function routeFromNextParams(params?: Promise<{
36
37
  slug?: string[];
37
38
  }> | {
@@ -5,6 +5,7 @@ import { S3ContentProvider } from '../providers/s3.js';
5
5
  import { GRADIAL_RENDER_INPUT_HEADER, getPendingRenderInput } from '../runtime/page.js';
6
6
  import { getUncachedEdgeConfigValue } from './edge-config.js';
7
7
  const RELEASE_HEADER = 'x-gradial-release-id';
8
+ const PREVIEW_HEADER = 'x-gradial-preview';
8
9
  export { FragmentNotFoundError, PageNotFoundError };
9
10
  export async function getPage(route = '', config = {}) {
10
11
  const runtimeInput = await getPageRuntimeRenderInput();
@@ -70,6 +71,16 @@ export async function getPageRuntimeRenderInput() {
70
71
  return null;
71
72
  }
72
73
  }
74
+ export async function isPreviewMode() {
75
+ try {
76
+ const current = await headers();
77
+ if (current.get(PREVIEW_HEADER) === '1') {
78
+ return current.get(RELEASE_HEADER) || '';
79
+ }
80
+ }
81
+ catch { }
82
+ return '';
83
+ }
73
84
  export async function routeFromNextParams(params) {
74
85
  if (!params)
75
86
  return '/';
@@ -82,8 +93,10 @@ export async function generateGradialStaticParams() {
82
93
  const { FileContentProvider } = await import('../providers/file.js');
83
94
  const provider = new FileContentProvider();
84
95
  const routes = await provider.listRoutes();
85
- return routes.map((entry) => ({
86
- slug: entry.path === '/' ? undefined : entry.path.replace(/^\/|\/$/g, '').split('/'),
96
+ return routes
97
+ .filter((entry) => entry.path !== '/')
98
+ .map((entry) => ({
99
+ slug: entry.path.replace(/^\/|\/$/g, '').split('/'),
87
100
  }));
88
101
  }
89
102
  export class ReleaseAssetNotFoundError extends Error {
@@ -0,0 +1,14 @@
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 ADDED
@@ -0,0 +1,33 @@
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gradial/aci",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",