@cmssy/core 8.0.4 → 9.1.0
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/content-client-D0EdiqbQ.d.cts +63 -0
- package/dist/content-client-D0EdiqbQ.d.ts +63 -0
- package/dist/index.cjs +10 -13
- package/dist/index.d.cts +18 -80
- package/dist/index.d.ts +18 -80
- package/dist/index.js +10 -13
- package/dist/preflight.cjs +273 -0
- package/dist/preflight.d.cts +23 -0
- package/dist/preflight.d.ts +23 -0
- package/dist/preflight.js +267 -0
- package/package.json +6 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { CmssyClientConfig, CmssyLayoutGroup, CmssyPageData, CmssyPageMeta, CmssyPageSummary } from '@cmssy/types';
|
|
2
|
+
|
|
3
|
+
/** HTTP-level failure from the cmssy API, with a machine-readable status. */
|
|
4
|
+
declare class CmssyRequestError extends Error {
|
|
5
|
+
readonly status: number;
|
|
6
|
+
constructor(message: string, status: number);
|
|
7
|
+
}
|
|
8
|
+
interface RetryPolicy {
|
|
9
|
+
/** Additional attempts after the first request (default 3). */
|
|
10
|
+
maxRetries?: number;
|
|
11
|
+
/** Exponential backoff base in ms: base * 2^attempt (default 300). */
|
|
12
|
+
baseDelayMs?: number;
|
|
13
|
+
/** Upper bound for any single wait, including Retry-After (default 3000). */
|
|
14
|
+
maxDelayMs?: number;
|
|
15
|
+
/** HTTP statuses that trigger a retry (default [429, 503]). */
|
|
16
|
+
retryStatuses?: number[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The cmssy cloud GraphQL delivery endpoint. It is the same for every workspace,
|
|
21
|
+
* so consumers never need to set it - `apiUrl` defaults to this. Self-hosted /
|
|
22
|
+
* staging deployments override it via config.
|
|
23
|
+
*/
|
|
24
|
+
declare const DEFAULT_CMSSY_API_URL = "https://api.cmssy.io/graphql";
|
|
25
|
+
declare function resolveApiUrl(apiUrl: string | undefined): string;
|
|
26
|
+
/**
|
|
27
|
+
* Public delivery endpoint for a workspace: the org-scoped path
|
|
28
|
+
* `{base}/public/{orgSlug}/{workspaceSlug}/graphql`. `apiUrl` is the GraphQL
|
|
29
|
+
* base (its trailing `/graphql` is stripped); the org path is what tells the
|
|
30
|
+
* backend which workspace to serve, so slugs only need to be unique per org.
|
|
31
|
+
*/
|
|
32
|
+
declare function resolvePublicUrl(config: CmssyClientConfig): string;
|
|
33
|
+
interface FetchLikeResponse {
|
|
34
|
+
ok: boolean;
|
|
35
|
+
status: number;
|
|
36
|
+
json: () => Promise<unknown>;
|
|
37
|
+
headers?: {
|
|
38
|
+
get: (name: string) => string | null;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
type FetchLike = (url: string, init: {
|
|
42
|
+
method: string;
|
|
43
|
+
headers: Record<string, string>;
|
|
44
|
+
body: string;
|
|
45
|
+
signal?: AbortSignal;
|
|
46
|
+
}) => Promise<FetchLikeResponse>;
|
|
47
|
+
interface FetchPageOptions {
|
|
48
|
+
previewSecret?: string;
|
|
49
|
+
devPreview?: boolean;
|
|
50
|
+
devToken?: string;
|
|
51
|
+
workspaceId?: string;
|
|
52
|
+
fetch?: FetchLike;
|
|
53
|
+
signal?: AbortSignal;
|
|
54
|
+
retry?: RetryPolicy | false;
|
|
55
|
+
}
|
|
56
|
+
declare function normalizeSlug(path: string | string[] | undefined): string;
|
|
57
|
+
declare function fetchPage(config: CmssyClientConfig, path: string | string[] | undefined, options?: FetchPageOptions): Promise<CmssyPageData | null>;
|
|
58
|
+
declare function fetchPageById(config: CmssyClientConfig, pageId: string, options?: Pick<FetchPageOptions, "fetch" | "signal" | "retry">): Promise<CmssyPageData | null>;
|
|
59
|
+
declare function fetchPages(config: CmssyClientConfig, options?: Pick<FetchPageOptions, "fetch" | "signal" | "retry">): Promise<CmssyPageSummary[]>;
|
|
60
|
+
declare function fetchPageMeta(config: CmssyClientConfig, path: string | string[] | undefined, options?: Pick<FetchPageOptions, "fetch" | "signal" | "retry">): Promise<CmssyPageMeta | null>;
|
|
61
|
+
declare function fetchLayouts(config: CmssyClientConfig, path: string | string[] | undefined, options?: FetchPageOptions): Promise<CmssyLayoutGroup[]>;
|
|
62
|
+
|
|
63
|
+
export { CmssyRequestError as C, DEFAULT_CMSSY_API_URL as D, type FetchLike as F, type RetryPolicy as R, type FetchLikeResponse as a, type FetchPageOptions as b, fetchPage as c, fetchPageById as d, fetchPageMeta as e, fetchLayouts as f, fetchPages as g, resolvePublicUrl as h, normalizeSlug as n, resolveApiUrl as r };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { CmssyClientConfig, CmssyLayoutGroup, CmssyPageData, CmssyPageMeta, CmssyPageSummary } from '@cmssy/types';
|
|
2
|
+
|
|
3
|
+
/** HTTP-level failure from the cmssy API, with a machine-readable status. */
|
|
4
|
+
declare class CmssyRequestError extends Error {
|
|
5
|
+
readonly status: number;
|
|
6
|
+
constructor(message: string, status: number);
|
|
7
|
+
}
|
|
8
|
+
interface RetryPolicy {
|
|
9
|
+
/** Additional attempts after the first request (default 3). */
|
|
10
|
+
maxRetries?: number;
|
|
11
|
+
/** Exponential backoff base in ms: base * 2^attempt (default 300). */
|
|
12
|
+
baseDelayMs?: number;
|
|
13
|
+
/** Upper bound for any single wait, including Retry-After (default 3000). */
|
|
14
|
+
maxDelayMs?: number;
|
|
15
|
+
/** HTTP statuses that trigger a retry (default [429, 503]). */
|
|
16
|
+
retryStatuses?: number[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The cmssy cloud GraphQL delivery endpoint. It is the same for every workspace,
|
|
21
|
+
* so consumers never need to set it - `apiUrl` defaults to this. Self-hosted /
|
|
22
|
+
* staging deployments override it via config.
|
|
23
|
+
*/
|
|
24
|
+
declare const DEFAULT_CMSSY_API_URL = "https://api.cmssy.io/graphql";
|
|
25
|
+
declare function resolveApiUrl(apiUrl: string | undefined): string;
|
|
26
|
+
/**
|
|
27
|
+
* Public delivery endpoint for a workspace: the org-scoped path
|
|
28
|
+
* `{base}/public/{orgSlug}/{workspaceSlug}/graphql`. `apiUrl` is the GraphQL
|
|
29
|
+
* base (its trailing `/graphql` is stripped); the org path is what tells the
|
|
30
|
+
* backend which workspace to serve, so slugs only need to be unique per org.
|
|
31
|
+
*/
|
|
32
|
+
declare function resolvePublicUrl(config: CmssyClientConfig): string;
|
|
33
|
+
interface FetchLikeResponse {
|
|
34
|
+
ok: boolean;
|
|
35
|
+
status: number;
|
|
36
|
+
json: () => Promise<unknown>;
|
|
37
|
+
headers?: {
|
|
38
|
+
get: (name: string) => string | null;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
type FetchLike = (url: string, init: {
|
|
42
|
+
method: string;
|
|
43
|
+
headers: Record<string, string>;
|
|
44
|
+
body: string;
|
|
45
|
+
signal?: AbortSignal;
|
|
46
|
+
}) => Promise<FetchLikeResponse>;
|
|
47
|
+
interface FetchPageOptions {
|
|
48
|
+
previewSecret?: string;
|
|
49
|
+
devPreview?: boolean;
|
|
50
|
+
devToken?: string;
|
|
51
|
+
workspaceId?: string;
|
|
52
|
+
fetch?: FetchLike;
|
|
53
|
+
signal?: AbortSignal;
|
|
54
|
+
retry?: RetryPolicy | false;
|
|
55
|
+
}
|
|
56
|
+
declare function normalizeSlug(path: string | string[] | undefined): string;
|
|
57
|
+
declare function fetchPage(config: CmssyClientConfig, path: string | string[] | undefined, options?: FetchPageOptions): Promise<CmssyPageData | null>;
|
|
58
|
+
declare function fetchPageById(config: CmssyClientConfig, pageId: string, options?: Pick<FetchPageOptions, "fetch" | "signal" | "retry">): Promise<CmssyPageData | null>;
|
|
59
|
+
declare function fetchPages(config: CmssyClientConfig, options?: Pick<FetchPageOptions, "fetch" | "signal" | "retry">): Promise<CmssyPageSummary[]>;
|
|
60
|
+
declare function fetchPageMeta(config: CmssyClientConfig, path: string | string[] | undefined, options?: Pick<FetchPageOptions, "fetch" | "signal" | "retry">): Promise<CmssyPageMeta | null>;
|
|
61
|
+
declare function fetchLayouts(config: CmssyClientConfig, path: string | string[] | undefined, options?: FetchPageOptions): Promise<CmssyLayoutGroup[]>;
|
|
62
|
+
|
|
63
|
+
export { CmssyRequestError as C, DEFAULT_CMSSY_API_URL as D, type FetchLike as F, type RetryPolicy as R, type FetchLikeResponse as a, type FetchPageOptions as b, fetchPage as c, fetchPageById as d, fetchPageMeta as e, fetchLayouts as f, fetchPages as g, resolvePublicUrl as h, normalizeSlug as n, resolveApiUrl as r };
|
package/dist/index.cjs
CHANGED
|
@@ -663,6 +663,14 @@ async function resolveForms(config, blocks, locale, defaultLocale, options) {
|
|
|
663
663
|
var TTL_MS = 6e4;
|
|
664
664
|
var MAX_ENTRIES = 64;
|
|
665
665
|
var cache = /* @__PURE__ */ new Map();
|
|
666
|
+
function localesFromSiteConfig(siteConfig) {
|
|
667
|
+
const defaultLocale = siteConfig?.defaultLanguage || "en";
|
|
668
|
+
const enabled = siteConfig?.enabledLanguages ?? [];
|
|
669
|
+
return {
|
|
670
|
+
defaultLocale,
|
|
671
|
+
locales: enabled.length > 0 ? enabled : [defaultLocale]
|
|
672
|
+
};
|
|
673
|
+
}
|
|
666
674
|
async function resolveSiteLocales(config, options) {
|
|
667
675
|
const key = `${resolveApiUrl(config.apiUrl)}::${config.org}::${config.workspaceSlug}`;
|
|
668
676
|
const cached = cache.get(key);
|
|
@@ -677,13 +685,7 @@ async function resolveSiteLocales(config, options) {
|
|
|
677
685
|
{ ...options, public: true, retry: options?.retry ?? {} },
|
|
678
686
|
"site config"
|
|
679
687
|
);
|
|
680
|
-
|
|
681
|
-
const defaultLocale = siteConfig?.defaultLanguage || "en";
|
|
682
|
-
const enabled = siteConfig?.enabledLanguages ?? [];
|
|
683
|
-
value = {
|
|
684
|
-
defaultLocale,
|
|
685
|
-
locales: enabled.length > 0 ? enabled : [defaultLocale]
|
|
686
|
-
};
|
|
688
|
+
value = localesFromSiteConfig(data.public?.siteConfig ?? null);
|
|
687
689
|
} catch {
|
|
688
690
|
value = { defaultLocale: "en", locales: ["en"] };
|
|
689
691
|
}
|
|
@@ -986,11 +988,6 @@ function applyCmssyCsp(response, options) {
|
|
|
986
988
|
}
|
|
987
989
|
|
|
988
990
|
// src/seo-paths.ts
|
|
989
|
-
function resolveSeoLocales(config, siteConfig) {
|
|
990
|
-
const defaultLocale = config.defaultLocale ?? siteConfig?.defaultLanguage ?? "en";
|
|
991
|
-
const locales = config.enabledLocales && config.enabledLocales.length > 0 ? config.enabledLocales : siteConfig?.enabledLanguages && siteConfig.enabledLanguages.length > 0 ? siteConfig.enabledLanguages : [defaultLocale];
|
|
992
|
-
return { defaultLocale, locales };
|
|
993
|
-
}
|
|
994
991
|
function normalizeSlug2(slug) {
|
|
995
992
|
if (slug === "/" || slug === "") return "/";
|
|
996
993
|
return slug.startsWith("/") ? slug : `/${slug}`;
|
|
@@ -1809,6 +1806,7 @@ exports.isProtocolCompatible = isProtocolCompatible;
|
|
|
1809
1806
|
exports.isVerifiedEditUrl = isVerifiedEditUrl;
|
|
1810
1807
|
exports.localeForPath = localeForPath;
|
|
1811
1808
|
exports.localeForPathname = localeForPathname;
|
|
1809
|
+
exports.localesFromSiteConfig = localesFromSiteConfig;
|
|
1812
1810
|
exports.localizeHref = localizeHref;
|
|
1813
1811
|
exports.localizeHtmlLinks = localizeHtmlLinks;
|
|
1814
1812
|
exports.localizedPath = localizedPath;
|
|
@@ -1822,7 +1820,6 @@ exports.resolveEditorOrigin = resolveEditorOrigin;
|
|
|
1822
1820
|
exports.resolveForms = resolveForms;
|
|
1823
1821
|
exports.resolveInitialTarget = resolveInitialTarget;
|
|
1824
1822
|
exports.resolvePublicUrl = resolvePublicUrl;
|
|
1825
|
-
exports.resolveSeoLocales = resolveSeoLocales;
|
|
1826
1823
|
exports.resolveSiteLocales = resolveSiteLocales;
|
|
1827
1824
|
exports.resolveWorkspaceId = resolveWorkspaceId;
|
|
1828
1825
|
exports.sealSession = sealSession;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as _cmssy_types from '@cmssy/types';
|
|
2
|
-
import { CmssyAuthConfig, CmssyClientConfig,
|
|
2
|
+
import { CmssyAuthConfig, CmssyClientConfig, CmssySiteConfig, RawBlock, CmssyFormDefinition, CmssySiteLocales, BuildBlockContextExtra, CmssyBlockContext, CmssyLocaleContext, FieldOptions, TypedField, BlockPropsSchema, InferBlockContent, BlockRect, BlockSchema, BlockMeta, CmssySessionPayload, SessionCookieOptions, CmssySessionUser, CmssyAddress, CmssyCart, CmssyOrder, CmssyProduct, MyOrdersResult, FetchProductOptions, FetchProductsOptions, CmssyProductPage, FetchOrderByTokenOptions, VerifyCmssyWebhookOptions, CmssyWebhookEvent } from '@cmssy/types';
|
|
3
3
|
export { BlockMeta, BlockPropsSchema, BlockRect, BlockSchema, BuildBlockContextExtra, CmssyAddress, CmssyAuthConfig, CmssyBlockAuthContext, CmssyBlockContext, CmssyBlockMember, CmssyBlockWorkspace, CmssyBranding, CmssyCart, CmssyCartDiscount, CmssyCartItem, CmssyCartItemSnapshot, CmssyClientConfig, CmssyFormDefinition, CmssyFormField, CmssyFormSettings, CmssyFormSubmitResponse, CmssyLayoutGroup, CmssyLayoutSettings, CmssyLocaleContext, CmssyLocalizedValue, CmssyModelDefinition, CmssyModelRecord, CmssyOrder, CmssyOrderDiscount, CmssyOrderItem, CmssyOrderTaxSummaryLine, CmssyPageData, CmssyPageMeta, CmssyPageSummary, CmssyPriceTier, CmssyProduct, CmssyProductPage, CmssyProductVariant, CmssyRecordList, CmssySessionPayload, CmssySessionUser, CmssyShippingMethod, CmssySiteConfig, CmssySiteLocales, CmssyStockState, CmssyTaxSummaryLine, CmssyWebhookEvent, CmssyWebhookOrder, FetchOrderByTokenOptions, FetchProductOptions, FetchProductsOptions, FieldCondition, FieldConditionGroup, FieldConditionLogic, FieldControl, FieldDefinition, FieldOptions, FieldType, InferBlockContent, MyOrdersResult, RawBlock, RawLayoutBlock, SessionCookieOptions, SubmitFormInput, TypedField, VerifyCmssyWebhookOptions, evaluateFieldConditionGroup } from '@cmssy/types';
|
|
4
|
+
import { F as FetchLike, R as RetryPolicy } from './content-client-D0EdiqbQ.cjs';
|
|
5
|
+
export { C as CmssyRequestError, D as DEFAULT_CMSSY_API_URL, a as FetchLikeResponse, b as FetchPageOptions, f as fetchLayouts, c as fetchPage, d as fetchPageById, e as fetchPageMeta, g as fetchPages, n as normalizeSlug, r as resolveApiUrl, h as resolvePublicUrl } from './content-client-D0EdiqbQ.cjs';
|
|
4
6
|
|
|
5
7
|
declare const DEFAULT_CMSSY_EDITOR_ORIGINS: string[];
|
|
6
8
|
declare function isDevelopment(): boolean;
|
|
@@ -37,9 +39,11 @@ interface CmssyConfig {
|
|
|
37
39
|
*/
|
|
38
40
|
siteUrl?: string;
|
|
39
41
|
auth?: CmssyAuthConfig;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Fallback locale resolver for a site whose URLs carry no language (e.g. a
|
|
44
|
+
* cookie or Accept-Language strategy). The workspace site config remains the
|
|
45
|
+
* source of truth for the default and enabled languages.
|
|
46
|
+
*/
|
|
43
47
|
resolveLocale?: () => string | Promise<string>;
|
|
44
48
|
}
|
|
45
49
|
/**
|
|
@@ -63,66 +67,6 @@ type CmssyEnvConfig = Omit<CmssyConfig, "org" | "workspaceSlug" | "draftSecret">
|
|
|
63
67
|
declare function defineCmssyConfig(config: CmssyEnvConfig): CmssyConfig;
|
|
64
68
|
declare function assertAuthConfig(config: CmssyConfig): CmssyAuthConfig;
|
|
65
69
|
|
|
66
|
-
/** HTTP-level failure from the cmssy API, with a machine-readable status. */
|
|
67
|
-
declare class CmssyRequestError extends Error {
|
|
68
|
-
readonly status: number;
|
|
69
|
-
constructor(message: string, status: number);
|
|
70
|
-
}
|
|
71
|
-
interface RetryPolicy {
|
|
72
|
-
/** Additional attempts after the first request (default 3). */
|
|
73
|
-
maxRetries?: number;
|
|
74
|
-
/** Exponential backoff base in ms: base * 2^attempt (default 300). */
|
|
75
|
-
baseDelayMs?: number;
|
|
76
|
-
/** Upper bound for any single wait, including Retry-After (default 3000). */
|
|
77
|
-
maxDelayMs?: number;
|
|
78
|
-
/** HTTP statuses that trigger a retry (default [429, 503]). */
|
|
79
|
-
retryStatuses?: number[];
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* The cmssy cloud GraphQL delivery endpoint. It is the same for every workspace,
|
|
84
|
-
* so consumers never need to set it - `apiUrl` defaults to this. Self-hosted /
|
|
85
|
-
* staging deployments override it via config.
|
|
86
|
-
*/
|
|
87
|
-
declare const DEFAULT_CMSSY_API_URL = "https://api.cmssy.io/graphql";
|
|
88
|
-
declare function resolveApiUrl(apiUrl: string | undefined): string;
|
|
89
|
-
/**
|
|
90
|
-
* Public delivery endpoint for a workspace: the org-scoped path
|
|
91
|
-
* `{base}/public/{orgSlug}/{workspaceSlug}/graphql`. `apiUrl` is the GraphQL
|
|
92
|
-
* base (its trailing `/graphql` is stripped); the org path is what tells the
|
|
93
|
-
* backend which workspace to serve, so slugs only need to be unique per org.
|
|
94
|
-
*/
|
|
95
|
-
declare function resolvePublicUrl(config: CmssyClientConfig): string;
|
|
96
|
-
interface FetchLikeResponse {
|
|
97
|
-
ok: boolean;
|
|
98
|
-
status: number;
|
|
99
|
-
json: () => Promise<unknown>;
|
|
100
|
-
headers?: {
|
|
101
|
-
get: (name: string) => string | null;
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
type FetchLike = (url: string, init: {
|
|
105
|
-
method: string;
|
|
106
|
-
headers: Record<string, string>;
|
|
107
|
-
body: string;
|
|
108
|
-
signal?: AbortSignal;
|
|
109
|
-
}) => Promise<FetchLikeResponse>;
|
|
110
|
-
interface FetchPageOptions {
|
|
111
|
-
previewSecret?: string;
|
|
112
|
-
devPreview?: boolean;
|
|
113
|
-
devToken?: string;
|
|
114
|
-
workspaceId?: string;
|
|
115
|
-
fetch?: FetchLike;
|
|
116
|
-
signal?: AbortSignal;
|
|
117
|
-
retry?: RetryPolicy | false;
|
|
118
|
-
}
|
|
119
|
-
declare function normalizeSlug(path: string | string[] | undefined): string;
|
|
120
|
-
declare function fetchPage(config: CmssyClientConfig, path: string | string[] | undefined, options?: FetchPageOptions): Promise<CmssyPageData | null>;
|
|
121
|
-
declare function fetchPageById(config: CmssyClientConfig, pageId: string, options?: Pick<FetchPageOptions, "fetch" | "signal" | "retry">): Promise<CmssyPageData | null>;
|
|
122
|
-
declare function fetchPages(config: CmssyClientConfig, options?: Pick<FetchPageOptions, "fetch" | "signal" | "retry">): Promise<CmssyPageSummary[]>;
|
|
123
|
-
declare function fetchPageMeta(config: CmssyClientConfig, path: string | string[] | undefined, options?: Pick<FetchPageOptions, "fetch" | "signal" | "retry">): Promise<CmssyPageMeta | null>;
|
|
124
|
-
declare function fetchLayouts(config: CmssyClientConfig, path: string | string[] | undefined, options?: FetchPageOptions): Promise<CmssyLayoutGroup[]>;
|
|
125
|
-
|
|
126
70
|
declare function getBlockContentForLanguage(content: unknown, locale: string, defaultLocale?: string, availableLocales?: string[]): Record<string, unknown>;
|
|
127
71
|
/**
|
|
128
72
|
* Coerce a block bucket (`style` / `advanced`) to a plain object. These buckets
|
|
@@ -180,6 +124,15 @@ declare function clearWorkspaceIdCache(): void;
|
|
|
180
124
|
declare function collectFormIds(blocks: RawBlock[], locale: string, defaultLocale: string): string[];
|
|
181
125
|
declare function resolveForms(config: CmssyClientConfig, blocks: RawBlock[], locale: string, defaultLocale: string, options?: QueryScopedOptions): Promise<Record<string, CmssyFormDefinition>>;
|
|
182
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Maps a workspace site config to its locale set. The single place that
|
|
129
|
+
* decides the default and enabled languages - the router and the SEO helpers
|
|
130
|
+
* must agree, so both go through here.
|
|
131
|
+
*/
|
|
132
|
+
declare function localesFromSiteConfig(siteConfig: {
|
|
133
|
+
defaultLanguage?: string | null;
|
|
134
|
+
enabledLanguages?: string[];
|
|
135
|
+
} | null): CmssySiteLocales;
|
|
183
136
|
declare function resolveSiteLocales(config: CmssyClientConfig, options?: GraphqlRequestOptions): Promise<CmssySiteLocales>;
|
|
184
137
|
declare function splitLocaleFromPath(path: string[] | undefined, siteLocales: CmssySiteLocales): {
|
|
185
138
|
locale: string;
|
|
@@ -378,21 +331,6 @@ declare function toCspOrigin(origin: string): string;
|
|
|
378
331
|
declare function cmssyCspHeaders(options: CmssyCspOptions): Record<string, string>;
|
|
379
332
|
declare function applyCmssyCsp<T extends MutableHeaders>(response: T, options: CmssyCspOptions): T;
|
|
380
333
|
|
|
381
|
-
/**
|
|
382
|
-
* Resolve the default locale and the full locale list from the SDK config,
|
|
383
|
-
* falling back to the workspace site config (defaultLanguage / enabledLanguages)
|
|
384
|
-
* then "en". Shared by sitemap + metadata so their hreflang/canonical agree.
|
|
385
|
-
*/
|
|
386
|
-
declare function resolveSeoLocales(config: {
|
|
387
|
-
defaultLocale?: string;
|
|
388
|
-
enabledLocales?: string[];
|
|
389
|
-
}, siteConfig: {
|
|
390
|
-
defaultLanguage?: string | null;
|
|
391
|
-
enabledLanguages?: string[];
|
|
392
|
-
} | null): {
|
|
393
|
-
defaultLocale: string;
|
|
394
|
-
locales: string[];
|
|
395
|
-
};
|
|
396
334
|
/**
|
|
397
335
|
* Maps a page slug to its path for a locale: the default locale gets no
|
|
398
336
|
* prefix, others get `/${locale}`. The homepage ("/") stays "/" (or "/${locale}").
|
|
@@ -506,4 +444,4 @@ declare class CmssyWebhookError extends Error {
|
|
|
506
444
|
*/
|
|
507
445
|
declare function verifyCmssyWebhook(options: VerifyCmssyWebhookOptions): Promise<CmssyWebhookEvent>;
|
|
508
446
|
|
|
509
|
-
export { type AppToEditorMessage, type AuthMutationResult, type AuthTokenResult, type BoundsMessage, CMSSY_EDIT_HEADER, CMSSY_EDIT_QUERY_PARAM, CMSSY_LOCALE_HEADER, CMSSY_SECRET_QUERY_PARAM, CMSSY_SESSION_COOKIE, type CartRequestContext, type CheckoutInput, type ClickMessage, type CmssyClient, type CmssyConfig, type CmssyCspOptions, type CmssyEnvConfig,
|
|
447
|
+
export { type AppToEditorMessage, type AuthMutationResult, type AuthTokenResult, type BoundsMessage, CMSSY_EDIT_HEADER, CMSSY_EDIT_QUERY_PARAM, CMSSY_LOCALE_HEADER, CMSSY_SECRET_QUERY_PARAM, CMSSY_SESSION_COOKIE, type CartRequestContext, type CheckoutInput, type ClickMessage, type CmssyClient, type CmssyConfig, type CmssyCspOptions, type CmssyEnvConfig, CmssyWebhookError, DEFAULT_CMSSY_EDITOR_ORIGINS, type EditorToAppMessage, FORM_QUERY, FetchLike, type GraphqlRequestOptions, MIN_SESSION_SECRET_LENGTH, MODEL_DEFINITIONS_QUERY, MODEL_RECORDS_QUERY, PRODUCTS_QUERY, PROTOCOL_VERSION, type ParentReadyMessage, type PatchMessage, type PostTarget, type QueryScopedOptions, type ReadyMessage, RetryPolicy, SESSION_MAX_AGE_SECONDS, SITE_CONFIG_QUERY, SUBMIT_FORM_MUTATION, type SelectMessage, applyCmssyCsp, asBucket, assertAuthConfig, backendAddToCart, backendApplyDiscount, backendCheckout, backendClearCart, backendForgotPassword, backendGetCart, backendMergeCart, backendMyOrder, backendMyOrders, backendOrderByToken, backendProduct, backendRefresh, backendRegister, backendRemoveDiscount, backendRemoveItem, backendResetPassword, backendSetShippingMethod, backendSignIn, backendSignOut, backendSignOutEverywhere, backendUpdateItem, backendVerifyEmail, buildBlockContext, buildLocaleSwitchHref, cachedWorkspaceId, clearWorkspaceIdCache, cmssyCspHeaders, cmssySecretsMatch, collectFormIds, createCmssyClient, decodeAccessClaims, defineCmssyConfig, fetchOrderByToken, fetchProduct, fetchProducts, fetchSiteConfig, fields, formatPrice, fractionDigits, fromMinorUnits, getBlockContentForLanguage, graphqlRequest, isAccessExpired, isDevelopment, isProtocolCompatible, isVerifiedEditUrl, localeForPath, localeForPathname, localesFromSiteConfig, localizeHref, localizeHtmlLinks, localizedPath, normalizeOrigin, openSession, parseEditorMessage, postToEditor, resolveEditorOrigin, resolveForms, resolveInitialTarget, resolveSiteLocales, resolveWorkspaceId, sealSession, sessionCookieOptions, splitCmssyLocale, splitLocaleFromPath, toCspOrigin, toMinorUnits, toSessionPayload, verifyCmssyWebhook };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as _cmssy_types from '@cmssy/types';
|
|
2
|
-
import { CmssyAuthConfig, CmssyClientConfig,
|
|
2
|
+
import { CmssyAuthConfig, CmssyClientConfig, CmssySiteConfig, RawBlock, CmssyFormDefinition, CmssySiteLocales, BuildBlockContextExtra, CmssyBlockContext, CmssyLocaleContext, FieldOptions, TypedField, BlockPropsSchema, InferBlockContent, BlockRect, BlockSchema, BlockMeta, CmssySessionPayload, SessionCookieOptions, CmssySessionUser, CmssyAddress, CmssyCart, CmssyOrder, CmssyProduct, MyOrdersResult, FetchProductOptions, FetchProductsOptions, CmssyProductPage, FetchOrderByTokenOptions, VerifyCmssyWebhookOptions, CmssyWebhookEvent } from '@cmssy/types';
|
|
3
3
|
export { BlockMeta, BlockPropsSchema, BlockRect, BlockSchema, BuildBlockContextExtra, CmssyAddress, CmssyAuthConfig, CmssyBlockAuthContext, CmssyBlockContext, CmssyBlockMember, CmssyBlockWorkspace, CmssyBranding, CmssyCart, CmssyCartDiscount, CmssyCartItem, CmssyCartItemSnapshot, CmssyClientConfig, CmssyFormDefinition, CmssyFormField, CmssyFormSettings, CmssyFormSubmitResponse, CmssyLayoutGroup, CmssyLayoutSettings, CmssyLocaleContext, CmssyLocalizedValue, CmssyModelDefinition, CmssyModelRecord, CmssyOrder, CmssyOrderDiscount, CmssyOrderItem, CmssyOrderTaxSummaryLine, CmssyPageData, CmssyPageMeta, CmssyPageSummary, CmssyPriceTier, CmssyProduct, CmssyProductPage, CmssyProductVariant, CmssyRecordList, CmssySessionPayload, CmssySessionUser, CmssyShippingMethod, CmssySiteConfig, CmssySiteLocales, CmssyStockState, CmssyTaxSummaryLine, CmssyWebhookEvent, CmssyWebhookOrder, FetchOrderByTokenOptions, FetchProductOptions, FetchProductsOptions, FieldCondition, FieldConditionGroup, FieldConditionLogic, FieldControl, FieldDefinition, FieldOptions, FieldType, InferBlockContent, MyOrdersResult, RawBlock, RawLayoutBlock, SessionCookieOptions, SubmitFormInput, TypedField, VerifyCmssyWebhookOptions, evaluateFieldConditionGroup } from '@cmssy/types';
|
|
4
|
+
import { F as FetchLike, R as RetryPolicy } from './content-client-D0EdiqbQ.js';
|
|
5
|
+
export { C as CmssyRequestError, D as DEFAULT_CMSSY_API_URL, a as FetchLikeResponse, b as FetchPageOptions, f as fetchLayouts, c as fetchPage, d as fetchPageById, e as fetchPageMeta, g as fetchPages, n as normalizeSlug, r as resolveApiUrl, h as resolvePublicUrl } from './content-client-D0EdiqbQ.js';
|
|
4
6
|
|
|
5
7
|
declare const DEFAULT_CMSSY_EDITOR_ORIGINS: string[];
|
|
6
8
|
declare function isDevelopment(): boolean;
|
|
@@ -37,9 +39,11 @@ interface CmssyConfig {
|
|
|
37
39
|
*/
|
|
38
40
|
siteUrl?: string;
|
|
39
41
|
auth?: CmssyAuthConfig;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Fallback locale resolver for a site whose URLs carry no language (e.g. a
|
|
44
|
+
* cookie or Accept-Language strategy). The workspace site config remains the
|
|
45
|
+
* source of truth for the default and enabled languages.
|
|
46
|
+
*/
|
|
43
47
|
resolveLocale?: () => string | Promise<string>;
|
|
44
48
|
}
|
|
45
49
|
/**
|
|
@@ -63,66 +67,6 @@ type CmssyEnvConfig = Omit<CmssyConfig, "org" | "workspaceSlug" | "draftSecret">
|
|
|
63
67
|
declare function defineCmssyConfig(config: CmssyEnvConfig): CmssyConfig;
|
|
64
68
|
declare function assertAuthConfig(config: CmssyConfig): CmssyAuthConfig;
|
|
65
69
|
|
|
66
|
-
/** HTTP-level failure from the cmssy API, with a machine-readable status. */
|
|
67
|
-
declare class CmssyRequestError extends Error {
|
|
68
|
-
readonly status: number;
|
|
69
|
-
constructor(message: string, status: number);
|
|
70
|
-
}
|
|
71
|
-
interface RetryPolicy {
|
|
72
|
-
/** Additional attempts after the first request (default 3). */
|
|
73
|
-
maxRetries?: number;
|
|
74
|
-
/** Exponential backoff base in ms: base * 2^attempt (default 300). */
|
|
75
|
-
baseDelayMs?: number;
|
|
76
|
-
/** Upper bound for any single wait, including Retry-After (default 3000). */
|
|
77
|
-
maxDelayMs?: number;
|
|
78
|
-
/** HTTP statuses that trigger a retry (default [429, 503]). */
|
|
79
|
-
retryStatuses?: number[];
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* The cmssy cloud GraphQL delivery endpoint. It is the same for every workspace,
|
|
84
|
-
* so consumers never need to set it - `apiUrl` defaults to this. Self-hosted /
|
|
85
|
-
* staging deployments override it via config.
|
|
86
|
-
*/
|
|
87
|
-
declare const DEFAULT_CMSSY_API_URL = "https://api.cmssy.io/graphql";
|
|
88
|
-
declare function resolveApiUrl(apiUrl: string | undefined): string;
|
|
89
|
-
/**
|
|
90
|
-
* Public delivery endpoint for a workspace: the org-scoped path
|
|
91
|
-
* `{base}/public/{orgSlug}/{workspaceSlug}/graphql`. `apiUrl` is the GraphQL
|
|
92
|
-
* base (its trailing `/graphql` is stripped); the org path is what tells the
|
|
93
|
-
* backend which workspace to serve, so slugs only need to be unique per org.
|
|
94
|
-
*/
|
|
95
|
-
declare function resolvePublicUrl(config: CmssyClientConfig): string;
|
|
96
|
-
interface FetchLikeResponse {
|
|
97
|
-
ok: boolean;
|
|
98
|
-
status: number;
|
|
99
|
-
json: () => Promise<unknown>;
|
|
100
|
-
headers?: {
|
|
101
|
-
get: (name: string) => string | null;
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
type FetchLike = (url: string, init: {
|
|
105
|
-
method: string;
|
|
106
|
-
headers: Record<string, string>;
|
|
107
|
-
body: string;
|
|
108
|
-
signal?: AbortSignal;
|
|
109
|
-
}) => Promise<FetchLikeResponse>;
|
|
110
|
-
interface FetchPageOptions {
|
|
111
|
-
previewSecret?: string;
|
|
112
|
-
devPreview?: boolean;
|
|
113
|
-
devToken?: string;
|
|
114
|
-
workspaceId?: string;
|
|
115
|
-
fetch?: FetchLike;
|
|
116
|
-
signal?: AbortSignal;
|
|
117
|
-
retry?: RetryPolicy | false;
|
|
118
|
-
}
|
|
119
|
-
declare function normalizeSlug(path: string | string[] | undefined): string;
|
|
120
|
-
declare function fetchPage(config: CmssyClientConfig, path: string | string[] | undefined, options?: FetchPageOptions): Promise<CmssyPageData | null>;
|
|
121
|
-
declare function fetchPageById(config: CmssyClientConfig, pageId: string, options?: Pick<FetchPageOptions, "fetch" | "signal" | "retry">): Promise<CmssyPageData | null>;
|
|
122
|
-
declare function fetchPages(config: CmssyClientConfig, options?: Pick<FetchPageOptions, "fetch" | "signal" | "retry">): Promise<CmssyPageSummary[]>;
|
|
123
|
-
declare function fetchPageMeta(config: CmssyClientConfig, path: string | string[] | undefined, options?: Pick<FetchPageOptions, "fetch" | "signal" | "retry">): Promise<CmssyPageMeta | null>;
|
|
124
|
-
declare function fetchLayouts(config: CmssyClientConfig, path: string | string[] | undefined, options?: FetchPageOptions): Promise<CmssyLayoutGroup[]>;
|
|
125
|
-
|
|
126
70
|
declare function getBlockContentForLanguage(content: unknown, locale: string, defaultLocale?: string, availableLocales?: string[]): Record<string, unknown>;
|
|
127
71
|
/**
|
|
128
72
|
* Coerce a block bucket (`style` / `advanced`) to a plain object. These buckets
|
|
@@ -180,6 +124,15 @@ declare function clearWorkspaceIdCache(): void;
|
|
|
180
124
|
declare function collectFormIds(blocks: RawBlock[], locale: string, defaultLocale: string): string[];
|
|
181
125
|
declare function resolveForms(config: CmssyClientConfig, blocks: RawBlock[], locale: string, defaultLocale: string, options?: QueryScopedOptions): Promise<Record<string, CmssyFormDefinition>>;
|
|
182
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Maps a workspace site config to its locale set. The single place that
|
|
129
|
+
* decides the default and enabled languages - the router and the SEO helpers
|
|
130
|
+
* must agree, so both go through here.
|
|
131
|
+
*/
|
|
132
|
+
declare function localesFromSiteConfig(siteConfig: {
|
|
133
|
+
defaultLanguage?: string | null;
|
|
134
|
+
enabledLanguages?: string[];
|
|
135
|
+
} | null): CmssySiteLocales;
|
|
183
136
|
declare function resolveSiteLocales(config: CmssyClientConfig, options?: GraphqlRequestOptions): Promise<CmssySiteLocales>;
|
|
184
137
|
declare function splitLocaleFromPath(path: string[] | undefined, siteLocales: CmssySiteLocales): {
|
|
185
138
|
locale: string;
|
|
@@ -378,21 +331,6 @@ declare function toCspOrigin(origin: string): string;
|
|
|
378
331
|
declare function cmssyCspHeaders(options: CmssyCspOptions): Record<string, string>;
|
|
379
332
|
declare function applyCmssyCsp<T extends MutableHeaders>(response: T, options: CmssyCspOptions): T;
|
|
380
333
|
|
|
381
|
-
/**
|
|
382
|
-
* Resolve the default locale and the full locale list from the SDK config,
|
|
383
|
-
* falling back to the workspace site config (defaultLanguage / enabledLanguages)
|
|
384
|
-
* then "en". Shared by sitemap + metadata so their hreflang/canonical agree.
|
|
385
|
-
*/
|
|
386
|
-
declare function resolveSeoLocales(config: {
|
|
387
|
-
defaultLocale?: string;
|
|
388
|
-
enabledLocales?: string[];
|
|
389
|
-
}, siteConfig: {
|
|
390
|
-
defaultLanguage?: string | null;
|
|
391
|
-
enabledLanguages?: string[];
|
|
392
|
-
} | null): {
|
|
393
|
-
defaultLocale: string;
|
|
394
|
-
locales: string[];
|
|
395
|
-
};
|
|
396
334
|
/**
|
|
397
335
|
* Maps a page slug to its path for a locale: the default locale gets no
|
|
398
336
|
* prefix, others get `/${locale}`. The homepage ("/") stays "/" (or "/${locale}").
|
|
@@ -506,4 +444,4 @@ declare class CmssyWebhookError extends Error {
|
|
|
506
444
|
*/
|
|
507
445
|
declare function verifyCmssyWebhook(options: VerifyCmssyWebhookOptions): Promise<CmssyWebhookEvent>;
|
|
508
446
|
|
|
509
|
-
export { type AppToEditorMessage, type AuthMutationResult, type AuthTokenResult, type BoundsMessage, CMSSY_EDIT_HEADER, CMSSY_EDIT_QUERY_PARAM, CMSSY_LOCALE_HEADER, CMSSY_SECRET_QUERY_PARAM, CMSSY_SESSION_COOKIE, type CartRequestContext, type CheckoutInput, type ClickMessage, type CmssyClient, type CmssyConfig, type CmssyCspOptions, type CmssyEnvConfig,
|
|
447
|
+
export { type AppToEditorMessage, type AuthMutationResult, type AuthTokenResult, type BoundsMessage, CMSSY_EDIT_HEADER, CMSSY_EDIT_QUERY_PARAM, CMSSY_LOCALE_HEADER, CMSSY_SECRET_QUERY_PARAM, CMSSY_SESSION_COOKIE, type CartRequestContext, type CheckoutInput, type ClickMessage, type CmssyClient, type CmssyConfig, type CmssyCspOptions, type CmssyEnvConfig, CmssyWebhookError, DEFAULT_CMSSY_EDITOR_ORIGINS, type EditorToAppMessage, FORM_QUERY, FetchLike, type GraphqlRequestOptions, MIN_SESSION_SECRET_LENGTH, MODEL_DEFINITIONS_QUERY, MODEL_RECORDS_QUERY, PRODUCTS_QUERY, PROTOCOL_VERSION, type ParentReadyMessage, type PatchMessage, type PostTarget, type QueryScopedOptions, type ReadyMessage, RetryPolicy, SESSION_MAX_AGE_SECONDS, SITE_CONFIG_QUERY, SUBMIT_FORM_MUTATION, type SelectMessage, applyCmssyCsp, asBucket, assertAuthConfig, backendAddToCart, backendApplyDiscount, backendCheckout, backendClearCart, backendForgotPassword, backendGetCart, backendMergeCart, backendMyOrder, backendMyOrders, backendOrderByToken, backendProduct, backendRefresh, backendRegister, backendRemoveDiscount, backendRemoveItem, backendResetPassword, backendSetShippingMethod, backendSignIn, backendSignOut, backendSignOutEverywhere, backendUpdateItem, backendVerifyEmail, buildBlockContext, buildLocaleSwitchHref, cachedWorkspaceId, clearWorkspaceIdCache, cmssyCspHeaders, cmssySecretsMatch, collectFormIds, createCmssyClient, decodeAccessClaims, defineCmssyConfig, fetchOrderByToken, fetchProduct, fetchProducts, fetchSiteConfig, fields, formatPrice, fractionDigits, fromMinorUnits, getBlockContentForLanguage, graphqlRequest, isAccessExpired, isDevelopment, isProtocolCompatible, isVerifiedEditUrl, localeForPath, localeForPathname, localesFromSiteConfig, localizeHref, localizeHtmlLinks, localizedPath, normalizeOrigin, openSession, parseEditorMessage, postToEditor, resolveEditorOrigin, resolveForms, resolveInitialTarget, resolveSiteLocales, resolveWorkspaceId, sealSession, sessionCookieOptions, splitCmssyLocale, splitLocaleFromPath, toCspOrigin, toMinorUnits, toSessionPayload, verifyCmssyWebhook };
|
package/dist/index.js
CHANGED
|
@@ -661,6 +661,14 @@ async function resolveForms(config, blocks, locale, defaultLocale, options) {
|
|
|
661
661
|
var TTL_MS = 6e4;
|
|
662
662
|
var MAX_ENTRIES = 64;
|
|
663
663
|
var cache = /* @__PURE__ */ new Map();
|
|
664
|
+
function localesFromSiteConfig(siteConfig) {
|
|
665
|
+
const defaultLocale = siteConfig?.defaultLanguage || "en";
|
|
666
|
+
const enabled = siteConfig?.enabledLanguages ?? [];
|
|
667
|
+
return {
|
|
668
|
+
defaultLocale,
|
|
669
|
+
locales: enabled.length > 0 ? enabled : [defaultLocale]
|
|
670
|
+
};
|
|
671
|
+
}
|
|
664
672
|
async function resolveSiteLocales(config, options) {
|
|
665
673
|
const key = `${resolveApiUrl(config.apiUrl)}::${config.org}::${config.workspaceSlug}`;
|
|
666
674
|
const cached = cache.get(key);
|
|
@@ -675,13 +683,7 @@ async function resolveSiteLocales(config, options) {
|
|
|
675
683
|
{ ...options, public: true, retry: options?.retry ?? {} },
|
|
676
684
|
"site config"
|
|
677
685
|
);
|
|
678
|
-
|
|
679
|
-
const defaultLocale = siteConfig?.defaultLanguage || "en";
|
|
680
|
-
const enabled = siteConfig?.enabledLanguages ?? [];
|
|
681
|
-
value = {
|
|
682
|
-
defaultLocale,
|
|
683
|
-
locales: enabled.length > 0 ? enabled : [defaultLocale]
|
|
684
|
-
};
|
|
686
|
+
value = localesFromSiteConfig(data.public?.siteConfig ?? null);
|
|
685
687
|
} catch {
|
|
686
688
|
value = { defaultLocale: "en", locales: ["en"] };
|
|
687
689
|
}
|
|
@@ -984,11 +986,6 @@ function applyCmssyCsp(response, options) {
|
|
|
984
986
|
}
|
|
985
987
|
|
|
986
988
|
// src/seo-paths.ts
|
|
987
|
-
function resolveSeoLocales(config, siteConfig) {
|
|
988
|
-
const defaultLocale = config.defaultLocale ?? siteConfig?.defaultLanguage ?? "en";
|
|
989
|
-
const locales = config.enabledLocales && config.enabledLocales.length > 0 ? config.enabledLocales : siteConfig?.enabledLanguages && siteConfig.enabledLanguages.length > 0 ? siteConfig.enabledLanguages : [defaultLocale];
|
|
990
|
-
return { defaultLocale, locales };
|
|
991
|
-
}
|
|
992
989
|
function normalizeSlug2(slug) {
|
|
993
990
|
if (slug === "/" || slug === "") return "/";
|
|
994
991
|
return slug.startsWith("/") ? slug : `/${slug}`;
|
|
@@ -1729,4 +1726,4 @@ async function verifyCmssyWebhook(options) {
|
|
|
1729
1726
|
return parsed;
|
|
1730
1727
|
}
|
|
1731
1728
|
|
|
1732
|
-
export { CMSSY_EDIT_HEADER, CMSSY_EDIT_QUERY_PARAM, CMSSY_LOCALE_HEADER, CMSSY_SECRET_QUERY_PARAM, CMSSY_SESSION_COOKIE, CmssyRequestError, CmssyWebhookError, DEFAULT_CMSSY_API_URL, DEFAULT_CMSSY_EDITOR_ORIGINS, FORM_QUERY, MIN_SESSION_SECRET_LENGTH, MODEL_DEFINITIONS_QUERY, MODEL_RECORDS_QUERY, PRODUCTS_QUERY, PROTOCOL_VERSION, SESSION_MAX_AGE_SECONDS, SITE_CONFIG_QUERY, SUBMIT_FORM_MUTATION, applyCmssyCsp, asBucket, assertAuthConfig, backendAddToCart, backendApplyDiscount, backendCheckout, backendClearCart, backendForgotPassword, backendGetCart, backendMergeCart, backendMyOrder, backendMyOrders, backendOrderByToken, backendProduct, backendRefresh, backendRegister, backendRemoveDiscount, backendRemoveItem, backendResetPassword, backendSetShippingMethod, backendSignIn, backendSignOut, backendSignOutEverywhere, backendUpdateItem, backendVerifyEmail, buildBlockContext, buildLocaleSwitchHref, cachedWorkspaceId, clearWorkspaceIdCache, cmssyCspHeaders, cmssySecretsMatch, collectFormIds, createCmssyClient, decodeAccessClaims, defineCmssyConfig, fetchLayouts, fetchOrderByToken, fetchPage, fetchPageById, fetchPageMeta, fetchPages, fetchProduct, fetchProducts, fetchSiteConfig, fields, formatPrice, fractionDigits, fromMinorUnits, getBlockContentForLanguage, graphqlRequest, isAccessExpired, isDevelopment, isProtocolCompatible, isVerifiedEditUrl, localeForPath, localeForPathname, localizeHref, localizeHtmlLinks, localizedPath, normalizeOrigin, normalizeSlug, openSession, parseEditorMessage, postToEditor, resolveApiUrl, resolveEditorOrigin, resolveForms, resolveInitialTarget, resolvePublicUrl,
|
|
1729
|
+
export { CMSSY_EDIT_HEADER, CMSSY_EDIT_QUERY_PARAM, CMSSY_LOCALE_HEADER, CMSSY_SECRET_QUERY_PARAM, CMSSY_SESSION_COOKIE, CmssyRequestError, CmssyWebhookError, DEFAULT_CMSSY_API_URL, DEFAULT_CMSSY_EDITOR_ORIGINS, FORM_QUERY, MIN_SESSION_SECRET_LENGTH, MODEL_DEFINITIONS_QUERY, MODEL_RECORDS_QUERY, PRODUCTS_QUERY, PROTOCOL_VERSION, SESSION_MAX_AGE_SECONDS, SITE_CONFIG_QUERY, SUBMIT_FORM_MUTATION, applyCmssyCsp, asBucket, assertAuthConfig, backendAddToCart, backendApplyDiscount, backendCheckout, backendClearCart, backendForgotPassword, backendGetCart, backendMergeCart, backendMyOrder, backendMyOrders, backendOrderByToken, backendProduct, backendRefresh, backendRegister, backendRemoveDiscount, backendRemoveItem, backendResetPassword, backendSetShippingMethod, backendSignIn, backendSignOut, backendSignOutEverywhere, backendUpdateItem, backendVerifyEmail, buildBlockContext, buildLocaleSwitchHref, cachedWorkspaceId, clearWorkspaceIdCache, cmssyCspHeaders, cmssySecretsMatch, collectFormIds, createCmssyClient, decodeAccessClaims, defineCmssyConfig, fetchLayouts, fetchOrderByToken, fetchPage, fetchPageById, fetchPageMeta, fetchPages, fetchProduct, fetchProducts, fetchSiteConfig, fields, formatPrice, fractionDigits, fromMinorUnits, getBlockContentForLanguage, graphqlRequest, isAccessExpired, isDevelopment, isProtocolCompatible, isVerifiedEditUrl, localeForPath, localeForPathname, localesFromSiteConfig, localizeHref, localizeHtmlLinks, localizedPath, normalizeOrigin, normalizeSlug, openSession, parseEditorMessage, postToEditor, resolveApiUrl, resolveEditorOrigin, resolveForms, resolveInitialTarget, resolvePublicUrl, resolveSiteLocales, resolveWorkspaceId, sealSession, sessionCookieOptions, splitCmssyLocale, splitLocaleFromPath, toCspOrigin, toMinorUnits, toSessionPayload, verifyCmssyWebhook };
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/content/content-client.ts
|
|
4
|
+
var DEFAULT_CMSSY_API_URL = "https://api.cmssy.io/graphql";
|
|
5
|
+
function resolveApiUrl(apiUrl) {
|
|
6
|
+
const explicit = apiUrl?.trim();
|
|
7
|
+
if (explicit) return explicit;
|
|
8
|
+
const env = globalThis.process?.env;
|
|
9
|
+
const fromEnv = env?.CMSSY_API_URL?.trim() ?? "";
|
|
10
|
+
return fromEnv.length > 0 ? fromEnv : DEFAULT_CMSSY_API_URL;
|
|
11
|
+
}
|
|
12
|
+
function resolvePublicUrl(config) {
|
|
13
|
+
const base = resolveApiUrl(config.apiUrl).replace(/\/graphql\/?$/, "");
|
|
14
|
+
return `${base}/public/${config.org}/${config.workspaceSlug}/graphql`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// src/preflight.ts
|
|
18
|
+
var CMSSY_ADMIN_ORIGIN = "https://www.cmssy.io";
|
|
19
|
+
var ALLOWED_FRAME_HOSTS = ["cmssy.io", "www.cmssy.io"];
|
|
20
|
+
var SETTINGS_PATH = "Settings \u2192 Headless";
|
|
21
|
+
var PREFLIGHT_SITE_CONFIG_QUERY = `query PreflightSiteConfig($workspaceSlug: String!) {
|
|
22
|
+
public {
|
|
23
|
+
siteConfig(workspaceSlug: $workspaceSlug) {
|
|
24
|
+
previewUrl
|
|
25
|
+
publicSiteUrl
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}`;
|
|
29
|
+
var DRAFT_SECRET_VALID_QUERY = `query PreflightDraftSecretValid($workspaceSlug: String!, $secret: String!) {
|
|
30
|
+
public {
|
|
31
|
+
draftSecretValid(workspaceSlug: $workspaceSlug, secret: $secret)
|
|
32
|
+
}
|
|
33
|
+
}`;
|
|
34
|
+
async function postPreflightQuery(config, query, variables) {
|
|
35
|
+
const doFetch = config.fetch ?? globalThis.fetch;
|
|
36
|
+
if (typeof doFetch !== "function") {
|
|
37
|
+
return {
|
|
38
|
+
kind: "network",
|
|
39
|
+
error: new Error("cmssy: no fetch implementation available")
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const url = resolvePublicUrl(config);
|
|
43
|
+
let response;
|
|
44
|
+
try {
|
|
45
|
+
response = await doFetch(url, {
|
|
46
|
+
method: "POST",
|
|
47
|
+
headers: { "content-type": "application/json" },
|
|
48
|
+
body: JSON.stringify({ query, variables })
|
|
49
|
+
});
|
|
50
|
+
} catch (error) {
|
|
51
|
+
return { kind: "network", error };
|
|
52
|
+
}
|
|
53
|
+
let envelope = null;
|
|
54
|
+
try {
|
|
55
|
+
envelope = await response.json();
|
|
56
|
+
} catch {
|
|
57
|
+
envelope = null;
|
|
58
|
+
}
|
|
59
|
+
const errors = Array.isArray(envelope?.errors) ? envelope.errors : [];
|
|
60
|
+
if (errors.length > 0) {
|
|
61
|
+
return { kind: "errors", errors, httpStatus: response.status };
|
|
62
|
+
}
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
return { kind: "http", status: response.status };
|
|
65
|
+
}
|
|
66
|
+
return { kind: "data", data: envelope?.data };
|
|
67
|
+
}
|
|
68
|
+
function errorMessages(errors) {
|
|
69
|
+
return errors.map((error) => error.message ?? "GraphQL error").join("; ");
|
|
70
|
+
}
|
|
71
|
+
function hasErrorCode(errors, code) {
|
|
72
|
+
return errors.some((error) => error.extensions?.code === code);
|
|
73
|
+
}
|
|
74
|
+
function isValidationError(errors) {
|
|
75
|
+
return errors.some(
|
|
76
|
+
(error) => error.extensions?.code === "GRAPHQL_VALIDATION_FAILED" || /cannot query field/i.test(error.message ?? "")
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
function isNotFound(errors, httpStatus) {
|
|
80
|
+
return httpStatus === 404 || hasErrorCode(errors, "NOT_FOUND") || errors.some((error) => /not found/i.test(error.message ?? ""));
|
|
81
|
+
}
|
|
82
|
+
async function checkWorkspaceReachable(config) {
|
|
83
|
+
const workspace = `${config.org}/${config.workspaceSlug}`;
|
|
84
|
+
const outcome = await postPreflightQuery(config, PREFLIGHT_SITE_CONFIG_QUERY, {
|
|
85
|
+
workspaceSlug: config.workspaceSlug
|
|
86
|
+
});
|
|
87
|
+
if (outcome.kind === "network") {
|
|
88
|
+
return {
|
|
89
|
+
status: "fail",
|
|
90
|
+
message: `cannot reach the cmssy API at ${resolvePublicUrl(config)}`,
|
|
91
|
+
fix: "check your network connection and CMSSY_API_URL (leave it unset for cmssy cloud)"
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (outcome.kind === "http" && outcome.status === 429 || outcome.kind === "errors" && (outcome.httpStatus === 429 || hasErrorCode(outcome.errors, "TOO_MANY_REQUESTS"))) {
|
|
95
|
+
return {
|
|
96
|
+
status: "fail",
|
|
97
|
+
message: `workspace ${workspace} is over its delivery limit (rate limited)`,
|
|
98
|
+
fix: "upgrade the organization plan or wait for the usage window to reset"
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (outcome.kind === "errors") {
|
|
102
|
+
if (isNotFound(outcome.errors, outcome.httpStatus)) {
|
|
103
|
+
return {
|
|
104
|
+
status: "fail",
|
|
105
|
+
message: `workspace ${workspace} was not found`,
|
|
106
|
+
fix: "check CMSSY_ORG_SLUG and CMSSY_WORKSPACE_SLUG against your dashboard URL"
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
status: "fail",
|
|
111
|
+
message: `the cmssy API rejected the request - ${errorMessages(outcome.errors)}`
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (outcome.kind === "http") {
|
|
115
|
+
if (outcome.status === 404) {
|
|
116
|
+
return {
|
|
117
|
+
status: "fail",
|
|
118
|
+
message: `workspace ${workspace} was not found`,
|
|
119
|
+
fix: "check CMSSY_ORG_SLUG and CMSSY_WORKSPACE_SLUG against your dashboard URL"
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
status: "fail",
|
|
124
|
+
message: `the cmssy API responded with HTTP ${outcome.status}`
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
const siteConfig = outcome.data?.public?.siteConfig;
|
|
128
|
+
if (!siteConfig) {
|
|
129
|
+
return {
|
|
130
|
+
status: "fail",
|
|
131
|
+
message: `workspace ${workspace} was not found`,
|
|
132
|
+
fix: "check CMSSY_ORG_SLUG and CMSSY_WORKSPACE_SLUG against your dashboard URL"
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
const previewUrl = siteConfig.previewUrl?.trim();
|
|
136
|
+
return {
|
|
137
|
+
status: "ok",
|
|
138
|
+
message: `workspace ${workspace} is reachable`,
|
|
139
|
+
...previewUrl ? { previewUrl } : {}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
async function checkDraftSecret(config) {
|
|
143
|
+
const secret = config.draftSecret?.trim();
|
|
144
|
+
if (!secret) {
|
|
145
|
+
return {
|
|
146
|
+
status: "fail",
|
|
147
|
+
message: "CMSSY_DRAFT_SECRET is not set",
|
|
148
|
+
fix: `copy the draft secret from ${SETTINGS_PATH}`
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
const outcome = await postPreflightQuery(config, DRAFT_SECRET_VALID_QUERY, {
|
|
152
|
+
workspaceSlug: config.workspaceSlug,
|
|
153
|
+
secret
|
|
154
|
+
});
|
|
155
|
+
if (outcome.kind === "network") {
|
|
156
|
+
return {
|
|
157
|
+
status: "unknown",
|
|
158
|
+
message: "could not verify the draft secret (network error)"
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
if (outcome.kind === "errors") {
|
|
162
|
+
if (isValidationError(outcome.errors)) {
|
|
163
|
+
return {
|
|
164
|
+
status: "unknown",
|
|
165
|
+
message: "this cmssy platform does not support draft secret verification yet"
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
status: "unknown",
|
|
170
|
+
message: `could not verify the draft secret - ${errorMessages(outcome.errors)}`
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
if (outcome.kind === "http") {
|
|
174
|
+
return {
|
|
175
|
+
status: "unknown",
|
|
176
|
+
message: `could not verify the draft secret (HTTP ${outcome.status})`
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
if (outcome.data?.public?.draftSecretValid === true) {
|
|
180
|
+
return { status: "ok", message: "the draft secret is valid" };
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
status: "fail",
|
|
184
|
+
message: "the draft secret does not match this workspace",
|
|
185
|
+
fix: `copy the secret from ${SETTINGS_PATH} into CMSSY_DRAFT_SECRET`
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function parseOrigin(value) {
|
|
189
|
+
try {
|
|
190
|
+
const origin = new URL(value).origin;
|
|
191
|
+
return origin === "null" ? null : origin;
|
|
192
|
+
} catch {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function checkPreviewUrl(previewUrl, devOrigin) {
|
|
197
|
+
const trimmed = previewUrl?.trim();
|
|
198
|
+
if (!trimmed) {
|
|
199
|
+
return {
|
|
200
|
+
status: "fail",
|
|
201
|
+
message: "no preview URL is set for this workspace",
|
|
202
|
+
fix: `paste ${devOrigin} into the preview URL field in ${SETTINGS_PATH}`
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
const previewOrigin = parseOrigin(trimmed);
|
|
206
|
+
if (!previewOrigin) {
|
|
207
|
+
return {
|
|
208
|
+
status: "fail",
|
|
209
|
+
message: `the workspace preview URL "${trimmed}" is not a valid URL`,
|
|
210
|
+
fix: `paste ${devOrigin} into the preview URL field in ${SETTINGS_PATH}`
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
const localOrigin = parseOrigin(devOrigin);
|
|
214
|
+
if (previewOrigin === localOrigin) {
|
|
215
|
+
return {
|
|
216
|
+
status: "ok",
|
|
217
|
+
message: `the workspace preview URL matches ${devOrigin}`
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
status: "fail",
|
|
222
|
+
message: `the workspace preview URL is ${previewOrigin} but your dev server runs at ${devOrigin}`,
|
|
223
|
+
fix: `paste ${devOrigin} into the preview URL field in ${SETTINGS_PATH}`
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function frameAncestorSources(cspHeaderValue) {
|
|
227
|
+
const directive = cspHeaderValue.split(";").map((part) => part.trim()).find((part) => /^frame-ancestors\b/i.test(part));
|
|
228
|
+
if (!directive) return null;
|
|
229
|
+
return directive.split(/\s+/).slice(1).map((source) => source.toLowerCase().replace(/^'|'$/g, ""));
|
|
230
|
+
}
|
|
231
|
+
function allowsCmssyAdmin(source) {
|
|
232
|
+
if (source === "*" || source === "https:") return true;
|
|
233
|
+
const host = source.replace(/^[a-z][a-z0-9+.-]*:\/\//, "").split("/")[0];
|
|
234
|
+
if (!host) return false;
|
|
235
|
+
if (host === "*.cmssy.io") return true;
|
|
236
|
+
return ALLOWED_FRAME_HOSTS.includes(host);
|
|
237
|
+
}
|
|
238
|
+
function checkFrameAncestors(cspHeaderValue) {
|
|
239
|
+
if (!cspHeaderValue || cspHeaderValue.trim().length === 0) {
|
|
240
|
+
return {
|
|
241
|
+
status: "ok",
|
|
242
|
+
message: "no Content-Security-Policy restricts framing"
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
const sources = frameAncestorSources(cspHeaderValue);
|
|
246
|
+
if (sources === null) {
|
|
247
|
+
return {
|
|
248
|
+
status: "ok",
|
|
249
|
+
message: "the Content-Security-Policy has no frame-ancestors directive"
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
if (sources.some(allowsCmssyAdmin)) {
|
|
253
|
+
return {
|
|
254
|
+
status: "ok",
|
|
255
|
+
message: "frame-ancestors allows the cmssy editor"
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
status: "fail",
|
|
260
|
+
message: `frame-ancestors (${sources.join(" ") || "'none'"}) blocks the cmssy editor`,
|
|
261
|
+
fix: "add https://cmssy.io https://www.cmssy.io to the frame-ancestors directive (applyCmssyCsp does this for you)"
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function buildEditorUrl(config, pageId) {
|
|
265
|
+
const base = `${CMSSY_ADMIN_ORIGIN}/dashboard/organizations/${config.org}/workspaces/${config.workspaceSlug}/editor`;
|
|
266
|
+
return pageId ? `${base}?pageId=${encodeURIComponent(pageId)}` : base;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
exports.buildEditorUrl = buildEditorUrl;
|
|
270
|
+
exports.checkDraftSecret = checkDraftSecret;
|
|
271
|
+
exports.checkFrameAncestors = checkFrameAncestors;
|
|
272
|
+
exports.checkPreviewUrl = checkPreviewUrl;
|
|
273
|
+
exports.checkWorkspaceReachable = checkWorkspaceReachable;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { F as FetchLike } from './content-client-D0EdiqbQ.cjs';
|
|
2
|
+
import { CmssyClientConfig } from '@cmssy/types';
|
|
3
|
+
|
|
4
|
+
type PreflightStatus = "ok" | "fail" | "unknown";
|
|
5
|
+
interface PreflightResult {
|
|
6
|
+
status: PreflightStatus;
|
|
7
|
+
message: string;
|
|
8
|
+
fix?: string;
|
|
9
|
+
}
|
|
10
|
+
interface WorkspaceReachableResult extends PreflightResult {
|
|
11
|
+
previewUrl?: string;
|
|
12
|
+
}
|
|
13
|
+
interface PreflightConfig extends CmssyClientConfig {
|
|
14
|
+
draftSecret?: string;
|
|
15
|
+
fetch?: FetchLike;
|
|
16
|
+
}
|
|
17
|
+
declare function checkWorkspaceReachable(config: PreflightConfig): Promise<WorkspaceReachableResult>;
|
|
18
|
+
declare function checkDraftSecret(config: PreflightConfig): Promise<PreflightResult>;
|
|
19
|
+
declare function checkPreviewUrl(previewUrl: string | null | undefined, devOrigin: string): PreflightResult;
|
|
20
|
+
declare function checkFrameAncestors(cspHeaderValue: string | null | undefined): PreflightResult;
|
|
21
|
+
declare function buildEditorUrl(config: Pick<CmssyClientConfig, "org" | "workspaceSlug">, pageId?: string): string;
|
|
22
|
+
|
|
23
|
+
export { type PreflightConfig, type PreflightResult, type PreflightStatus, type WorkspaceReachableResult, buildEditorUrl, checkDraftSecret, checkFrameAncestors, checkPreviewUrl, checkWorkspaceReachable };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { F as FetchLike } from './content-client-D0EdiqbQ.js';
|
|
2
|
+
import { CmssyClientConfig } from '@cmssy/types';
|
|
3
|
+
|
|
4
|
+
type PreflightStatus = "ok" | "fail" | "unknown";
|
|
5
|
+
interface PreflightResult {
|
|
6
|
+
status: PreflightStatus;
|
|
7
|
+
message: string;
|
|
8
|
+
fix?: string;
|
|
9
|
+
}
|
|
10
|
+
interface WorkspaceReachableResult extends PreflightResult {
|
|
11
|
+
previewUrl?: string;
|
|
12
|
+
}
|
|
13
|
+
interface PreflightConfig extends CmssyClientConfig {
|
|
14
|
+
draftSecret?: string;
|
|
15
|
+
fetch?: FetchLike;
|
|
16
|
+
}
|
|
17
|
+
declare function checkWorkspaceReachable(config: PreflightConfig): Promise<WorkspaceReachableResult>;
|
|
18
|
+
declare function checkDraftSecret(config: PreflightConfig): Promise<PreflightResult>;
|
|
19
|
+
declare function checkPreviewUrl(previewUrl: string | null | undefined, devOrigin: string): PreflightResult;
|
|
20
|
+
declare function checkFrameAncestors(cspHeaderValue: string | null | undefined): PreflightResult;
|
|
21
|
+
declare function buildEditorUrl(config: Pick<CmssyClientConfig, "org" | "workspaceSlug">, pageId?: string): string;
|
|
22
|
+
|
|
23
|
+
export { type PreflightConfig, type PreflightResult, type PreflightStatus, type WorkspaceReachableResult, buildEditorUrl, checkDraftSecret, checkFrameAncestors, checkPreviewUrl, checkWorkspaceReachable };
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// src/content/content-client.ts
|
|
2
|
+
var DEFAULT_CMSSY_API_URL = "https://api.cmssy.io/graphql";
|
|
3
|
+
function resolveApiUrl(apiUrl) {
|
|
4
|
+
const explicit = apiUrl?.trim();
|
|
5
|
+
if (explicit) return explicit;
|
|
6
|
+
const env = globalThis.process?.env;
|
|
7
|
+
const fromEnv = env?.CMSSY_API_URL?.trim() ?? "";
|
|
8
|
+
return fromEnv.length > 0 ? fromEnv : DEFAULT_CMSSY_API_URL;
|
|
9
|
+
}
|
|
10
|
+
function resolvePublicUrl(config) {
|
|
11
|
+
const base = resolveApiUrl(config.apiUrl).replace(/\/graphql\/?$/, "");
|
|
12
|
+
return `${base}/public/${config.org}/${config.workspaceSlug}/graphql`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// src/preflight.ts
|
|
16
|
+
var CMSSY_ADMIN_ORIGIN = "https://www.cmssy.io";
|
|
17
|
+
var ALLOWED_FRAME_HOSTS = ["cmssy.io", "www.cmssy.io"];
|
|
18
|
+
var SETTINGS_PATH = "Settings \u2192 Headless";
|
|
19
|
+
var PREFLIGHT_SITE_CONFIG_QUERY = `query PreflightSiteConfig($workspaceSlug: String!) {
|
|
20
|
+
public {
|
|
21
|
+
siteConfig(workspaceSlug: $workspaceSlug) {
|
|
22
|
+
previewUrl
|
|
23
|
+
publicSiteUrl
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}`;
|
|
27
|
+
var DRAFT_SECRET_VALID_QUERY = `query PreflightDraftSecretValid($workspaceSlug: String!, $secret: String!) {
|
|
28
|
+
public {
|
|
29
|
+
draftSecretValid(workspaceSlug: $workspaceSlug, secret: $secret)
|
|
30
|
+
}
|
|
31
|
+
}`;
|
|
32
|
+
async function postPreflightQuery(config, query, variables) {
|
|
33
|
+
const doFetch = config.fetch ?? globalThis.fetch;
|
|
34
|
+
if (typeof doFetch !== "function") {
|
|
35
|
+
return {
|
|
36
|
+
kind: "network",
|
|
37
|
+
error: new Error("cmssy: no fetch implementation available")
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const url = resolvePublicUrl(config);
|
|
41
|
+
let response;
|
|
42
|
+
try {
|
|
43
|
+
response = await doFetch(url, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers: { "content-type": "application/json" },
|
|
46
|
+
body: JSON.stringify({ query, variables })
|
|
47
|
+
});
|
|
48
|
+
} catch (error) {
|
|
49
|
+
return { kind: "network", error };
|
|
50
|
+
}
|
|
51
|
+
let envelope = null;
|
|
52
|
+
try {
|
|
53
|
+
envelope = await response.json();
|
|
54
|
+
} catch {
|
|
55
|
+
envelope = null;
|
|
56
|
+
}
|
|
57
|
+
const errors = Array.isArray(envelope?.errors) ? envelope.errors : [];
|
|
58
|
+
if (errors.length > 0) {
|
|
59
|
+
return { kind: "errors", errors, httpStatus: response.status };
|
|
60
|
+
}
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
return { kind: "http", status: response.status };
|
|
63
|
+
}
|
|
64
|
+
return { kind: "data", data: envelope?.data };
|
|
65
|
+
}
|
|
66
|
+
function errorMessages(errors) {
|
|
67
|
+
return errors.map((error) => error.message ?? "GraphQL error").join("; ");
|
|
68
|
+
}
|
|
69
|
+
function hasErrorCode(errors, code) {
|
|
70
|
+
return errors.some((error) => error.extensions?.code === code);
|
|
71
|
+
}
|
|
72
|
+
function isValidationError(errors) {
|
|
73
|
+
return errors.some(
|
|
74
|
+
(error) => error.extensions?.code === "GRAPHQL_VALIDATION_FAILED" || /cannot query field/i.test(error.message ?? "")
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
function isNotFound(errors, httpStatus) {
|
|
78
|
+
return httpStatus === 404 || hasErrorCode(errors, "NOT_FOUND") || errors.some((error) => /not found/i.test(error.message ?? ""));
|
|
79
|
+
}
|
|
80
|
+
async function checkWorkspaceReachable(config) {
|
|
81
|
+
const workspace = `${config.org}/${config.workspaceSlug}`;
|
|
82
|
+
const outcome = await postPreflightQuery(config, PREFLIGHT_SITE_CONFIG_QUERY, {
|
|
83
|
+
workspaceSlug: config.workspaceSlug
|
|
84
|
+
});
|
|
85
|
+
if (outcome.kind === "network") {
|
|
86
|
+
return {
|
|
87
|
+
status: "fail",
|
|
88
|
+
message: `cannot reach the cmssy API at ${resolvePublicUrl(config)}`,
|
|
89
|
+
fix: "check your network connection and CMSSY_API_URL (leave it unset for cmssy cloud)"
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (outcome.kind === "http" && outcome.status === 429 || outcome.kind === "errors" && (outcome.httpStatus === 429 || hasErrorCode(outcome.errors, "TOO_MANY_REQUESTS"))) {
|
|
93
|
+
return {
|
|
94
|
+
status: "fail",
|
|
95
|
+
message: `workspace ${workspace} is over its delivery limit (rate limited)`,
|
|
96
|
+
fix: "upgrade the organization plan or wait for the usage window to reset"
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (outcome.kind === "errors") {
|
|
100
|
+
if (isNotFound(outcome.errors, outcome.httpStatus)) {
|
|
101
|
+
return {
|
|
102
|
+
status: "fail",
|
|
103
|
+
message: `workspace ${workspace} was not found`,
|
|
104
|
+
fix: "check CMSSY_ORG_SLUG and CMSSY_WORKSPACE_SLUG against your dashboard URL"
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
status: "fail",
|
|
109
|
+
message: `the cmssy API rejected the request - ${errorMessages(outcome.errors)}`
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (outcome.kind === "http") {
|
|
113
|
+
if (outcome.status === 404) {
|
|
114
|
+
return {
|
|
115
|
+
status: "fail",
|
|
116
|
+
message: `workspace ${workspace} was not found`,
|
|
117
|
+
fix: "check CMSSY_ORG_SLUG and CMSSY_WORKSPACE_SLUG against your dashboard URL"
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
status: "fail",
|
|
122
|
+
message: `the cmssy API responded with HTTP ${outcome.status}`
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
const siteConfig = outcome.data?.public?.siteConfig;
|
|
126
|
+
if (!siteConfig) {
|
|
127
|
+
return {
|
|
128
|
+
status: "fail",
|
|
129
|
+
message: `workspace ${workspace} was not found`,
|
|
130
|
+
fix: "check CMSSY_ORG_SLUG and CMSSY_WORKSPACE_SLUG against your dashboard URL"
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const previewUrl = siteConfig.previewUrl?.trim();
|
|
134
|
+
return {
|
|
135
|
+
status: "ok",
|
|
136
|
+
message: `workspace ${workspace} is reachable`,
|
|
137
|
+
...previewUrl ? { previewUrl } : {}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
async function checkDraftSecret(config) {
|
|
141
|
+
const secret = config.draftSecret?.trim();
|
|
142
|
+
if (!secret) {
|
|
143
|
+
return {
|
|
144
|
+
status: "fail",
|
|
145
|
+
message: "CMSSY_DRAFT_SECRET is not set",
|
|
146
|
+
fix: `copy the draft secret from ${SETTINGS_PATH}`
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
const outcome = await postPreflightQuery(config, DRAFT_SECRET_VALID_QUERY, {
|
|
150
|
+
workspaceSlug: config.workspaceSlug,
|
|
151
|
+
secret
|
|
152
|
+
});
|
|
153
|
+
if (outcome.kind === "network") {
|
|
154
|
+
return {
|
|
155
|
+
status: "unknown",
|
|
156
|
+
message: "could not verify the draft secret (network error)"
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
if (outcome.kind === "errors") {
|
|
160
|
+
if (isValidationError(outcome.errors)) {
|
|
161
|
+
return {
|
|
162
|
+
status: "unknown",
|
|
163
|
+
message: "this cmssy platform does not support draft secret verification yet"
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
status: "unknown",
|
|
168
|
+
message: `could not verify the draft secret - ${errorMessages(outcome.errors)}`
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
if (outcome.kind === "http") {
|
|
172
|
+
return {
|
|
173
|
+
status: "unknown",
|
|
174
|
+
message: `could not verify the draft secret (HTTP ${outcome.status})`
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
if (outcome.data?.public?.draftSecretValid === true) {
|
|
178
|
+
return { status: "ok", message: "the draft secret is valid" };
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
status: "fail",
|
|
182
|
+
message: "the draft secret does not match this workspace",
|
|
183
|
+
fix: `copy the secret from ${SETTINGS_PATH} into CMSSY_DRAFT_SECRET`
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function parseOrigin(value) {
|
|
187
|
+
try {
|
|
188
|
+
const origin = new URL(value).origin;
|
|
189
|
+
return origin === "null" ? null : origin;
|
|
190
|
+
} catch {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function checkPreviewUrl(previewUrl, devOrigin) {
|
|
195
|
+
const trimmed = previewUrl?.trim();
|
|
196
|
+
if (!trimmed) {
|
|
197
|
+
return {
|
|
198
|
+
status: "fail",
|
|
199
|
+
message: "no preview URL is set for this workspace",
|
|
200
|
+
fix: `paste ${devOrigin} into the preview URL field in ${SETTINGS_PATH}`
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
const previewOrigin = parseOrigin(trimmed);
|
|
204
|
+
if (!previewOrigin) {
|
|
205
|
+
return {
|
|
206
|
+
status: "fail",
|
|
207
|
+
message: `the workspace preview URL "${trimmed}" is not a valid URL`,
|
|
208
|
+
fix: `paste ${devOrigin} into the preview URL field in ${SETTINGS_PATH}`
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
const localOrigin = parseOrigin(devOrigin);
|
|
212
|
+
if (previewOrigin === localOrigin) {
|
|
213
|
+
return {
|
|
214
|
+
status: "ok",
|
|
215
|
+
message: `the workspace preview URL matches ${devOrigin}`
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
status: "fail",
|
|
220
|
+
message: `the workspace preview URL is ${previewOrigin} but your dev server runs at ${devOrigin}`,
|
|
221
|
+
fix: `paste ${devOrigin} into the preview URL field in ${SETTINGS_PATH}`
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function frameAncestorSources(cspHeaderValue) {
|
|
225
|
+
const directive = cspHeaderValue.split(";").map((part) => part.trim()).find((part) => /^frame-ancestors\b/i.test(part));
|
|
226
|
+
if (!directive) return null;
|
|
227
|
+
return directive.split(/\s+/).slice(1).map((source) => source.toLowerCase().replace(/^'|'$/g, ""));
|
|
228
|
+
}
|
|
229
|
+
function allowsCmssyAdmin(source) {
|
|
230
|
+
if (source === "*" || source === "https:") return true;
|
|
231
|
+
const host = source.replace(/^[a-z][a-z0-9+.-]*:\/\//, "").split("/")[0];
|
|
232
|
+
if (!host) return false;
|
|
233
|
+
if (host === "*.cmssy.io") return true;
|
|
234
|
+
return ALLOWED_FRAME_HOSTS.includes(host);
|
|
235
|
+
}
|
|
236
|
+
function checkFrameAncestors(cspHeaderValue) {
|
|
237
|
+
if (!cspHeaderValue || cspHeaderValue.trim().length === 0) {
|
|
238
|
+
return {
|
|
239
|
+
status: "ok",
|
|
240
|
+
message: "no Content-Security-Policy restricts framing"
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
const sources = frameAncestorSources(cspHeaderValue);
|
|
244
|
+
if (sources === null) {
|
|
245
|
+
return {
|
|
246
|
+
status: "ok",
|
|
247
|
+
message: "the Content-Security-Policy has no frame-ancestors directive"
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
if (sources.some(allowsCmssyAdmin)) {
|
|
251
|
+
return {
|
|
252
|
+
status: "ok",
|
|
253
|
+
message: "frame-ancestors allows the cmssy editor"
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
return {
|
|
257
|
+
status: "fail",
|
|
258
|
+
message: `frame-ancestors (${sources.join(" ") || "'none'"}) blocks the cmssy editor`,
|
|
259
|
+
fix: "add https://cmssy.io https://www.cmssy.io to the frame-ancestors directive (applyCmssyCsp does this for you)"
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function buildEditorUrl(config, pageId) {
|
|
263
|
+
const base = `${CMSSY_ADMIN_ORIGIN}/dashboard/organizations/${config.org}/workspaces/${config.workspaceSlug}/editor`;
|
|
264
|
+
return pageId ? `${base}?pageId=${encodeURIComponent(pageId)}` : base;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export { buildEditorUrl, checkDraftSecret, checkFrameAncestors, checkPreviewUrl, checkWorkspaceReachable };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmssy/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.1.0",
|
|
4
4
|
"description": "Framework-agnostic cmssy client: content, commerce, config, editor protocol. No React, no Next.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cmssy",
|
|
@@ -31,6 +31,11 @@
|
|
|
31
31
|
"types": "./dist/testing.d.ts",
|
|
32
32
|
"import": "./dist/testing.js",
|
|
33
33
|
"require": "./dist/testing.cjs"
|
|
34
|
+
},
|
|
35
|
+
"./preflight": {
|
|
36
|
+
"types": "./dist/preflight.d.ts",
|
|
37
|
+
"import": "./dist/preflight.js",
|
|
38
|
+
"require": "./dist/preflight.cjs"
|
|
34
39
|
}
|
|
35
40
|
},
|
|
36
41
|
"main": "./dist/index.cjs",
|