@cmssy/next 4.6.2 → 4.7.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/dist/config-DNaPqq1f.d.cts +63 -0
- package/dist/config-DNaPqq1f.d.ts +63 -0
- package/dist/index.d.cts +5 -63
- package/dist/index.d.ts +5 -63
- package/dist/preset.cjs +235 -0
- package/dist/preset.d.cts +81 -0
- package/dist/preset.d.ts +81 -0
- package/dist/preset.js +231 -0
- package/dist/testing.cjs +2 -6
- package/dist/testing.js +2 -6
- package/package.json +8 -3
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { CmssyAuthConfig } from '@cmssy/types';
|
|
2
|
+
|
|
3
|
+
declare const DEFAULT_CMSSY_EDITOR_ORIGINS: string[];
|
|
4
|
+
declare function resolveEditorOrigin(editorOrigin: string | string[] | undefined): string | string[];
|
|
5
|
+
interface CmssyNextConfig {
|
|
6
|
+
/**
|
|
7
|
+
* Full GraphQL delivery endpoint. Defaults to the cmssy cloud endpoint
|
|
8
|
+
* (`https://api.cmssy.io/graphql`); set it only for self-hosted / staging.
|
|
9
|
+
*/
|
|
10
|
+
apiUrl?: string;
|
|
11
|
+
/** Organization slug - part of the org-scoped delivery path. */
|
|
12
|
+
org: string;
|
|
13
|
+
workspaceSlug: string;
|
|
14
|
+
draftSecret: string;
|
|
15
|
+
/**
|
|
16
|
+
* A cmssy API token (`cs_…`) that opts this app into the editor-controlled dev
|
|
17
|
+
* preview. In development only, the SDK sends it on every page fetch so the
|
|
18
|
+
* backend can resolve the token's user and honour that user's dev-preview flag
|
|
19
|
+
* (toggled from the editor's dev-mode switch): flag on + a saved dev draft ⇒
|
|
20
|
+
* the draft overlay renders, otherwise published content. Server-only (never
|
|
21
|
+
* reaches the client); ignored outside development. See the Quickstart
|
|
22
|
+
* "Dev preview" section.
|
|
23
|
+
*/
|
|
24
|
+
devToken?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Origin allowed to frame your app in the editor. Defaults to
|
|
27
|
+
* {@link DEFAULT_CMSSY_EDITOR_ORIGINS}; set it only for self-hosted admins.
|
|
28
|
+
*/
|
|
29
|
+
editorOrigin?: string | string[];
|
|
30
|
+
/**
|
|
31
|
+
* Canonical absolute site URL (e.g. https://cmssy.com), used by
|
|
32
|
+
* createCmssyRobots / createCmssySitemap. When omitted the helpers derive the
|
|
33
|
+
* origin from the request `host` header at render time (multi-domain safe).
|
|
34
|
+
*/
|
|
35
|
+
siteUrl?: string;
|
|
36
|
+
auth?: CmssyAuthConfig;
|
|
37
|
+
defaultLocale?: string;
|
|
38
|
+
/** All languages enabled on the workspace; exposed to blocks via context.locale.enabled. */
|
|
39
|
+
enabledLocales?: string[];
|
|
40
|
+
resolveLocale?: () => string | Promise<string>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Env-shaped input for {@link defineCmssyConfig}: the required string fields are
|
|
44
|
+
* widened to `string | undefined` so a config can pass `process.env.*` straight
|
|
45
|
+
* through without a `?? ""` fallback (which would mask a missing value as an
|
|
46
|
+
* empty string) or a cast.
|
|
47
|
+
*/
|
|
48
|
+
type CmssyEnvConfig = Omit<CmssyNextConfig, "org" | "workspaceSlug" | "draftSecret"> & {
|
|
49
|
+
org?: string;
|
|
50
|
+
workspaceSlug?: string;
|
|
51
|
+
draftSecret?: string;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Validates an env-sourced config and returns a strictly-typed
|
|
55
|
+
* {@link CmssyNextConfig}. Pass raw `process.env.*` values; this throws a clear,
|
|
56
|
+
* actionable error listing any missing required variables (rendered by the
|
|
57
|
+
* Next.js error overlay / boundary), so the app fails fast instead of running
|
|
58
|
+
* with silently-empty config.
|
|
59
|
+
*/
|
|
60
|
+
declare function defineCmssyConfig(config: CmssyEnvConfig): CmssyNextConfig;
|
|
61
|
+
declare function assertAuthConfig(config: CmssyNextConfig): CmssyAuthConfig;
|
|
62
|
+
|
|
63
|
+
export { type CmssyNextConfig as C, DEFAULT_CMSSY_EDITOR_ORIGINS as D, type CmssyEnvConfig as a, assertAuthConfig as b, defineCmssyConfig as d, resolveEditorOrigin as r };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { CmssyAuthConfig } from '@cmssy/types';
|
|
2
|
+
|
|
3
|
+
declare const DEFAULT_CMSSY_EDITOR_ORIGINS: string[];
|
|
4
|
+
declare function resolveEditorOrigin(editorOrigin: string | string[] | undefined): string | string[];
|
|
5
|
+
interface CmssyNextConfig {
|
|
6
|
+
/**
|
|
7
|
+
* Full GraphQL delivery endpoint. Defaults to the cmssy cloud endpoint
|
|
8
|
+
* (`https://api.cmssy.io/graphql`); set it only for self-hosted / staging.
|
|
9
|
+
*/
|
|
10
|
+
apiUrl?: string;
|
|
11
|
+
/** Organization slug - part of the org-scoped delivery path. */
|
|
12
|
+
org: string;
|
|
13
|
+
workspaceSlug: string;
|
|
14
|
+
draftSecret: string;
|
|
15
|
+
/**
|
|
16
|
+
* A cmssy API token (`cs_…`) that opts this app into the editor-controlled dev
|
|
17
|
+
* preview. In development only, the SDK sends it on every page fetch so the
|
|
18
|
+
* backend can resolve the token's user and honour that user's dev-preview flag
|
|
19
|
+
* (toggled from the editor's dev-mode switch): flag on + a saved dev draft ⇒
|
|
20
|
+
* the draft overlay renders, otherwise published content. Server-only (never
|
|
21
|
+
* reaches the client); ignored outside development. See the Quickstart
|
|
22
|
+
* "Dev preview" section.
|
|
23
|
+
*/
|
|
24
|
+
devToken?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Origin allowed to frame your app in the editor. Defaults to
|
|
27
|
+
* {@link DEFAULT_CMSSY_EDITOR_ORIGINS}; set it only for self-hosted admins.
|
|
28
|
+
*/
|
|
29
|
+
editorOrigin?: string | string[];
|
|
30
|
+
/**
|
|
31
|
+
* Canonical absolute site URL (e.g. https://cmssy.com), used by
|
|
32
|
+
* createCmssyRobots / createCmssySitemap. When omitted the helpers derive the
|
|
33
|
+
* origin from the request `host` header at render time (multi-domain safe).
|
|
34
|
+
*/
|
|
35
|
+
siteUrl?: string;
|
|
36
|
+
auth?: CmssyAuthConfig;
|
|
37
|
+
defaultLocale?: string;
|
|
38
|
+
/** All languages enabled on the workspace; exposed to blocks via context.locale.enabled. */
|
|
39
|
+
enabledLocales?: string[];
|
|
40
|
+
resolveLocale?: () => string | Promise<string>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Env-shaped input for {@link defineCmssyConfig}: the required string fields are
|
|
44
|
+
* widened to `string | undefined` so a config can pass `process.env.*` straight
|
|
45
|
+
* through without a `?? ""` fallback (which would mask a missing value as an
|
|
46
|
+
* empty string) or a cast.
|
|
47
|
+
*/
|
|
48
|
+
type CmssyEnvConfig = Omit<CmssyNextConfig, "org" | "workspaceSlug" | "draftSecret"> & {
|
|
49
|
+
org?: string;
|
|
50
|
+
workspaceSlug?: string;
|
|
51
|
+
draftSecret?: string;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Validates an env-sourced config and returns a strictly-typed
|
|
55
|
+
* {@link CmssyNextConfig}. Pass raw `process.env.*` values; this throws a clear,
|
|
56
|
+
* actionable error listing any missing required variables (rendered by the
|
|
57
|
+
* Next.js error overlay / boundary), so the app fails fast instead of running
|
|
58
|
+
* with silently-empty config.
|
|
59
|
+
*/
|
|
60
|
+
declare function defineCmssyConfig(config: CmssyEnvConfig): CmssyNextConfig;
|
|
61
|
+
declare function assertAuthConfig(config: CmssyNextConfig): CmssyAuthConfig;
|
|
62
|
+
|
|
63
|
+
export { type CmssyNextConfig as C, DEFAULT_CMSSY_EDITOR_ORIGINS as D, type CmssyEnvConfig as a, assertAuthConfig as b, defineCmssyConfig as d, resolveEditorOrigin as r };
|
package/dist/index.d.cts
CHANGED
|
@@ -3,70 +3,12 @@ import { ComponentType, ReactNode } from 'react';
|
|
|
3
3
|
import { CmssyPageData, CmssyFormDefinition, BlockDefinition, CmssyClientConfig } from '@cmssy/react';
|
|
4
4
|
export { DEFAULT_CMSSY_API_URL, FieldCondition, FieldConditionGroup, FieldConditionLogic, evaluateFieldConditionGroup, resolveApiUrl } from '@cmssy/react';
|
|
5
5
|
import { EditBridgeConfig } from '@cmssy/react/client';
|
|
6
|
-
import {
|
|
7
|
-
export {
|
|
6
|
+
import { C as CmssyNextConfig } from './config-DNaPqq1f.cjs';
|
|
7
|
+
export { a as CmssyEnvConfig, D as DEFAULT_CMSSY_EDITOR_ORIGINS, b as assertAuthConfig, d as defineCmssyConfig, r as resolveEditorOrigin } from './config-DNaPqq1f.cjs';
|
|
8
8
|
import { NextRequest, NextResponse } from 'next/server';
|
|
9
9
|
import { MetadataRoute, Metadata } from 'next';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
declare function resolveEditorOrigin(editorOrigin: string | string[] | undefined): string | string[];
|
|
13
|
-
interface CmssyNextConfig {
|
|
14
|
-
/**
|
|
15
|
-
* Full GraphQL delivery endpoint. Defaults to the cmssy cloud endpoint
|
|
16
|
-
* (`https://api.cmssy.io/graphql`); set it only for self-hosted / staging.
|
|
17
|
-
*/
|
|
18
|
-
apiUrl?: string;
|
|
19
|
-
/** Organization slug - part of the org-scoped delivery path. */
|
|
20
|
-
org: string;
|
|
21
|
-
workspaceSlug: string;
|
|
22
|
-
draftSecret: string;
|
|
23
|
-
/**
|
|
24
|
-
* A cmssy API token (`cs_…`) that opts this app into the editor-controlled dev
|
|
25
|
-
* preview. In development only, the SDK sends it on every page fetch so the
|
|
26
|
-
* backend can resolve the token's user and honour that user's dev-preview flag
|
|
27
|
-
* (toggled from the editor's dev-mode switch): flag on + a saved dev draft ⇒
|
|
28
|
-
* the draft overlay renders, otherwise published content. Server-only (never
|
|
29
|
-
* reaches the client); ignored outside development. See the Quickstart
|
|
30
|
-
* "Dev preview" section.
|
|
31
|
-
*/
|
|
32
|
-
devToken?: string;
|
|
33
|
-
/**
|
|
34
|
-
* Origin allowed to frame your app in the editor. Defaults to
|
|
35
|
-
* {@link DEFAULT_CMSSY_EDITOR_ORIGINS}; set it only for self-hosted admins.
|
|
36
|
-
*/
|
|
37
|
-
editorOrigin?: string | string[];
|
|
38
|
-
/**
|
|
39
|
-
* Canonical absolute site URL (e.g. https://cmssy.com), used by
|
|
40
|
-
* createCmssyRobots / createCmssySitemap. When omitted the helpers derive the
|
|
41
|
-
* origin from the request `host` header at render time (multi-domain safe).
|
|
42
|
-
*/
|
|
43
|
-
siteUrl?: string;
|
|
44
|
-
auth?: CmssyAuthConfig;
|
|
45
|
-
defaultLocale?: string;
|
|
46
|
-
/** All languages enabled on the workspace; exposed to blocks via context.locale.enabled. */
|
|
47
|
-
enabledLocales?: string[];
|
|
48
|
-
resolveLocale?: () => string | Promise<string>;
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Env-shaped input for {@link defineCmssyConfig}: the required string fields are
|
|
52
|
-
* widened to `string | undefined` so a config can pass `process.env.*` straight
|
|
53
|
-
* through without a `?? ""` fallback (which would mask a missing value as an
|
|
54
|
-
* empty string) or a cast.
|
|
55
|
-
*/
|
|
56
|
-
type CmssyEnvConfig = Omit<CmssyNextConfig, "org" | "workspaceSlug" | "draftSecret"> & {
|
|
57
|
-
org?: string;
|
|
58
|
-
workspaceSlug?: string;
|
|
59
|
-
draftSecret?: string;
|
|
60
|
-
};
|
|
61
|
-
/**
|
|
62
|
-
* Validates an env-sourced config and returns a strictly-typed
|
|
63
|
-
* {@link CmssyNextConfig}. Pass raw `process.env.*` values; this throws a clear,
|
|
64
|
-
* actionable error listing any missing required variables (rendered by the
|
|
65
|
-
* Next.js error overlay / boundary), so the app fails fast instead of running
|
|
66
|
-
* with silently-empty config.
|
|
67
|
-
*/
|
|
68
|
-
declare function defineCmssyConfig(config: CmssyEnvConfig): CmssyNextConfig;
|
|
69
|
-
declare function assertAuthConfig(config: CmssyNextConfig): CmssyAuthConfig;
|
|
10
|
+
import { CmssySessionPayload, SessionCookieOptions, CmssySessionUser, FetchProductOptions, CmssyProduct, FetchProductsOptions, CmssyProductPage, FetchOrderByTokenOptions, CmssyOrder, VerifyCmssyWebhookOptions, CmssyWebhookEvent } from '@cmssy/types';
|
|
11
|
+
export { CmssyAuthConfig, CmssyProductPage, CmssySessionPayload, CmssySessionUser, CmssyStockState, CmssyWebhookEvent, CmssyWebhookOrder, FetchOrderByTokenOptions, FetchProductOptions, FetchProductsOptions, MyOrdersResult, SessionCookieOptions, VerifyCmssyWebhookOptions } from '@cmssy/types';
|
|
70
12
|
|
|
71
13
|
interface CmssyEditorProps {
|
|
72
14
|
page: CmssyPageData;
|
|
@@ -385,4 +327,4 @@ declare class CmssyWebhookError extends Error {
|
|
|
385
327
|
*/
|
|
386
328
|
declare function verifyCmssyWebhook(options: VerifyCmssyWebhookOptions): CmssyWebhookEvent;
|
|
387
329
|
|
|
388
|
-
export { type BuildCmssyMetadataOptions, CMSSY_CART_COOKIE, CMSSY_EDIT_HEADER, CMSSY_EDIT_PATH_PREFIX, CMSSY_EDIT_QUERY_PARAM, CMSSY_LOCALE_HEADER, CMSSY_SECRET_QUERY_PARAM, CMSSY_SESSION_COOKIE, type CmssyAuthMiddleware, type CmssyAuthRouteHandlers, type CmssyCartRouteHandlers, type CmssyCspOptions, type CmssyDraftRouteConfig, type CmssyEditorProps,
|
|
330
|
+
export { type BuildCmssyMetadataOptions, CMSSY_CART_COOKIE, CMSSY_EDIT_HEADER, CMSSY_EDIT_PATH_PREFIX, CMSSY_EDIT_QUERY_PARAM, CMSSY_LOCALE_HEADER, CMSSY_SECRET_QUERY_PARAM, CMSSY_SESSION_COOKIE, type CmssyAuthMiddleware, type CmssyAuthRouteHandlers, type CmssyCartRouteHandlers, type CmssyCspOptions, type CmssyDraftRouteConfig, type CmssyEditorProps, CmssyNextConfig, type CmssyOrdersRouteHandlers, type CmssySitemapContext, CmssyWebhookError, type CreateCmssyNotFoundOptions, type CreateCmssyPageOptions, type CreateCmssyRobotsOptions, type CreateCmssySitemapOptions, SESSION_MAX_AGE_SECONDS, applyCmssyCsp, buildCmssyMetadata, cmssyCspHeaders, cmssyEditRewrite, createCmssyAuthMiddleware, createCmssyAuthRoute, createCmssyCartRoute, createCmssyEditMiddleware, createCmssyEditPage, createCmssyLocaleMiddleware, createCmssyNotFound, createCmssyOrdersRoute, createCmssyPage, createCmssyRobots, createCmssySitemap, createDraftRoute, fetchOrderByToken, fetchProduct, fetchProducts, getCmssyAccessToken, getCmssyLocale, getCmssyUser, isAccessExpired, isCmssyEditMode, isCmssyEditRequest, localeForPathname, openSession, resolveLocaleFromPathname, sealSession, sessionCookieOptions, splitCmssyLocale, verifyCmssyWebhook };
|
package/dist/index.d.ts
CHANGED
|
@@ -3,70 +3,12 @@ import { ComponentType, ReactNode } from 'react';
|
|
|
3
3
|
import { CmssyPageData, CmssyFormDefinition, BlockDefinition, CmssyClientConfig } from '@cmssy/react';
|
|
4
4
|
export { DEFAULT_CMSSY_API_URL, FieldCondition, FieldConditionGroup, FieldConditionLogic, evaluateFieldConditionGroup, resolveApiUrl } from '@cmssy/react';
|
|
5
5
|
import { EditBridgeConfig } from '@cmssy/react/client';
|
|
6
|
-
import {
|
|
7
|
-
export {
|
|
6
|
+
import { C as CmssyNextConfig } from './config-DNaPqq1f.js';
|
|
7
|
+
export { a as CmssyEnvConfig, D as DEFAULT_CMSSY_EDITOR_ORIGINS, b as assertAuthConfig, d as defineCmssyConfig, r as resolveEditorOrigin } from './config-DNaPqq1f.js';
|
|
8
8
|
import { NextRequest, NextResponse } from 'next/server';
|
|
9
9
|
import { MetadataRoute, Metadata } from 'next';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
declare function resolveEditorOrigin(editorOrigin: string | string[] | undefined): string | string[];
|
|
13
|
-
interface CmssyNextConfig {
|
|
14
|
-
/**
|
|
15
|
-
* Full GraphQL delivery endpoint. Defaults to the cmssy cloud endpoint
|
|
16
|
-
* (`https://api.cmssy.io/graphql`); set it only for self-hosted / staging.
|
|
17
|
-
*/
|
|
18
|
-
apiUrl?: string;
|
|
19
|
-
/** Organization slug - part of the org-scoped delivery path. */
|
|
20
|
-
org: string;
|
|
21
|
-
workspaceSlug: string;
|
|
22
|
-
draftSecret: string;
|
|
23
|
-
/**
|
|
24
|
-
* A cmssy API token (`cs_…`) that opts this app into the editor-controlled dev
|
|
25
|
-
* preview. In development only, the SDK sends it on every page fetch so the
|
|
26
|
-
* backend can resolve the token's user and honour that user's dev-preview flag
|
|
27
|
-
* (toggled from the editor's dev-mode switch): flag on + a saved dev draft ⇒
|
|
28
|
-
* the draft overlay renders, otherwise published content. Server-only (never
|
|
29
|
-
* reaches the client); ignored outside development. See the Quickstart
|
|
30
|
-
* "Dev preview" section.
|
|
31
|
-
*/
|
|
32
|
-
devToken?: string;
|
|
33
|
-
/**
|
|
34
|
-
* Origin allowed to frame your app in the editor. Defaults to
|
|
35
|
-
* {@link DEFAULT_CMSSY_EDITOR_ORIGINS}; set it only for self-hosted admins.
|
|
36
|
-
*/
|
|
37
|
-
editorOrigin?: string | string[];
|
|
38
|
-
/**
|
|
39
|
-
* Canonical absolute site URL (e.g. https://cmssy.com), used by
|
|
40
|
-
* createCmssyRobots / createCmssySitemap. When omitted the helpers derive the
|
|
41
|
-
* origin from the request `host` header at render time (multi-domain safe).
|
|
42
|
-
*/
|
|
43
|
-
siteUrl?: string;
|
|
44
|
-
auth?: CmssyAuthConfig;
|
|
45
|
-
defaultLocale?: string;
|
|
46
|
-
/** All languages enabled on the workspace; exposed to blocks via context.locale.enabled. */
|
|
47
|
-
enabledLocales?: string[];
|
|
48
|
-
resolveLocale?: () => string | Promise<string>;
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Env-shaped input for {@link defineCmssyConfig}: the required string fields are
|
|
52
|
-
* widened to `string | undefined` so a config can pass `process.env.*` straight
|
|
53
|
-
* through without a `?? ""` fallback (which would mask a missing value as an
|
|
54
|
-
* empty string) or a cast.
|
|
55
|
-
*/
|
|
56
|
-
type CmssyEnvConfig = Omit<CmssyNextConfig, "org" | "workspaceSlug" | "draftSecret"> & {
|
|
57
|
-
org?: string;
|
|
58
|
-
workspaceSlug?: string;
|
|
59
|
-
draftSecret?: string;
|
|
60
|
-
};
|
|
61
|
-
/**
|
|
62
|
-
* Validates an env-sourced config and returns a strictly-typed
|
|
63
|
-
* {@link CmssyNextConfig}. Pass raw `process.env.*` values; this throws a clear,
|
|
64
|
-
* actionable error listing any missing required variables (rendered by the
|
|
65
|
-
* Next.js error overlay / boundary), so the app fails fast instead of running
|
|
66
|
-
* with silently-empty config.
|
|
67
|
-
*/
|
|
68
|
-
declare function defineCmssyConfig(config: CmssyEnvConfig): CmssyNextConfig;
|
|
69
|
-
declare function assertAuthConfig(config: CmssyNextConfig): CmssyAuthConfig;
|
|
10
|
+
import { CmssySessionPayload, SessionCookieOptions, CmssySessionUser, FetchProductOptions, CmssyProduct, FetchProductsOptions, CmssyProductPage, FetchOrderByTokenOptions, CmssyOrder, VerifyCmssyWebhookOptions, CmssyWebhookEvent } from '@cmssy/types';
|
|
11
|
+
export { CmssyAuthConfig, CmssyProductPage, CmssySessionPayload, CmssySessionUser, CmssyStockState, CmssyWebhookEvent, CmssyWebhookOrder, FetchOrderByTokenOptions, FetchProductOptions, FetchProductsOptions, MyOrdersResult, SessionCookieOptions, VerifyCmssyWebhookOptions } from '@cmssy/types';
|
|
70
12
|
|
|
71
13
|
interface CmssyEditorProps {
|
|
72
14
|
page: CmssyPageData;
|
|
@@ -385,4 +327,4 @@ declare class CmssyWebhookError extends Error {
|
|
|
385
327
|
*/
|
|
386
328
|
declare function verifyCmssyWebhook(options: VerifyCmssyWebhookOptions): CmssyWebhookEvent;
|
|
387
329
|
|
|
388
|
-
export { type BuildCmssyMetadataOptions, CMSSY_CART_COOKIE, CMSSY_EDIT_HEADER, CMSSY_EDIT_PATH_PREFIX, CMSSY_EDIT_QUERY_PARAM, CMSSY_LOCALE_HEADER, CMSSY_SECRET_QUERY_PARAM, CMSSY_SESSION_COOKIE, type CmssyAuthMiddleware, type CmssyAuthRouteHandlers, type CmssyCartRouteHandlers, type CmssyCspOptions, type CmssyDraftRouteConfig, type CmssyEditorProps,
|
|
330
|
+
export { type BuildCmssyMetadataOptions, CMSSY_CART_COOKIE, CMSSY_EDIT_HEADER, CMSSY_EDIT_PATH_PREFIX, CMSSY_EDIT_QUERY_PARAM, CMSSY_LOCALE_HEADER, CMSSY_SECRET_QUERY_PARAM, CMSSY_SESSION_COOKIE, type CmssyAuthMiddleware, type CmssyAuthRouteHandlers, type CmssyCartRouteHandlers, type CmssyCspOptions, type CmssyDraftRouteConfig, type CmssyEditorProps, CmssyNextConfig, type CmssyOrdersRouteHandlers, type CmssySitemapContext, CmssyWebhookError, type CreateCmssyNotFoundOptions, type CreateCmssyPageOptions, type CreateCmssyRobotsOptions, type CreateCmssySitemapOptions, SESSION_MAX_AGE_SECONDS, applyCmssyCsp, buildCmssyMetadata, cmssyCspHeaders, cmssyEditRewrite, createCmssyAuthMiddleware, createCmssyAuthRoute, createCmssyCartRoute, createCmssyEditMiddleware, createCmssyEditPage, createCmssyLocaleMiddleware, createCmssyNotFound, createCmssyOrdersRoute, createCmssyPage, createCmssyRobots, createCmssySitemap, createDraftRoute, fetchOrderByToken, fetchProduct, fetchProducts, getCmssyAccessToken, getCmssyLocale, getCmssyUser, isAccessExpired, isCmssyEditMode, isCmssyEditRequest, localeForPathname, openSession, resolveLocaleFromPathname, sealSession, sessionCookieOptions, splitCmssyLocale, verifyCmssyWebhook };
|
package/dist/preset.cjs
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var server = require('next/server');
|
|
4
|
+
var react = require('@cmssy/react');
|
|
5
|
+
var headers = require('next/headers');
|
|
6
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
7
|
+
|
|
8
|
+
// src/preset/proxy.ts
|
|
9
|
+
|
|
10
|
+
// src/config.ts
|
|
11
|
+
var DEFAULT_CMSSY_EDITOR_ORIGINS = [
|
|
12
|
+
"https://cmssy.io",
|
|
13
|
+
"https://www.cmssy.io"
|
|
14
|
+
];
|
|
15
|
+
function parseEditorOriginEnv(raw) {
|
|
16
|
+
if (!raw) return void 0;
|
|
17
|
+
const parts = raw.split(",").map((o) => o.trim()).filter((o) => o.length > 0);
|
|
18
|
+
if (parts.length === 0) return void 0;
|
|
19
|
+
return parts.length === 1 ? parts[0] : parts;
|
|
20
|
+
}
|
|
21
|
+
function isDevelopment() {
|
|
22
|
+
return typeof process !== "undefined" && process.env.NODE_ENV === "development";
|
|
23
|
+
}
|
|
24
|
+
function resolveEditorOrigin(editorOrigin) {
|
|
25
|
+
const value = editorOrigin ?? (typeof process !== "undefined" ? parseEditorOriginEnv(process.env.CMSSY_EDITOR_ORIGIN) : void 0);
|
|
26
|
+
if (value === void 0) {
|
|
27
|
+
return isDevelopment() ? "*" : DEFAULT_CMSSY_EDITOR_ORIGINS;
|
|
28
|
+
}
|
|
29
|
+
if (Array.isArray(value)) {
|
|
30
|
+
const cleaned = value.filter((o) => o && o.trim().length > 0);
|
|
31
|
+
return cleaned.length > 0 ? cleaned : DEFAULT_CMSSY_EDITOR_ORIGINS;
|
|
32
|
+
}
|
|
33
|
+
return value.trim().length > 0 ? value : DEFAULT_CMSSY_EDITOR_ORIGINS;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/csp.ts
|
|
37
|
+
function toCspOrigin(origin) {
|
|
38
|
+
if (origin === "*") return "*";
|
|
39
|
+
let parsed;
|
|
40
|
+
try {
|
|
41
|
+
parsed = new URL(origin);
|
|
42
|
+
} catch {
|
|
43
|
+
throw new Error(`cmssy: invalid editorOrigin "${origin}"`);
|
|
44
|
+
}
|
|
45
|
+
if (parsed.origin === "null") {
|
|
46
|
+
throw new Error(`cmssy: editorOrigin "${origin}" has no usable origin`);
|
|
47
|
+
}
|
|
48
|
+
return parsed.origin;
|
|
49
|
+
}
|
|
50
|
+
function frameAncestors(editorOrigin) {
|
|
51
|
+
const resolved = resolveEditorOrigin(editorOrigin);
|
|
52
|
+
const origins = Array.isArray(resolved) ? resolved : [resolved];
|
|
53
|
+
if (origins.length === 0) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
"cmssy: editorOrigin must contain at least one valid origin"
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
const normalized = origins.map((origin) => toCspOrigin(origin.trim()));
|
|
59
|
+
return `frame-ancestors ${normalized.join(" ")}`;
|
|
60
|
+
}
|
|
61
|
+
function mergeFrameAncestors(existing, directive) {
|
|
62
|
+
if (!existing) return directive;
|
|
63
|
+
const kept = existing.split(";").map((part) => part.trim()).filter((part) => part.length > 0 && !/^frame-ancestors\b/i.test(part));
|
|
64
|
+
kept.push(directive);
|
|
65
|
+
return kept.join("; ");
|
|
66
|
+
}
|
|
67
|
+
function applyCmssyCsp(response, options) {
|
|
68
|
+
const merged = mergeFrameAncestors(
|
|
69
|
+
response.headers.get("Content-Security-Policy"),
|
|
70
|
+
frameAncestors(options.editorOrigin)
|
|
71
|
+
);
|
|
72
|
+
response.headers.set("Content-Security-Policy", merged);
|
|
73
|
+
response.headers.delete("X-Frame-Options");
|
|
74
|
+
return response;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/secret-match.ts
|
|
78
|
+
var MAX_SECRET_LENGTH = 256;
|
|
79
|
+
async function cmssySecretsMatch(a, b) {
|
|
80
|
+
if (a.length > MAX_SECRET_LENGTH || b.length > MAX_SECRET_LENGTH) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const encoder = new TextEncoder();
|
|
84
|
+
const [ha, hb] = await Promise.all([
|
|
85
|
+
crypto.subtle.digest("SHA-256", encoder.encode(a)),
|
|
86
|
+
crypto.subtle.digest("SHA-256", encoder.encode(b))
|
|
87
|
+
]);
|
|
88
|
+
const va = new Uint8Array(ha);
|
|
89
|
+
const vb = new Uint8Array(hb);
|
|
90
|
+
let diff = 0;
|
|
91
|
+
for (let i = 0; i < va.length; i += 1) {
|
|
92
|
+
diff |= (va[i] ?? 0) ^ (vb[i] ?? 0);
|
|
93
|
+
}
|
|
94
|
+
return diff === 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/edit-mode.ts
|
|
98
|
+
var CMSSY_EDIT_HEADER = "x-cmssy-edit";
|
|
99
|
+
var CMSSY_EDIT_QUERY_PARAM = "cmssyEdit";
|
|
100
|
+
var CMSSY_SECRET_QUERY_PARAM = "cmssySecret";
|
|
101
|
+
async function isCmssyEditMode() {
|
|
102
|
+
const h = await headers.headers();
|
|
103
|
+
return h.get(CMSSY_EDIT_HEADER) === "1";
|
|
104
|
+
}
|
|
105
|
+
var CMSSY_EDIT_PATH_PREFIX = "/cmssy-edit";
|
|
106
|
+
async function cmssyEditRewrite(request, config, options = {}) {
|
|
107
|
+
const { pathname, searchParams } = request.nextUrl;
|
|
108
|
+
if (pathname.startsWith(CMSSY_EDIT_PATH_PREFIX)) return null;
|
|
109
|
+
if (!searchParams.getAll(CMSSY_EDIT_QUERY_PARAM).includes("1")) return null;
|
|
110
|
+
const provided = searchParams.get(CMSSY_SECRET_QUERY_PARAM);
|
|
111
|
+
if (!provided || !config.draftSecret) return null;
|
|
112
|
+
if (!await cmssySecretsMatch(provided, config.draftSecret)) return null;
|
|
113
|
+
const url = request.nextUrl.clone();
|
|
114
|
+
url.pathname = `${CMSSY_EDIT_PATH_PREFIX}${pathname === "/" ? "" : pathname}`;
|
|
115
|
+
warnIfEditRouteMissing(url);
|
|
116
|
+
return server.NextResponse.rewrite(
|
|
117
|
+
url,
|
|
118
|
+
options.requestHeaders ? { request: { headers: options.requestHeaders } } : void 0
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
var probed = false;
|
|
122
|
+
function warnIfEditRouteMissing(url) {
|
|
123
|
+
if (process.env.NODE_ENV === "production" || probed) return;
|
|
124
|
+
probed = true;
|
|
125
|
+
void fetch(url, { method: "HEAD" }).then((response) => {
|
|
126
|
+
if (response.status !== 404) return;
|
|
127
|
+
console.error(
|
|
128
|
+
`[cmssy] The editor request was rewritten to ${url.pathname}, but nothing is mounted there (404). Add the edit route:
|
|
129
|
+
|
|
130
|
+
// app/cmssy-edit/[[...path]]/page.tsx
|
|
131
|
+
export const dynamic = "force-dynamic";
|
|
132
|
+
export default createCmssyEditPage(cmssy, blocks, { editor: CmssyEditor });
|
|
133
|
+
|
|
134
|
+
Until then the editor preview stays blank.`
|
|
135
|
+
);
|
|
136
|
+
}).catch(() => {
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
var CMSSY_LOCALE_HEADER = "x-cmssy-locale";
|
|
140
|
+
async function localeForPathname(config, pathname) {
|
|
141
|
+
const siteLocales = await react.resolveSiteLocales(config);
|
|
142
|
+
const segments = pathname.split("/").filter(Boolean);
|
|
143
|
+
return react.splitLocaleFromPath(segments, siteLocales).locale;
|
|
144
|
+
}
|
|
145
|
+
async function getCmssyLocale(config, options) {
|
|
146
|
+
const { headers: headers2 } = await import('next/headers');
|
|
147
|
+
const headerList = await headers2();
|
|
148
|
+
const fromHeader = headerList.get(CMSSY_LOCALE_HEADER);
|
|
149
|
+
if (fromHeader) return fromHeader;
|
|
150
|
+
const { defaultLocale } = await react.resolveSiteLocales(config);
|
|
151
|
+
return defaultLocale;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/preset/proxy.ts
|
|
155
|
+
function createCmssyProxy(config, options = {}) {
|
|
156
|
+
return async function cmssyProxy(request) {
|
|
157
|
+
const { pathname } = request.nextUrl;
|
|
158
|
+
const requestHeaders = new Headers(request.headers);
|
|
159
|
+
requestHeaders.delete(CMSSY_EDIT_HEADER);
|
|
160
|
+
requestHeaders.delete(CMSSY_LOCALE_HEADER);
|
|
161
|
+
const locale = await localeForPathname(config, pathname);
|
|
162
|
+
requestHeaders.set(CMSSY_LOCALE_HEADER, locale);
|
|
163
|
+
const editHeaders = new Headers(requestHeaders);
|
|
164
|
+
editHeaders.set(CMSSY_EDIT_HEADER, "1");
|
|
165
|
+
const editRewrite = await cmssyEditRewrite(request, config, {
|
|
166
|
+
requestHeaders: editHeaders
|
|
167
|
+
});
|
|
168
|
+
if (editRewrite) {
|
|
169
|
+
applyCmssyCsp(editRewrite, { editorOrigin: config.editorOrigin });
|
|
170
|
+
return editRewrite;
|
|
171
|
+
}
|
|
172
|
+
if (options.stripLocalePrefix && pathname.startsWith(`/${locale}`)) {
|
|
173
|
+
const { defaultLocale } = await react.resolveSiteLocales(config);
|
|
174
|
+
if (locale !== defaultLocale) {
|
|
175
|
+
const url = request.nextUrl.clone();
|
|
176
|
+
url.pathname = pathname.slice(locale.length + 1) || "/";
|
|
177
|
+
return server.NextResponse.rewrite(url, {
|
|
178
|
+
request: { headers: requestHeaders }
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return server.NextResponse.next({ request: { headers: requestHeaders } });
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
var cmssyProxyMatcher = ["/((?!_next/|api/|.*\\..*).*)"];
|
|
186
|
+
async function CmssyChrome({
|
|
187
|
+
config,
|
|
188
|
+
blocks,
|
|
189
|
+
position,
|
|
190
|
+
page = "/",
|
|
191
|
+
editable
|
|
192
|
+
}) {
|
|
193
|
+
const editMode = await isCmssyEditMode();
|
|
194
|
+
const [groups, locale, siteLocales] = await Promise.all([
|
|
195
|
+
react.fetchLayouts(
|
|
196
|
+
config,
|
|
197
|
+
page,
|
|
198
|
+
editMode ? { previewSecret: config.draftSecret } : void 0
|
|
199
|
+
),
|
|
200
|
+
getCmssyLocale(config),
|
|
201
|
+
react.resolveSiteLocales(config)
|
|
202
|
+
]);
|
|
203
|
+
if (editMode && editable) {
|
|
204
|
+
const origin = resolveEditorOrigin(config.editorOrigin);
|
|
205
|
+
const Editable = editable;
|
|
206
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
207
|
+
Editable,
|
|
208
|
+
{
|
|
209
|
+
groups,
|
|
210
|
+
position,
|
|
211
|
+
locale,
|
|
212
|
+
defaultLocale: siteLocales.defaultLocale,
|
|
213
|
+
enabledLocales: siteLocales.locales,
|
|
214
|
+
edit: {
|
|
215
|
+
editorOrigin: (Array.isArray(origin) ? origin[0] : origin) ?? ""
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
221
|
+
react.CmssyServerLayout,
|
|
222
|
+
{
|
|
223
|
+
groups,
|
|
224
|
+
blocks,
|
|
225
|
+
position,
|
|
226
|
+
locale,
|
|
227
|
+
defaultLocale: siteLocales.defaultLocale,
|
|
228
|
+
enabledLocales: siteLocales.locales
|
|
229
|
+
}
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
exports.CmssyChrome = CmssyChrome;
|
|
234
|
+
exports.cmssyProxyMatcher = cmssyProxyMatcher;
|
|
235
|
+
exports.createCmssyProxy = createCmssyProxy;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { C as CmssyNextConfig } from './config-DNaPqq1f.cjs';
|
|
3
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
4
|
+
import { ComponentType } from 'react';
|
|
5
|
+
import { BlockDefinition, CmssyLayoutGroup } from '@cmssy/react';
|
|
6
|
+
import '@cmssy/types';
|
|
7
|
+
|
|
8
|
+
interface CmssyProxyOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Strip the language prefix before the app sees it, so static routes like
|
|
11
|
+
* `/shop/cart` serve `/no/shop/cart` too. Leave it off for a catch-all app
|
|
12
|
+
* that reads the language off the path itself.
|
|
13
|
+
*/
|
|
14
|
+
stripLocalePrefix?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The whole middleware a cmssy app needs, in the order it has to happen:
|
|
18
|
+
*
|
|
19
|
+
* 1. resolve the language and pass it on (a route cannot read the prefix it
|
|
20
|
+
* never sees);
|
|
21
|
+
* 2. send a VERIFIED editor request to /cmssy-edit, carrying that language and
|
|
22
|
+
* the edit flag - the public pages are static, and a static page never sees
|
|
23
|
+
* the query string that would put it in edit mode;
|
|
24
|
+
* 3. strip the language prefix for everything else, if asked.
|
|
25
|
+
*
|
|
26
|
+
* The order is not a detail: resolve the locale after the rewrite and the editor
|
|
27
|
+
* preview renders in the wrong language; forget the edit flag and the header and
|
|
28
|
+
* footer become markup the editor can select but not fill. Both mistakes shipped
|
|
29
|
+
* before this existed.
|
|
30
|
+
*/
|
|
31
|
+
declare function createCmssyProxy(config: CmssyNextConfig, options?: CmssyProxyOptions): (request: NextRequest) => Promise<NextResponse>;
|
|
32
|
+
/**
|
|
33
|
+
* The matcher a cmssy app wants: everything except Next internals, API routes
|
|
34
|
+
* and files with an extension.
|
|
35
|
+
*
|
|
36
|
+
* Next parses `export const config` at COMPILE time, so it rejects an imported
|
|
37
|
+
* constant - copy this value into your proxy literally:
|
|
38
|
+
*
|
|
39
|
+
* export const config = { matcher: ["/((?!_next/|api/|.*\\..*).*)"] };
|
|
40
|
+
*/
|
|
41
|
+
declare const cmssyProxyMatcher: string[];
|
|
42
|
+
|
|
43
|
+
interface CmssyChromeProps {
|
|
44
|
+
config: CmssyNextConfig;
|
|
45
|
+
blocks: BlockDefinition[];
|
|
46
|
+
position: string;
|
|
47
|
+
/**
|
|
48
|
+
* The page whose layout to render. Defaults to "/" - the site chrome.
|
|
49
|
+
*/
|
|
50
|
+
page?: string;
|
|
51
|
+
/**
|
|
52
|
+
* The client wrapper around CmssyLazyLayout, rendered in edit mode. Without it
|
|
53
|
+
* the header and the footer are markup the editor can select and cannot fill.
|
|
54
|
+
*
|
|
55
|
+
* A COMPONENT, not a function to call: it lives on the client, and the server
|
|
56
|
+
* can only render it.
|
|
57
|
+
*/
|
|
58
|
+
editable?: ComponentType<{
|
|
59
|
+
groups: CmssyLayoutGroup[];
|
|
60
|
+
position: string;
|
|
61
|
+
locale: string;
|
|
62
|
+
defaultLocale: string;
|
|
63
|
+
enabledLocales: string[];
|
|
64
|
+
edit: {
|
|
65
|
+
editorOrigin: string;
|
|
66
|
+
};
|
|
67
|
+
}>;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The site chrome, rendered the way each mode needs it:
|
|
71
|
+
*
|
|
72
|
+
* - published traffic: server-rendered layout blocks (static, no client cost);
|
|
73
|
+
* - the editor: the same blocks through the edit bridge, fetched with the
|
|
74
|
+
* preview secret, so what you see is the draft you are editing.
|
|
75
|
+
*
|
|
76
|
+
* Getting this wrong is invisible - the site looks right and the editor shows a
|
|
77
|
+
* header it cannot edit, or the published version of one. Both shipped before.
|
|
78
|
+
*/
|
|
79
|
+
declare function CmssyChrome({ config, blocks, position, page, editable, }: CmssyChromeProps): Promise<react_jsx_runtime.JSX.Element>;
|
|
80
|
+
|
|
81
|
+
export { CmssyChrome, type CmssyChromeProps, type CmssyProxyOptions, cmssyProxyMatcher, createCmssyProxy };
|
package/dist/preset.d.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { C as CmssyNextConfig } from './config-DNaPqq1f.js';
|
|
3
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
4
|
+
import { ComponentType } from 'react';
|
|
5
|
+
import { BlockDefinition, CmssyLayoutGroup } from '@cmssy/react';
|
|
6
|
+
import '@cmssy/types';
|
|
7
|
+
|
|
8
|
+
interface CmssyProxyOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Strip the language prefix before the app sees it, so static routes like
|
|
11
|
+
* `/shop/cart` serve `/no/shop/cart` too. Leave it off for a catch-all app
|
|
12
|
+
* that reads the language off the path itself.
|
|
13
|
+
*/
|
|
14
|
+
stripLocalePrefix?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The whole middleware a cmssy app needs, in the order it has to happen:
|
|
18
|
+
*
|
|
19
|
+
* 1. resolve the language and pass it on (a route cannot read the prefix it
|
|
20
|
+
* never sees);
|
|
21
|
+
* 2. send a VERIFIED editor request to /cmssy-edit, carrying that language and
|
|
22
|
+
* the edit flag - the public pages are static, and a static page never sees
|
|
23
|
+
* the query string that would put it in edit mode;
|
|
24
|
+
* 3. strip the language prefix for everything else, if asked.
|
|
25
|
+
*
|
|
26
|
+
* The order is not a detail: resolve the locale after the rewrite and the editor
|
|
27
|
+
* preview renders in the wrong language; forget the edit flag and the header and
|
|
28
|
+
* footer become markup the editor can select but not fill. Both mistakes shipped
|
|
29
|
+
* before this existed.
|
|
30
|
+
*/
|
|
31
|
+
declare function createCmssyProxy(config: CmssyNextConfig, options?: CmssyProxyOptions): (request: NextRequest) => Promise<NextResponse>;
|
|
32
|
+
/**
|
|
33
|
+
* The matcher a cmssy app wants: everything except Next internals, API routes
|
|
34
|
+
* and files with an extension.
|
|
35
|
+
*
|
|
36
|
+
* Next parses `export const config` at COMPILE time, so it rejects an imported
|
|
37
|
+
* constant - copy this value into your proxy literally:
|
|
38
|
+
*
|
|
39
|
+
* export const config = { matcher: ["/((?!_next/|api/|.*\\..*).*)"] };
|
|
40
|
+
*/
|
|
41
|
+
declare const cmssyProxyMatcher: string[];
|
|
42
|
+
|
|
43
|
+
interface CmssyChromeProps {
|
|
44
|
+
config: CmssyNextConfig;
|
|
45
|
+
blocks: BlockDefinition[];
|
|
46
|
+
position: string;
|
|
47
|
+
/**
|
|
48
|
+
* The page whose layout to render. Defaults to "/" - the site chrome.
|
|
49
|
+
*/
|
|
50
|
+
page?: string;
|
|
51
|
+
/**
|
|
52
|
+
* The client wrapper around CmssyLazyLayout, rendered in edit mode. Without it
|
|
53
|
+
* the header and the footer are markup the editor can select and cannot fill.
|
|
54
|
+
*
|
|
55
|
+
* A COMPONENT, not a function to call: it lives on the client, and the server
|
|
56
|
+
* can only render it.
|
|
57
|
+
*/
|
|
58
|
+
editable?: ComponentType<{
|
|
59
|
+
groups: CmssyLayoutGroup[];
|
|
60
|
+
position: string;
|
|
61
|
+
locale: string;
|
|
62
|
+
defaultLocale: string;
|
|
63
|
+
enabledLocales: string[];
|
|
64
|
+
edit: {
|
|
65
|
+
editorOrigin: string;
|
|
66
|
+
};
|
|
67
|
+
}>;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The site chrome, rendered the way each mode needs it:
|
|
71
|
+
*
|
|
72
|
+
* - published traffic: server-rendered layout blocks (static, no client cost);
|
|
73
|
+
* - the editor: the same blocks through the edit bridge, fetched with the
|
|
74
|
+
* preview secret, so what you see is the draft you are editing.
|
|
75
|
+
*
|
|
76
|
+
* Getting this wrong is invisible - the site looks right and the editor shows a
|
|
77
|
+
* header it cannot edit, or the published version of one. Both shipped before.
|
|
78
|
+
*/
|
|
79
|
+
declare function CmssyChrome({ config, blocks, position, page, editable, }: CmssyChromeProps): Promise<react_jsx_runtime.JSX.Element>;
|
|
80
|
+
|
|
81
|
+
export { CmssyChrome, type CmssyChromeProps, type CmssyProxyOptions, cmssyProxyMatcher, createCmssyProxy };
|
package/dist/preset.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { resolveSiteLocales, fetchLayouts, CmssyServerLayout, splitLocaleFromPath } from '@cmssy/react';
|
|
3
|
+
import { headers } from 'next/headers';
|
|
4
|
+
import { jsx } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
// src/preset/proxy.ts
|
|
7
|
+
|
|
8
|
+
// src/config.ts
|
|
9
|
+
var DEFAULT_CMSSY_EDITOR_ORIGINS = [
|
|
10
|
+
"https://cmssy.io",
|
|
11
|
+
"https://www.cmssy.io"
|
|
12
|
+
];
|
|
13
|
+
function parseEditorOriginEnv(raw) {
|
|
14
|
+
if (!raw) return void 0;
|
|
15
|
+
const parts = raw.split(",").map((o) => o.trim()).filter((o) => o.length > 0);
|
|
16
|
+
if (parts.length === 0) return void 0;
|
|
17
|
+
return parts.length === 1 ? parts[0] : parts;
|
|
18
|
+
}
|
|
19
|
+
function isDevelopment() {
|
|
20
|
+
return typeof process !== "undefined" && process.env.NODE_ENV === "development";
|
|
21
|
+
}
|
|
22
|
+
function resolveEditorOrigin(editorOrigin) {
|
|
23
|
+
const value = editorOrigin ?? (typeof process !== "undefined" ? parseEditorOriginEnv(process.env.CMSSY_EDITOR_ORIGIN) : void 0);
|
|
24
|
+
if (value === void 0) {
|
|
25
|
+
return isDevelopment() ? "*" : DEFAULT_CMSSY_EDITOR_ORIGINS;
|
|
26
|
+
}
|
|
27
|
+
if (Array.isArray(value)) {
|
|
28
|
+
const cleaned = value.filter((o) => o && o.trim().length > 0);
|
|
29
|
+
return cleaned.length > 0 ? cleaned : DEFAULT_CMSSY_EDITOR_ORIGINS;
|
|
30
|
+
}
|
|
31
|
+
return value.trim().length > 0 ? value : DEFAULT_CMSSY_EDITOR_ORIGINS;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/csp.ts
|
|
35
|
+
function toCspOrigin(origin) {
|
|
36
|
+
if (origin === "*") return "*";
|
|
37
|
+
let parsed;
|
|
38
|
+
try {
|
|
39
|
+
parsed = new URL(origin);
|
|
40
|
+
} catch {
|
|
41
|
+
throw new Error(`cmssy: invalid editorOrigin "${origin}"`);
|
|
42
|
+
}
|
|
43
|
+
if (parsed.origin === "null") {
|
|
44
|
+
throw new Error(`cmssy: editorOrigin "${origin}" has no usable origin`);
|
|
45
|
+
}
|
|
46
|
+
return parsed.origin;
|
|
47
|
+
}
|
|
48
|
+
function frameAncestors(editorOrigin) {
|
|
49
|
+
const resolved = resolveEditorOrigin(editorOrigin);
|
|
50
|
+
const origins = Array.isArray(resolved) ? resolved : [resolved];
|
|
51
|
+
if (origins.length === 0) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
"cmssy: editorOrigin must contain at least one valid origin"
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
const normalized = origins.map((origin) => toCspOrigin(origin.trim()));
|
|
57
|
+
return `frame-ancestors ${normalized.join(" ")}`;
|
|
58
|
+
}
|
|
59
|
+
function mergeFrameAncestors(existing, directive) {
|
|
60
|
+
if (!existing) return directive;
|
|
61
|
+
const kept = existing.split(";").map((part) => part.trim()).filter((part) => part.length > 0 && !/^frame-ancestors\b/i.test(part));
|
|
62
|
+
kept.push(directive);
|
|
63
|
+
return kept.join("; ");
|
|
64
|
+
}
|
|
65
|
+
function applyCmssyCsp(response, options) {
|
|
66
|
+
const merged = mergeFrameAncestors(
|
|
67
|
+
response.headers.get("Content-Security-Policy"),
|
|
68
|
+
frameAncestors(options.editorOrigin)
|
|
69
|
+
);
|
|
70
|
+
response.headers.set("Content-Security-Policy", merged);
|
|
71
|
+
response.headers.delete("X-Frame-Options");
|
|
72
|
+
return response;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/secret-match.ts
|
|
76
|
+
var MAX_SECRET_LENGTH = 256;
|
|
77
|
+
async function cmssySecretsMatch(a, b) {
|
|
78
|
+
if (a.length > MAX_SECRET_LENGTH || b.length > MAX_SECRET_LENGTH) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
const encoder = new TextEncoder();
|
|
82
|
+
const [ha, hb] = await Promise.all([
|
|
83
|
+
crypto.subtle.digest("SHA-256", encoder.encode(a)),
|
|
84
|
+
crypto.subtle.digest("SHA-256", encoder.encode(b))
|
|
85
|
+
]);
|
|
86
|
+
const va = new Uint8Array(ha);
|
|
87
|
+
const vb = new Uint8Array(hb);
|
|
88
|
+
let diff = 0;
|
|
89
|
+
for (let i = 0; i < va.length; i += 1) {
|
|
90
|
+
diff |= (va[i] ?? 0) ^ (vb[i] ?? 0);
|
|
91
|
+
}
|
|
92
|
+
return diff === 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/edit-mode.ts
|
|
96
|
+
var CMSSY_EDIT_HEADER = "x-cmssy-edit";
|
|
97
|
+
var CMSSY_EDIT_QUERY_PARAM = "cmssyEdit";
|
|
98
|
+
var CMSSY_SECRET_QUERY_PARAM = "cmssySecret";
|
|
99
|
+
async function isCmssyEditMode() {
|
|
100
|
+
const h = await headers();
|
|
101
|
+
return h.get(CMSSY_EDIT_HEADER) === "1";
|
|
102
|
+
}
|
|
103
|
+
var CMSSY_EDIT_PATH_PREFIX = "/cmssy-edit";
|
|
104
|
+
async function cmssyEditRewrite(request, config, options = {}) {
|
|
105
|
+
const { pathname, searchParams } = request.nextUrl;
|
|
106
|
+
if (pathname.startsWith(CMSSY_EDIT_PATH_PREFIX)) return null;
|
|
107
|
+
if (!searchParams.getAll(CMSSY_EDIT_QUERY_PARAM).includes("1")) return null;
|
|
108
|
+
const provided = searchParams.get(CMSSY_SECRET_QUERY_PARAM);
|
|
109
|
+
if (!provided || !config.draftSecret) return null;
|
|
110
|
+
if (!await cmssySecretsMatch(provided, config.draftSecret)) return null;
|
|
111
|
+
const url = request.nextUrl.clone();
|
|
112
|
+
url.pathname = `${CMSSY_EDIT_PATH_PREFIX}${pathname === "/" ? "" : pathname}`;
|
|
113
|
+
warnIfEditRouteMissing(url);
|
|
114
|
+
return NextResponse.rewrite(
|
|
115
|
+
url,
|
|
116
|
+
options.requestHeaders ? { request: { headers: options.requestHeaders } } : void 0
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
var probed = false;
|
|
120
|
+
function warnIfEditRouteMissing(url) {
|
|
121
|
+
if (process.env.NODE_ENV === "production" || probed) return;
|
|
122
|
+
probed = true;
|
|
123
|
+
void fetch(url, { method: "HEAD" }).then((response) => {
|
|
124
|
+
if (response.status !== 404) return;
|
|
125
|
+
console.error(
|
|
126
|
+
`[cmssy] The editor request was rewritten to ${url.pathname}, but nothing is mounted there (404). Add the edit route:
|
|
127
|
+
|
|
128
|
+
// app/cmssy-edit/[[...path]]/page.tsx
|
|
129
|
+
export const dynamic = "force-dynamic";
|
|
130
|
+
export default createCmssyEditPage(cmssy, blocks, { editor: CmssyEditor });
|
|
131
|
+
|
|
132
|
+
Until then the editor preview stays blank.`
|
|
133
|
+
);
|
|
134
|
+
}).catch(() => {
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
var CMSSY_LOCALE_HEADER = "x-cmssy-locale";
|
|
138
|
+
async function localeForPathname(config, pathname) {
|
|
139
|
+
const siteLocales = await resolveSiteLocales(config);
|
|
140
|
+
const segments = pathname.split("/").filter(Boolean);
|
|
141
|
+
return splitLocaleFromPath(segments, siteLocales).locale;
|
|
142
|
+
}
|
|
143
|
+
async function getCmssyLocale(config, options) {
|
|
144
|
+
const { headers: headers2 } = await import('next/headers');
|
|
145
|
+
const headerList = await headers2();
|
|
146
|
+
const fromHeader = headerList.get(CMSSY_LOCALE_HEADER);
|
|
147
|
+
if (fromHeader) return fromHeader;
|
|
148
|
+
const { defaultLocale } = await resolveSiteLocales(config);
|
|
149
|
+
return defaultLocale;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/preset/proxy.ts
|
|
153
|
+
function createCmssyProxy(config, options = {}) {
|
|
154
|
+
return async function cmssyProxy(request) {
|
|
155
|
+
const { pathname } = request.nextUrl;
|
|
156
|
+
const requestHeaders = new Headers(request.headers);
|
|
157
|
+
requestHeaders.delete(CMSSY_EDIT_HEADER);
|
|
158
|
+
requestHeaders.delete(CMSSY_LOCALE_HEADER);
|
|
159
|
+
const locale = await localeForPathname(config, pathname);
|
|
160
|
+
requestHeaders.set(CMSSY_LOCALE_HEADER, locale);
|
|
161
|
+
const editHeaders = new Headers(requestHeaders);
|
|
162
|
+
editHeaders.set(CMSSY_EDIT_HEADER, "1");
|
|
163
|
+
const editRewrite = await cmssyEditRewrite(request, config, {
|
|
164
|
+
requestHeaders: editHeaders
|
|
165
|
+
});
|
|
166
|
+
if (editRewrite) {
|
|
167
|
+
applyCmssyCsp(editRewrite, { editorOrigin: config.editorOrigin });
|
|
168
|
+
return editRewrite;
|
|
169
|
+
}
|
|
170
|
+
if (options.stripLocalePrefix && pathname.startsWith(`/${locale}`)) {
|
|
171
|
+
const { defaultLocale } = await resolveSiteLocales(config);
|
|
172
|
+
if (locale !== defaultLocale) {
|
|
173
|
+
const url = request.nextUrl.clone();
|
|
174
|
+
url.pathname = pathname.slice(locale.length + 1) || "/";
|
|
175
|
+
return NextResponse.rewrite(url, {
|
|
176
|
+
request: { headers: requestHeaders }
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return NextResponse.next({ request: { headers: requestHeaders } });
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
var cmssyProxyMatcher = ["/((?!_next/|api/|.*\\..*).*)"];
|
|
184
|
+
async function CmssyChrome({
|
|
185
|
+
config,
|
|
186
|
+
blocks,
|
|
187
|
+
position,
|
|
188
|
+
page = "/",
|
|
189
|
+
editable
|
|
190
|
+
}) {
|
|
191
|
+
const editMode = await isCmssyEditMode();
|
|
192
|
+
const [groups, locale, siteLocales] = await Promise.all([
|
|
193
|
+
fetchLayouts(
|
|
194
|
+
config,
|
|
195
|
+
page,
|
|
196
|
+
editMode ? { previewSecret: config.draftSecret } : void 0
|
|
197
|
+
),
|
|
198
|
+
getCmssyLocale(config),
|
|
199
|
+
resolveSiteLocales(config)
|
|
200
|
+
]);
|
|
201
|
+
if (editMode && editable) {
|
|
202
|
+
const origin = resolveEditorOrigin(config.editorOrigin);
|
|
203
|
+
const Editable = editable;
|
|
204
|
+
return /* @__PURE__ */ jsx(
|
|
205
|
+
Editable,
|
|
206
|
+
{
|
|
207
|
+
groups,
|
|
208
|
+
position,
|
|
209
|
+
locale,
|
|
210
|
+
defaultLocale: siteLocales.defaultLocale,
|
|
211
|
+
enabledLocales: siteLocales.locales,
|
|
212
|
+
edit: {
|
|
213
|
+
editorOrigin: (Array.isArray(origin) ? origin[0] : origin) ?? ""
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
return /* @__PURE__ */ jsx(
|
|
219
|
+
CmssyServerLayout,
|
|
220
|
+
{
|
|
221
|
+
groups,
|
|
222
|
+
blocks,
|
|
223
|
+
position,
|
|
224
|
+
locale,
|
|
225
|
+
defaultLocale: siteLocales.defaultLocale,
|
|
226
|
+
enabledLocales: siteLocales.locales
|
|
227
|
+
}
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export { CmssyChrome, cmssyProxyMatcher, createCmssyProxy };
|
package/dist/testing.cjs
CHANGED
|
@@ -18,11 +18,7 @@ async function checkCmssyEditMode(options) {
|
|
|
18
18
|
if (EDITOR_MARKER.test(publicPage.body)) {
|
|
19
19
|
failures.push(`public ${path}: the editor is mounted on a public page`);
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
failures.push(
|
|
23
|
-
`public ${path}: no server-rendered chrome - the layout blocks are missing`
|
|
24
|
-
);
|
|
25
|
-
}
|
|
21
|
+
const hasServerChrome = SERVER_CHROME.test(publicPage.body);
|
|
26
22
|
const unverified = await html(url(`${path}?cmssyEdit=1`));
|
|
27
23
|
if (EDITOR_MARKER.test(unverified.body)) {
|
|
28
24
|
failures.push(
|
|
@@ -40,7 +36,7 @@ async function checkCmssyEditMode(options) {
|
|
|
40
36
|
`edit ${path}: no editor in the response - is the /cmssy-edit route mounted?`
|
|
41
37
|
);
|
|
42
38
|
}
|
|
43
|
-
if (SERVER_CHROME.test(verified.body)) {
|
|
39
|
+
if (hasServerChrome && SERVER_CHROME.test(verified.body)) {
|
|
44
40
|
failures.push(
|
|
45
41
|
`edit ${path}: the chrome is still server-rendered - the header and footer will be selectable but have no fields (is CMSSY_EDIT_HEADER set on the rewrite?)`
|
|
46
42
|
);
|
package/dist/testing.js
CHANGED
|
@@ -16,11 +16,7 @@ async function checkCmssyEditMode(options) {
|
|
|
16
16
|
if (EDITOR_MARKER.test(publicPage.body)) {
|
|
17
17
|
failures.push(`public ${path}: the editor is mounted on a public page`);
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
failures.push(
|
|
21
|
-
`public ${path}: no server-rendered chrome - the layout blocks are missing`
|
|
22
|
-
);
|
|
23
|
-
}
|
|
19
|
+
const hasServerChrome = SERVER_CHROME.test(publicPage.body);
|
|
24
20
|
const unverified = await html(url(`${path}?cmssyEdit=1`));
|
|
25
21
|
if (EDITOR_MARKER.test(unverified.body)) {
|
|
26
22
|
failures.push(
|
|
@@ -38,7 +34,7 @@ async function checkCmssyEditMode(options) {
|
|
|
38
34
|
`edit ${path}: no editor in the response - is the /cmssy-edit route mounted?`
|
|
39
35
|
);
|
|
40
36
|
}
|
|
41
|
-
if (SERVER_CHROME.test(verified.body)) {
|
|
37
|
+
if (hasServerChrome && SERVER_CHROME.test(verified.body)) {
|
|
42
38
|
failures.push(
|
|
43
39
|
`edit ${path}: the chrome is still server-rendered - the header and footer will be selectable but have no fields (is CMSSY_EDIT_HEADER set on the rewrite?)`
|
|
44
40
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmssy/next",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.7.1",
|
|
4
4
|
"description": "Next.js App Router bindings for cmssy headless sites (createCmssyPage + draft preview)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cmssy",
|
|
@@ -37,6 +37,11 @@
|
|
|
37
37
|
"types": "./dist/testing.d.ts",
|
|
38
38
|
"import": "./dist/testing.js",
|
|
39
39
|
"require": "./dist/testing.cjs"
|
|
40
|
+
},
|
|
41
|
+
"./preset": {
|
|
42
|
+
"types": "./dist/preset.d.ts",
|
|
43
|
+
"import": "./dist/preset.js",
|
|
44
|
+
"require": "./dist/preset.cjs"
|
|
40
45
|
}
|
|
41
46
|
},
|
|
42
47
|
"main": "./dist/index.cjs",
|
|
@@ -46,7 +51,7 @@
|
|
|
46
51
|
"dist"
|
|
47
52
|
],
|
|
48
53
|
"peerDependencies": {
|
|
49
|
-
"@cmssy/react": "^4.
|
|
54
|
+
"@cmssy/react": "^4.7.1",
|
|
50
55
|
"next": ">=15",
|
|
51
56
|
"react": "^18.2.0 || ^19.0.0",
|
|
52
57
|
"react-dom": "^18.2.0 || ^19.0.0"
|
|
@@ -60,7 +65,7 @@
|
|
|
60
65
|
"tsup": "^8.3.0",
|
|
61
66
|
"typescript": "^5.6.0",
|
|
62
67
|
"vitest": "^2.1.0",
|
|
63
|
-
"@cmssy/react": "4.
|
|
68
|
+
"@cmssy/react": "4.7.1"
|
|
64
69
|
},
|
|
65
70
|
"dependencies": {
|
|
66
71
|
"jose": "^6.2.3",
|