@actuate-media/cms-core 0.11.0 → 0.11.2

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 (376) hide show
  1. package/dist/__tests__/actions/document-crud.test.js +5 -1
  2. package/dist/__tests__/actions/document-crud.test.js.map +1 -1
  3. package/dist/__tests__/api/admin-contracts.test.js.map +1 -1
  4. package/dist/__tests__/api/public-globals.test.js.map +1 -1
  5. package/dist/__tests__/auth/password.test.js.map +1 -1
  6. package/dist/__tests__/auth/session.test.js.map +1 -1
  7. package/dist/__tests__/codegen/generate-types.test.js.map +1 -1
  8. package/dist/__tests__/next.test.js +1 -3
  9. package/dist/__tests__/next.test.js.map +1 -1
  10. package/dist/__tests__/scheduling/scheduling.test.js +28 -4
  11. package/dist/__tests__/scheduling/scheduling.test.js.map +1 -1
  12. package/dist/__tests__/security/access.test.js +1 -1
  13. package/dist/__tests__/security/access.test.js.map +1 -1
  14. package/dist/__tests__/security/audit.test.js.map +1 -1
  15. package/dist/__tests__/security/client-ip.test.js.map +1 -1
  16. package/dist/__tests__/security/csrf.test.js.map +1 -1
  17. package/dist/__tests__/security/ip-allowlist.test.js.map +1 -1
  18. package/dist/__tests__/security/rate-limit.test.js.map +1 -1
  19. package/dist/__tests__/security/reauth.test.js.map +1 -1
  20. package/dist/__tests__/security/redact.test.js.map +1 -1
  21. package/dist/__tests__/security/sanitize.test.js.map +1 -1
  22. package/dist/__tests__/security/secret-storage.test.js.map +1 -1
  23. package/dist/__tests__/security/upload-magic.test.js.map +1 -1
  24. package/dist/__tests__/server-site.test.js.map +1 -1
  25. package/dist/__tests__/site.test.js +5 -2
  26. package/dist/__tests__/site.test.js.map +1 -1
  27. package/dist/__tests__/webhooks/webhooks.test.js.map +1 -1
  28. package/dist/a11y/index.d.ts +1 -1
  29. package/dist/a11y/index.d.ts.map +1 -1
  30. package/dist/a11y/index.js +23 -20
  31. package/dist/a11y/index.js.map +1 -1
  32. package/dist/actions.d.ts +1 -1
  33. package/dist/actions.d.ts.map +1 -1
  34. package/dist/actions.js +45 -38
  35. package/dist/actions.js.map +1 -1
  36. package/dist/api/handler-factory.d.ts.map +1 -1
  37. package/dist/api/handler-factory.js +15 -8
  38. package/dist/api/handler-factory.js.map +1 -1
  39. package/dist/api/handlers.d.ts.map +1 -1
  40. package/dist/api/handlers.js +287 -112
  41. package/dist/api/handlers.js.map +1 -1
  42. package/dist/api/index.d.ts.map +1 -1
  43. package/dist/api/index.js.map +1 -1
  44. package/dist/api/openapi.d.ts.map +1 -1
  45. package/dist/api/openapi.js +151 -30
  46. package/dist/api/openapi.js.map +1 -1
  47. package/dist/api/router.d.ts +6 -6
  48. package/dist/api/router.d.ts.map +1 -1
  49. package/dist/api/router.js +27 -10
  50. package/dist/api/router.js.map +1 -1
  51. package/dist/auth/index.d.ts +12 -12
  52. package/dist/auth/index.d.ts.map +1 -1
  53. package/dist/auth/index.js +9 -9
  54. package/dist/auth/index.js.map +1 -1
  55. package/dist/auth/mfa-pending.d.ts.map +1 -1
  56. package/dist/auth/mfa-pending.js.map +1 -1
  57. package/dist/auth/oauth.d.ts.map +1 -1
  58. package/dist/auth/oauth.js +15 -7
  59. package/dist/auth/oauth.js.map +1 -1
  60. package/dist/auth/password.d.ts +1 -1
  61. package/dist/auth/password.d.ts.map +1 -1
  62. package/dist/auth/password.js +14 -14
  63. package/dist/auth/password.js.map +1 -1
  64. package/dist/auth/providers/github.d.ts +1 -1
  65. package/dist/auth/providers/github.d.ts.map +1 -1
  66. package/dist/auth/providers/github.js +2 -2
  67. package/dist/auth/providers/github.js.map +1 -1
  68. package/dist/auth/providers/google.d.ts +1 -1
  69. package/dist/auth/providers/google.d.ts.map +1 -1
  70. package/dist/auth/providers/google.js +2 -2
  71. package/dist/auth/providers/google.js.map +1 -1
  72. package/dist/auth/providers/microsoft.d.ts +1 -1
  73. package/dist/auth/providers/microsoft.d.ts.map +1 -1
  74. package/dist/auth/providers/microsoft.js +2 -2
  75. package/dist/auth/providers/microsoft.js.map +1 -1
  76. package/dist/auth/reset-email.d.ts.map +1 -1
  77. package/dist/auth/reset-email.js +1 -1
  78. package/dist/auth/reset-email.js.map +1 -1
  79. package/dist/auth/reset.d.ts.map +1 -1
  80. package/dist/auth/reset.js +9 -9
  81. package/dist/auth/reset.js.map +1 -1
  82. package/dist/auth/session.d.ts.map +1 -1
  83. package/dist/auth/session.js +6 -6
  84. package/dist/auth/session.js.map +1 -1
  85. package/dist/auth/totp.d.ts.map +1 -1
  86. package/dist/auth/totp.js +8 -2
  87. package/dist/auth/totp.js.map +1 -1
  88. package/dist/backup/index.d.ts +2 -2
  89. package/dist/backup/index.d.ts.map +1 -1
  90. package/dist/backup/index.js +5 -5
  91. package/dist/backup/index.js.map +1 -1
  92. package/dist/cache/index.d.ts +1 -1
  93. package/dist/cache/index.d.ts.map +1 -1
  94. package/dist/cache/index.js +1 -1
  95. package/dist/cache/index.js.map +1 -1
  96. package/dist/client.d.ts +1 -1
  97. package/dist/client.d.ts.map +1 -1
  98. package/dist/client.js +8 -8
  99. package/dist/client.js.map +1 -1
  100. package/dist/codegen/index.d.ts +1 -1
  101. package/dist/codegen/index.d.ts.map +1 -1
  102. package/dist/codegen/index.js +170 -174
  103. package/dist/codegen/index.js.map +1 -1
  104. package/dist/collections/index.d.ts +1 -1
  105. package/dist/collections/index.d.ts.map +1 -1
  106. package/dist/collections/index.js.map +1 -1
  107. package/dist/config/define.d.ts +2 -2
  108. package/dist/config/define.d.ts.map +1 -1
  109. package/dist/config/define.js +1 -1
  110. package/dist/config/define.js.map +1 -1
  111. package/dist/config/index.d.ts +3 -3
  112. package/dist/config/index.d.ts.map +1 -1
  113. package/dist/config/index.js +32 -18
  114. package/dist/config/index.js.map +1 -1
  115. package/dist/config/types.d.ts +26 -26
  116. package/dist/config/types.d.ts.map +1 -1
  117. package/dist/content/ai-api.d.ts.map +1 -1
  118. package/dist/content/ai-api.js +8 -2
  119. package/dist/content/ai-api.js.map +1 -1
  120. package/dist/content/content-graph.d.ts +1 -1
  121. package/dist/content/content-graph.d.ts.map +1 -1
  122. package/dist/content/content-graph.js +7 -7
  123. package/dist/content/content-graph.js.map +1 -1
  124. package/dist/content/extract.js +13 -13
  125. package/dist/content/extract.js.map +1 -1
  126. package/dist/content/index.d.ts +7 -7
  127. package/dist/content/index.d.ts.map +1 -1
  128. package/dist/content/index.js +4 -4
  129. package/dist/content/index.js.map +1 -1
  130. package/dist/content/structured-data.d.ts +3 -3
  131. package/dist/content/structured-data.d.ts.map +1 -1
  132. package/dist/content/structured-data.js +65 -67
  133. package/dist/content/structured-data.js.map +1 -1
  134. package/dist/db/adapters/mysql.d.ts.map +1 -1
  135. package/dist/db/adapters/mysql.js.map +1 -1
  136. package/dist/db/adapters/postgres.d.ts.map +1 -1
  137. package/dist/db/adapters/postgres.js.map +1 -1
  138. package/dist/db/adapters/sqlite.d.ts.map +1 -1
  139. package/dist/db/adapters/sqlite.js.map +1 -1
  140. package/dist/db/create-adapter.d.ts.map +1 -1
  141. package/dist/db/create-adapter.js.map +1 -1
  142. package/dist/db/index.d.ts +1 -1
  143. package/dist/db/index.d.ts.map +1 -1
  144. package/dist/db/index.js +1 -1
  145. package/dist/db/index.js.map +1 -1
  146. package/dist/db.d.ts +1 -1
  147. package/dist/db.d.ts.map +1 -1
  148. package/dist/db.js +1 -1
  149. package/dist/db.js.map +1 -1
  150. package/dist/fields/index.d.ts +2 -2
  151. package/dist/fields/index.d.ts.map +1 -1
  152. package/dist/fields/index.js +51 -47
  153. package/dist/fields/index.js.map +1 -1
  154. package/dist/forms/analytics.d.ts.map +1 -1
  155. package/dist/forms/analytics.js.map +1 -1
  156. package/dist/forms/attribution.d.ts.map +1 -1
  157. package/dist/forms/attribution.js +7 -2
  158. package/dist/forms/attribution.js.map +1 -1
  159. package/dist/forms/index.d.ts.map +1 -1
  160. package/dist/forms/index.js.map +1 -1
  161. package/dist/graphql/index.d.ts.map +1 -1
  162. package/dist/graphql/index.js.map +1 -1
  163. package/dist/graphql/resolvers.d.ts.map +1 -1
  164. package/dist/graphql/resolvers.js +17 -21
  165. package/dist/graphql/resolvers.js.map +1 -1
  166. package/dist/graphql/schema-builder.d.ts.map +1 -1
  167. package/dist/graphql/schema-builder.js.map +1 -1
  168. package/dist/health/index.d.ts +2 -2
  169. package/dist/health/index.d.ts.map +1 -1
  170. package/dist/health/index.js +9 -9
  171. package/dist/health/index.js.map +1 -1
  172. package/dist/i18n/index.d.ts +1 -1
  173. package/dist/i18n/index.d.ts.map +1 -1
  174. package/dist/i18n/index.js +2 -2
  175. package/dist/i18n/index.js.map +1 -1
  176. package/dist/index.d.ts +78 -78
  177. package/dist/index.d.ts.map +1 -1
  178. package/dist/index.js +43 -43
  179. package/dist/index.js.map +1 -1
  180. package/dist/media/index.d.ts +2 -2
  181. package/dist/media/index.d.ts.map +1 -1
  182. package/dist/media/index.js +1 -1
  183. package/dist/media/index.js.map +1 -1
  184. package/dist/media/optimize.d.ts.map +1 -1
  185. package/dist/media/optimize.js +7 -4
  186. package/dist/media/optimize.js.map +1 -1
  187. package/dist/middleware.d.ts.map +1 -1
  188. package/dist/middleware.js +3 -3
  189. package/dist/middleware.js.map +1 -1
  190. package/dist/multisite/index.d.ts.map +1 -1
  191. package/dist/multisite/index.js +4 -4
  192. package/dist/multisite/index.js.map +1 -1
  193. package/dist/next/preview.d.ts.map +1 -1
  194. package/dist/next/preview.js.map +1 -1
  195. package/dist/next.d.ts.map +1 -1
  196. package/dist/next.js +4 -5
  197. package/dist/next.js.map +1 -1
  198. package/dist/notifications/index.d.ts +1 -1
  199. package/dist/notifications/index.d.ts.map +1 -1
  200. package/dist/notifications/index.js +5 -5
  201. package/dist/notifications/index.js.map +1 -1
  202. package/dist/page-builder/__tests__/a11y-fix.test.js +1 -5
  203. package/dist/page-builder/__tests__/a11y-fix.test.js.map +1 -1
  204. package/dist/page-builder/__tests__/blocks.test.js +4 -0
  205. package/dist/page-builder/__tests__/blocks.test.js.map +1 -1
  206. package/dist/page-builder/__tests__/design-scorer.test.js +44 -11
  207. package/dist/page-builder/__tests__/design-scorer.test.js.map +1 -1
  208. package/dist/page-builder/__tests__/schema.test.js +12 -12
  209. package/dist/page-builder/__tests__/schema.test.js.map +1 -1
  210. package/dist/page-builder/__tests__/seo-analyzer.test.js +27 -13
  211. package/dist/page-builder/__tests__/seo-analyzer.test.js.map +1 -1
  212. package/dist/page-builder/ai-pipeline.d.ts.map +1 -1
  213. package/dist/page-builder/ai-pipeline.js +1 -3
  214. package/dist/page-builder/ai-pipeline.js.map +1 -1
  215. package/dist/page-builder/blocks.d.ts.map +1 -1
  216. package/dist/page-builder/blocks.js +45 -9
  217. package/dist/page-builder/blocks.js.map +1 -1
  218. package/dist/page-builder/design-scorer.d.ts.map +1 -1
  219. package/dist/page-builder/design-scorer.js +249 -41
  220. package/dist/page-builder/design-scorer.js.map +1 -1
  221. package/dist/page-builder/index.d.ts +3 -3
  222. package/dist/page-builder/index.d.ts.map +1 -1
  223. package/dist/page-builder/index.js +2 -2
  224. package/dist/page-builder/index.js.map +1 -1
  225. package/dist/page-builder/seo-analyzer.d.ts.map +1 -1
  226. package/dist/page-builder/seo-analyzer.js +252 -56
  227. package/dist/page-builder/seo-analyzer.js.map +1 -1
  228. package/dist/page-builder/templates.d.ts.map +1 -1
  229. package/dist/page-builder/templates.js +45 -16
  230. package/dist/page-builder/templates.js.map +1 -1
  231. package/dist/page-builder/tree.d.ts.map +1 -1
  232. package/dist/page-builder/tree.js.map +1 -1
  233. package/dist/page-builder/validate.js.map +1 -1
  234. package/dist/presence/index.d.ts.map +1 -1
  235. package/dist/presence/index.js +2 -2
  236. package/dist/presence/index.js.map +1 -1
  237. package/dist/preview/index.d.ts.map +1 -1
  238. package/dist/preview/index.js.map +1 -1
  239. package/dist/privacy/index.d.ts +1 -1
  240. package/dist/privacy/index.d.ts.map +1 -1
  241. package/dist/privacy/index.js +3 -3
  242. package/dist/privacy/index.js.map +1 -1
  243. package/dist/relationships/index.d.ts.map +1 -1
  244. package/dist/relationships/index.js +1 -1
  245. package/dist/relationships/index.js.map +1 -1
  246. package/dist/scheduling/index.d.ts +2 -2
  247. package/dist/scheduling/index.d.ts.map +1 -1
  248. package/dist/scheduling/index.js +3 -1
  249. package/dist/scheduling/index.js.map +1 -1
  250. package/dist/search/index.d.ts.map +1 -1
  251. package/dist/search/index.js +1 -3
  252. package/dist/search/index.js.map +1 -1
  253. package/dist/security/access.d.ts +4 -4
  254. package/dist/security/access.d.ts.map +1 -1
  255. package/dist/security/access.js +11 -15
  256. package/dist/security/access.js.map +1 -1
  257. package/dist/security/anomaly-detection.d.ts.map +1 -1
  258. package/dist/security/anomaly-detection.js +5 -5
  259. package/dist/security/anomaly-detection.js.map +1 -1
  260. package/dist/security/api-key-enhanced.d.ts +2 -2
  261. package/dist/security/api-key-enhanced.d.ts.map +1 -1
  262. package/dist/security/api-key-enhanced.js +5 -5
  263. package/dist/security/api-key-enhanced.js.map +1 -1
  264. package/dist/security/audit.d.ts.map +1 -1
  265. package/dist/security/audit.js.map +1 -1
  266. package/dist/security/breach-check.js.map +1 -1
  267. package/dist/security/captcha.d.ts.map +1 -1
  268. package/dist/security/captcha.js.map +1 -1
  269. package/dist/security/client-ip.d.ts.map +1 -1
  270. package/dist/security/client-ip.js +4 -1
  271. package/dist/security/client-ip.js.map +1 -1
  272. package/dist/security/cors.d.ts +1 -1
  273. package/dist/security/cors.d.ts.map +1 -1
  274. package/dist/security/cors.js +12 -12
  275. package/dist/security/cors.js.map +1 -1
  276. package/dist/security/csp-nonces.js +11 -11
  277. package/dist/security/csp-nonces.js.map +1 -1
  278. package/dist/security/csrf.js +2 -2
  279. package/dist/security/csrf.js.map +1 -1
  280. package/dist/security/encrypted-fields.d.ts.map +1 -1
  281. package/dist/security/encrypted-fields.js +7 -4
  282. package/dist/security/encrypted-fields.js.map +1 -1
  283. package/dist/security/headers.d.ts.map +1 -1
  284. package/dist/security/headers.js +12 -12
  285. package/dist/security/headers.js.map +1 -1
  286. package/dist/security/index.d.ts +39 -39
  287. package/dist/security/index.d.ts.map +1 -1
  288. package/dist/security/index.js +25 -25
  289. package/dist/security/index.js.map +1 -1
  290. package/dist/security/internal-keys.d.ts.map +1 -1
  291. package/dist/security/internal-keys.js.map +1 -1
  292. package/dist/security/ip-allowlist.js +2 -4
  293. package/dist/security/ip-allowlist.js.map +1 -1
  294. package/dist/security/middleware.d.ts +2 -2
  295. package/dist/security/middleware.d.ts.map +1 -1
  296. package/dist/security/middleware.js +11 -11
  297. package/dist/security/middleware.js.map +1 -1
  298. package/dist/security/rate-limit.d.ts +0 -4
  299. package/dist/security/rate-limit.d.ts.map +1 -1
  300. package/dist/security/rate-limit.js +33 -3
  301. package/dist/security/rate-limit.js.map +1 -1
  302. package/dist/security/reauth.d.ts +1 -1
  303. package/dist/security/reauth.d.ts.map +1 -1
  304. package/dist/security/reauth.js.map +1 -1
  305. package/dist/security/redact.d.ts.map +1 -1
  306. package/dist/security/redact.js +4 -1
  307. package/dist/security/redact.js.map +1 -1
  308. package/dist/security/safe-fetch.d.ts.map +1 -1
  309. package/dist/security/safe-fetch.js.map +1 -1
  310. package/dist/security/sanitize.d.ts.map +1 -1
  311. package/dist/security/sanitize.js +40 -8
  312. package/dist/security/sanitize.js.map +1 -1
  313. package/dist/security/secret-storage.js +6 -6
  314. package/dist/security/secret-storage.js.map +1 -1
  315. package/dist/security/security-txt.d.ts.map +1 -1
  316. package/dist/security/security-txt.js +2 -2
  317. package/dist/security/security-txt.js.map +1 -1
  318. package/dist/security/session-limits.d.ts +1 -1
  319. package/dist/security/session-limits.d.ts.map +1 -1
  320. package/dist/security/session-limits.js +1 -1
  321. package/dist/security/session-limits.js.map +1 -1
  322. package/dist/security/upload.d.ts.map +1 -1
  323. package/dist/security/upload.js +26 -20
  324. package/dist/security/upload.js.map +1 -1
  325. package/dist/security/webhook.d.ts.map +1 -1
  326. package/dist/security/webhook.js +12 -8
  327. package/dist/security/webhook.js.map +1 -1
  328. package/dist/seo/analysis.d.ts.map +1 -1
  329. package/dist/seo/analysis.js +25 -13
  330. package/dist/seo/analysis.js.map +1 -1
  331. package/dist/seo/index.d.ts +9 -9
  332. package/dist/seo/index.d.ts.map +1 -1
  333. package/dist/seo/index.js +4 -4
  334. package/dist/seo/index.js.map +1 -1
  335. package/dist/seo/llms-txt.js +1 -3
  336. package/dist/seo/llms-txt.js.map +1 -1
  337. package/dist/server-site.d.ts.map +1 -1
  338. package/dist/server-site.js +12 -14
  339. package/dist/server-site.js.map +1 -1
  340. package/dist/setup/index.d.ts.map +1 -1
  341. package/dist/setup/index.js.map +1 -1
  342. package/dist/site.d.ts.map +1 -1
  343. package/dist/site.js +7 -3
  344. package/dist/site.js.map +1 -1
  345. package/dist/storage/index.d.ts.map +1 -1
  346. package/dist/storage/index.js.map +1 -1
  347. package/dist/templates/index.d.ts.map +1 -1
  348. package/dist/templates/index.js +3 -3
  349. package/dist/templates/index.js.map +1 -1
  350. package/dist/upgrade/changelog.d.ts +1 -1
  351. package/dist/upgrade/changelog.d.ts.map +1 -1
  352. package/dist/upgrade/changelog.js +12 -12
  353. package/dist/upgrade/changelog.js.map +1 -1
  354. package/dist/upgrade/index.d.ts +6 -6
  355. package/dist/upgrade/index.d.ts.map +1 -1
  356. package/dist/upgrade/index.js +3 -3
  357. package/dist/upgrade/index.js.map +1 -1
  358. package/dist/upgrade/upgrade-pr.d.ts.map +1 -1
  359. package/dist/upgrade/upgrade-pr.js +36 -36
  360. package/dist/upgrade/upgrade-pr.js.map +1 -1
  361. package/dist/upgrade/version-check.d.ts +1 -1
  362. package/dist/upgrade/version-check.d.ts.map +1 -1
  363. package/dist/upgrade/version-check.js +13 -13
  364. package/dist/upgrade/version-check.js.map +1 -1
  365. package/dist/webhooks/index.d.ts +1 -1
  366. package/dist/webhooks/index.d.ts.map +1 -1
  367. package/dist/webhooks/index.js +4 -4
  368. package/dist/webhooks/index.js.map +1 -1
  369. package/dist/workflow/index.d.ts.map +1 -1
  370. package/dist/workflow/index.js.map +1 -1
  371. package/dist/workflows/index.d.ts +1 -1
  372. package/dist/workflows/index.d.ts.map +1 -1
  373. package/dist/workflows/index.js +3 -3
  374. package/dist/workflows/index.js.map +1 -1
  375. package/package.json +1 -1
  376. package/prisma/seed.ts +31 -31
@@ -16,7 +16,7 @@ import { verifyCaptcha, getCaptchaConfig } from '../security/captcha.js';
16
16
  import { checkForUpdates } from '../upgrade/version-check.js';
17
17
  import { createUpgradePR } from '../upgrade/upgrade-pr.js';
18
18
  import { encryptField, decryptField } from '../security/encrypted-fields.js';
19
- import { encryptSecret, decryptSecret, encryptStringArray } from '../security/secret-storage.js';
19
+ import { encryptSecret, decryptSecret, encryptStringArray, } from '../security/secret-storage.js';
20
20
  import { createRateLimiter } from '../security/rate-limit.js';
21
21
  import { generateOpenAPISpec } from './openapi.js';
22
22
  import { createSSEPresenceAdapter } from '../presence/index.js';
@@ -71,7 +71,9 @@ function mediaUrl(storageKey) {
71
71
  const value = String(storageKey ?? '');
72
72
  if (!value)
73
73
  return '';
74
- return value.startsWith('http://') || value.startsWith('https://') || value.startsWith('/') ? value : '';
74
+ return value.startsWith('http://') || value.startsWith('https://') || value.startsWith('/')
75
+ ? value
76
+ : '';
75
77
  }
76
78
  function normalizeMediaItem(media) {
77
79
  const width = typeof media.width === 'number' ? media.width : null;
@@ -167,9 +169,9 @@ function hasModel(d, name) {
167
169
  }
168
170
  }
169
171
  function modelNotAvailable(name) {
170
- return errorResponse(`The "${name}" model is not available in your Prisma schema. `
171
- + 'Run `actuate db:init` for new schemas, or carefully update the existing Actuate block, create/apply a Prisma migration, then regenerate Prisma Client. '
172
- + 'See https://actuatecms.dev/docs/database-setup for required models.', 501);
172
+ return errorResponse(`The "${name}" model is not available in your Prisma schema. ` +
173
+ 'Run `actuate db:init` for new schemas, or carefully update the existing Actuate block, create/apply a Prisma migration, then regenerate Prisma Client. ' +
174
+ 'See https://actuatecms.dev/docs/database-setup for required models.', 501);
173
175
  }
174
176
  async function safeCount(model, where) {
175
177
  try {
@@ -232,28 +234,34 @@ function isAllowedStorageUrl(url) {
232
234
  }
233
235
  }
234
236
  const ALLOWED_SORT_FIELDS = new Set([
235
- 'createdAt', 'updatedAt', 'publishedAt', 'status', 'collection',
237
+ 'createdAt',
238
+ 'updatedAt',
239
+ 'publishedAt',
240
+ 'status',
241
+ 'collection',
236
242
  ]);
237
243
  let _secretMissing = false;
238
244
  let _secretWarningLogged = false;
239
245
  function getSessionSecret() {
240
- const secret = process.env.CMS_SECRET
241
- ?? process.env.CMS_SESSION_SECRET
242
- ?? globalThis.__actuateConfig?.secret;
246
+ const secret = process.env.CMS_SECRET ??
247
+ process.env.CMS_SESSION_SECRET ??
248
+ globalThis.__actuateConfig?.secret;
243
249
  if (!secret) {
244
250
  _secretMissing = true;
245
251
  if (!_secretWarningLogged) {
246
252
  _secretWarningLogged = true;
247
- console.error('[Actuate CMS] Missing CMS secret. Set the CMS_SECRET environment variable (min 32 characters) '
248
- + 'or pass `secret` in your actuate.config.ts. '
249
- + 'Generate one with: node -e "console.log(require(\'crypto\').randomBytes(32).toString(\'hex\'))" '
250
- + '-- All authenticated API routes will return 503 until this is configured.');
253
+ console.error('[Actuate CMS] Missing CMS secret. Set the CMS_SECRET environment variable (min 32 characters) ' +
254
+ 'or pass `secret` in your actuate.config.ts. ' +
255
+ "Generate one with: node -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\" " +
256
+ '-- All authenticated API routes will return 503 until this is configured.');
251
257
  }
252
258
  throw new Error('CMS secret not configured');
253
259
  }
254
260
  if (secret.length < 32) {
255
- throw new Error('[Actuate CMS] CMS secret must be at least 32 characters (got ' + secret.length + '). '
256
- + 'Generate a secure value with: node -e "console.log(require(\'crypto\').randomBytes(32).toString(\'hex\'))"');
261
+ throw new Error('[Actuate CMS] CMS secret must be at least 32 characters (got ' +
262
+ secret.length +
263
+ '). ' +
264
+ "Generate a secure value with: node -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\"");
257
265
  }
258
266
  _secretMissing = false;
259
267
  return secret;
@@ -378,6 +386,14 @@ const formLimiterGlobal = createRateLimiter({ maxRequests: 10, windowMs: 60_000
378
386
  const aiGenerateLimiter = createRateLimiter({ maxRequests: 20, windowMs: 60 * 60 * 1000 });
379
387
  const linkHealthLimiter = createRateLimiter({ maxRequests: 4, windowMs: 60 * 60 * 1000 });
380
388
  async function checkRateLimitAsync(limiter, key) {
389
+ // Explicit, environment-gated bypass for test harnesses. Production never
390
+ // sets this — Vercel + the deploy guide both omit it. We deliberately do
391
+ // NOT key off `NODE_ENV === 'test'` because Next.js builds run with
392
+ // NODE_ENV=test in some CI configurations and we want rate limiting
393
+ // exercised by unit tests that explicitly opt in.
394
+ if (process.env.ACTUATE_DISABLE_RATE_LIMIT === '1') {
395
+ return true;
396
+ }
381
397
  const result = await limiter.check(key);
382
398
  return result.allowed;
383
399
  }
@@ -417,9 +433,7 @@ async function enforceSessionLimitsForUser(d, userId) {
417
433
  }
418
434
  }
419
435
  function getAdminPath() {
420
- return process.env.ACTUATE_ADMIN_PATH
421
- ?? globalThis.__actuateConfig?.admin?.path
422
- ?? '/admin';
436
+ return (process.env.ACTUATE_ADMIN_PATH ?? globalThis.__actuateConfig?.admin?.path ?? '/admin');
423
437
  }
424
438
  class ModelNotAvailableError extends Error {
425
439
  model;
@@ -442,7 +456,9 @@ export function registerCMSRoutes(router) {
442
456
  if (typeof prop !== 'string' || prop.startsWith('$') || prop === 'then')
443
457
  return undefined;
444
458
  return new Proxy({}, {
445
- get() { throw new ModelNotAvailableError(String(prop)); },
459
+ get() {
460
+ throw new ModelNotAvailableError(String(prop));
461
+ },
446
462
  });
447
463
  },
448
464
  });
@@ -463,7 +479,9 @@ export function registerCMSRoutes(router) {
463
479
  if (val !== undefined && val !== null)
464
480
  return val;
465
481
  return new Proxy({}, {
466
- get() { throw new ModelNotAvailableError(String(prop)); },
482
+ get() {
483
+ throw new ModelNotAvailableError(String(prop));
484
+ },
467
485
  });
468
486
  },
469
487
  });
@@ -475,12 +493,7 @@ export function registerCMSRoutes(router) {
475
493
  router.get('/auth/csrf', async () => {
476
494
  const token = await generateCsrfToken();
477
495
  const isProduction = process.env.NODE_ENV === 'production';
478
- const cookieFlags = [
479
- `actuate_csrf=${token}`,
480
- 'Path=/',
481
- 'SameSite=Lax',
482
- 'Max-Age=86400',
483
- ];
496
+ const cookieFlags = [`actuate_csrf=${token}`, 'Path=/', 'SameSite=Lax', 'Max-Age=86400'];
484
497
  if (isProduction)
485
498
  cookieFlags.push('Secure');
486
499
  const response = json({ data: { token } });
@@ -523,7 +536,7 @@ export function registerCMSRoutes(router) {
523
536
  // ---------------------------------------------------------------------------
524
537
  router.post('/auth/login', async (request) => {
525
538
  try {
526
- const body = await request.json();
539
+ const body = (await request.json());
527
540
  const { email, password } = body;
528
541
  if (!email || !password) {
529
542
  return errorResponse('Email and password are required', 400);
@@ -607,12 +620,7 @@ export function registerCMSRoutes(router) {
607
620
  if (isProduction)
608
621
  sessionCookie.push('Secure');
609
622
  const csrfToken = await generateCsrfToken();
610
- const csrfCookie = [
611
- `actuate_csrf=${csrfToken}`,
612
- 'Path=/',
613
- 'SameSite=Lax',
614
- 'Max-Age=86400',
615
- ];
623
+ const csrfCookie = [`actuate_csrf=${csrfToken}`, 'Path=/', 'SameSite=Lax', 'Max-Age=86400'];
616
624
  if (isProduction)
617
625
  csrfCookie.push('Secure');
618
626
  response.headers.append('Set-Cookie', sessionCookie.join('; '));
@@ -651,7 +659,7 @@ export function registerCMSRoutes(router) {
651
659
  });
652
660
  router.post('/auth/forgot-password', async (request) => {
653
661
  try {
654
- const body = await request.json();
662
+ const body = (await request.json());
655
663
  const { email } = body;
656
664
  if (!email) {
657
665
  return errorResponse('Email is required', 400);
@@ -684,7 +692,7 @@ export function registerCMSRoutes(router) {
684
692
  });
685
693
  router.post('/auth/reset-password', async (request) => {
686
694
  try {
687
- const body = await request.json();
695
+ const body = (await request.json());
688
696
  const { token, password } = body;
689
697
  if (!token || !password) {
690
698
  return errorResponse('Token and new password are required', 400);
@@ -764,7 +772,10 @@ export function registerCMSRoutes(router) {
764
772
  if (reauthErr)
765
773
  return reauthErr;
766
774
  const { generateTOTPSecret, generateTOTPUri, generateBackupCodes } = await import('../auth/totp.js');
767
- const user = await db().user.findUnique({ where: { id: auth.session.userId }, select: { email: true, totpEnabled: true } });
775
+ const user = await db().user.findUnique({
776
+ where: { id: auth.session.userId },
777
+ select: { email: true, totpEnabled: true },
778
+ });
768
779
  if (!user)
769
780
  return errorResponse('User not found', 404);
770
781
  if (user.totpEnabled)
@@ -791,11 +802,14 @@ export function registerCMSRoutes(router) {
791
802
  const auth = await requireAuth(request);
792
803
  if (auth.error)
793
804
  return auth.error;
794
- const body = await request.json();
805
+ const body = (await request.json());
795
806
  if (!body.code)
796
807
  return errorResponse('Code is required', 400);
797
808
  const { verifyTOTP } = await import('../auth/totp.js');
798
- const user = await db().user.findUnique({ where: { id: auth.session.userId }, select: { totpSecret: true } });
809
+ const user = await db().user.findUnique({
810
+ where: { id: auth.session.userId },
811
+ select: { totpSecret: true },
812
+ });
799
813
  if (!user?.totpSecret)
800
814
  return errorResponse('TOTP not set up', 400);
801
815
  const secret = await decryptSecret(user.totpSecret);
@@ -845,7 +859,7 @@ export function registerCMSRoutes(router) {
845
859
  });
846
860
  router.post('/auth/totp/login', async (request) => {
847
861
  try {
848
- const body = await request.json();
862
+ const body = (await request.json());
849
863
  if (!body.mfaPendingToken || !body.code) {
850
864
  return errorResponse('mfaPendingToken and code are required', 400);
851
865
  }
@@ -883,7 +897,15 @@ export function registerCMSRoutes(router) {
883
897
  const { verifyTOTP } = await import('../auth/totp.js');
884
898
  const user = await db().user.findUnique({
885
899
  where: { id: pending.userId },
886
- select: { id: true, email: true, name: true, role: true, totpSecret: true, totpEnabled: true, isActive: true },
900
+ select: {
901
+ id: true,
902
+ email: true,
903
+ name: true,
904
+ role: true,
905
+ totpSecret: true,
906
+ totpEnabled: true,
907
+ isActive: true,
908
+ },
887
909
  });
888
910
  if (!user || !user.isActive || !user.totpEnabled || !user.totpSecret) {
889
911
  return errorResponse('Invalid request', 400);
@@ -1133,7 +1155,7 @@ export function registerCMSRoutes(router) {
1133
1155
  const roleErr = requireRole(auth.session.role, WRITE_ROLES);
1134
1156
  if (roleErr)
1135
1157
  return roleErr;
1136
- const body = await request.json();
1158
+ const body = (await request.json());
1137
1159
  const ctx = buildActionContext(auth.session, db());
1138
1160
  const doc = await createDocument(params.slug, body, ctx);
1139
1161
  await logEvent({
@@ -1155,7 +1177,7 @@ export function registerCMSRoutes(router) {
1155
1177
  const roleErr = requireRole(auth.session.role, WRITE_ROLES);
1156
1178
  if (roleErr)
1157
1179
  return roleErr;
1158
- const body = await request.json();
1180
+ const body = (await request.json());
1159
1181
  const ctx = buildActionContext(auth.session, db());
1160
1182
  const doc = await updateDocument(params.slug, params.id, body, ctx);
1161
1183
  await logEvent({
@@ -1241,7 +1263,7 @@ export function registerCMSRoutes(router) {
1241
1263
  const auth = await requireAuth(request);
1242
1264
  if (auth.error)
1243
1265
  return auth.error;
1244
- const body = await request.json();
1266
+ const body = (await request.json());
1245
1267
  if (!body.filename || !body.contentType) {
1246
1268
  return errorResponse('filename and contentType are required', 400);
1247
1269
  }
@@ -1328,8 +1350,96 @@ export function registerCMSRoutes(router) {
1328
1350
  // Strip <script>, on*, javascript: URLs, foreignObject, etc.
1329
1351
  const xml = new TextDecoder('utf-8', { fatal: false }).decode(arrayBuffer);
1330
1352
  const sanitized = sanitizeHtml(xml, {
1331
- allowedTags: ['svg', 'g', 'path', 'circle', 'ellipse', 'line', 'polygon', 'polyline', 'rect', 'text', 'tspan', 'defs', 'use', 'symbol', 'title', 'desc', 'style', 'linearGradient', 'radialGradient', 'stop', 'mask', 'clipPath', 'pattern', 'filter', 'feGaussianBlur', 'feColorMatrix', 'feOffset', 'feBlend', 'feFlood', 'feComposite', 'feMerge', 'feMergeNode'],
1332
- allowedAttributes: { '*': ['id', 'class', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin', 'opacity', 'transform', 'd', 'cx', 'cy', 'r', 'rx', 'ry', 'x', 'y', 'x1', 'y1', 'x2', 'y2', 'width', 'height', 'viewBox', 'xmlns', 'xmlns:xlink', 'preserveAspectRatio', 'points', 'points', 'offset', 'stop-color', 'stop-opacity', 'gradientUnits', 'gradientTransform', 'href', 'xlink:href', 'fill-rule', 'clip-rule', 'mask', 'clip-path', 'filter', 'patternUnits', 'patternContentUnits', 'in', 'in2', 'result', 'mode', 'values', 'type', 'stdDeviation', 'dx', 'dy'] },
1353
+ allowedTags: [
1354
+ 'svg',
1355
+ 'g',
1356
+ 'path',
1357
+ 'circle',
1358
+ 'ellipse',
1359
+ 'line',
1360
+ 'polygon',
1361
+ 'polyline',
1362
+ 'rect',
1363
+ 'text',
1364
+ 'tspan',
1365
+ 'defs',
1366
+ 'use',
1367
+ 'symbol',
1368
+ 'title',
1369
+ 'desc',
1370
+ 'style',
1371
+ 'linearGradient',
1372
+ 'radialGradient',
1373
+ 'stop',
1374
+ 'mask',
1375
+ 'clipPath',
1376
+ 'pattern',
1377
+ 'filter',
1378
+ 'feGaussianBlur',
1379
+ 'feColorMatrix',
1380
+ 'feOffset',
1381
+ 'feBlend',
1382
+ 'feFlood',
1383
+ 'feComposite',
1384
+ 'feMerge',
1385
+ 'feMergeNode',
1386
+ ],
1387
+ allowedAttributes: {
1388
+ '*': [
1389
+ 'id',
1390
+ 'class',
1391
+ 'fill',
1392
+ 'stroke',
1393
+ 'stroke-width',
1394
+ 'stroke-linecap',
1395
+ 'stroke-linejoin',
1396
+ 'opacity',
1397
+ 'transform',
1398
+ 'd',
1399
+ 'cx',
1400
+ 'cy',
1401
+ 'r',
1402
+ 'rx',
1403
+ 'ry',
1404
+ 'x',
1405
+ 'y',
1406
+ 'x1',
1407
+ 'y1',
1408
+ 'x2',
1409
+ 'y2',
1410
+ 'width',
1411
+ 'height',
1412
+ 'viewBox',
1413
+ 'xmlns',
1414
+ 'xmlns:xlink',
1415
+ 'preserveAspectRatio',
1416
+ 'points',
1417
+ 'points',
1418
+ 'offset',
1419
+ 'stop-color',
1420
+ 'stop-opacity',
1421
+ 'gradientUnits',
1422
+ 'gradientTransform',
1423
+ 'href',
1424
+ 'xlink:href',
1425
+ 'fill-rule',
1426
+ 'clip-rule',
1427
+ 'mask',
1428
+ 'clip-path',
1429
+ 'filter',
1430
+ 'patternUnits',
1431
+ 'patternContentUnits',
1432
+ 'in',
1433
+ 'in2',
1434
+ 'result',
1435
+ 'mode',
1436
+ 'values',
1437
+ 'type',
1438
+ 'stdDeviation',
1439
+ 'dx',
1440
+ 'dy',
1441
+ ],
1442
+ },
1333
1443
  });
1334
1444
  uploadBuffer = Buffer.from(sanitized, 'utf-8');
1335
1445
  finalSize = uploadBuffer.byteLength;
@@ -1467,7 +1577,9 @@ export function registerCMSRoutes(router) {
1467
1577
  try {
1468
1578
  await blob.del(media.storageKey);
1469
1579
  }
1470
- catch { /* best-effort */ }
1580
+ catch {
1581
+ /* best-effort */
1582
+ }
1471
1583
  }
1472
1584
  catch {
1473
1585
  newPublicUrl = `/api/cms/media/file/${newStorageKey}`;
@@ -1511,7 +1623,7 @@ export function registerCMSRoutes(router) {
1511
1623
  const roleErr = requireRole(auth.session.role, WRITE_ROLES);
1512
1624
  if (roleErr)
1513
1625
  return roleErr;
1514
- const body = await request.json();
1626
+ const body = (await request.json());
1515
1627
  const updated = await db().media.update({
1516
1628
  where: { id: params.id },
1517
1629
  data: {
@@ -1636,7 +1748,7 @@ export function registerCMSRoutes(router) {
1636
1748
  if (!encKey) {
1637
1749
  return errorResponse('CMS_ENCRYPTION_KEY is required to store encrypted credentials.', 400);
1638
1750
  }
1639
- const body = await request.json();
1751
+ const body = (await request.json());
1640
1752
  if (body.githubRepo && !/^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/.test(body.githubRepo)) {
1641
1753
  return errorResponse('Invalid repository format. Use owner/repo.', 400);
1642
1754
  }
@@ -1678,7 +1790,7 @@ export function registerCMSRoutes(router) {
1678
1790
  if (!session || session.role !== 'admin') {
1679
1791
  return errorResponse('Unauthorized — admin only', 403);
1680
1792
  }
1681
- const body = await request.json();
1793
+ const body = (await request.json());
1682
1794
  if (!body.targetVersion) {
1683
1795
  return errorResponse('targetVersion is required', 400);
1684
1796
  }
@@ -1743,7 +1855,7 @@ export function registerCMSRoutes(router) {
1743
1855
  if (!(await checkRateLimitAsync(loginLimiter, `setup:${clientIp}`))) {
1744
1856
  return errorResponse('Too many setup attempts', 429);
1745
1857
  }
1746
- const body = await request.json();
1858
+ const body = (await request.json());
1747
1859
  if (!body.name || !body.email || !body.password) {
1748
1860
  return errorResponse('Name, email, and password are required', 400);
1749
1861
  }
@@ -1768,9 +1880,17 @@ export function registerCMSRoutes(router) {
1768
1880
  // Health endpoint -- reports available models and CMS version
1769
1881
  // ---------------------------------------------------------------------------
1770
1882
  const CMS_EXPECTED_MODELS = [
1771
- 'document', 'media', 'user', 'session', 'version',
1772
- 'folder', 'redirect', 'formSubmission', 'auditLog',
1773
- 'webhookEndpoint', 'webhookDeliveryLog',
1883
+ 'document',
1884
+ 'media',
1885
+ 'user',
1886
+ 'session',
1887
+ 'version',
1888
+ 'folder',
1889
+ 'redirect',
1890
+ 'formSubmission',
1891
+ 'auditLog',
1892
+ 'webhookEndpoint',
1893
+ 'webhookDeliveryLog',
1774
1894
  ];
1775
1895
  router.get('/health', async () => {
1776
1896
  const cmsVersion = globalThis.__actuateCoreVersion ?? '0.0.0';
@@ -1889,8 +2009,12 @@ export function registerCMSRoutes(router) {
1889
2009
  orderBy: { updatedAt: 'desc' },
1890
2010
  take: 20,
1891
2011
  select: {
1892
- id: true, title: true, status: true, collection: true,
1893
- updatedAt: true, createdById: true,
2012
+ id: true,
2013
+ title: true,
2014
+ status: true,
2015
+ collection: true,
2016
+ updatedAt: true,
2017
+ createdById: true,
1894
2018
  createdBy: { select: { name: true, email: true } },
1895
2019
  },
1896
2020
  }),
@@ -1965,13 +2089,31 @@ export function registerCMSRoutes(router) {
1965
2089
  const canSeeUserDirectory = auth.session.role === 'ADMIN' || auth.session.role === 'EDITOR';
1966
2090
  const [documents, media, users] = await Promise.all([
1967
2091
  safeFindMany(d.document, {
1968
- where: { deletedAt: null, OR: [{ title: { contains: q, mode: 'insensitive' } }, { plainText: { contains: q, mode: 'insensitive' } }] },
2092
+ where: {
2093
+ deletedAt: null,
2094
+ OR: [
2095
+ { title: { contains: q, mode: 'insensitive' } },
2096
+ { plainText: { contains: q, mode: 'insensitive' } },
2097
+ ],
2098
+ },
1969
2099
  take: 10,
1970
2100
  orderBy: { updatedAt: 'desc' },
1971
- select: { id: true, title: true, slug: true, collection: true, status: true, updatedAt: true },
2101
+ select: {
2102
+ id: true,
2103
+ title: true,
2104
+ slug: true,
2105
+ collection: true,
2106
+ status: true,
2107
+ updatedAt: true,
2108
+ },
1972
2109
  }),
1973
2110
  safeFindMany(d.media, {
1974
- where: { OR: [{ filename: { contains: q, mode: 'insensitive' } }, { altText: { contains: q, mode: 'insensitive' } }] },
2111
+ where: {
2112
+ OR: [
2113
+ { filename: { contains: q, mode: 'insensitive' } },
2114
+ { altText: { contains: q, mode: 'insensitive' } },
2115
+ ],
2116
+ },
1975
2117
  take: 5,
1976
2118
  orderBy: { createdAt: 'desc' },
1977
2119
  select: { id: true, filename: true, altText: true, mimeType: true, storageKey: true },
@@ -2143,7 +2285,7 @@ export function registerCMSRoutes(router) {
2143
2285
  return errorResponse('Form not found', 404);
2144
2286
  }
2145
2287
  const formData = (form.data ?? {});
2146
- const body = await request.json();
2288
+ const body = (await request.json());
2147
2289
  if (!body.fields || typeof body.fields !== 'object') {
2148
2290
  return errorResponse('Missing or invalid "fields" in request body', 400);
2149
2291
  }
@@ -2163,14 +2305,10 @@ export function registerCMSRoutes(router) {
2163
2305
  submittedAt: new Date(),
2164
2306
  },
2165
2307
  });
2166
- // Fire form hooks asynchronously (email notification, webhooks)
2167
2308
  (async () => {
2168
2309
  try {
2169
2310
  const config = globalThis.__actuateConfig;
2170
- const hooks = [
2171
- ...(config?.plugins?.forms?.hooks ?? []),
2172
- ...(config?._pluginHooks ?? []),
2173
- ];
2311
+ const hooks = [...(config?.plugins?.forms?.hooks ?? []), ...(config?._pluginHooks ?? [])];
2174
2312
  const formHooks = hooks.filter((h) => h.event === 'afterCreate:form-submissions');
2175
2313
  for (const hook of formHooks) {
2176
2314
  await hook.handler({ formId, data: body.fields });
@@ -2220,7 +2358,7 @@ export function registerCMSRoutes(router) {
2220
2358
  return auth.error;
2221
2359
  if (auth.session.role !== 'ADMIN')
2222
2360
  return errorResponse('Admin access required', 403);
2223
- const body = await request.json();
2361
+ const body = (await request.json());
2224
2362
  const source = String(body.source ?? body.from ?? '').trim();
2225
2363
  const destination = String(body.destination ?? body.to ?? '').trim();
2226
2364
  const requestedStatus = Number(body.statusCode ?? body.type);
@@ -2255,7 +2393,9 @@ export function registerCMSRoutes(router) {
2255
2393
  try {
2256
2394
  allowed.add(new URL(siteUrl).hostname.toLowerCase());
2257
2395
  }
2258
- catch { /* noop */ }
2396
+ catch {
2397
+ /* noop */
2398
+ }
2259
2399
  }
2260
2400
  if (!allowed.has(destUrl.hostname.toLowerCase())) {
2261
2401
  return errorResponse('External redirect destinations must be to an allowlisted host. Add the host to `redirects.allowedExternalHosts` in your CMS config.', 400);
@@ -2389,7 +2529,9 @@ export function registerCMSRoutes(router) {
2389
2529
  try {
2390
2530
  await resp.body?.cancel();
2391
2531
  }
2392
- catch { /* noop */ }
2532
+ catch {
2533
+ /* noop */
2534
+ }
2393
2535
  }
2394
2536
  catch (err) {
2395
2537
  status = err instanceof SsrfBlockedError ? -1 : 0;
@@ -2428,7 +2570,14 @@ export function registerCMSRoutes(router) {
2428
2570
  return roleErr;
2429
2571
  const documents = await db().document.findMany({
2430
2572
  where: { status: 'PUBLISHED', deletedAt: null },
2431
- select: { id: true, title: true, slug: true, collection: true, data: true, plainText: true },
2573
+ select: {
2574
+ id: true,
2575
+ title: true,
2576
+ slug: true,
2577
+ collection: true,
2578
+ data: true,
2579
+ plainText: true,
2580
+ },
2432
2581
  });
2433
2582
  const issues = [];
2434
2583
  for (const doc of documents) {
@@ -2445,7 +2594,11 @@ export function registerCMSRoutes(router) {
2445
2594
  const plainText = (doc.plainText ?? '');
2446
2595
  if (plainText.length > 0 && plainText.length < 300)
2447
2596
  problems.push('Content is too short (< 300 characters)');
2448
- const content = typeof data.body === 'string' ? data.body : typeof data.content === 'string' ? data.content : '';
2597
+ const content = typeof data.body === 'string'
2598
+ ? data.body
2599
+ : typeof data.content === 'string'
2600
+ ? data.content
2601
+ : '';
2449
2602
  if (content) {
2450
2603
  const imgMatches = content.match(/<img\b[^>]*>/gi) ?? [];
2451
2604
  const missingAlt = imgMatches.filter((img) => !img.includes('alt=')).length;
@@ -2455,7 +2608,12 @@ export function registerCMSRoutes(router) {
2455
2608
  problems.push('No H1 heading found in content');
2456
2609
  }
2457
2610
  if (problems.length > 0) {
2458
- issues.push({ documentId: doc.id, title: doc.title ?? 'Untitled', slug: doc.slug ?? '', problems });
2611
+ issues.push({
2612
+ documentId: doc.id,
2613
+ title: doc.title ?? 'Untitled',
2614
+ slug: doc.slug ?? '',
2615
+ problems,
2616
+ });
2459
2617
  }
2460
2618
  }
2461
2619
  const total = documents.length;
@@ -2539,7 +2697,10 @@ export function registerCMSRoutes(router) {
2539
2697
  return errorResponse('Not found', 404);
2540
2698
  const data = doc.data || {};
2541
2699
  const title = doc.title || data.title || '';
2542
- const keywords = title.split(/\s+/).filter((w) => w.length > 3).slice(0, 5);
2700
+ const keywords = title
2701
+ .split(/\s+/)
2702
+ .filter((w) => w.length > 3)
2703
+ .slice(0, 5);
2543
2704
  if (keywords.length === 0) {
2544
2705
  return new Response(JSON.stringify({ suggestions: [] }), {
2545
2706
  status: 200,
@@ -2702,12 +2863,12 @@ export function registerCMSRoutes(router) {
2702
2863
  const auth = await requireAuth(request);
2703
2864
  if (auth.error)
2704
2865
  return auth.error;
2705
- const docs = await safeFindMany(db().document, {
2866
+ const docs = (await safeFindMany(db().document, {
2706
2867
  where: { deletedAt: null, status: 'PUBLISHED' },
2707
2868
  select: { id: true, title: true, collection: true, data: true, plainText: true },
2708
2869
  orderBy: { updatedAt: 'desc' },
2709
2870
  take: 50,
2710
- });
2871
+ }));
2711
2872
  let missingMetaDescriptions = 0;
2712
2873
  let missingAltText = 0;
2713
2874
  const topContent = [];
@@ -2715,7 +2876,11 @@ export function registerCMSRoutes(router) {
2715
2876
  const data = doc.data ?? {};
2716
2877
  if (!data.metaDescription && !data.seoDescription)
2717
2878
  missingMetaDescriptions++;
2718
- const content = typeof data.body === 'string' ? data.body : typeof data.content === 'string' ? data.content : '';
2879
+ const content = typeof data.body === 'string'
2880
+ ? data.body
2881
+ : typeof data.content === 'string'
2882
+ ? data.content
2883
+ : '';
2719
2884
  if (content) {
2720
2885
  const imgMatches = content.match(/<img\b[^>]*>/gi) ?? [];
2721
2886
  const noAlt = imgMatches.filter((img) => !img.includes('alt=')).length;
@@ -2745,7 +2910,11 @@ export function registerCMSRoutes(router) {
2745
2910
  continue;
2746
2911
  seen.add(clean);
2747
2912
  try {
2748
- const resp = await fetch(clean, { method: 'HEAD', redirect: 'manual', signal: AbortSignal.timeout(3000) });
2913
+ const resp = await fetch(clean, {
2914
+ method: 'HEAD',
2915
+ redirect: 'manual',
2916
+ signal: AbortSignal.timeout(3000),
2917
+ });
2749
2918
  if (resp.status >= 400)
2750
2919
  brokenInternalLinks++;
2751
2920
  }
@@ -2755,7 +2924,9 @@ export function registerCMSRoutes(router) {
2755
2924
  }
2756
2925
  }
2757
2926
  }
2758
- catch { /* best effort */ }
2927
+ catch {
2928
+ /* best effort */
2929
+ }
2759
2930
  return json({
2760
2931
  data: {
2761
2932
  totalPages: docs.length,
@@ -2796,7 +2967,10 @@ export function registerCMSRoutes(router) {
2796
2967
  }
2797
2968
  }
2798
2969
  if (unresolved.size > 0 && layoutConfig.inherit !== false) {
2799
- const segments = path.replace(/^\/|\/$/g, '').split('/').filter(Boolean);
2970
+ const segments = path
2971
+ .replace(/^\/|\/$/g, '')
2972
+ .split('/')
2973
+ .filter(Boolean);
2800
2974
  const walkDepth = Math.min(segments.length - 1, MAX_RESOLVE_DEPTH);
2801
2975
  for (let i = walkDepth; i > 0 && unresolved.size > 0; i--) {
2802
2976
  const parentSlug = segments.slice(0, i).join('/');
@@ -2805,10 +2979,7 @@ export function registerCMSRoutes(router) {
2805
2979
  collection: matchedCollection,
2806
2980
  deletedAt: null,
2807
2981
  status: 'PUBLISHED',
2808
- OR: [
2809
- { data: { path: ['slug'], equals: parentSlug } },
2810
- { slug: parentSlug },
2811
- ],
2982
+ OR: [{ data: { path: ['slug'], equals: parentSlug } }, { slug: parentSlug }],
2812
2983
  },
2813
2984
  select: { data: true },
2814
2985
  });
@@ -2865,14 +3036,17 @@ export function registerCMSRoutes(router) {
2865
3036
  if (!pathParam) {
2866
3037
  return errorResponse('Missing required "path" query parameter', 400);
2867
3038
  }
2868
- const segments = pathParam.replace(/^\/|\/$/g, '').split('/').filter(Boolean);
3039
+ const segments = pathParam
3040
+ .replace(/^\/|\/$/g, '')
3041
+ .split('/')
3042
+ .filter(Boolean);
2869
3043
  const configCollections = globalThis.__actuateConfig?.collections ?? {};
2870
3044
  const collectionDefs = Object.values(configCollections);
2871
3045
  let matchedCollection = null;
2872
3046
  let docSlug = null;
2873
3047
  if (segments.length === 0) {
2874
3048
  // Root path — find a page-type collection and look for "home" or "index"
2875
- const pageCol = collectionDefs.find((c) => c.type === 'page' && !((c.urlPrefix ?? '').replace(/^\/|\/$/g, '')));
3049
+ const pageCol = collectionDefs.find((c) => c.type === 'page' && !(c.urlPrefix ?? '').replace(/^\/|\/$/g, ''));
2876
3050
  matchedCollection = pageCol?.slug ?? 'pages';
2877
3051
  docSlug = 'home';
2878
3052
  }
@@ -2918,16 +3092,13 @@ export function registerCMSRoutes(router) {
2918
3092
  { slug: 'home' },
2919
3093
  { slug: 'index' },
2920
3094
  ]
2921
- : [
2922
- { data: { path: ['slug'], equals: docSlug } },
2923
- { slug: docSlug },
2924
- ],
3095
+ : [{ data: { path: ['slug'], equals: docSlug } }, { slug: docSlug }],
2925
3096
  },
2926
3097
  });
2927
3098
  if (!doc) {
2928
3099
  return errorResponse('Document not found', 404);
2929
3100
  }
2930
- const docData = (doc.data && typeof doc.data === 'object') ? doc.data : {};
3101
+ const docData = doc.data && typeof doc.data === 'object' ? doc.data : {};
2931
3102
  const layout = await resolveLayout(pathParam, docData, matchedCollection);
2932
3103
  const { _layout: _omit, ...cleanData } = docData;
2933
3104
  return json({
@@ -2979,7 +3150,7 @@ export function registerCMSRoutes(router) {
2979
3150
  const roleErr = requireRole(auth.session.role, WRITE_ROLES);
2980
3151
  if (roleErr)
2981
3152
  return roleErr;
2982
- const body = await request.json();
3153
+ const body = (await request.json());
2983
3154
  if (!body.name || !body.scope)
2984
3155
  return errorResponse('name and scope are required', 400);
2985
3156
  // A child folder MUST live in the same scope as its parent — without
@@ -3014,7 +3185,7 @@ export function registerCMSRoutes(router) {
3014
3185
  const roleErr = requireRole(auth.session.role, WRITE_ROLES);
3015
3186
  if (roleErr)
3016
3187
  return roleErr;
3017
- const body = await request.json();
3188
+ const body = (await request.json());
3018
3189
  const existing = await db().folder.findUnique({ where: { id: params.id } });
3019
3190
  if (!existing)
3020
3191
  return errorResponse('Folder not found', 404);
@@ -3064,7 +3235,9 @@ export function registerCMSRoutes(router) {
3064
3235
  if (!folder)
3065
3236
  return errorResponse('Folder not found', 404);
3066
3237
  const [docCount, mediaCount, childCount] = await Promise.all([
3067
- hasModel(d, 'document') ? d.document.count({ where: { folderId: params.id, deletedAt: null } }) : 0,
3238
+ hasModel(d, 'document')
3239
+ ? d.document.count({ where: { folderId: params.id, deletedAt: null } })
3240
+ : 0,
3068
3241
  hasModel(d, 'media') ? d.media.count({ where: { folderId: params.id } }) : 0,
3069
3242
  d.folder.count({ where: { parentId: params.id } }),
3070
3243
  ]);
@@ -3086,7 +3259,7 @@ export function registerCMSRoutes(router) {
3086
3259
  const roleErr = requireRole(auth.session.role, WRITE_ROLES);
3087
3260
  if (roleErr)
3088
3261
  return roleErr;
3089
- const body = await request.json();
3262
+ const body = (await request.json());
3090
3263
  // Confirm the target folder is in the documents scope before moving in.
3091
3264
  if (body.folderId) {
3092
3265
  const target = await db().folder.findUnique({ where: { id: body.folderId } });
@@ -3114,7 +3287,7 @@ export function registerCMSRoutes(router) {
3114
3287
  const roleErr = requireRole(auth.session.role, WRITE_ROLES);
3115
3288
  if (roleErr)
3116
3289
  return roleErr;
3117
- const body = await request.json();
3290
+ const body = (await request.json());
3118
3291
  if (body.folderId) {
3119
3292
  const target = await db().folder.findUnique({ where: { id: body.folderId } });
3120
3293
  if (!target)
@@ -3141,7 +3314,7 @@ export function registerCMSRoutes(router) {
3141
3314
  const auth = await requireAuth(request);
3142
3315
  if (auth.error)
3143
3316
  return auth.error;
3144
- const body = await request.json();
3317
+ const body = (await request.json());
3145
3318
  if (!body.collection || !body.documentId) {
3146
3319
  return errorResponse('collection and documentId are required', 400);
3147
3320
  }
@@ -3183,7 +3356,7 @@ export function registerCMSRoutes(router) {
3183
3356
  const auth = await requireAuth(request);
3184
3357
  if (auth.error)
3185
3358
  return auth.error;
3186
- const body = await request.json();
3359
+ const body = (await request.json());
3187
3360
  if (!body.stage)
3188
3361
  return errorResponse('Stage is required', 400);
3189
3362
  const { transitionDocument } = await import('../workflow/index.js');
@@ -3210,7 +3383,9 @@ export function registerCMSRoutes(router) {
3210
3383
  return errorResponse('Document not found', 404);
3211
3384
  const stage = (doc.workflowStage ?? 'DRAFT');
3212
3385
  const transitions = getAvailableTransitions(stage, auth.session.role);
3213
- return json({ data: { stage, transitions, reviewerId: doc.reviewerId, reviewNote: doc.reviewNote } });
3386
+ return json({
3387
+ data: { stage, transitions, reviewerId: doc.reviewerId, reviewNote: doc.reviewNote },
3388
+ });
3214
3389
  }
3215
3390
  catch (err) {
3216
3391
  return internalError(err);
@@ -3336,16 +3511,12 @@ export function registerCMSRoutes(router) {
3336
3511
  // allowed. Integrators that want to gate a global must set
3337
3512
  // `access.read` explicitly (returning `false` for public).
3338
3513
  const readAccess = globalConfig.access?.read;
3339
- const allowed = readAccess
3340
- ? await readAccess({ user: null, doc })
3341
- : true;
3514
+ const allowed = readAccess ? await readAccess({ user: null, doc }) : true;
3342
3515
  if (!allowed) {
3343
3516
  return errorResponse('Forbidden', 403);
3344
3517
  }
3345
3518
  return json({
3346
- data: doc.data && typeof doc.data === 'object'
3347
- ? doc.data
3348
- : {},
3519
+ data: doc.data && typeof doc.data === 'object' ? doc.data : {},
3349
3520
  });
3350
3521
  }
3351
3522
  catch (err) {
@@ -3376,7 +3547,7 @@ export function registerCMSRoutes(router) {
3376
3547
  const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
3377
3548
  if (roleErr)
3378
3549
  return roleErr;
3379
- const body = await request.json();
3550
+ const body = (await request.json());
3380
3551
  const ctx = buildActionContext(auth.session, db());
3381
3552
  const global = await updateGlobal(params.slug, body, ctx);
3382
3553
  return json({ data: global });
@@ -3412,7 +3583,7 @@ export function registerCMSRoutes(router) {
3412
3583
  const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
3413
3584
  if (roleErr)
3414
3585
  return roleErr;
3415
- const body = await request.json();
3586
+ const body = (await request.json());
3416
3587
  if (!body.url || !body.events?.length) {
3417
3588
  return errorResponse('url and events are required', 400);
3418
3589
  }
@@ -3444,7 +3615,7 @@ export function registerCMSRoutes(router) {
3444
3615
  const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
3445
3616
  if (roleErr)
3446
3617
  return roleErr;
3447
- const body = await request.json();
3618
+ const body = (await request.json());
3448
3619
  const { updateEndpoint } = await import('../webhooks/index.js');
3449
3620
  const updated = await updateEndpoint(params.id, body);
3450
3621
  return json({ data: updated });
@@ -3599,7 +3770,7 @@ export function registerCMSRoutes(router) {
3599
3770
  const d = db();
3600
3771
  if (!hasModel(d, 'scriptTag'))
3601
3772
  return modelNotAvailable('ScriptTag');
3602
- const body = await request.json();
3773
+ const body = (await request.json());
3603
3774
  if (!body.name || !body.code || !body.placement) {
3604
3775
  return errorResponse('name, code, and placement are required', 400);
3605
3776
  }
@@ -3648,7 +3819,7 @@ export function registerCMSRoutes(router) {
3648
3819
  const existing = await d.scriptTag.findUnique({ where: { id: params.id } });
3649
3820
  if (!existing)
3650
3821
  return errorResponse('Script tag not found', 404);
3651
- const body = await request.json();
3822
+ const body = (await request.json());
3652
3823
  const update = {};
3653
3824
  if (body.name !== undefined)
3654
3825
  update.name = body.name;
@@ -3830,7 +4001,7 @@ export function registerCMSRoutes(router) {
3830
4001
  const d = db();
3831
4002
  if (!hasModel(d, 'pageTemplate'))
3832
4003
  return modelNotAvailable('PageTemplate');
3833
- const body = await request.json();
4004
+ const body = (await request.json());
3834
4005
  if (!body.name)
3835
4006
  return errorResponse('name is required', 400);
3836
4007
  if (!body.tree)
@@ -3881,7 +4052,7 @@ export function registerCMSRoutes(router) {
3881
4052
  return errorResponse('Template not found', 404);
3882
4053
  if (existing.builtIn)
3883
4054
  return errorResponse('Cannot update built-in templates', 403);
3884
- const body = await request.json();
4055
+ const body = (await request.json());
3885
4056
  const update = {};
3886
4057
  if (body.name !== undefined)
3887
4058
  update.name = body.name;
@@ -4029,7 +4200,7 @@ export function registerCMSRoutes(router) {
4029
4200
  const d = db();
4030
4201
  if (!hasModel(d, 'savedSection'))
4031
4202
  return modelNotAvailable('SavedSection');
4032
- const body = await request.json();
4203
+ const body = (await request.json());
4033
4204
  if (!body.name)
4034
4205
  return errorResponse('name is required', 400);
4035
4206
  if (!body.tree)
@@ -4078,7 +4249,7 @@ export function registerCMSRoutes(router) {
4078
4249
  const existing = await d.savedSection.findUnique({ where: { id: params.id } });
4079
4250
  if (!existing)
4080
4251
  return errorResponse('Saved section not found', 404);
4081
- const body = await request.json();
4252
+ const body = (await request.json());
4082
4253
  const update = {};
4083
4254
  if (body.name !== undefined)
4084
4255
  update.name = body.name;
@@ -4295,7 +4466,11 @@ export function registerCMSRoutes(router) {
4295
4466
  await logEvent({
4296
4467
  event: 'settings_changed',
4297
4468
  userId: auth.session.userId,
4298
- details: { action: 'a11y_auto_fix', fixedCount: result.report.fixedCount, remainingCount: result.report.remainingCount },
4469
+ details: {
4470
+ action: 'a11y_auto_fix',
4471
+ fixedCount: result.report.fixedCount,
4472
+ remainingCount: result.report.remainingCount,
4473
+ },
4299
4474
  });
4300
4475
  return json({ data: result });
4301
4476
  }