@407dev/cms-astro 0.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/README.md +69 -0
- package/dist/chunk-2TKIQIOE.js +118 -0
- package/dist/chunk-2TKIQIOE.js.map +1 -0
- package/dist/chunk-53OQ5RXJ.js +912 -0
- package/dist/chunk-53OQ5RXJ.js.map +1 -0
- package/dist/chunk-56OMNQLH.js +1013 -0
- package/dist/chunk-56OMNQLH.js.map +1 -0
- package/dist/chunk-5AOOMQ7K.js +1031 -0
- package/dist/chunk-5AOOMQ7K.js.map +1 -0
- package/dist/chunk-5XRUCFDC.js +743 -0
- package/dist/chunk-5XRUCFDC.js.map +1 -0
- package/dist/chunk-62ZHCOLZ.js +25 -0
- package/dist/chunk-62ZHCOLZ.js.map +1 -0
- package/dist/chunk-6AZLRSYA.js +126 -0
- package/dist/chunk-6AZLRSYA.js.map +1 -0
- package/dist/chunk-6L3ZTCFB.js +14 -0
- package/dist/chunk-6L3ZTCFB.js.map +1 -0
- package/dist/chunk-6O7ZNNDT.js +697 -0
- package/dist/chunk-6O7ZNNDT.js.map +1 -0
- package/dist/chunk-6PQIXTMK.js +697 -0
- package/dist/chunk-6PQIXTMK.js.map +1 -0
- package/dist/chunk-7AW6M7QW.js +1013 -0
- package/dist/chunk-7AW6M7QW.js.map +1 -0
- package/dist/chunk-7E3XJLKX.js +73 -0
- package/dist/chunk-7E3XJLKX.js.map +1 -0
- package/dist/chunk-7LM54OTA.js +1031 -0
- package/dist/chunk-7LM54OTA.js.map +1 -0
- package/dist/chunk-7LMUFCWX.js +126 -0
- package/dist/chunk-7LMUFCWX.js.map +1 -0
- package/dist/chunk-A3OGL4OX.js +706 -0
- package/dist/chunk-A3OGL4OX.js.map +1 -0
- package/dist/chunk-AO4UAWOV.js +1030 -0
- package/dist/chunk-AO4UAWOV.js.map +1 -0
- package/dist/chunk-B4YYLS7Y.js +14 -0
- package/dist/chunk-B4YYLS7Y.js.map +1 -0
- package/dist/chunk-BDFVGXM2.js +697 -0
- package/dist/chunk-BDFVGXM2.js.map +1 -0
- package/dist/chunk-CXM7NZVX.js +1031 -0
- package/dist/chunk-CXM7NZVX.js.map +1 -0
- package/dist/chunk-DMLOBKYE.js +96 -0
- package/dist/chunk-DMLOBKYE.js.map +1 -0
- package/dist/chunk-DXJSYGS6.js +743 -0
- package/dist/chunk-DXJSYGS6.js.map +1 -0
- package/dist/chunk-EFUSAQZM.js +72 -0
- package/dist/chunk-EFUSAQZM.js.map +1 -0
- package/dist/chunk-F7KDGPBN.js +1013 -0
- package/dist/chunk-F7KDGPBN.js.map +1 -0
- package/dist/chunk-FBSQT45X.js +126 -0
- package/dist/chunk-FBSQT45X.js.map +1 -0
- package/dist/chunk-FEPDOMVH.js +697 -0
- package/dist/chunk-FEPDOMVH.js.map +1 -0
- package/dist/chunk-FQDOL2QW.js +25 -0
- package/dist/chunk-FQDOL2QW.js.map +1 -0
- package/dist/chunk-GB3LGQJM.js +109 -0
- package/dist/chunk-GB3LGQJM.js.map +1 -0
- package/dist/chunk-HCNP6R3P.js +736 -0
- package/dist/chunk-HCNP6R3P.js.map +1 -0
- package/dist/chunk-J6WEJDRI.js +743 -0
- package/dist/chunk-J6WEJDRI.js.map +1 -0
- package/dist/chunk-JHEAPNJ2.js +117 -0
- package/dist/chunk-JHEAPNJ2.js.map +1 -0
- package/dist/chunk-JOJDJ6GW.js +25 -0
- package/dist/chunk-JOJDJ6GW.js.map +1 -0
- package/dist/chunk-KTDV6WYB.js +202 -0
- package/dist/chunk-KTDV6WYB.js.map +1 -0
- package/dist/chunk-KZWJBKPC.js +734 -0
- package/dist/chunk-KZWJBKPC.js.map +1 -0
- package/dist/chunk-LEAQBGL6.js +109 -0
- package/dist/chunk-LEAQBGL6.js.map +1 -0
- package/dist/chunk-LGZJL2Q7.js +697 -0
- package/dist/chunk-LGZJL2Q7.js.map +1 -0
- package/dist/chunk-LQPBU46R.js +109 -0
- package/dist/chunk-LQPBU46R.js.map +1 -0
- package/dist/chunk-M55NNUTY.js +1013 -0
- package/dist/chunk-M55NNUTY.js.map +1 -0
- package/dist/chunk-M6NCYCC3.js +1013 -0
- package/dist/chunk-M6NCYCC3.js.map +1 -0
- package/dist/chunk-MGN7OZAU.js +109 -0
- package/dist/chunk-MGN7OZAU.js.map +1 -0
- package/dist/chunk-MGYZOX4K.js +1013 -0
- package/dist/chunk-MGYZOX4K.js.map +1 -0
- package/dist/chunk-MY4IEKJS.js +1031 -0
- package/dist/chunk-MY4IEKJS.js.map +1 -0
- package/dist/chunk-NCQRGAB4.js +705 -0
- package/dist/chunk-NCQRGAB4.js.map +1 -0
- package/dist/chunk-NI4NHXI4.js +697 -0
- package/dist/chunk-NI4NHXI4.js.map +1 -0
- package/dist/chunk-NPI5N7AU.js +14 -0
- package/dist/chunk-NPI5N7AU.js.map +1 -0
- package/dist/chunk-QN4CPMJF.js +126 -0
- package/dist/chunk-QN4CPMJF.js.map +1 -0
- package/dist/chunk-QYUVAKMC.js +1013 -0
- package/dist/chunk-QYUVAKMC.js.map +1 -0
- package/dist/chunk-RAAT6PDT.js +25 -0
- package/dist/chunk-RAAT6PDT.js.map +1 -0
- package/dist/chunk-RDPO5VPS.js +1013 -0
- package/dist/chunk-RDPO5VPS.js.map +1 -0
- package/dist/chunk-RQHIW2PF.js +25 -0
- package/dist/chunk-RQHIW2PF.js.map +1 -0
- package/dist/chunk-RREVHA52.js +722 -0
- package/dist/chunk-RREVHA52.js.map +1 -0
- package/dist/chunk-S7PT4PZN.js +25 -0
- package/dist/chunk-S7PT4PZN.js.map +1 -0
- package/dist/chunk-SUXV5M32.js +109 -0
- package/dist/chunk-SUXV5M32.js.map +1 -0
- package/dist/chunk-SZEORUY6.js +696 -0
- package/dist/chunk-SZEORUY6.js.map +1 -0
- package/dist/chunk-T7NGGWGY.js +25 -0
- package/dist/chunk-T7NGGWGY.js.map +1 -0
- package/dist/chunk-UEVWP5VE.js +696 -0
- package/dist/chunk-UEVWP5VE.js.map +1 -0
- package/dist/chunk-UJNKNP7U.js +748 -0
- package/dist/chunk-UJNKNP7U.js.map +1 -0
- package/dist/chunk-V4XVFWFI.js +1013 -0
- package/dist/chunk-V4XVFWFI.js.map +1 -0
- package/dist/chunk-VJU2CIKZ.js +729 -0
- package/dist/chunk-VJU2CIKZ.js.map +1 -0
- package/dist/chunk-VRQUP5L3.js +202 -0
- package/dist/chunk-VRQUP5L3.js.map +1 -0
- package/dist/chunk-WBJ5E7CL.js +742 -0
- package/dist/chunk-WBJ5E7CL.js.map +1 -0
- package/dist/chunk-XJPVYMZM.js +24 -0
- package/dist/chunk-XJPVYMZM.js.map +1 -0
- package/dist/chunk-Y3Q65C7R.js +14 -0
- package/dist/chunk-Y3Q65C7R.js.map +1 -0
- package/dist/chunk-YHEGFPTR.js +1030 -0
- package/dist/chunk-YHEGFPTR.js.map +1 -0
- package/dist/chunk-ZDLBN46C.js +697 -0
- package/dist/chunk-ZDLBN46C.js.map +1 -0
- package/dist/dev/index.d.ts +122 -0
- package/dist/dev/index.js +662 -0
- package/dist/dev/index.js.map +1 -0
- package/dist/fields/collections.d.ts +18 -0
- package/dist/fields/collections.js +37 -0
- package/dist/fields/collections.js.map +1 -0
- package/dist/index.d.ts +235 -0
- package/dist/index.js +956 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/index.d.ts +8 -0
- package/dist/middleware/index.js +13 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/preview-BEkLMD6N.d.ts +23 -0
- package/dist/preview-D9yewK8x.d.ts +24 -0
- package/dist/preview-DJEA4fj7.d.ts +23 -0
- package/dist/preview-DeD1Jn7U.d.ts +23 -0
- package/dist/toolbar/app.d.ts +28 -0
- package/dist/toolbar/app.js +1176 -0
- package/dist/toolbar/app.js.map +1 -0
- package/dist/types-15jfCQKb.d.ts +141 -0
- package/dist/types-BREdL9Gx.d.ts +140 -0
- package/dist/types-C6sUZNa2.d.ts +104 -0
- package/dist/types-CEYS5Fzc.d.ts +112 -0
- package/dist/types-DpeHpobR.d.ts +104 -0
- package/dist/types-NaKJe6Pg.d.ts +140 -0
- package/package.json +90 -0
- package/src/CmsHead.astro +25 -0
- package/src/CmsImage.astro +62 -0
- package/src/CmsVideo.astro +185 -0
- package/src/RichText.astro +32 -0
- package/src/client.test.ts +227 -0
- package/src/client.ts +164 -0
- package/src/dev/config.test.ts +258 -0
- package/src/dev/config.ts +319 -0
- package/src/dev/index.ts +5 -0
- package/src/dev/preview-token.ts +39 -0
- package/src/dev/routes.test.ts +354 -0
- package/src/dev/routes.ts +245 -0
- package/src/dev/token.test.ts +280 -0
- package/src/dev/token.ts +201 -0
- package/src/dev/verifier.test.ts +63 -0
- package/src/dev/verifier.ts +161 -0
- package/src/fields/attrs.ts +24 -0
- package/src/fields/collection.ts +72 -0
- package/src/fields/collections.ts +35 -0
- package/src/fields/entryScope.test.ts +62 -0
- package/src/fields/entryScope.ts +240 -0
- package/src/fields/group.ts +51 -0
- package/src/fields/helpers.test.ts +51 -0
- package/src/fields/helpers.ts +214 -0
- package/src/fields/index.ts +46 -0
- package/src/fields/scope.ts +21 -0
- package/src/fields/types.ts +119 -0
- package/src/head.test.ts +74 -0
- package/src/head.ts +123 -0
- package/src/image.test.ts +42 -0
- package/src/image.ts +64 -0
- package/src/index.test.ts +76 -0
- package/src/index.ts +13 -0
- package/src/integration.ts +218 -0
- package/src/jwt.ts +131 -0
- package/src/middleware/index.ts +4 -0
- package/src/middleware/preview.test.ts +184 -0
- package/src/middleware/preview.ts +206 -0
- package/src/preview/bridge-script.test.ts +683 -0
- package/src/preview/bridge-script.ts +763 -0
- package/src/preview/origins.ts +29 -0
- package/src/preview.ts +49 -0
- package/src/richtext/RichTextNode.astro +130 -0
- package/src/richtext/render.ts +248 -0
- package/src/richtext.test.ts +139 -0
- package/src/runtime/bundle.ts +28 -0
- package/src/runtime/store.ts +130 -0
- package/src/siteFiles.test.ts +93 -0
- package/src/siteFiles.ts +111 -0
- package/src/toolbar/app.test.ts +191 -0
- package/src/toolbar/app.ts +1091 -0
- package/src/toolbar/icon.ts +5 -0
- package/src/toolbar/widgets.test.ts +153 -0
- package/src/toolbar/widgets.ts +365 -0
- package/src/types.test.ts +154 -0
- package/src/types.ts +163 -0
- package/src/version.ts +3 -0
|
@@ -0,0 +1,662 @@
|
|
|
1
|
+
import { mintDevPreviewToken } from '../chunk-7LMUFCWX.js';
|
|
2
|
+
export { DEV_PREVIEW_TOKEN_TTL_SECONDS, mintDevPreviewToken } from '../chunk-7LMUFCWX.js';
|
|
3
|
+
import { DEFAULT_DEV_API_URL } from '../chunk-B4YYLS7Y.js';
|
|
4
|
+
import { updateBundleFieldValue } from '../chunk-DMLOBKYE.js';
|
|
5
|
+
import { resolveEditorOrigins } from '../chunk-XJPVYMZM.js';
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import crypto from 'crypto';
|
|
9
|
+
import { hashSchema } from '@407dev/blocks';
|
|
10
|
+
|
|
11
|
+
var WORKSPACE_ELEVATED_ROLES = /* @__PURE__ */ new Set(["owner", "editor"]);
|
|
12
|
+
function collectWorkspaceRoles(payload) {
|
|
13
|
+
const appMetadata = payload.app_metadata;
|
|
14
|
+
if (!appMetadata || typeof appMetadata !== "object") return [];
|
|
15
|
+
const roles = [];
|
|
16
|
+
if (typeof appMetadata.workspace_id === "string" && typeof appMetadata.role === "string") {
|
|
17
|
+
roles.push(appMetadata.role);
|
|
18
|
+
}
|
|
19
|
+
const workspaces = appMetadata.workspaces;
|
|
20
|
+
if (workspaces && typeof workspaces === "object") {
|
|
21
|
+
for (const entry of Object.values(workspaces)) {
|
|
22
|
+
const role = entry?.role;
|
|
23
|
+
if (typeof role === "string") roles.push(role);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return roles;
|
|
27
|
+
}
|
|
28
|
+
function base64UrlDecode(str) {
|
|
29
|
+
let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
30
|
+
while (base64.length % 4 !== 0) {
|
|
31
|
+
base64 += "=";
|
|
32
|
+
}
|
|
33
|
+
return Buffer.from(base64, "base64").toString("utf-8");
|
|
34
|
+
}
|
|
35
|
+
function validateDevToken(token, options) {
|
|
36
|
+
const allowElevated = typeof options === "boolean" ? options : Boolean(options?.allowElevated);
|
|
37
|
+
if (!token || typeof token !== "string" || !token.trim()) {
|
|
38
|
+
return { ok: false, reason: "CMS_DEV_TOKEN is missing or empty" };
|
|
39
|
+
}
|
|
40
|
+
const parts = token.trim().split(".");
|
|
41
|
+
if (parts.length !== 3) {
|
|
42
|
+
return { ok: false, reason: "CMS_DEV_TOKEN is not a valid 3-part JWT" };
|
|
43
|
+
}
|
|
44
|
+
let payload;
|
|
45
|
+
try {
|
|
46
|
+
const decoded = base64UrlDecode(parts[1]);
|
|
47
|
+
payload = JSON.parse(decoded);
|
|
48
|
+
if (!payload || typeof payload !== "object") {
|
|
49
|
+
return { ok: false, reason: "CMS_DEV_TOKEN payload is not a valid JSON object" };
|
|
50
|
+
}
|
|
51
|
+
} catch {
|
|
52
|
+
return { ok: false, reason: "CMS_DEV_TOKEN payload is not decodable" };
|
|
53
|
+
}
|
|
54
|
+
if (payload.role === "service_role") {
|
|
55
|
+
return {
|
|
56
|
+
ok: false,
|
|
57
|
+
reason: "CMS_DEV_TOKEN must not be a service_role key; authenticated user JWT required"
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (payload.role !== "authenticated") {
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
reason: `CMS_DEV_TOKEN has invalid role '${String(payload.role || "none")}'; expected 'authenticated'`
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (typeof payload.exp === "number" && payload.exp * 1e3 <= Date.now()) {
|
|
67
|
+
return { ok: false, reason: "CMS_DEV_TOKEN has expired" };
|
|
68
|
+
}
|
|
69
|
+
const workspaceRoles = collectWorkspaceRoles(payload);
|
|
70
|
+
if (!allowElevated) {
|
|
71
|
+
const elevatedRole = workspaceRoles.find((role) => WORKSPACE_ELEVATED_ROLES.has(role));
|
|
72
|
+
if (elevatedRole) {
|
|
73
|
+
return {
|
|
74
|
+
ok: false,
|
|
75
|
+
reason: `CMS_DEV_TOKEN grants workspace '${elevatedRole}' scope; only workspace-member tokens are permitted`
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const sub = typeof payload.sub === "string" ? payload.sub : "";
|
|
80
|
+
if (!sub) {
|
|
81
|
+
return { ok: false, reason: "CMS_DEV_TOKEN is missing sub claim" };
|
|
82
|
+
}
|
|
83
|
+
const email = typeof payload.email === "string" ? payload.email : typeof payload.user_metadata?.email === "string" ? payload.user_metadata.email : void 0;
|
|
84
|
+
const exp = typeof payload.exp === "number" ? payload.exp : void 0;
|
|
85
|
+
const workspaceRole = workspaceRoles[0];
|
|
86
|
+
return {
|
|
87
|
+
ok: true,
|
|
88
|
+
identity: {
|
|
89
|
+
sub,
|
|
90
|
+
email,
|
|
91
|
+
exp,
|
|
92
|
+
workspaceRole
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function signDevToken(secret, siteIdOrOptions, workspaceIdParam, userIdParam) {
|
|
97
|
+
let siteId = "00000000-0000-0000-0000-000000000002";
|
|
98
|
+
let workspaceId = "00000000-0000-0000-0000-000000000001";
|
|
99
|
+
let userId = "00000000-0000-0000-0000-000000000003";
|
|
100
|
+
if (typeof siteIdOrOptions === "object" && siteIdOrOptions !== null) {
|
|
101
|
+
if (siteIdOrOptions.siteId) siteId = siteIdOrOptions.siteId;
|
|
102
|
+
if (siteIdOrOptions.workspaceId) workspaceId = siteIdOrOptions.workspaceId;
|
|
103
|
+
if (siteIdOrOptions.userId) userId = siteIdOrOptions.userId;
|
|
104
|
+
} else {
|
|
105
|
+
if (typeof siteIdOrOptions === "string") siteId = siteIdOrOptions;
|
|
106
|
+
if (typeof workspaceIdParam === "string") workspaceId = workspaceIdParam;
|
|
107
|
+
if (typeof userIdParam === "string") userId = userIdParam;
|
|
108
|
+
}
|
|
109
|
+
const header = { alg: "HS256", typ: "JWT" };
|
|
110
|
+
const payload = {
|
|
111
|
+
aud: "authenticated",
|
|
112
|
+
role: "authenticated",
|
|
113
|
+
sub: userId,
|
|
114
|
+
email: "dev@example.com",
|
|
115
|
+
app_metadata: {
|
|
116
|
+
workspace_id: workspaceId,
|
|
117
|
+
site_id: siteId,
|
|
118
|
+
role: "viewer",
|
|
119
|
+
workspaces: {
|
|
120
|
+
[workspaceId]: { role: "viewer" }
|
|
121
|
+
},
|
|
122
|
+
sites: {
|
|
123
|
+
[siteId]: { role: "editor", workspace_id: workspaceId }
|
|
124
|
+
},
|
|
125
|
+
site_roles: {
|
|
126
|
+
[siteId]: "editor"
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
user_metadata: { role: "viewer" },
|
|
130
|
+
iat: Math.floor(Date.now() / 1e3),
|
|
131
|
+
exp: Math.floor(Date.now() / 1e3) + 3600 * 24 * 365
|
|
132
|
+
};
|
|
133
|
+
const encodedHeader = Buffer.from(JSON.stringify(header)).toString("base64url");
|
|
134
|
+
const encodedPayload = Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
135
|
+
const data = `${encodedHeader}.${encodedPayload}`;
|
|
136
|
+
const signature = crypto.createHmac("sha256", secret).update(data).digest("base64url");
|
|
137
|
+
return `${data}.${signature}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/dev/config.ts
|
|
141
|
+
async function defaultLoadCredential(apiUrl, fetchFn) {
|
|
142
|
+
try {
|
|
143
|
+
const cliPkg = "@407dev/cli";
|
|
144
|
+
const cliModule = await import(
|
|
145
|
+
/* @vite-ignore */
|
|
146
|
+
cliPkg
|
|
147
|
+
);
|
|
148
|
+
if (!cliModule?.getStoredCredentials || !cliModule?.refreshCredentialsIfNeeded) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
const creds = cliModule.getStoredCredentials(apiUrl);
|
|
152
|
+
if (!creds) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
const refreshed = await cliModule.refreshCredentialsIfNeeded(apiUrl, creds, void 0, fetchFn);
|
|
156
|
+
return refreshed?.access_token || null;
|
|
157
|
+
} catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async function refreshDevConfigToken(devConfig, options = {}) {
|
|
162
|
+
if (devConfig.tokenSource !== "cli") {
|
|
163
|
+
return devConfig;
|
|
164
|
+
}
|
|
165
|
+
const exp = devConfig.identity?.exp;
|
|
166
|
+
if (typeof exp === "number" && exp - Math.floor(Date.now() / 1e3) > 60) {
|
|
167
|
+
return devConfig;
|
|
168
|
+
}
|
|
169
|
+
const loadCredential = options.loadCredential ?? ((url) => defaultLoadCredential(url, options.fetchFn));
|
|
170
|
+
try {
|
|
171
|
+
const refreshedToken = await loadCredential(devConfig.apiUrl);
|
|
172
|
+
if (!refreshedToken?.trim()) {
|
|
173
|
+
return devConfig;
|
|
174
|
+
}
|
|
175
|
+
const validation = validateDevToken(refreshedToken, { allowElevated: true });
|
|
176
|
+
if (!validation.ok) {
|
|
177
|
+
return devConfig;
|
|
178
|
+
}
|
|
179
|
+
devConfig.devToken = refreshedToken.trim();
|
|
180
|
+
devConfig.identity = validation.identity;
|
|
181
|
+
} catch {
|
|
182
|
+
}
|
|
183
|
+
return devConfig;
|
|
184
|
+
}
|
|
185
|
+
async function resolveDevConfig(options = {}) {
|
|
186
|
+
const siteDir = options.siteDir ? path.resolve(options.siteDir) : process.cwd();
|
|
187
|
+
const fetchClient = options.fetchFn || fetch;
|
|
188
|
+
let env = {};
|
|
189
|
+
try {
|
|
190
|
+
const vitePkg = "vite";
|
|
191
|
+
const vite = await import(
|
|
192
|
+
/* @vite-ignore */
|
|
193
|
+
vitePkg
|
|
194
|
+
);
|
|
195
|
+
if (typeof vite.loadEnv === "function") {
|
|
196
|
+
env = vite.loadEnv("development", siteDir, "");
|
|
197
|
+
}
|
|
198
|
+
} catch {
|
|
199
|
+
}
|
|
200
|
+
let devToken = env.CMS_DEV_TOKEN || process.env.CMS_DEV_TOKEN || "";
|
|
201
|
+
let tokenSource = devToken ? "env" : void 0;
|
|
202
|
+
const devSiteId = env.CMS_DEV_SITE || process.env.CMS_DEV_SITE || "";
|
|
203
|
+
const apiUrl = (options.apiUrl || env.CMS_API_URL || process.env.CMS_API_URL || DEFAULT_DEV_API_URL).replace(/\/$/, "");
|
|
204
|
+
const siteId = options.siteId || env.CMS_SITE_ID || process.env.CMS_SITE_ID || "";
|
|
205
|
+
if (!devToken) {
|
|
206
|
+
const loadCredential = options.loadCredential ?? ((url) => defaultLoadCredential(url, fetchClient));
|
|
207
|
+
try {
|
|
208
|
+
const cliToken = await loadCredential(apiUrl);
|
|
209
|
+
if (cliToken?.trim()) {
|
|
210
|
+
devToken = cliToken.trim();
|
|
211
|
+
tokenSource = "cli";
|
|
212
|
+
}
|
|
213
|
+
} catch {
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (!devToken && (apiUrl.includes("127.0.0.1") || apiUrl.includes("localhost"))) {
|
|
217
|
+
const jwtSecret = env.SUPABASE_JWT_SECRET || process.env.SUPABASE_JWT_SECRET || "super-secret-jwt-token-with-at-least-32-characters-long";
|
|
218
|
+
try {
|
|
219
|
+
devToken = signDevToken(jwtSecret, siteId || devSiteId || void 0);
|
|
220
|
+
tokenSource = "local-mint";
|
|
221
|
+
} catch {
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (!devToken || !devToken.trim()) {
|
|
225
|
+
return {
|
|
226
|
+
status: "unconfigured",
|
|
227
|
+
siteId,
|
|
228
|
+
devSiteId: devSiteId || void 0,
|
|
229
|
+
apiUrl,
|
|
230
|
+
mismatch: null,
|
|
231
|
+
reason: "Not logged in. Run `cms login` to enable dev-time editing."
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
const tokenValidation = validateDevToken(devToken, {
|
|
235
|
+
allowElevated: tokenSource === "cli"
|
|
236
|
+
});
|
|
237
|
+
if (!tokenValidation.ok) {
|
|
238
|
+
return {
|
|
239
|
+
status: "refused",
|
|
240
|
+
siteId,
|
|
241
|
+
devSiteId: devSiteId || void 0,
|
|
242
|
+
apiUrl,
|
|
243
|
+
mismatch: null,
|
|
244
|
+
reason: tokenValidation.reason,
|
|
245
|
+
tokenSource
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
const identity = tokenValidation.identity;
|
|
249
|
+
const effectiveSiteId = siteId || devSiteId;
|
|
250
|
+
if (!effectiveSiteId) {
|
|
251
|
+
return {
|
|
252
|
+
status: "refused",
|
|
253
|
+
siteId,
|
|
254
|
+
devSiteId: devSiteId || void 0,
|
|
255
|
+
apiUrl,
|
|
256
|
+
identity,
|
|
257
|
+
mismatch: null,
|
|
258
|
+
reason: "No site ID provided (set CMS_SITE_ID or siteId option)",
|
|
259
|
+
tokenSource
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
try {
|
|
263
|
+
const probeRes = await fetchClient(`${apiUrl}/sites/${encodeURIComponent(effectiveSiteId)}`, {
|
|
264
|
+
method: "GET",
|
|
265
|
+
headers: {
|
|
266
|
+
Authorization: `Bearer ${devToken}`
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
if (!probeRes.ok) {
|
|
270
|
+
let errorDetail = "";
|
|
271
|
+
try {
|
|
272
|
+
const body = await probeRes.json();
|
|
273
|
+
errorDetail = body.error || "";
|
|
274
|
+
} catch {
|
|
275
|
+
errorDetail = probeRes.statusText || String(probeRes.status);
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
status: "refused",
|
|
279
|
+
siteId: effectiveSiteId,
|
|
280
|
+
devSiteId: devSiteId || void 0,
|
|
281
|
+
apiUrl,
|
|
282
|
+
identity,
|
|
283
|
+
mismatch: null,
|
|
284
|
+
reason: `Site '${effectiveSiteId}' inaccessible with dev token (HTTP ${probeRes.status}: ${errorDetail})`,
|
|
285
|
+
tokenSource
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
} catch (err) {
|
|
289
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
290
|
+
return {
|
|
291
|
+
status: "refused",
|
|
292
|
+
siteId: effectiveSiteId,
|
|
293
|
+
devSiteId: devSiteId || void 0,
|
|
294
|
+
apiUrl,
|
|
295
|
+
identity,
|
|
296
|
+
mismatch: null,
|
|
297
|
+
reason: `Failed to connect to CMS API at ${apiUrl}: ${msg}`,
|
|
298
|
+
tokenSource
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
let mismatch = null;
|
|
302
|
+
if (devSiteId && siteId && devSiteId !== siteId) {
|
|
303
|
+
mismatch = {
|
|
304
|
+
source: "CMS_DEV_SITE",
|
|
305
|
+
expected: siteId,
|
|
306
|
+
actual: devSiteId
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
if (!mismatch) {
|
|
310
|
+
const bundlePath = path.join(siteDir, ".cms", "bundle.json");
|
|
311
|
+
if (fs.existsSync(bundlePath)) {
|
|
312
|
+
try {
|
|
313
|
+
const bundleContent = JSON.parse(fs.readFileSync(bundlePath, "utf-8"));
|
|
314
|
+
if (bundleContent?.site_id && siteId && bundleContent.site_id !== siteId) {
|
|
315
|
+
mismatch = {
|
|
316
|
+
source: ".cms/bundle.json",
|
|
317
|
+
expected: siteId,
|
|
318
|
+
actual: bundleContent.site_id
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
} catch {
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (!mismatch) {
|
|
326
|
+
const statePath = path.join(siteDir, ".cms", "state.json");
|
|
327
|
+
if (fs.existsSync(statePath)) {
|
|
328
|
+
try {
|
|
329
|
+
const stateContent = JSON.parse(fs.readFileSync(statePath, "utf-8"));
|
|
330
|
+
const stateSiteKeys = stateContent?.sites ? Object.keys(stateContent.sites) : [];
|
|
331
|
+
if (stateSiteKeys.length > 0 && siteId && !stateSiteKeys.includes(siteId)) {
|
|
332
|
+
mismatch = {
|
|
333
|
+
source: ".cms/state.json",
|
|
334
|
+
expected: siteId,
|
|
335
|
+
actual: stateSiteKeys.join(", ")
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
} catch {
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (mismatch) {
|
|
343
|
+
return {
|
|
344
|
+
status: "read-only",
|
|
345
|
+
siteId: effectiveSiteId,
|
|
346
|
+
devSiteId: devSiteId || void 0,
|
|
347
|
+
apiUrl,
|
|
348
|
+
devToken,
|
|
349
|
+
identity,
|
|
350
|
+
mismatch,
|
|
351
|
+
tokenSource
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
return {
|
|
355
|
+
status: "ok",
|
|
356
|
+
siteId: effectiveSiteId,
|
|
357
|
+
devSiteId: devSiteId || effectiveSiteId,
|
|
358
|
+
apiUrl,
|
|
359
|
+
devToken,
|
|
360
|
+
identity,
|
|
361
|
+
mismatch: null,
|
|
362
|
+
tokenSource
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// src/dev/routes.ts
|
|
367
|
+
function isLoopbackAddress(remoteAddress) {
|
|
368
|
+
if (!remoteAddress) return false;
|
|
369
|
+
const cleaned = remoteAddress.trim();
|
|
370
|
+
if (cleaned === "127.0.0.1" || cleaned === "::1" || cleaned === "::ffff:127.0.0.1" || cleaned === "localhost") {
|
|
371
|
+
return true;
|
|
372
|
+
}
|
|
373
|
+
if (cleaned.startsWith("::ffff:127.") || cleaned === "0:0:0:0:0:0:0:1") {
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
function sendJson(res, statusCode, data) {
|
|
379
|
+
res.statusCode = statusCode;
|
|
380
|
+
res.setHeader("Content-Type", "application/json");
|
|
381
|
+
res.end(JSON.stringify(data));
|
|
382
|
+
}
|
|
383
|
+
async function readJsonBody(req) {
|
|
384
|
+
return new Promise((resolve, reject) => {
|
|
385
|
+
let body = "";
|
|
386
|
+
req.on("data", (chunk) => {
|
|
387
|
+
body += chunk;
|
|
388
|
+
});
|
|
389
|
+
req.on("end", () => {
|
|
390
|
+
try {
|
|
391
|
+
resolve(body ? JSON.parse(body) : {});
|
|
392
|
+
} catch (err) {
|
|
393
|
+
reject(err);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
req.on("error", reject);
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
function createDevApiMiddleware(options) {
|
|
400
|
+
const {
|
|
401
|
+
devConfig,
|
|
402
|
+
client,
|
|
403
|
+
verifier,
|
|
404
|
+
previewSecret,
|
|
405
|
+
editorOrigins,
|
|
406
|
+
fetchFn = fetch,
|
|
407
|
+
loadCredential
|
|
408
|
+
} = options;
|
|
409
|
+
return async function devApiMiddleware(req, res, next) {
|
|
410
|
+
const rawUrl = req.url || "";
|
|
411
|
+
const [pathname] = rawUrl.split("?");
|
|
412
|
+
if (!pathname.startsWith("/_cms/dev")) {
|
|
413
|
+
return next();
|
|
414
|
+
}
|
|
415
|
+
const remoteAddress = req.socket?.remoteAddress;
|
|
416
|
+
if (!isLoopbackAddress(remoteAddress)) {
|
|
417
|
+
return sendJson(res, 403, {
|
|
418
|
+
error: "Forbidden: CMS dev routes accept loopback callers only"
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
await refreshDevConfigToken(devConfig, { loadCredential, fetchFn });
|
|
422
|
+
if (devConfig.status === "refused" || devConfig.status === "unconfigured") {
|
|
423
|
+
return sendJson(res, 503, {
|
|
424
|
+
error: devConfig.reason || "CMS dev routes unconfigured",
|
|
425
|
+
status: devConfig.status
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
if (pathname === "/_cms/dev/status" && req.method === "GET") {
|
|
429
|
+
const diag = verifier ? await verifier.getDiagnostics() : {
|
|
430
|
+
available: false,
|
|
431
|
+
errors: [],
|
|
432
|
+
warnings: [],
|
|
433
|
+
localHash: null,
|
|
434
|
+
pushedHash: null,
|
|
435
|
+
fresh: true
|
|
436
|
+
};
|
|
437
|
+
return sendJson(res, 200, {
|
|
438
|
+
status: devConfig.status,
|
|
439
|
+
siteId: devConfig.siteId,
|
|
440
|
+
devSiteId: devConfig.devSiteId,
|
|
441
|
+
apiUrl: devConfig.apiUrl,
|
|
442
|
+
env: { CMS_API_URL: devConfig.apiUrl },
|
|
443
|
+
identity: devConfig.identity || null,
|
|
444
|
+
mismatch: devConfig.mismatch || null,
|
|
445
|
+
manifest: {
|
|
446
|
+
fresh: diag.fresh,
|
|
447
|
+
localHash: diag.localHash,
|
|
448
|
+
pushedHash: diag.pushedHash
|
|
449
|
+
},
|
|
450
|
+
verifier: {
|
|
451
|
+
available: diag.available,
|
|
452
|
+
errors: diag.errors,
|
|
453
|
+
warnings: diag.warnings
|
|
454
|
+
},
|
|
455
|
+
editorOrigins: resolveEditorOrigins(editorOrigins)
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
if (pathname === "/_cms/dev/bundle" && req.method === "GET") {
|
|
459
|
+
try {
|
|
460
|
+
let bundle = null;
|
|
461
|
+
if (client) {
|
|
462
|
+
try {
|
|
463
|
+
const previewToken = await mintDevPreviewToken(
|
|
464
|
+
devConfig.siteId,
|
|
465
|
+
previewSecret || client.previewSecret
|
|
466
|
+
);
|
|
467
|
+
bundle = await client.getBundle(previewToken ? { previewToken } : void 0);
|
|
468
|
+
} catch {
|
|
469
|
+
bundle = null;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
let fields = [];
|
|
473
|
+
try {
|
|
474
|
+
const fieldsRes = await fetchFn(
|
|
475
|
+
`${devConfig.apiUrl}/sites/${encodeURIComponent(devConfig.siteId)}/fields?include_orphaned=1`,
|
|
476
|
+
{
|
|
477
|
+
method: "GET",
|
|
478
|
+
headers: {
|
|
479
|
+
Authorization: `Bearer ${devConfig.devToken}`
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
);
|
|
483
|
+
if (fieldsRes.ok) {
|
|
484
|
+
const fieldsJson = await fieldsRes.json();
|
|
485
|
+
fields = Array.isArray(fieldsJson.data) ? fieldsJson.data : [];
|
|
486
|
+
}
|
|
487
|
+
} catch {
|
|
488
|
+
}
|
|
489
|
+
return sendJson(res, 200, {
|
|
490
|
+
bundle,
|
|
491
|
+
fields
|
|
492
|
+
});
|
|
493
|
+
} catch (err) {
|
|
494
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
495
|
+
return sendJson(res, 500, { error: `Failed to load bundle: ${msg}` });
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (pathname === "/_cms/dev/value" && req.method === "PATCH") {
|
|
499
|
+
if (devConfig.status === "read-only") {
|
|
500
|
+
return sendJson(res, 403, {
|
|
501
|
+
error: "Toolbar is in read-only mode due to site ID mismatch",
|
|
502
|
+
mismatch: devConfig.mismatch
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
let body;
|
|
506
|
+
try {
|
|
507
|
+
body = await readJsonBody(req);
|
|
508
|
+
} catch {
|
|
509
|
+
return sendJson(res, 400, { error: "Invalid JSON request body" });
|
|
510
|
+
}
|
|
511
|
+
if (!body.key || typeof body.key !== "string") {
|
|
512
|
+
return sendJson(res, 400, { error: "Field key is required" });
|
|
513
|
+
}
|
|
514
|
+
try {
|
|
515
|
+
const upstreamRes = await fetchFn(
|
|
516
|
+
`${devConfig.apiUrl}/sites/${encodeURIComponent(devConfig.siteId)}/fields/${encodeURIComponent(body.key)}`,
|
|
517
|
+
{
|
|
518
|
+
method: "PATCH",
|
|
519
|
+
headers: {
|
|
520
|
+
"Content-Type": "application/json",
|
|
521
|
+
Authorization: `Bearer ${devConfig.devToken}`
|
|
522
|
+
},
|
|
523
|
+
body: JSON.stringify({ value: body.value })
|
|
524
|
+
}
|
|
525
|
+
);
|
|
526
|
+
const upstreamBodyText = await upstreamRes.text();
|
|
527
|
+
if (upstreamRes.ok) {
|
|
528
|
+
updateBundleFieldValue(body.key, body.value);
|
|
529
|
+
}
|
|
530
|
+
res.statusCode = upstreamRes.status;
|
|
531
|
+
res.setHeader("Content-Type", "application/json");
|
|
532
|
+
res.end(upstreamBodyText);
|
|
533
|
+
return;
|
|
534
|
+
} catch (err) {
|
|
535
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
536
|
+
return sendJson(res, 502, { error: `Upstream API request failed: ${msg}` });
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return sendJson(res, 404, { error: "Not Found" });
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
function cmsDevApiPlugin(options) {
|
|
543
|
+
return {
|
|
544
|
+
name: "cms-dev-api",
|
|
545
|
+
apply: "serve",
|
|
546
|
+
enforce: "pre",
|
|
547
|
+
configureServer(server) {
|
|
548
|
+
if (options.verifier) {
|
|
549
|
+
options.verifier.attachServer(server);
|
|
550
|
+
}
|
|
551
|
+
server.middlewares.use(createDevApiMiddleware(options));
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
async function createVerifier(options) {
|
|
556
|
+
const { siteDir, siteId } = options;
|
|
557
|
+
let cliModule = null;
|
|
558
|
+
try {
|
|
559
|
+
const cliPkg = "@407dev/cli";
|
|
560
|
+
cliModule = await import(
|
|
561
|
+
/* @vite-ignore */
|
|
562
|
+
cliPkg
|
|
563
|
+
);
|
|
564
|
+
} catch {
|
|
565
|
+
cliModule = null;
|
|
566
|
+
}
|
|
567
|
+
let cachedState = {
|
|
568
|
+
available: Boolean(cliModule),
|
|
569
|
+
errors: [],
|
|
570
|
+
warnings: [],
|
|
571
|
+
localHash: null,
|
|
572
|
+
pushedHash: null,
|
|
573
|
+
fresh: true
|
|
574
|
+
};
|
|
575
|
+
let activeServer = null;
|
|
576
|
+
let debounceTimer = null;
|
|
577
|
+
async function run() {
|
|
578
|
+
if (!cliModule) {
|
|
579
|
+
cachedState = {
|
|
580
|
+
available: false,
|
|
581
|
+
errors: [],
|
|
582
|
+
warnings: [],
|
|
583
|
+
localHash: null,
|
|
584
|
+
pushedHash: null,
|
|
585
|
+
fresh: true
|
|
586
|
+
};
|
|
587
|
+
return cachedState;
|
|
588
|
+
}
|
|
589
|
+
try {
|
|
590
|
+
const extractResult = await cliModule.extractProject(siteDir, { noSync: true });
|
|
591
|
+
const localHash = hashSchema(extractResult.manifest);
|
|
592
|
+
const stateStorePath = path.join(siteDir, ".cms", "state.json");
|
|
593
|
+
const store = new cliModule.StateStore(stateStorePath);
|
|
594
|
+
const pushedHash = store.getSiteManifestHash(siteId);
|
|
595
|
+
const fresh = Boolean(localHash && pushedHash && localHash === pushedHash);
|
|
596
|
+
const errors = (extractResult.errors || []).map((e) => ({
|
|
597
|
+
type: "error",
|
|
598
|
+
message: e.message,
|
|
599
|
+
file: e.file,
|
|
600
|
+
line: e.line,
|
|
601
|
+
column: e.column,
|
|
602
|
+
code: e.code
|
|
603
|
+
}));
|
|
604
|
+
const warnings = (extractResult.warnings || []).map((w) => ({
|
|
605
|
+
type: "warning",
|
|
606
|
+
message: w.message,
|
|
607
|
+
file: w.file,
|
|
608
|
+
line: w.line,
|
|
609
|
+
column: w.column,
|
|
610
|
+
code: w.code
|
|
611
|
+
}));
|
|
612
|
+
cachedState = {
|
|
613
|
+
available: true,
|
|
614
|
+
errors,
|
|
615
|
+
warnings,
|
|
616
|
+
localHash,
|
|
617
|
+
pushedHash,
|
|
618
|
+
fresh
|
|
619
|
+
};
|
|
620
|
+
if (activeServer?.hot) {
|
|
621
|
+
activeServer.hot.send({
|
|
622
|
+
type: "custom",
|
|
623
|
+
event: "cms:diagnostics",
|
|
624
|
+
data: cachedState
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
return cachedState;
|
|
628
|
+
} catch (_err) {
|
|
629
|
+
return cachedState;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
function handleFileEvent(filePath) {
|
|
633
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
634
|
+
if (normalized.includes("/src/") && /\.(astro|ts|tsx|js|mjs)$/.test(normalized) && !normalized.endsWith(".d.ts")) {
|
|
635
|
+
if (debounceTimer) {
|
|
636
|
+
clearTimeout(debounceTimer);
|
|
637
|
+
}
|
|
638
|
+
debounceTimer = setTimeout(() => {
|
|
639
|
+
debounceTimer = null;
|
|
640
|
+
run();
|
|
641
|
+
}, 300);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
function attachServer(server) {
|
|
645
|
+
activeServer = server;
|
|
646
|
+
server.watcher.on("change", handleFileEvent);
|
|
647
|
+
server.watcher.on("add", handleFileEvent);
|
|
648
|
+
server.watcher.on("unlink", handleFileEvent);
|
|
649
|
+
}
|
|
650
|
+
if (cliModule) {
|
|
651
|
+
await run();
|
|
652
|
+
}
|
|
653
|
+
return {
|
|
654
|
+
getDiagnostics: async () => cachedState,
|
|
655
|
+
run,
|
|
656
|
+
attachServer
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
export { cmsDevApiPlugin, createDevApiMiddleware, createVerifier, isLoopbackAddress, refreshDevConfigToken, resolveDevConfig, signDevToken, validateDevToken };
|
|
661
|
+
//# sourceMappingURL=index.js.map
|
|
662
|
+
//# sourceMappingURL=index.js.map
|