@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.
@@ -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
- const siteConfig = data.public?.siteConfig ?? null;
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, CmssyLayoutGroup, CmssyPageData, CmssyPageMeta, CmssyPageSummary, 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';
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
- defaultLocale?: string;
41
- /** All languages enabled on the workspace; exposed to blocks via context.locale.enabled. */
42
- enabledLocales?: string[];
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, CmssyRequestError, CmssyWebhookError, DEFAULT_CMSSY_API_URL, DEFAULT_CMSSY_EDITOR_ORIGINS, type EditorToAppMessage, FORM_QUERY, type FetchLike, type FetchLikeResponse, type FetchPageOptions, 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, type 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, 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, resolveSeoLocales, resolveSiteLocales, resolveWorkspaceId, sealSession, sessionCookieOptions, splitCmssyLocale, splitLocaleFromPath, toCspOrigin, toMinorUnits, toSessionPayload, verifyCmssyWebhook };
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, CmssyLayoutGroup, CmssyPageData, CmssyPageMeta, CmssyPageSummary, 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';
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
- defaultLocale?: string;
41
- /** All languages enabled on the workspace; exposed to blocks via context.locale.enabled. */
42
- enabledLocales?: string[];
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, CmssyRequestError, CmssyWebhookError, DEFAULT_CMSSY_API_URL, DEFAULT_CMSSY_EDITOR_ORIGINS, type EditorToAppMessage, FORM_QUERY, type FetchLike, type FetchLikeResponse, type FetchPageOptions, 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, type 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, 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, resolveSeoLocales, resolveSiteLocales, resolveWorkspaceId, sealSession, sessionCookieOptions, splitCmssyLocale, splitLocaleFromPath, toCspOrigin, toMinorUnits, toSessionPayload, verifyCmssyWebhook };
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
- const siteConfig = data.public?.siteConfig ?? null;
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, resolveSeoLocales, resolveSiteLocales, resolveWorkspaceId, sealSession, sessionCookieOptions, splitCmssyLocale, splitLocaleFromPath, toCspOrigin, toMinorUnits, toSessionPayload, verifyCmssyWebhook };
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": "8.0.4",
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",