@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.
Files changed (212) hide show
  1. package/README.md +69 -0
  2. package/dist/chunk-2TKIQIOE.js +118 -0
  3. package/dist/chunk-2TKIQIOE.js.map +1 -0
  4. package/dist/chunk-53OQ5RXJ.js +912 -0
  5. package/dist/chunk-53OQ5RXJ.js.map +1 -0
  6. package/dist/chunk-56OMNQLH.js +1013 -0
  7. package/dist/chunk-56OMNQLH.js.map +1 -0
  8. package/dist/chunk-5AOOMQ7K.js +1031 -0
  9. package/dist/chunk-5AOOMQ7K.js.map +1 -0
  10. package/dist/chunk-5XRUCFDC.js +743 -0
  11. package/dist/chunk-5XRUCFDC.js.map +1 -0
  12. package/dist/chunk-62ZHCOLZ.js +25 -0
  13. package/dist/chunk-62ZHCOLZ.js.map +1 -0
  14. package/dist/chunk-6AZLRSYA.js +126 -0
  15. package/dist/chunk-6AZLRSYA.js.map +1 -0
  16. package/dist/chunk-6L3ZTCFB.js +14 -0
  17. package/dist/chunk-6L3ZTCFB.js.map +1 -0
  18. package/dist/chunk-6O7ZNNDT.js +697 -0
  19. package/dist/chunk-6O7ZNNDT.js.map +1 -0
  20. package/dist/chunk-6PQIXTMK.js +697 -0
  21. package/dist/chunk-6PQIXTMK.js.map +1 -0
  22. package/dist/chunk-7AW6M7QW.js +1013 -0
  23. package/dist/chunk-7AW6M7QW.js.map +1 -0
  24. package/dist/chunk-7E3XJLKX.js +73 -0
  25. package/dist/chunk-7E3XJLKX.js.map +1 -0
  26. package/dist/chunk-7LM54OTA.js +1031 -0
  27. package/dist/chunk-7LM54OTA.js.map +1 -0
  28. package/dist/chunk-7LMUFCWX.js +126 -0
  29. package/dist/chunk-7LMUFCWX.js.map +1 -0
  30. package/dist/chunk-A3OGL4OX.js +706 -0
  31. package/dist/chunk-A3OGL4OX.js.map +1 -0
  32. package/dist/chunk-AO4UAWOV.js +1030 -0
  33. package/dist/chunk-AO4UAWOV.js.map +1 -0
  34. package/dist/chunk-B4YYLS7Y.js +14 -0
  35. package/dist/chunk-B4YYLS7Y.js.map +1 -0
  36. package/dist/chunk-BDFVGXM2.js +697 -0
  37. package/dist/chunk-BDFVGXM2.js.map +1 -0
  38. package/dist/chunk-CXM7NZVX.js +1031 -0
  39. package/dist/chunk-CXM7NZVX.js.map +1 -0
  40. package/dist/chunk-DMLOBKYE.js +96 -0
  41. package/dist/chunk-DMLOBKYE.js.map +1 -0
  42. package/dist/chunk-DXJSYGS6.js +743 -0
  43. package/dist/chunk-DXJSYGS6.js.map +1 -0
  44. package/dist/chunk-EFUSAQZM.js +72 -0
  45. package/dist/chunk-EFUSAQZM.js.map +1 -0
  46. package/dist/chunk-F7KDGPBN.js +1013 -0
  47. package/dist/chunk-F7KDGPBN.js.map +1 -0
  48. package/dist/chunk-FBSQT45X.js +126 -0
  49. package/dist/chunk-FBSQT45X.js.map +1 -0
  50. package/dist/chunk-FEPDOMVH.js +697 -0
  51. package/dist/chunk-FEPDOMVH.js.map +1 -0
  52. package/dist/chunk-FQDOL2QW.js +25 -0
  53. package/dist/chunk-FQDOL2QW.js.map +1 -0
  54. package/dist/chunk-GB3LGQJM.js +109 -0
  55. package/dist/chunk-GB3LGQJM.js.map +1 -0
  56. package/dist/chunk-HCNP6R3P.js +736 -0
  57. package/dist/chunk-HCNP6R3P.js.map +1 -0
  58. package/dist/chunk-J6WEJDRI.js +743 -0
  59. package/dist/chunk-J6WEJDRI.js.map +1 -0
  60. package/dist/chunk-JHEAPNJ2.js +117 -0
  61. package/dist/chunk-JHEAPNJ2.js.map +1 -0
  62. package/dist/chunk-JOJDJ6GW.js +25 -0
  63. package/dist/chunk-JOJDJ6GW.js.map +1 -0
  64. package/dist/chunk-KTDV6WYB.js +202 -0
  65. package/dist/chunk-KTDV6WYB.js.map +1 -0
  66. package/dist/chunk-KZWJBKPC.js +734 -0
  67. package/dist/chunk-KZWJBKPC.js.map +1 -0
  68. package/dist/chunk-LEAQBGL6.js +109 -0
  69. package/dist/chunk-LEAQBGL6.js.map +1 -0
  70. package/dist/chunk-LGZJL2Q7.js +697 -0
  71. package/dist/chunk-LGZJL2Q7.js.map +1 -0
  72. package/dist/chunk-LQPBU46R.js +109 -0
  73. package/dist/chunk-LQPBU46R.js.map +1 -0
  74. package/dist/chunk-M55NNUTY.js +1013 -0
  75. package/dist/chunk-M55NNUTY.js.map +1 -0
  76. package/dist/chunk-M6NCYCC3.js +1013 -0
  77. package/dist/chunk-M6NCYCC3.js.map +1 -0
  78. package/dist/chunk-MGN7OZAU.js +109 -0
  79. package/dist/chunk-MGN7OZAU.js.map +1 -0
  80. package/dist/chunk-MGYZOX4K.js +1013 -0
  81. package/dist/chunk-MGYZOX4K.js.map +1 -0
  82. package/dist/chunk-MY4IEKJS.js +1031 -0
  83. package/dist/chunk-MY4IEKJS.js.map +1 -0
  84. package/dist/chunk-NCQRGAB4.js +705 -0
  85. package/dist/chunk-NCQRGAB4.js.map +1 -0
  86. package/dist/chunk-NI4NHXI4.js +697 -0
  87. package/dist/chunk-NI4NHXI4.js.map +1 -0
  88. package/dist/chunk-NPI5N7AU.js +14 -0
  89. package/dist/chunk-NPI5N7AU.js.map +1 -0
  90. package/dist/chunk-QN4CPMJF.js +126 -0
  91. package/dist/chunk-QN4CPMJF.js.map +1 -0
  92. package/dist/chunk-QYUVAKMC.js +1013 -0
  93. package/dist/chunk-QYUVAKMC.js.map +1 -0
  94. package/dist/chunk-RAAT6PDT.js +25 -0
  95. package/dist/chunk-RAAT6PDT.js.map +1 -0
  96. package/dist/chunk-RDPO5VPS.js +1013 -0
  97. package/dist/chunk-RDPO5VPS.js.map +1 -0
  98. package/dist/chunk-RQHIW2PF.js +25 -0
  99. package/dist/chunk-RQHIW2PF.js.map +1 -0
  100. package/dist/chunk-RREVHA52.js +722 -0
  101. package/dist/chunk-RREVHA52.js.map +1 -0
  102. package/dist/chunk-S7PT4PZN.js +25 -0
  103. package/dist/chunk-S7PT4PZN.js.map +1 -0
  104. package/dist/chunk-SUXV5M32.js +109 -0
  105. package/dist/chunk-SUXV5M32.js.map +1 -0
  106. package/dist/chunk-SZEORUY6.js +696 -0
  107. package/dist/chunk-SZEORUY6.js.map +1 -0
  108. package/dist/chunk-T7NGGWGY.js +25 -0
  109. package/dist/chunk-T7NGGWGY.js.map +1 -0
  110. package/dist/chunk-UEVWP5VE.js +696 -0
  111. package/dist/chunk-UEVWP5VE.js.map +1 -0
  112. package/dist/chunk-UJNKNP7U.js +748 -0
  113. package/dist/chunk-UJNKNP7U.js.map +1 -0
  114. package/dist/chunk-V4XVFWFI.js +1013 -0
  115. package/dist/chunk-V4XVFWFI.js.map +1 -0
  116. package/dist/chunk-VJU2CIKZ.js +729 -0
  117. package/dist/chunk-VJU2CIKZ.js.map +1 -0
  118. package/dist/chunk-VRQUP5L3.js +202 -0
  119. package/dist/chunk-VRQUP5L3.js.map +1 -0
  120. package/dist/chunk-WBJ5E7CL.js +742 -0
  121. package/dist/chunk-WBJ5E7CL.js.map +1 -0
  122. package/dist/chunk-XJPVYMZM.js +24 -0
  123. package/dist/chunk-XJPVYMZM.js.map +1 -0
  124. package/dist/chunk-Y3Q65C7R.js +14 -0
  125. package/dist/chunk-Y3Q65C7R.js.map +1 -0
  126. package/dist/chunk-YHEGFPTR.js +1030 -0
  127. package/dist/chunk-YHEGFPTR.js.map +1 -0
  128. package/dist/chunk-ZDLBN46C.js +697 -0
  129. package/dist/chunk-ZDLBN46C.js.map +1 -0
  130. package/dist/dev/index.d.ts +122 -0
  131. package/dist/dev/index.js +662 -0
  132. package/dist/dev/index.js.map +1 -0
  133. package/dist/fields/collections.d.ts +18 -0
  134. package/dist/fields/collections.js +37 -0
  135. package/dist/fields/collections.js.map +1 -0
  136. package/dist/index.d.ts +235 -0
  137. package/dist/index.js +956 -0
  138. package/dist/index.js.map +1 -0
  139. package/dist/middleware/index.d.ts +8 -0
  140. package/dist/middleware/index.js +13 -0
  141. package/dist/middleware/index.js.map +1 -0
  142. package/dist/preview-BEkLMD6N.d.ts +23 -0
  143. package/dist/preview-D9yewK8x.d.ts +24 -0
  144. package/dist/preview-DJEA4fj7.d.ts +23 -0
  145. package/dist/preview-DeD1Jn7U.d.ts +23 -0
  146. package/dist/toolbar/app.d.ts +28 -0
  147. package/dist/toolbar/app.js +1176 -0
  148. package/dist/toolbar/app.js.map +1 -0
  149. package/dist/types-15jfCQKb.d.ts +141 -0
  150. package/dist/types-BREdL9Gx.d.ts +140 -0
  151. package/dist/types-C6sUZNa2.d.ts +104 -0
  152. package/dist/types-CEYS5Fzc.d.ts +112 -0
  153. package/dist/types-DpeHpobR.d.ts +104 -0
  154. package/dist/types-NaKJe6Pg.d.ts +140 -0
  155. package/package.json +90 -0
  156. package/src/CmsHead.astro +25 -0
  157. package/src/CmsImage.astro +62 -0
  158. package/src/CmsVideo.astro +185 -0
  159. package/src/RichText.astro +32 -0
  160. package/src/client.test.ts +227 -0
  161. package/src/client.ts +164 -0
  162. package/src/dev/config.test.ts +258 -0
  163. package/src/dev/config.ts +319 -0
  164. package/src/dev/index.ts +5 -0
  165. package/src/dev/preview-token.ts +39 -0
  166. package/src/dev/routes.test.ts +354 -0
  167. package/src/dev/routes.ts +245 -0
  168. package/src/dev/token.test.ts +280 -0
  169. package/src/dev/token.ts +201 -0
  170. package/src/dev/verifier.test.ts +63 -0
  171. package/src/dev/verifier.ts +161 -0
  172. package/src/fields/attrs.ts +24 -0
  173. package/src/fields/collection.ts +72 -0
  174. package/src/fields/collections.ts +35 -0
  175. package/src/fields/entryScope.test.ts +62 -0
  176. package/src/fields/entryScope.ts +240 -0
  177. package/src/fields/group.ts +51 -0
  178. package/src/fields/helpers.test.ts +51 -0
  179. package/src/fields/helpers.ts +214 -0
  180. package/src/fields/index.ts +46 -0
  181. package/src/fields/scope.ts +21 -0
  182. package/src/fields/types.ts +119 -0
  183. package/src/head.test.ts +74 -0
  184. package/src/head.ts +123 -0
  185. package/src/image.test.ts +42 -0
  186. package/src/image.ts +64 -0
  187. package/src/index.test.ts +76 -0
  188. package/src/index.ts +13 -0
  189. package/src/integration.ts +218 -0
  190. package/src/jwt.ts +131 -0
  191. package/src/middleware/index.ts +4 -0
  192. package/src/middleware/preview.test.ts +184 -0
  193. package/src/middleware/preview.ts +206 -0
  194. package/src/preview/bridge-script.test.ts +683 -0
  195. package/src/preview/bridge-script.ts +763 -0
  196. package/src/preview/origins.ts +29 -0
  197. package/src/preview.ts +49 -0
  198. package/src/richtext/RichTextNode.astro +130 -0
  199. package/src/richtext/render.ts +248 -0
  200. package/src/richtext.test.ts +139 -0
  201. package/src/runtime/bundle.ts +28 -0
  202. package/src/runtime/store.ts +130 -0
  203. package/src/siteFiles.test.ts +93 -0
  204. package/src/siteFiles.ts +111 -0
  205. package/src/toolbar/app.test.ts +191 -0
  206. package/src/toolbar/app.ts +1091 -0
  207. package/src/toolbar/icon.ts +5 -0
  208. package/src/toolbar/widgets.test.ts +153 -0
  209. package/src/toolbar/widgets.ts +365 -0
  210. package/src/types.test.ts +154 -0
  211. package/src/types.ts +163 -0
  212. 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