@cmssy/core 9.1.0 → 9.2.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.
@@ -14,6 +14,159 @@ function resolvePublicUrl(config) {
14
14
  return `${base}/public/${config.org}/${config.workspaceSlug}/graphql`;
15
15
  }
16
16
 
17
+ // src/config.ts
18
+ var DEFAULT_CMSSY_EDITOR_ORIGINS = [
19
+ "https://cmssy.io",
20
+ "https://www.cmssy.io"
21
+ ];
22
+
23
+ // src/edit-diagnostics.ts
24
+ var REQUIRED_ENV = [
25
+ ["org", "CMSSY_ORG_SLUG"],
26
+ ["workspaceSlug", "CMSSY_WORKSPACE_SLUG"],
27
+ ["draftSecret", "CMSSY_DRAFT_SECRET"]
28
+ ];
29
+ var SETTINGS_FIX = "copy the values from Settings \u2192 Headless in the cmssy dashboard, or run npx @cmssy/cli link";
30
+ async function collectEditDiagnostics(input) {
31
+ const { config } = input;
32
+ const checks = [];
33
+ const missing = REQUIRED_ENV.filter(([key]) => {
34
+ const value = config[key];
35
+ return !(typeof value === "string" && value.trim());
36
+ }).map(([, env]) => env);
37
+ if (missing.length > 0) {
38
+ checks.push({
39
+ name: "configuration",
40
+ status: "fail",
41
+ message: `missing environment variables: ${missing.join(", ")}`,
42
+ fix: SETTINGS_FIX
43
+ });
44
+ } else {
45
+ checks.push({
46
+ name: "configuration",
47
+ status: "ok",
48
+ message: "CMSSY_ORG_SLUG, CMSSY_WORKSPACE_SLUG and CMSSY_DRAFT_SECRET are set"
49
+ });
50
+ }
51
+ const org = config.org?.trim();
52
+ const workspaceSlug = config.workspaceSlug?.trim();
53
+ const workspace = org && workspaceSlug ? `${org}/${workspaceSlug}` : null;
54
+ let previewUrl;
55
+ if (org && workspaceSlug) {
56
+ const preflight = {
57
+ apiUrl: config.apiUrl,
58
+ org,
59
+ workspaceSlug,
60
+ ...input.fetch ? { fetch: input.fetch } : {}
61
+ };
62
+ const reachable = await checkWorkspaceReachable(preflight);
63
+ previewUrl = reachable.previewUrl;
64
+ checks.push({
65
+ name: "workspace",
66
+ status: reachable.status,
67
+ message: reachable.message,
68
+ ...reachable.fix ? { fix: reachable.fix } : {}
69
+ });
70
+ const draftSecret = config.draftSecret?.trim();
71
+ if (draftSecret) {
72
+ checks.push(
73
+ await diagnoseSecret(
74
+ { ...preflight, draftSecret },
75
+ input.providedSecret
76
+ )
77
+ );
78
+ }
79
+ }
80
+ if (input.devOrigin) {
81
+ const preview = checkPreviewUrl(previewUrl ?? null, input.devOrigin);
82
+ checks.push({
83
+ name: "preview URL",
84
+ status: preview.status,
85
+ message: preview.message,
86
+ ...preview.fix ? { fix: preview.fix } : {}
87
+ });
88
+ }
89
+ checks.push({
90
+ name: "frame-ancestors",
91
+ status: "unknown",
92
+ message: "CSP blocking cannot be detected from inside the frame - if this page never appears in the editor at all, the likely causes are the preview URL pointing elsewhere or a Content-Security-Policy frame-ancestors that blocks the editor",
93
+ fix: `frame-ancestors must allow ${DEFAULT_CMSSY_EDITOR_ORIGINS.join(" ")} (cmssyCspHeaders / applyCmssyCsp set this for you)`
94
+ });
95
+ return { workspace, checks };
96
+ }
97
+ async function diagnoseSecret(preflight, providedSecret) {
98
+ const name = "draft secret";
99
+ if (!providedSecret?.trim()) {
100
+ return {
101
+ name,
102
+ status: "fail",
103
+ message: "the request carried no cmssySecret - a bare cmssyEdit=1 is never trusted",
104
+ fix: "open the page from the cmssy editor, which appends the cmssySecret itself"
105
+ };
106
+ }
107
+ const platform = await checkDraftSecret(preflight);
108
+ if (platform.status === "fail") {
109
+ return {
110
+ name,
111
+ status: "fail",
112
+ message: `the provided cmssySecret does not match CMSSY_DRAFT_SECRET, and ${platform.message}`,
113
+ ...platform.fix ? { fix: platform.fix } : {}
114
+ };
115
+ }
116
+ if (platform.status === "ok") {
117
+ return {
118
+ name,
119
+ status: "fail",
120
+ message: "CMSSY_DRAFT_SECRET matches this workspace, but the cmssySecret sent with the request does not match it",
121
+ fix: "reload the editor so it opens the preview with the current secret"
122
+ };
123
+ }
124
+ return {
125
+ name,
126
+ status: "unknown",
127
+ message: `could not verify against the platform (${platform.message}); the provided cmssySecret failed local verification against CMSSY_DRAFT_SECRET`,
128
+ fix: `make sure CMSSY_DRAFT_SECRET matches Settings \u2192 Headless, then reload the editor`
129
+ };
130
+ }
131
+ var STATUS_LABEL = {
132
+ ok: "OK",
133
+ fail: "FAIL",
134
+ unknown: "?"
135
+ };
136
+ var DIAGNOSTICS_CSS = `
137
+ .cmssy-diagnostics{font:14px/1.6 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;max-width:44rem;margin:3rem auto;padding:0 1.5rem;color:#1f2328}
138
+ .cmssy-diagnostics h1{font-size:1.15rem;margin:0 0 .5rem}
139
+ .cmssy-diagnostics p{margin:.5rem 0}
140
+ .cmssy-diagnostics ul{list-style:none;margin:1.5rem 0;padding:0}
141
+ .cmssy-diagnostics li{margin:0 0 .75rem;padding:.75rem 1rem;border:1px solid #d0d7de;border-radius:6px}
142
+ .cmssy-diagnostics .cmssy-diagnostics-status{font-weight:700;margin-right:.5rem}
143
+ .cmssy-diagnostics .cmssy-diagnostics-ok{color:#1a7f37}
144
+ .cmssy-diagnostics .cmssy-diagnostics-fail{color:#c5273c}
145
+ .cmssy-diagnostics .cmssy-diagnostics-unknown{color:#9a6700}
146
+ .cmssy-diagnostics .cmssy-diagnostics-fix{display:block;margin-top:.25rem;opacity:.75}
147
+ .cmssy-diagnostics .cmssy-diagnostics-note{opacity:.65}
148
+ @media (prefers-color-scheme:dark){
149
+ .cmssy-diagnostics{color:#e6edf3}
150
+ .cmssy-diagnostics li{border-color:#30363d}
151
+ .cmssy-diagnostics .cmssy-diagnostics-ok{color:#3fb950}
152
+ .cmssy-diagnostics .cmssy-diagnostics-fail{color:#f85149}
153
+ .cmssy-diagnostics .cmssy-diagnostics-unknown{color:#d29922}
154
+ }
155
+ `.trim();
156
+ function escapeHtml(value) {
157
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
158
+ }
159
+ function renderEditDiagnostics(diagnostics) {
160
+ const items = diagnostics.checks.map(
161
+ (check) => `<li><span class="cmssy-diagnostics-status cmssy-diagnostics-${check.status}">${STATUS_LABEL[check.status]}</span><strong>${escapeHtml(check.name)}</strong>: ${escapeHtml(check.message)}${check.fix ? `<span class="cmssy-diagnostics-fix">fix: ${escapeHtml(check.fix)}</span>` : ""}</li>`
162
+ ).join("");
163
+ const workspaceLine = diagnostics.workspace ? `<p>workspace: <strong>${escapeHtml(diagnostics.workspace)}</strong></p>` : "";
164
+ return `<style>${DIAGNOSTICS_CSS}</style><main class="cmssy-diagnostics"><h1>cmssy editor diagnostics</h1><p>The editor request could not be verified, so the editor preview cannot render.</p>${workspaceLine}<ul>${items}</ul><p class="cmssy-diagnostics-note">This page is shown in development only - production keeps serving a 404 here.</p></main>`;
165
+ }
166
+ function renderEditDiagnosticsDocument(diagnostics) {
167
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><title>cmssy editor diagnostics</title><style>body{margin:0;background:#fff}@media (prefers-color-scheme:dark){body{background:#0d1117}}</style></head><body>${renderEditDiagnostics(diagnostics)}</body></html>`;
168
+ }
169
+
17
170
  // src/preflight.ts
18
171
  var CMSSY_ADMIN_ORIGIN = "https://www.cmssy.io";
19
172
  var ALLOWED_FRAME_HOSTS = ["cmssy.io", "www.cmssy.io"];
@@ -271,3 +424,6 @@ exports.checkDraftSecret = checkDraftSecret;
271
424
  exports.checkFrameAncestors = checkFrameAncestors;
272
425
  exports.checkPreviewUrl = checkPreviewUrl;
273
426
  exports.checkWorkspaceReachable = checkWorkspaceReachable;
427
+ exports.collectEditDiagnostics = collectEditDiagnostics;
428
+ exports.renderEditDiagnostics = renderEditDiagnostics;
429
+ exports.renderEditDiagnosticsDocument = renderEditDiagnosticsDocument;
@@ -1,6 +1,32 @@
1
1
  import { F as FetchLike } from './content-client-D0EdiqbQ.cjs';
2
2
  import { CmssyClientConfig } from '@cmssy/types';
3
3
 
4
+ interface EditDiagnosticsConfig {
5
+ apiUrl?: string;
6
+ org?: string;
7
+ workspaceSlug?: string;
8
+ draftSecret?: string;
9
+ }
10
+ interface EditDiagnosticsInput {
11
+ config: EditDiagnosticsConfig;
12
+ providedSecret?: string | null;
13
+ devOrigin?: string;
14
+ fetch?: FetchLike;
15
+ }
16
+ interface EditDiagnosticsCheck {
17
+ name: string;
18
+ status: PreflightStatus;
19
+ message: string;
20
+ fix?: string;
21
+ }
22
+ interface EditDiagnostics {
23
+ workspace: string | null;
24
+ checks: EditDiagnosticsCheck[];
25
+ }
26
+ declare function collectEditDiagnostics(input: EditDiagnosticsInput): Promise<EditDiagnostics>;
27
+ declare function renderEditDiagnostics(diagnostics: EditDiagnostics): string;
28
+ declare function renderEditDiagnosticsDocument(diagnostics: EditDiagnostics): string;
29
+
4
30
  type PreflightStatus = "ok" | "fail" | "unknown";
5
31
  interface PreflightResult {
6
32
  status: PreflightStatus;
@@ -20,4 +46,4 @@ declare function checkPreviewUrl(previewUrl: string | null | undefined, devOrigi
20
46
  declare function checkFrameAncestors(cspHeaderValue: string | null | undefined): PreflightResult;
21
47
  declare function buildEditorUrl(config: Pick<CmssyClientConfig, "org" | "workspaceSlug">, pageId?: string): string;
22
48
 
23
- export { type PreflightConfig, type PreflightResult, type PreflightStatus, type WorkspaceReachableResult, buildEditorUrl, checkDraftSecret, checkFrameAncestors, checkPreviewUrl, checkWorkspaceReachable };
49
+ export { type EditDiagnostics, type EditDiagnosticsCheck, type EditDiagnosticsConfig, type EditDiagnosticsInput, type PreflightConfig, type PreflightResult, type PreflightStatus, type WorkspaceReachableResult, buildEditorUrl, checkDraftSecret, checkFrameAncestors, checkPreviewUrl, checkWorkspaceReachable, collectEditDiagnostics, renderEditDiagnostics, renderEditDiagnosticsDocument };
@@ -1,6 +1,32 @@
1
1
  import { F as FetchLike } from './content-client-D0EdiqbQ.js';
2
2
  import { CmssyClientConfig } from '@cmssy/types';
3
3
 
4
+ interface EditDiagnosticsConfig {
5
+ apiUrl?: string;
6
+ org?: string;
7
+ workspaceSlug?: string;
8
+ draftSecret?: string;
9
+ }
10
+ interface EditDiagnosticsInput {
11
+ config: EditDiagnosticsConfig;
12
+ providedSecret?: string | null;
13
+ devOrigin?: string;
14
+ fetch?: FetchLike;
15
+ }
16
+ interface EditDiagnosticsCheck {
17
+ name: string;
18
+ status: PreflightStatus;
19
+ message: string;
20
+ fix?: string;
21
+ }
22
+ interface EditDiagnostics {
23
+ workspace: string | null;
24
+ checks: EditDiagnosticsCheck[];
25
+ }
26
+ declare function collectEditDiagnostics(input: EditDiagnosticsInput): Promise<EditDiagnostics>;
27
+ declare function renderEditDiagnostics(diagnostics: EditDiagnostics): string;
28
+ declare function renderEditDiagnosticsDocument(diagnostics: EditDiagnostics): string;
29
+
4
30
  type PreflightStatus = "ok" | "fail" | "unknown";
5
31
  interface PreflightResult {
6
32
  status: PreflightStatus;
@@ -20,4 +46,4 @@ declare function checkPreviewUrl(previewUrl: string | null | undefined, devOrigi
20
46
  declare function checkFrameAncestors(cspHeaderValue: string | null | undefined): PreflightResult;
21
47
  declare function buildEditorUrl(config: Pick<CmssyClientConfig, "org" | "workspaceSlug">, pageId?: string): string;
22
48
 
23
- export { type PreflightConfig, type PreflightResult, type PreflightStatus, type WorkspaceReachableResult, buildEditorUrl, checkDraftSecret, checkFrameAncestors, checkPreviewUrl, checkWorkspaceReachable };
49
+ export { type EditDiagnostics, type EditDiagnosticsCheck, type EditDiagnosticsConfig, type EditDiagnosticsInput, type PreflightConfig, type PreflightResult, type PreflightStatus, type WorkspaceReachableResult, buildEditorUrl, checkDraftSecret, checkFrameAncestors, checkPreviewUrl, checkWorkspaceReachable, collectEditDiagnostics, renderEditDiagnostics, renderEditDiagnosticsDocument };
package/dist/preflight.js CHANGED
@@ -12,6 +12,159 @@ function resolvePublicUrl(config) {
12
12
  return `${base}/public/${config.org}/${config.workspaceSlug}/graphql`;
13
13
  }
14
14
 
15
+ // src/config.ts
16
+ var DEFAULT_CMSSY_EDITOR_ORIGINS = [
17
+ "https://cmssy.io",
18
+ "https://www.cmssy.io"
19
+ ];
20
+
21
+ // src/edit-diagnostics.ts
22
+ var REQUIRED_ENV = [
23
+ ["org", "CMSSY_ORG_SLUG"],
24
+ ["workspaceSlug", "CMSSY_WORKSPACE_SLUG"],
25
+ ["draftSecret", "CMSSY_DRAFT_SECRET"]
26
+ ];
27
+ var SETTINGS_FIX = "copy the values from Settings \u2192 Headless in the cmssy dashboard, or run npx @cmssy/cli link";
28
+ async function collectEditDiagnostics(input) {
29
+ const { config } = input;
30
+ const checks = [];
31
+ const missing = REQUIRED_ENV.filter(([key]) => {
32
+ const value = config[key];
33
+ return !(typeof value === "string" && value.trim());
34
+ }).map(([, env]) => env);
35
+ if (missing.length > 0) {
36
+ checks.push({
37
+ name: "configuration",
38
+ status: "fail",
39
+ message: `missing environment variables: ${missing.join(", ")}`,
40
+ fix: SETTINGS_FIX
41
+ });
42
+ } else {
43
+ checks.push({
44
+ name: "configuration",
45
+ status: "ok",
46
+ message: "CMSSY_ORG_SLUG, CMSSY_WORKSPACE_SLUG and CMSSY_DRAFT_SECRET are set"
47
+ });
48
+ }
49
+ const org = config.org?.trim();
50
+ const workspaceSlug = config.workspaceSlug?.trim();
51
+ const workspace = org && workspaceSlug ? `${org}/${workspaceSlug}` : null;
52
+ let previewUrl;
53
+ if (org && workspaceSlug) {
54
+ const preflight = {
55
+ apiUrl: config.apiUrl,
56
+ org,
57
+ workspaceSlug,
58
+ ...input.fetch ? { fetch: input.fetch } : {}
59
+ };
60
+ const reachable = await checkWorkspaceReachable(preflight);
61
+ previewUrl = reachable.previewUrl;
62
+ checks.push({
63
+ name: "workspace",
64
+ status: reachable.status,
65
+ message: reachable.message,
66
+ ...reachable.fix ? { fix: reachable.fix } : {}
67
+ });
68
+ const draftSecret = config.draftSecret?.trim();
69
+ if (draftSecret) {
70
+ checks.push(
71
+ await diagnoseSecret(
72
+ { ...preflight, draftSecret },
73
+ input.providedSecret
74
+ )
75
+ );
76
+ }
77
+ }
78
+ if (input.devOrigin) {
79
+ const preview = checkPreviewUrl(previewUrl ?? null, input.devOrigin);
80
+ checks.push({
81
+ name: "preview URL",
82
+ status: preview.status,
83
+ message: preview.message,
84
+ ...preview.fix ? { fix: preview.fix } : {}
85
+ });
86
+ }
87
+ checks.push({
88
+ name: "frame-ancestors",
89
+ status: "unknown",
90
+ message: "CSP blocking cannot be detected from inside the frame - if this page never appears in the editor at all, the likely causes are the preview URL pointing elsewhere or a Content-Security-Policy frame-ancestors that blocks the editor",
91
+ fix: `frame-ancestors must allow ${DEFAULT_CMSSY_EDITOR_ORIGINS.join(" ")} (cmssyCspHeaders / applyCmssyCsp set this for you)`
92
+ });
93
+ return { workspace, checks };
94
+ }
95
+ async function diagnoseSecret(preflight, providedSecret) {
96
+ const name = "draft secret";
97
+ if (!providedSecret?.trim()) {
98
+ return {
99
+ name,
100
+ status: "fail",
101
+ message: "the request carried no cmssySecret - a bare cmssyEdit=1 is never trusted",
102
+ fix: "open the page from the cmssy editor, which appends the cmssySecret itself"
103
+ };
104
+ }
105
+ const platform = await checkDraftSecret(preflight);
106
+ if (platform.status === "fail") {
107
+ return {
108
+ name,
109
+ status: "fail",
110
+ message: `the provided cmssySecret does not match CMSSY_DRAFT_SECRET, and ${platform.message}`,
111
+ ...platform.fix ? { fix: platform.fix } : {}
112
+ };
113
+ }
114
+ if (platform.status === "ok") {
115
+ return {
116
+ name,
117
+ status: "fail",
118
+ message: "CMSSY_DRAFT_SECRET matches this workspace, but the cmssySecret sent with the request does not match it",
119
+ fix: "reload the editor so it opens the preview with the current secret"
120
+ };
121
+ }
122
+ return {
123
+ name,
124
+ status: "unknown",
125
+ message: `could not verify against the platform (${platform.message}); the provided cmssySecret failed local verification against CMSSY_DRAFT_SECRET`,
126
+ fix: `make sure CMSSY_DRAFT_SECRET matches Settings \u2192 Headless, then reload the editor`
127
+ };
128
+ }
129
+ var STATUS_LABEL = {
130
+ ok: "OK",
131
+ fail: "FAIL",
132
+ unknown: "?"
133
+ };
134
+ var DIAGNOSTICS_CSS = `
135
+ .cmssy-diagnostics{font:14px/1.6 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;max-width:44rem;margin:3rem auto;padding:0 1.5rem;color:#1f2328}
136
+ .cmssy-diagnostics h1{font-size:1.15rem;margin:0 0 .5rem}
137
+ .cmssy-diagnostics p{margin:.5rem 0}
138
+ .cmssy-diagnostics ul{list-style:none;margin:1.5rem 0;padding:0}
139
+ .cmssy-diagnostics li{margin:0 0 .75rem;padding:.75rem 1rem;border:1px solid #d0d7de;border-radius:6px}
140
+ .cmssy-diagnostics .cmssy-diagnostics-status{font-weight:700;margin-right:.5rem}
141
+ .cmssy-diagnostics .cmssy-diagnostics-ok{color:#1a7f37}
142
+ .cmssy-diagnostics .cmssy-diagnostics-fail{color:#c5273c}
143
+ .cmssy-diagnostics .cmssy-diagnostics-unknown{color:#9a6700}
144
+ .cmssy-diagnostics .cmssy-diagnostics-fix{display:block;margin-top:.25rem;opacity:.75}
145
+ .cmssy-diagnostics .cmssy-diagnostics-note{opacity:.65}
146
+ @media (prefers-color-scheme:dark){
147
+ .cmssy-diagnostics{color:#e6edf3}
148
+ .cmssy-diagnostics li{border-color:#30363d}
149
+ .cmssy-diagnostics .cmssy-diagnostics-ok{color:#3fb950}
150
+ .cmssy-diagnostics .cmssy-diagnostics-fail{color:#f85149}
151
+ .cmssy-diagnostics .cmssy-diagnostics-unknown{color:#d29922}
152
+ }
153
+ `.trim();
154
+ function escapeHtml(value) {
155
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
156
+ }
157
+ function renderEditDiagnostics(diagnostics) {
158
+ const items = diagnostics.checks.map(
159
+ (check) => `<li><span class="cmssy-diagnostics-status cmssy-diagnostics-${check.status}">${STATUS_LABEL[check.status]}</span><strong>${escapeHtml(check.name)}</strong>: ${escapeHtml(check.message)}${check.fix ? `<span class="cmssy-diagnostics-fix">fix: ${escapeHtml(check.fix)}</span>` : ""}</li>`
160
+ ).join("");
161
+ const workspaceLine = diagnostics.workspace ? `<p>workspace: <strong>${escapeHtml(diagnostics.workspace)}</strong></p>` : "";
162
+ return `<style>${DIAGNOSTICS_CSS}</style><main class="cmssy-diagnostics"><h1>cmssy editor diagnostics</h1><p>The editor request could not be verified, so the editor preview cannot render.</p>${workspaceLine}<ul>${items}</ul><p class="cmssy-diagnostics-note">This page is shown in development only - production keeps serving a 404 here.</p></main>`;
163
+ }
164
+ function renderEditDiagnosticsDocument(diagnostics) {
165
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><title>cmssy editor diagnostics</title><style>body{margin:0;background:#fff}@media (prefers-color-scheme:dark){body{background:#0d1117}}</style></head><body>${renderEditDiagnostics(diagnostics)}</body></html>`;
166
+ }
167
+
15
168
  // src/preflight.ts
16
169
  var CMSSY_ADMIN_ORIGIN = "https://www.cmssy.io";
17
170
  var ALLOWED_FRAME_HOSTS = ["cmssy.io", "www.cmssy.io"];
@@ -264,4 +417,4 @@ function buildEditorUrl(config, pageId) {
264
417
  return pageId ? `${base}?pageId=${encodeURIComponent(pageId)}` : base;
265
418
  }
266
419
 
267
- export { buildEditorUrl, checkDraftSecret, checkFrameAncestors, checkPreviewUrl, checkWorkspaceReachable };
420
+ export { buildEditorUrl, checkDraftSecret, checkFrameAncestors, checkPreviewUrl, checkWorkspaceReachable, collectEditDiagnostics, renderEditDiagnostics, renderEditDiagnosticsDocument };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmssy/core",
3
- "version": "9.1.0",
3
+ "version": "9.2.0",
4
4
  "description": "Framework-agnostic cmssy client: content, commerce, config, editor protocol. No React, no Next.",
5
5
  "keywords": [
6
6
  "cmssy",