octocms 0.4.17 → 0.4.19

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 (239) hide show
  1. package/README.md +1 -1
  2. package/dist/admin/actions/agent.d.ts.map +1 -1
  3. package/dist/admin/actions/agent.js +3 -4
  4. package/dist/admin/actions/agent.js.map +1 -1
  5. package/dist/admin/actions/entries.d.ts.map +1 -1
  6. package/dist/admin/actions/entries.js +79 -48
  7. package/dist/admin/actions/entries.js.map +1 -1
  8. package/dist/admin/actions/files.d.ts +0 -1
  9. package/dist/admin/actions/files.d.ts.map +1 -1
  10. package/dist/admin/actions/files.js +66 -66
  11. package/dist/admin/actions/files.js.map +1 -1
  12. package/dist/admin/actions/git.d.ts.map +1 -1
  13. package/dist/admin/actions/git.js +0 -2
  14. package/dist/admin/actions/git.js.map +1 -1
  15. package/dist/admin/actions/media.d.ts.map +1 -1
  16. package/dist/admin/actions/media.js +53 -24
  17. package/dist/admin/actions/media.js.map +1 -1
  18. package/dist/admin/actions/utils.d.ts +12 -20
  19. package/dist/admin/actions/utils.d.ts.map +1 -1
  20. package/dist/admin/actions/utils.js +10 -5
  21. package/dist/admin/actions/utils.js.map +1 -1
  22. package/dist/admin/auth/cookies.d.ts +16 -0
  23. package/dist/admin/auth/cookies.d.ts.map +1 -0
  24. package/dist/admin/auth/cookies.js +45 -0
  25. package/dist/admin/auth/cookies.js.map +1 -0
  26. package/dist/admin/auth/env.d.ts +6 -0
  27. package/dist/admin/auth/env.d.ts.map +1 -0
  28. package/dist/admin/auth/env.js +25 -0
  29. package/dist/admin/auth/env.js.map +1 -0
  30. package/dist/admin/auth/oauthApp.d.ts +8 -0
  31. package/dist/admin/auth/oauthApp.d.ts.map +1 -0
  32. package/dist/admin/auth/oauthApp.js +64 -0
  33. package/dist/admin/auth/oauthApp.js.map +1 -0
  34. package/dist/admin/auth/seal.d.ts +6 -0
  35. package/dist/admin/auth/seal.d.ts.map +1 -0
  36. package/dist/admin/auth/seal.js +61 -0
  37. package/dist/admin/auth/seal.js.map +1 -0
  38. package/dist/admin/auth/session.d.ts +19 -0
  39. package/dist/admin/auth/session.d.ts.map +1 -0
  40. package/dist/admin/auth/session.js +89 -0
  41. package/dist/admin/auth/session.js.map +1 -0
  42. package/dist/admin/auth/types.d.ts +17 -0
  43. package/dist/admin/auth/types.d.ts.map +1 -0
  44. package/dist/admin/auth/types.js +1 -0
  45. package/dist/admin/auth/types.js.map +1 -0
  46. package/dist/admin/authRoutes.d.ts +14 -0
  47. package/dist/admin/authRoutes.d.ts.map +1 -0
  48. package/dist/admin/authRoutes.js +147 -0
  49. package/dist/admin/authRoutes.js.map +1 -0
  50. package/dist/admin/error.d.ts +9 -0
  51. package/dist/admin/error.d.ts.map +1 -0
  52. package/dist/admin/error.js +7 -0
  53. package/dist/admin/error.js.map +1 -0
  54. package/dist/admin/github.d.ts +20 -2
  55. package/dist/admin/github.d.ts.map +1 -1
  56. package/dist/admin/github.js +130 -25
  57. package/dist/admin/github.js.map +1 -1
  58. package/dist/admin/index.d.ts +5 -7
  59. package/dist/admin/index.d.ts.map +1 -1
  60. package/dist/admin/index.js +2 -3
  61. package/dist/admin/index.js.map +1 -1
  62. package/dist/admin/pages/AdminErrorView.d.ts +1 -1
  63. package/dist/admin/pages/AdminErrorView.js.map +1 -1
  64. package/dist/admin/pages/AdminLayout.d.ts +1 -1
  65. package/dist/admin/pages/AdminLayout.d.ts.map +1 -1
  66. package/dist/admin/pages/AdminLayout.js +1 -1
  67. package/dist/admin/pages/AdminLayout.js.map +1 -1
  68. package/dist/admin/pages/CollectionPage.d.ts.map +1 -1
  69. package/dist/admin/pages/CollectionPage.js +2 -3
  70. package/dist/admin/pages/CollectionPage.js.map +1 -1
  71. package/dist/admin/pages/ContentModelPage.d.ts.map +1 -1
  72. package/dist/admin/pages/ContentModelPage.js +2 -3
  73. package/dist/admin/pages/ContentModelPage.js.map +1 -1
  74. package/dist/admin/pages/ContentPage.d.ts.map +1 -1
  75. package/dist/admin/pages/ContentPage.js +2 -3
  76. package/dist/admin/pages/ContentPage.js.map +1 -1
  77. package/dist/admin/pages/ContentTypePage.d.ts.map +1 -1
  78. package/dist/admin/pages/ContentTypePage.js +2 -3
  79. package/dist/admin/pages/ContentTypePage.js.map +1 -1
  80. package/dist/admin/pages/EntryPage.d.ts.map +1 -1
  81. package/dist/admin/pages/EntryPage.js +2 -3
  82. package/dist/admin/pages/EntryPage.js.map +1 -1
  83. package/dist/admin/pages/MediaAssetPage.d.ts.map +1 -1
  84. package/dist/admin/pages/MediaAssetPage.js +2 -3
  85. package/dist/admin/pages/MediaAssetPage.js.map +1 -1
  86. package/dist/admin/pages/MediaPage.d.ts.map +1 -1
  87. package/dist/admin/pages/MediaPage.js +2 -3
  88. package/dist/admin/pages/MediaPage.js.map +1 -1
  89. package/dist/admin/provider.d.ts +1 -3
  90. package/dist/admin/provider.d.ts.map +1 -1
  91. package/dist/admin/provider.js +2 -7
  92. package/dist/admin/provider.js.map +1 -1
  93. package/dist/admin/query/hooks/useEntryList.d.ts +1 -1
  94. package/dist/admin/query/hooks/useEntryList.d.ts.map +1 -1
  95. package/dist/admin/store/contentStore.d.ts +20 -1
  96. package/dist/admin/store/contentStore.d.ts.map +1 -1
  97. package/dist/admin/store/contentStore.js +297 -16
  98. package/dist/admin/store/contentStore.js.map +1 -1
  99. package/dist/admin/store/contentStoreFetch.d.ts +54 -14
  100. package/dist/admin/store/contentStoreFetch.d.ts.map +1 -1
  101. package/dist/admin/store/contentStoreFetch.js +197 -65
  102. package/dist/admin/store/contentStoreFetch.js.map +1 -1
  103. package/dist/admin/store/contentStoreNextCache.d.ts +18 -0
  104. package/dist/admin/store/contentStoreNextCache.d.ts.map +1 -0
  105. package/dist/admin/store/contentStoreNextCache.js +166 -0
  106. package/dist/admin/store/contentStoreNextCache.js.map +1 -0
  107. package/dist/admin/store/contentStoreTypes.d.ts +44 -0
  108. package/dist/admin/store/contentStoreTypes.d.ts.map +1 -1
  109. package/dist/admin/store/contentStoreTypes.js +76 -0
  110. package/dist/admin/store/contentStoreTypes.js.map +1 -1
  111. package/dist/admin/theme/toggle.d.ts.map +1 -1
  112. package/dist/admin/theme/toggle.js +2 -11
  113. package/dist/admin/theme/toggle.js.map +1 -1
  114. package/dist/agent/chatApi.d.ts +2 -2
  115. package/dist/agent/chatApi.d.ts.map +1 -1
  116. package/dist/agent/chatApi.js +3 -4
  117. package/dist/agent/chatApi.js.map +1 -1
  118. package/dist/{agentDocs-6LQD3JUN.js → agentDocs-PVLGO5EN.js} +2 -2
  119. package/dist/{chunk-Q73JSGXV.js → chunk-G6BYS6LI.js} +23 -1
  120. package/dist/chunk-G6BYS6LI.js.map +1 -0
  121. package/dist/{chunk-FZROCUU3.js → chunk-OJ3KR7WR.js} +54 -9
  122. package/dist/chunk-OJ3KR7WR.js.map +1 -0
  123. package/dist/chunk-R6TFBC3O.js +7 -0
  124. package/dist/{chunk-CIKOAIJP.js.map → chunk-R6TFBC3O.js.map} +1 -1
  125. package/dist/cli/index.js +7 -7
  126. package/dist/cli/lib/schemaDocs.d.ts.map +1 -1
  127. package/dist/cli/lib/schemaDocs.js +14 -1
  128. package/dist/cli/lib/schemaDocs.js.map +1 -1
  129. package/dist/cli/lib/templates.js +44 -7
  130. package/dist/cli/lib/templates.js.map +1 -1
  131. package/dist/cli/lib/validateConfig.d.ts.map +1 -1
  132. package/dist/cli/lib/validateConfig.js +22 -0
  133. package/dist/cli/lib/validateConfig.js.map +1 -1
  134. package/dist/components/Chat/ChatAgentSetup.js +1 -1
  135. package/dist/components/Chat/ChatAgentSetup.js.map +1 -1
  136. package/dist/components/Chat/useChatStream.d.ts.map +1 -1
  137. package/dist/components/Chat/useChatStream.js +2 -1
  138. package/dist/components/Chat/useChatStream.js.map +1 -1
  139. package/dist/components/ContentModel/SchemaOptionFieldInput.js +1 -1
  140. package/dist/components/ContentModel/SchemaOptionFieldInput.js.map +1 -1
  141. package/dist/components/Dashboard/DashboardContent.js +1 -1
  142. package/dist/components/Dashboard/DashboardContent.js.map +1 -1
  143. package/dist/components/Layout/Layout.d.ts.map +1 -1
  144. package/dist/components/Layout/Layout.js +24 -21
  145. package/dist/components/Layout/Layout.js.map +1 -1
  146. package/dist/components/Layout/LeftNavItem.js +1 -1
  147. package/dist/components/Layout/LeftNavItem.js.map +1 -1
  148. package/dist/components/Layout/TopHeader.js +2 -2
  149. package/dist/components/Layout/TopHeader.js.map +1 -1
  150. package/dist/components/Layout/UserAccountDialog.d.ts.map +1 -1
  151. package/dist/components/Layout/UserAccountDialog.js +3 -2
  152. package/dist/components/Layout/UserAccountDialog.js.map +1 -1
  153. package/dist/components/MediaAsset/skeletons/MediaMetadataFormSkeleton.js +1 -1
  154. package/dist/components/MediaAsset/skeletons/MediaMetadataFormSkeleton.js.map +1 -1
  155. package/dist/components/OctoCmsSurfaceMarker.d.ts +3 -0
  156. package/dist/components/OctoCmsSurfaceMarker.d.ts.map +1 -0
  157. package/dist/components/OctoCmsSurfaceMarker.js +21 -0
  158. package/dist/components/OctoCmsSurfaceMarker.js.map +1 -0
  159. package/dist/components/public/RichTextContent.js +3 -3
  160. package/dist/components/public/RichTextContent.js.map +1 -1
  161. package/dist/components/public/SearchBox.d.ts.map +1 -1
  162. package/dist/components/public/SearchBox.js +2 -1
  163. package/dist/components/public/SearchBox.js.map +1 -1
  164. package/dist/components/ui/BranchChip/BranchChip.js +1 -1
  165. package/dist/components/ui/BranchChip/BranchChip.js.map +1 -1
  166. package/dist/components/ui/FormField/richtext/ConditionEmbedEditor.js +1 -1
  167. package/dist/components/ui/FormField/richtext/ConditionEmbedEditor.js.map +1 -1
  168. package/dist/components/ui/FormField/richtext/ImageEmbedEditor.js +5 -5
  169. package/dist/components/ui/FormField/richtext/ImageEmbedEditor.js.map +1 -1
  170. package/dist/components/ui/FormField/richtext/ReferenceEmbedEditor.d.ts.map +1 -1
  171. package/dist/components/ui/FormField/richtext/ReferenceEmbedEditor.js +3 -3
  172. package/dist/components/ui/FormField/richtext/ReferenceEmbedEditor.js.map +1 -1
  173. package/dist/components/ui/StatusBadge/StatusBadge.js +3 -3
  174. package/dist/components/ui/StatusBadge/StatusBadge.js.map +1 -1
  175. package/dist/{embeddingsGen-FXWCPGB7.js → embeddingsGen-2RI5O6UB.js} +3 -3
  176. package/dist/{github-7HIP6RW3.js → github-AZDUTXBU.js} +194 -122
  177. package/dist/github-AZDUTXBU.js.map +1 -0
  178. package/dist/github-public.d.ts +1 -1
  179. package/dist/github-public.d.ts.map +1 -1
  180. package/dist/github-public.js +2 -1
  181. package/dist/github-public.js.map +1 -1
  182. package/dist/globals.css +1286 -1247
  183. package/dist/hooks/useCmsSession.d.ts +10 -0
  184. package/dist/hooks/useCmsSession.d.ts.map +1 -0
  185. package/dist/hooks/useCmsSession.js +48 -0
  186. package/dist/hooks/useCmsSession.js.map +1 -0
  187. package/dist/{init-TZFZEP4O.js → init-QAWQ6FOL.js} +15 -10
  188. package/dist/init-QAWQ6FOL.js.map +1 -0
  189. package/dist/lib/adminCacheConfig.d.ts +9 -0
  190. package/dist/lib/adminCacheConfig.d.ts.map +1 -0
  191. package/dist/lib/adminCacheConfig.js +20 -0
  192. package/dist/lib/adminCacheConfig.js.map +1 -0
  193. package/dist/lib/cmsSurface.d.ts +3 -0
  194. package/dist/lib/cmsSurface.d.ts.map +1 -0
  195. package/dist/lib/cmsSurface.js +6 -0
  196. package/dist/lib/cmsSurface.js.map +1 -0
  197. package/dist/lib/githubContentMode.d.ts +5 -0
  198. package/dist/lib/githubContentMode.d.ts.map +1 -1
  199. package/dist/lib/githubContentMode.js +5 -1
  200. package/dist/lib/githubContentMode.js.map +1 -1
  201. package/dist/lib/octocmsApiRoutes.d.ts +15 -0
  202. package/dist/lib/octocmsApiRoutes.d.ts.map +1 -0
  203. package/dist/lib/octocmsApiRoutes.js +15 -0
  204. package/dist/lib/octocmsApiRoutes.js.map +1 -0
  205. package/dist/query.cjs +7 -7
  206. package/dist/query.cjs.map +1 -1
  207. package/dist/query.d.ts.map +1 -1
  208. package/dist/query.js +14 -8
  209. package/dist/query.js.map +1 -1
  210. package/dist/types.cjs.map +1 -1
  211. package/dist/types.d.ts +14 -0
  212. package/dist/types.d.ts.map +1 -1
  213. package/dist/{typesGen-QWWTC4SZ.js → typesGen-HK3YO52N.js} +2 -2
  214. package/dist/{update-7KYYD57J.js → update-7EDULZ3C.js} +77 -28
  215. package/dist/update-7EDULZ3C.js.map +1 -0
  216. package/dist/{validate-AAW4IGWD.js → validate-UVZSNCOX.js} +2 -2
  217. package/dist/withOctoCMS.cjs +4 -0
  218. package/dist/withOctoCMS.cjs.map +1 -1
  219. package/dist/withOctoCMS.d.ts.map +1 -1
  220. package/dist/withOctoCMS.js +4 -0
  221. package/dist/withOctoCMS.js.map +1 -1
  222. package/docs/admin-cache.md +25 -0
  223. package/docs/editing-schema.md +2 -0
  224. package/globals.css +1286 -1247
  225. package/package.json +7 -6
  226. package/dist/admin/auth.d.ts +0 -3
  227. package/dist/admin/auth.d.ts.map +0 -1
  228. package/dist/admin/auth.js +0 -28
  229. package/dist/admin/auth.js.map +0 -1
  230. package/dist/chunk-CIKOAIJP.js +0 -7
  231. package/dist/chunk-FZROCUU3.js.map +0 -1
  232. package/dist/chunk-Q73JSGXV.js.map +0 -1
  233. package/dist/github-7HIP6RW3.js.map +0 -1
  234. package/dist/init-TZFZEP4O.js.map +0 -1
  235. package/dist/update-7KYYD57J.js.map +0 -1
  236. /package/dist/{agentDocs-6LQD3JUN.js.map → agentDocs-PVLGO5EN.js.map} +0 -0
  237. /package/dist/{embeddingsGen-FXWCPGB7.js.map → embeddingsGen-2RI5O6UB.js.map} +0 -0
  238. /package/dist/{typesGen-QWWTC4SZ.js.map → typesGen-HK3YO52N.js.map} +0 -0
  239. /package/dist/{validate-AAW4IGWD.js.map → validate-UVZSNCOX.js.map} +0 -0
package/README.md CHANGED
@@ -47,7 +47,7 @@ Visit `http://localhost:3000/cms` to open the editor.
47
47
  ```bash
48
48
  GITHUB_ID= # GitHub App client ID
49
49
  GITHUB_SECRET= # GitHub App client secret
50
- NEXTAUTH_SECRET= # Random string: openssl rand -base64 32
50
+ NEXTAUTH_SECRET= # Random string: openssl rand -base64 32
51
51
  NEXTAUTH_URL=http://localhost:3000
52
52
 
53
53
  # Required in production
@@ -1 +1 @@
1
- {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../../admin/actions/agent.ts"],"names":[],"mappings":"AAEA,OAAO,kBAAkB,CAAC;AAS1B,MAAM,MAAM,iBAAiB,GACzB;IAAE,OAAO,EAAE,KAAK,CAAA;CAAE,GAClB;IACE,OAAO,EAAE,IAAI,CAAC;IACd,QAAQ,EAAE,WAAW,GAAG,QAAQ,GAAG,OAAO,CAAC;IAC3C,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEN;;;;;;GAMG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAOvE;AAED,MAAM,MAAM,0BAA0B,GAClC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC;AAEvE;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAsBjG;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,CAAC,CAYzF"}
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../../admin/actions/agent.ts"],"names":[],"mappings":"AAEA,OAAO,kBAAkB,CAAC;AAO1B,MAAM,MAAM,iBAAiB,GACzB;IAAE,OAAO,EAAE,KAAK,CAAA;CAAE,GAClB;IACE,OAAO,EAAE,IAAI,CAAC;IACd,QAAQ,EAAE,WAAW,GAAG,QAAQ,GAAG,OAAO,CAAC;IAC3C,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEN;;;;;;GAMG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAOvE;AAED,MAAM,MAAM,0BAA0B,GAClC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC;AAEvE;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAsBjG;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,CAAC,CAYzF"}
@@ -1,11 +1,10 @@
1
1
  "use server";
2
2
  import "../../chunk-B5LE2OEC.js";
3
3
  import "./registerConfig";
4
- import { getServerSession } from "next-auth/next";
5
4
  import { getAgentConfig } from "../../agent/configStore";
6
5
  import { getAgentStatus, isAgentEnabled } from "../../agent/featureFlag";
7
6
  import { acceptProposal, isProposal } from "../../agent/proposals";
8
- import { authOptions } from "../auth";
7
+ import { getCmsSession } from "../auth/session";
9
8
  async function getAgentClientStatus() {
10
9
  const cfg = getAgentConfig();
11
10
  if (!cfg || !isAgentEnabled(cfg)) return { enabled: false };
@@ -18,7 +17,7 @@ async function acceptProposalAction(proposal) {
18
17
  if (!cfg || !isAgentEnabled(cfg)) {
19
18
  throw new Error("Chat agent is disabled.");
20
19
  }
21
- const session = await getServerSession(authOptions);
20
+ const session = await getCmsSession();
22
21
  if (!session) {
23
22
  throw new Error("Unauthorized.");
24
23
  }
@@ -36,7 +35,7 @@ async function rejectProposalAction(_reason) {
36
35
  if (!cfg || !isAgentEnabled(cfg)) {
37
36
  throw new Error("Chat agent is disabled.");
38
37
  }
39
- const session = await getServerSession(authOptions);
38
+ const session = await getCmsSession();
40
39
  if (!session) {
41
40
  throw new Error("Unauthorized.");
42
41
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../admin/actions/agent.ts"],"sourcesContent":["'use server';\n\nimport './registerConfig';\n\nimport { getServerSession } from 'next-auth/next';\n\nimport { getAgentConfig } from '../../agent/configStore';\nimport { getAgentStatus, isAgentEnabled } from '../../agent/featureFlag';\nimport { acceptProposal, isProposal, type AcceptResult } from '../../agent/proposals';\nimport { authOptions } from '../auth';\n\nexport type AgentClientStatus =\n | { enabled: false }\n | {\n enabled: true;\n provider: 'anthropic' | 'openai' | 'local';\n model: string;\n };\n\n/**\n * Server-side check exposed to the admin client (Header nav link).\n *\n * Never returns the API key. Returns `{ enabled: false }` when the chat API is\n * disabled (missing config, key, or budget). The `/cms/chat` page still renders\n * a setup guide; this action is for optional client UI that needs a boolean gate.\n */\nexport async function getAgentClientStatus(): Promise<AgentClientStatus> {\n const cfg = getAgentConfig();\n if (!cfg || !isAgentEnabled(cfg)) return { enabled: false };\n // Recompute via getAgentStatus so we exercise the same code path the route uses.\n const status = getAgentStatus(cfg);\n if (!status.enabled) return { enabled: false };\n return { enabled: true, provider: cfg.provider.type, model: cfg.provider.model };\n}\n\nexport type AcceptProposalActionResult =\n | { ok: true; entryPath: string }\n | { ok: false; error: string; fieldErrors?: Record<string, string> };\n\n/**\n * Server action — apply a chat-agent edit/create proposal.\n *\n * Replaces the previous `POST /api/agent/proposals/accept` Route Handler. The\n * client (`useChatStream`) calls this directly via the Server Action transport\n * — no public endpoint, no thin re-export file.\n *\n * Stateless by design: the entire proposal payload arrives over the wire and\n * is re-validated here (and again inside `acceptProposal` via the schema\n * validator + `saveFile`).\n *\n * Returns `{ ok: false, error }` on validation / write failures so the client\n * can render an inline error without try/catch around the action call.\n * Throws only when the agent is disabled or the caller is unauthenticated —\n * those are caller-bug shapes, not user-facing flow.\n */\nexport async function acceptProposalAction(proposal: unknown): Promise<AcceptProposalActionResult> {\n const cfg = getAgentConfig();\n if (!cfg || !isAgentEnabled(cfg)) {\n throw new Error('Chat agent is disabled.');\n }\n\n const session = await getServerSession(authOptions);\n if (!session) {\n throw new Error('Unauthorized.');\n }\n\n if (!isProposal(proposal)) {\n return { ok: false, error: 'Invalid proposal payload.' };\n }\n\n const result: AcceptResult = await acceptProposal(proposal);\n if (!result.ok) {\n return result.fieldErrors\n ? { ok: false, error: result.error, fieldErrors: result.fieldErrors }\n : { ok: false, error: result.error };\n }\n return { ok: true, entryPath: result.entryPath };\n}\n\n/**\n * Server action — record that the user dismissed a proposal.\n *\n * Replaces the previous `POST /api/agent/proposals/reject` Route Handler.\n * There's no server-side proposal record to mark rejected; this just\n * acknowledges the click. The client reflects rejection in its own state and\n * feeds the rejection back to the model on the next chat turn as a synthetic\n * system note. The `reason` argument is currently ignored — kept for forward\n * compatibility (e.g. analytics / per-rejection telemetry).\n *\n * Auth-gated to keep the surface consistent with `acceptProposalAction`.\n */\nexport async function rejectProposalAction(_reason?: string | null): Promise<{ ok: true }> {\n const cfg = getAgentConfig();\n if (!cfg || !isAgentEnabled(cfg)) {\n throw new Error('Chat agent is disabled.');\n }\n\n const session = await getServerSession(authOptions);\n if (!session) {\n throw new Error('Unauthorized.');\n }\n\n return { ok: true };\n}\n"],"mappings":";;AAEA,OAAO;AAEP,SAAS,wBAAwB;AAEjC,SAAS,sBAAsB;AAC/B,SAAS,gBAAgB,sBAAsB;AAC/C,SAAS,gBAAgB,kBAAqC;AAC9D,SAAS,mBAAmB;AAiB5B,eAAsB,uBAAmD;AACvE,QAAM,MAAM,eAAe;AAC3B,MAAI,CAAC,OAAO,CAAC,eAAe,GAAG,EAAG,QAAO,EAAE,SAAS,MAAM;AAE1D,QAAM,SAAS,eAAe,GAAG;AACjC,MAAI,CAAC,OAAO,QAAS,QAAO,EAAE,SAAS,MAAM;AAC7C,SAAO,EAAE,SAAS,MAAM,UAAU,IAAI,SAAS,MAAM,OAAO,IAAI,SAAS,MAAM;AACjF;AAsBA,eAAsB,qBAAqB,UAAwD;AACjG,QAAM,MAAM,eAAe;AAC3B,MAAI,CAAC,OAAO,CAAC,eAAe,GAAG,GAAG;AAChC,UAAM,IAAI,MAAM,yBAAyB;AAAA,EAC3C;AAEA,QAAM,UAAU,MAAM,iBAAiB,WAAW;AAClD,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,eAAe;AAAA,EACjC;AAEA,MAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,WAAO,EAAE,IAAI,OAAO,OAAO,4BAA4B;AAAA,EACzD;AAEA,QAAM,SAAuB,MAAM,eAAe,QAAQ;AAC1D,MAAI,CAAC,OAAO,IAAI;AACd,WAAO,OAAO,cACV,EAAE,IAAI,OAAO,OAAO,OAAO,OAAO,aAAa,OAAO,YAAY,IAClE,EAAE,IAAI,OAAO,OAAO,OAAO,MAAM;AAAA,EACvC;AACA,SAAO,EAAE,IAAI,MAAM,WAAW,OAAO,UAAU;AACjD;AAcA,eAAsB,qBAAqB,SAAgD;AACzF,QAAM,MAAM,eAAe;AAC3B,MAAI,CAAC,OAAO,CAAC,eAAe,GAAG,GAAG;AAChC,UAAM,IAAI,MAAM,yBAAyB;AAAA,EAC3C;AAEA,QAAM,UAAU,MAAM,iBAAiB,WAAW;AAClD,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,eAAe;AAAA,EACjC;AAEA,SAAO,EAAE,IAAI,KAAK;AACpB;","names":[]}
1
+ {"version":3,"sources":["../../../admin/actions/agent.ts"],"sourcesContent":["'use server';\n\nimport './registerConfig';\n\nimport { getAgentConfig } from '../../agent/configStore';\nimport { getAgentStatus, isAgentEnabled } from '../../agent/featureFlag';\nimport { acceptProposal, isProposal, type AcceptResult } from '../../agent/proposals';\nimport { getCmsSession } from '../auth/session';\n\nexport type AgentClientStatus =\n | { enabled: false }\n | {\n enabled: true;\n provider: 'anthropic' | 'openai' | 'local';\n model: string;\n };\n\n/**\n * Server-side check exposed to the admin client (Header nav link).\n *\n * Never returns the API key. Returns `{ enabled: false }` when the chat API is\n * disabled (missing config, key, or budget). The `/cms/chat` page still renders\n * a setup guide; this action is for optional client UI that needs a boolean gate.\n */\nexport async function getAgentClientStatus(): Promise<AgentClientStatus> {\n const cfg = getAgentConfig();\n if (!cfg || !isAgentEnabled(cfg)) return { enabled: false };\n // Recompute via getAgentStatus so we exercise the same code path the route uses.\n const status = getAgentStatus(cfg);\n if (!status.enabled) return { enabled: false };\n return { enabled: true, provider: cfg.provider.type, model: cfg.provider.model };\n}\n\nexport type AcceptProposalActionResult =\n | { ok: true; entryPath: string }\n | { ok: false; error: string; fieldErrors?: Record<string, string> };\n\n/**\n * Server action — apply a chat-agent edit/create proposal.\n *\n * Replaces the previous `POST /api/agent/proposals/accept` Route Handler. The\n * client (`useChatStream`) calls this directly via the Server Action transport\n * — no public endpoint, no thin re-export file.\n *\n * Stateless by design: the entire proposal payload arrives over the wire and\n * is re-validated here (and again inside `acceptProposal` via the schema\n * validator + `saveFile`).\n *\n * Returns `{ ok: false, error }` on validation / write failures so the client\n * can render an inline error without try/catch around the action call.\n * Throws only when the agent is disabled or the caller is unauthenticated —\n * those are caller-bug shapes, not user-facing flow.\n */\nexport async function acceptProposalAction(proposal: unknown): Promise<AcceptProposalActionResult> {\n const cfg = getAgentConfig();\n if (!cfg || !isAgentEnabled(cfg)) {\n throw new Error('Chat agent is disabled.');\n }\n\n const session = await getCmsSession();\n if (!session) {\n throw new Error('Unauthorized.');\n }\n\n if (!isProposal(proposal)) {\n return { ok: false, error: 'Invalid proposal payload.' };\n }\n\n const result: AcceptResult = await acceptProposal(proposal);\n if (!result.ok) {\n return result.fieldErrors\n ? { ok: false, error: result.error, fieldErrors: result.fieldErrors }\n : { ok: false, error: result.error };\n }\n return { ok: true, entryPath: result.entryPath };\n}\n\n/**\n * Server action — record that the user dismissed a proposal.\n *\n * Replaces the previous `POST /api/agent/proposals/reject` Route Handler.\n * There's no server-side proposal record to mark rejected; this just\n * acknowledges the click. The client reflects rejection in its own state and\n * feeds the rejection back to the model on the next chat turn as a synthetic\n * system note. The `reason` argument is currently ignored — kept for forward\n * compatibility (e.g. analytics / per-rejection telemetry).\n *\n * Auth-gated to keep the surface consistent with `acceptProposalAction`.\n */\nexport async function rejectProposalAction(_reason?: string | null): Promise<{ ok: true }> {\n const cfg = getAgentConfig();\n if (!cfg || !isAgentEnabled(cfg)) {\n throw new Error('Chat agent is disabled.');\n }\n\n const session = await getCmsSession();\n if (!session) {\n throw new Error('Unauthorized.');\n }\n\n return { ok: true };\n}\n"],"mappings":";;AAEA,OAAO;AAEP,SAAS,sBAAsB;AAC/B,SAAS,gBAAgB,sBAAsB;AAC/C,SAAS,gBAAgB,kBAAqC;AAC9D,SAAS,qBAAqB;AAiB9B,eAAsB,uBAAmD;AACvE,QAAM,MAAM,eAAe;AAC3B,MAAI,CAAC,OAAO,CAAC,eAAe,GAAG,EAAG,QAAO,EAAE,SAAS,MAAM;AAE1D,QAAM,SAAS,eAAe,GAAG;AACjC,MAAI,CAAC,OAAO,QAAS,QAAO,EAAE,SAAS,MAAM;AAC7C,SAAO,EAAE,SAAS,MAAM,UAAU,IAAI,SAAS,MAAM,OAAO,IAAI,SAAS,MAAM;AACjF;AAsBA,eAAsB,qBAAqB,UAAwD;AACjG,QAAM,MAAM,eAAe;AAC3B,MAAI,CAAC,OAAO,CAAC,eAAe,GAAG,GAAG;AAChC,UAAM,IAAI,MAAM,yBAAyB;AAAA,EAC3C;AAEA,QAAM,UAAU,MAAM,cAAc;AACpC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,eAAe;AAAA,EACjC;AAEA,MAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,WAAO,EAAE,IAAI,OAAO,OAAO,4BAA4B;AAAA,EACzD;AAEA,QAAM,SAAuB,MAAM,eAAe,QAAQ;AAC1D,MAAI,CAAC,OAAO,IAAI;AACd,WAAO,OAAO,cACV,EAAE,IAAI,OAAO,OAAO,OAAO,OAAO,aAAa,OAAO,YAAY,IAClE,EAAE,IAAI,OAAO,OAAO,OAAO,MAAM;AAAA,EACvC;AACA,SAAO,EAAE,IAAI,MAAM,WAAW,OAAO,UAAU;AACjD;AAcA,eAAsB,qBAAqB,SAAgD;AACzF,QAAM,MAAM,eAAe;AAC3B,MAAI,CAAC,OAAO,CAAC,eAAe,GAAG,GAAG;AAChC,UAAM,IAAI,MAAM,yBAAyB;AAAA,EAC3C;AAEA,QAAM,UAAU,MAAM,cAAc;AACpC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,eAAe;AAAA,EACjC;AAEA,SAAO,EAAE,IAAI,KAAK;AACpB;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"entries.d.ts","sourceRoot":"","sources":["../../../admin/actions/entries.ts"],"names":[],"mappings":"AAEA,OAAO,kBAAkB,CAAC;AAO1B,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,aAAa,CAAC;AAO9D,eAAO,MAAM,YAAY,GAAU,aAAY,MAAa,KAAG,OAAO,CAAC,aAAa,EAAE,CAyErF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAAU,oBAAoB,MAAM,KAAG,OAAO,CAAC,aAAa,EAAE,CA6D3F,CAAC"}
1
+ {"version":3,"file":"entries.d.ts","sourceRoot":"","sources":["../../../admin/actions/entries.ts"],"names":[],"mappings":"AAEA,OAAO,kBAAkB,CAAC;AAS1B,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,aAAa,CAAC;AAQ9D,eAAO,MAAM,YAAY,GAAU,aAAY,MAAa,KAAG,OAAO,CAAC,aAAa,EAAE,CAiGrF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAAU,oBAAoB,MAAM,KAAG,OAAO,CAAC,aAAa,EAAE,CAuE3F,CAAC"}
@@ -3,23 +3,46 @@ import "../../chunk-B5LE2OEC.js";
3
3
  import "./registerConfig";
4
4
  import fsPromises from "fs/promises";
5
5
  import path from "path";
6
+ import { cookies } from "next/headers";
6
7
  import { getConfig } from "../../lib/configStore";
7
8
  import { getContentFiles, getFile, getFileJson } from "./files";
8
9
  import { isProductionMode } from "../github";
9
10
  import { getMediaEntries } from "./media";
10
11
  import { getEntryTitleField } from "./utils";
12
+ import { getStoredEntryListSnapshot, getStoredEntryReferencePaths } from "../store/contentStore";
11
13
  const getEntryList = async (collection = "**") => {
14
+ var _a;
12
15
  const config = getConfig();
13
- const files = await getContentFiles(collection);
14
- const mediaList = await getMediaEntries().catch(() => []);
16
+ const isProd = isProductionMode();
17
+ let storedSnapshot = null;
18
+ if (isProd) {
19
+ try {
20
+ const activeBranch = (_a = (await cookies()).get("cms-active-branch")) == null ? void 0 : _a.value;
21
+ storedSnapshot = await getStoredEntryListSnapshot(collection, activeBranch);
22
+ } catch (e) {
23
+ }
24
+ }
25
+ const files = storedSnapshot ? storedSnapshot.entries.map((entry) => entry.path) : await getContentFiles(collection);
26
+ const storedContentByPath = storedSnapshot ? new Map(storedSnapshot.entries.map((entry) => [entry.path, entry.content])) : null;
15
27
  const mediaById = /* @__PURE__ */ new Map();
16
- for (const m of mediaList) {
17
- mediaById.set(m.id, { ext: m.extension, publicUrl: m.publicUrl });
28
+ if (storedSnapshot) {
29
+ for (const entry of storedSnapshot.mediaEntries) {
30
+ const sys = entry.content.sys;
31
+ const fields = entry.content.fields;
32
+ if (typeof (sys == null ? void 0 : sys.id) === "string" && typeof (fields == null ? void 0 : fields.extension) === "string") {
33
+ mediaById.set(sys.id, { publicUrl: `/media/${sys.id}.${fields.extension}` });
34
+ }
35
+ }
36
+ } else {
37
+ const mediaList = await getMediaEntries().catch(() => []);
38
+ for (const media of mediaList) {
39
+ mediaById.set(media.id, { publicUrl: media.publicUrl });
40
+ }
18
41
  }
19
42
  const imageFieldKeyByType = /* @__PURE__ */ new Map();
20
43
  function firstImageFieldKey(type) {
21
- var _a, _b;
22
- if (imageFieldKeyByType.has(type)) return (_a = imageFieldKeyByType.get(type)) != null ? _a : null;
44
+ var _a2, _b;
45
+ if (imageFieldKeyByType.has(type)) return (_a2 = imageFieldKeyByType.get(type)) != null ? _a2 : null;
23
46
  const collection2 = config.collections[type];
24
47
  if (!collection2) {
25
48
  imageFieldKeyByType.set(type, null);
@@ -29,10 +52,9 @@ const getEntryList = async (collection = "**") => {
29
52
  imageFieldKeyByType.set(type, key);
30
53
  return key;
31
54
  }
32
- const isProd = isProductionMode();
33
55
  const items = await Promise.all(
34
56
  files.map(async (file) => {
35
- var _a, _b, _c;
57
+ var _a2, _b, _c, _d;
36
58
  const nameWithoutFolder = file.replace(`${config.contentFolder}/`, "").replace(".json", "");
37
59
  const parts = nameWithoutFolder.split("/");
38
60
  const type = parts[0];
@@ -43,7 +65,7 @@ const getEntryList = async (collection = "**") => {
43
65
  let updatedAt;
44
66
  let thumbnailUrl;
45
67
  const [contentResult, statResult] = await Promise.allSettled([
46
- getFileJson(file),
68
+ storedContentByPath ? Promise.resolve((_a2 = storedContentByPath.get(file)) != null ? _a2 : null) : getFileJson(file),
47
69
  isProd ? Promise.resolve(null) : fsPromises.stat(path.join(
48
70
  /*turbopackIgnore: true*/
49
71
  process.cwd(),
@@ -52,18 +74,18 @@ const getEntryList = async (collection = "**") => {
52
74
  ]);
53
75
  if (contentResult.status === "fulfilled" && contentResult.value) {
54
76
  const content = contentResult.value;
55
- if (titleField && ((_a = content == null ? void 0 : content.fields) == null ? void 0 : _a[titleField])) {
77
+ if (titleField && ((_b = content == null ? void 0 : content.fields) == null ? void 0 : _b[titleField])) {
56
78
  title = content.fields[titleField];
57
79
  }
58
80
  const imgKey = firstImageFieldKey(type);
59
81
  if (imgKey) {
60
- const value = (_b = content == null ? void 0 : content.fields) == null ? void 0 : _b[imgKey];
82
+ const value = (_c = content == null ? void 0 : content.fields) == null ? void 0 : _c[imgKey];
61
83
  if (typeof value === "string" && value.trim()) {
62
84
  const hit = mediaById.get(value.trim());
63
85
  if (hit) thumbnailUrl = hit.publicUrl;
64
86
  }
65
87
  }
66
- if ((_c = content == null ? void 0 : content.sys) == null ? void 0 : _c.status) {
88
+ if ((_d = content == null ? void 0 : content.sys) == null ? void 0 : _d.status) {
67
89
  status = content.sys.status;
68
90
  }
69
91
  }
@@ -77,54 +99,63 @@ const getEntryList = async (collection = "**") => {
77
99
  return items;
78
100
  };
79
101
  const getEntryBacklinks = async (targetReferenceKey) => {
80
- var _a, _b, _c, _d;
102
+ var _a, _b, _c, _d, _e;
81
103
  const config = getConfig();
82
- const allFiles = await getContentFiles("**");
104
+ let indexedFiles = null;
105
+ if (isProductionMode()) {
106
+ try {
107
+ const activeBranch = (_a = (await cookies()).get("cms-active-branch")) == null ? void 0 : _a.value;
108
+ indexedFiles = await getStoredEntryReferencePaths(targetReferenceKey, activeBranch);
109
+ } catch (e) {
110
+ }
111
+ }
112
+ const allFiles = indexedFiles != null ? indexedFiles : await getContentFiles("**");
83
113
  const backlinks = [];
84
114
  for (const file of allFiles) {
85
115
  try {
86
116
  const content = await getFile(file);
87
- const type = (_a = content == null ? void 0 : content.sys) == null ? void 0 : _a.type;
117
+ const type = (_b = content == null ? void 0 : content.sys) == null ? void 0 : _b.type;
88
118
  if (!type) continue;
89
119
  const collection = config.collections[type];
90
120
  if (!collection) continue;
91
- const referenceFieldKeys = Object.keys(collection.fields).filter(
92
- (k) => collection.fields[k].format === "reference"
93
- );
94
- if (referenceFieldKeys.length === 0) continue;
95
- let found = false;
96
- for (const fieldKey of referenceFieldKeys) {
97
- const fieldValue = (_b = content == null ? void 0 : content.fields) == null ? void 0 : _b[fieldKey];
98
- if (!fieldValue) continue;
99
- let keys = [];
100
- if (typeof fieldValue === "string") {
101
- try {
102
- const parsed = JSON.parse(fieldValue);
103
- keys = Array.isArray(parsed) ? parsed : [fieldValue];
104
- } catch (e) {
105
- keys = [fieldValue];
121
+ if (indexedFiles === null) {
122
+ const referenceFieldKeys = Object.keys(collection.fields).filter(
123
+ (k) => collection.fields[k].format === "reference"
124
+ );
125
+ if (referenceFieldKeys.length === 0) continue;
126
+ let found = false;
127
+ for (const fieldKey of referenceFieldKeys) {
128
+ const fieldValue = (_c = content == null ? void 0 : content.fields) == null ? void 0 : _c[fieldKey];
129
+ if (!fieldValue) continue;
130
+ let keys = [];
131
+ if (typeof fieldValue === "string") {
132
+ try {
133
+ const parsed = JSON.parse(fieldValue);
134
+ keys = Array.isArray(parsed) ? parsed : [fieldValue];
135
+ } catch (e) {
136
+ keys = [fieldValue];
137
+ }
138
+ } else if (Array.isArray(fieldValue)) {
139
+ keys = fieldValue;
140
+ }
141
+ if (keys.includes(targetReferenceKey)) {
142
+ found = true;
143
+ break;
106
144
  }
107
- } else if (Array.isArray(fieldValue)) {
108
- keys = fieldValue;
109
- }
110
- if (keys.includes(targetReferenceKey)) {
111
- found = true;
112
- break;
113
145
  }
146
+ if (!found) continue;
114
147
  }
115
- if (found) {
116
- const nameWithoutFolder = file.replace(`${config.contentFolder}/`, "").replace(".json", "");
117
- const parts = nameWithoutFolder.split("/");
118
- const id = parts[parts.length - 1];
119
- const titleField = getEntryTitleField(type);
120
- let title = id;
121
- if (titleField && ((_c = content == null ? void 0 : content.fields) == null ? void 0 : _c[titleField])) {
122
- title = content.fields[titleField];
123
- }
124
- const status = ((_d = content == null ? void 0 : content.sys) == null ? void 0 : _d.status) || "merged";
125
- backlinks.push({ type, id, path: file, title, status });
148
+ const nameWithoutFolder = file.replace(`${config.contentFolder}/`, "").replace(".json", "");
149
+ const parts = nameWithoutFolder.split("/");
150
+ const id = parts[parts.length - 1];
151
+ const titleField = getEntryTitleField(type);
152
+ let title = id;
153
+ if (titleField && ((_d = content == null ? void 0 : content.fields) == null ? void 0 : _d[titleField])) {
154
+ title = content.fields[titleField];
126
155
  }
127
- } catch (_e) {
156
+ const status = ((_e = content == null ? void 0 : content.sys) == null ? void 0 : _e.status) || "merged";
157
+ backlinks.push({ type, id, path: file, title, status });
158
+ } catch (_e2) {
128
159
  }
129
160
  }
130
161
  return backlinks;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../admin/actions/entries.ts"],"sourcesContent":["'use server';\n\nimport './registerConfig';\n\nimport fsPromises from 'fs/promises';\nimport path from 'path';\n\nimport { getConfig } from '../../lib/configStore';\nimport type { Config } from '../types';\nimport type { EntryListItem, EntryStatus } from '../../types';\n\nimport { getContentFiles, getFile, getFileJson } from './files';\nimport { isProductionMode } from '../github';\nimport { getMediaEntries } from './media';\nimport { getEntryTitleField } from './utils';\n\nexport const getEntryList = async (collection: string = '**'): Promise<EntryListItem[]> => {\n const config = getConfig();\n const files = await getContentFiles(collection);\n // Build a media lookup so we can resolve thumbnail URLs for any entry that\n // has an `image` field. One batched call regardless of entry count.\n const mediaList = await getMediaEntries().catch(() => []);\n const mediaById = new Map<string, { ext: string; publicUrl: string }>();\n for (const m of mediaList) {\n mediaById.set(m.id, { ext: m.extension, publicUrl: m.publicUrl });\n }\n\n const imageFieldKeyByType = new Map<string, string | null>();\n function firstImageFieldKey(type: string): string | null {\n if (imageFieldKeyByType.has(type)) return imageFieldKeyByType.get(type) ?? null;\n const collection = (config as Config).collections[type as keyof Config['collections']];\n if (!collection) {\n imageFieldKeyByType.set(type, null);\n return null;\n }\n const key = Object.keys(collection.fields).find((k) => collection.fields[k].format === 'image') ?? null;\n imageFieldKeyByType.set(type, key);\n return key;\n }\n\n const isProd = isProductionMode();\n\n const items = await Promise.all(\n files.map(async (file) => {\n const nameWithoutFolder = file.replace(`${config.contentFolder}/`, '').replace('.json', '');\n const parts = nameWithoutFolder.split('/');\n const type = parts[0];\n const id = parts[parts.length - 1];\n const titleField = getEntryTitleField(type);\n\n let title = id;\n let status: EntryStatus = 'merged';\n let updatedAt: string | undefined;\n let thumbnailUrl: string | undefined;\n\n const [contentResult, statResult] = await Promise.allSettled([\n getFileJson(file),\n isProd ? Promise.resolve(null) : fsPromises.stat(path.join(/*turbopackIgnore: true*/ process.cwd(), file)),\n ]);\n\n if (contentResult.status === 'fulfilled' && contentResult.value) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const content = contentResult.value as any;\n if (titleField && content?.fields?.[titleField]) {\n title = content.fields[titleField];\n }\n const imgKey = firstImageFieldKey(type);\n if (imgKey) {\n const value = content?.fields?.[imgKey];\n if (typeof value === 'string' && value.trim()) {\n const hit = mediaById.get(value.trim());\n if (hit) thumbnailUrl = hit.publicUrl;\n }\n }\n if (content?.sys?.status) {\n status = content.sys.status;\n }\n }\n\n if (!isProd && statResult.status === 'fulfilled' && statResult.value) {\n updatedAt = (statResult.value as { mtime: Date }).mtime.toISOString();\n }\n\n return { type, id, path: file, title, status, updatedAt, thumbnailUrl };\n }),\n );\n\n items.sort((a, b) => a.title.localeCompare(b.title));\n return items;\n};\n\n/**\n * Find all content entries that reference the given entry via reference fields.\n * Returns entries that contain `targetReferenceKey` in any reference field value.\n */\nexport const getEntryBacklinks = async (targetReferenceKey: string): Promise<EntryListItem[]> => {\n const config = getConfig();\n const allFiles = await getContentFiles('**');\n const backlinks: EntryListItem[] = [];\n\n for (const file of allFiles) {\n try {\n const content = await getFile(file);\n const type = content?.sys?.type;\n if (!type) continue;\n\n const collection = config.collections[type as keyof Config['collections']];\n if (!collection) continue;\n\n const referenceFieldKeys = Object.keys(collection.fields).filter(\n (k) => collection.fields[k].format === 'reference',\n );\n if (referenceFieldKeys.length === 0) continue;\n\n let found = false;\n for (const fieldKey of referenceFieldKeys) {\n const fieldValue = content?.fields?.[fieldKey];\n if (!fieldValue) continue;\n\n // Reference values can be a single string (cardinality 'one') or a JSON array string (cardinality 'many')\n let keys: string[] = [];\n if (typeof fieldValue === 'string') {\n try {\n const parsed = JSON.parse(fieldValue);\n keys = Array.isArray(parsed) ? parsed : [fieldValue];\n } catch {\n keys = [fieldValue];\n }\n } else if (Array.isArray(fieldValue)) {\n keys = fieldValue;\n }\n\n if (keys.includes(targetReferenceKey)) {\n found = true;\n break;\n }\n }\n\n if (found) {\n const nameWithoutFolder = file.replace(`${config.contentFolder}/`, '').replace('.json', '');\n const parts = nameWithoutFolder.split('/');\n const id = parts[parts.length - 1];\n const titleField = getEntryTitleField(type);\n let title = id;\n if (titleField && content?.fields?.[titleField]) {\n title = content.fields[titleField];\n }\n const status: EntryStatus = content?.sys?.status || 'merged';\n backlinks.push({ type, id, path: file, title, status });\n }\n } catch (_e) {\n // Skip files that can't be read\n }\n }\n\n return backlinks;\n};\n"],"mappings":";;AAEA,OAAO;AAEP,OAAO,gBAAgB;AACvB,OAAO,UAAU;AAEjB,SAAS,iBAAiB;AAI1B,SAAS,iBAAiB,SAAS,mBAAmB;AACtD,SAAS,wBAAwB;AACjC,SAAS,uBAAuB;AAChC,SAAS,0BAA0B;AAE5B,MAAM,eAAe,OAAO,aAAqB,SAAmC;AACzF,QAAM,SAAS,UAAU;AACzB,QAAM,QAAQ,MAAM,gBAAgB,UAAU;AAG9C,QAAM,YAAY,MAAM,gBAAgB,EAAE,MAAM,MAAM,CAAC,CAAC;AACxD,QAAM,YAAY,oBAAI,IAAgD;AACtE,aAAW,KAAK,WAAW;AACzB,cAAU,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,WAAW,EAAE,UAAU,CAAC;AAAA,EAClE;AAEA,QAAM,sBAAsB,oBAAI,IAA2B;AAC3D,WAAS,mBAAmB,MAA6B;AA5B3D;AA6BI,QAAI,oBAAoB,IAAI,IAAI,EAAG,SAAO,yBAAoB,IAAI,IAAI,MAA5B,YAAiC;AAC3E,UAAMA,cAAc,OAAkB,YAAY,IAAmC;AACrF,QAAI,CAACA,aAAY;AACf,0BAAoB,IAAI,MAAM,IAAI;AAClC,aAAO;AAAA,IACT;AACA,UAAM,OAAM,YAAO,KAAKA,YAAW,MAAM,EAAE,KAAK,CAAC,MAAMA,YAAW,OAAO,CAAC,EAAE,WAAW,OAAO,MAAlF,YAAuF;AACnG,wBAAoB,IAAI,MAAM,GAAG;AACjC,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,iBAAiB;AAEhC,QAAM,QAAQ,MAAM,QAAQ;AAAA,IAC1B,MAAM,IAAI,OAAO,SAAS;AA3C9B;AA4CM,YAAM,oBAAoB,KAAK,QAAQ,GAAG,OAAO,aAAa,KAAK,EAAE,EAAE,QAAQ,SAAS,EAAE;AAC1F,YAAM,QAAQ,kBAAkB,MAAM,GAAG;AACzC,YAAM,OAAO,MAAM,CAAC;AACpB,YAAM,KAAK,MAAM,MAAM,SAAS,CAAC;AACjC,YAAM,aAAa,mBAAmB,IAAI;AAE1C,UAAI,QAAQ;AACZ,UAAI,SAAsB;AAC1B,UAAI;AACJ,UAAI;AAEJ,YAAM,CAAC,eAAe,UAAU,IAAI,MAAM,QAAQ,WAAW;AAAA,QAC3D,YAAY,IAAI;AAAA,QAChB,SAAS,QAAQ,QAAQ,IAAI,IAAI,WAAW,KAAK,KAAK;AAAA;AAAA,UAA+B,QAAQ,IAAI;AAAA,UAAG;AAAA,QAAI,CAAC;AAAA,MAC3G,CAAC;AAED,UAAI,cAAc,WAAW,eAAe,cAAc,OAAO;AAE/D,cAAM,UAAU,cAAc;AAC9B,YAAI,gBAAc,wCAAS,WAAT,mBAAkB,cAAa;AAC/C,kBAAQ,QAAQ,OAAO,UAAU;AAAA,QACnC;AACA,cAAM,SAAS,mBAAmB,IAAI;AACtC,YAAI,QAAQ;AACV,gBAAM,SAAQ,wCAAS,WAAT,mBAAkB;AAChC,cAAI,OAAO,UAAU,YAAY,MAAM,KAAK,GAAG;AAC7C,kBAAM,MAAM,UAAU,IAAI,MAAM,KAAK,CAAC;AACtC,gBAAI,IAAK,gBAAe,IAAI;AAAA,UAC9B;AAAA,QACF;AACA,aAAI,wCAAS,QAAT,mBAAc,QAAQ;AACxB,mBAAS,QAAQ,IAAI;AAAA,QACvB;AAAA,MACF;AAEA,UAAI,CAAC,UAAU,WAAW,WAAW,eAAe,WAAW,OAAO;AACpE,oBAAa,WAAW,MAA0B,MAAM,YAAY;AAAA,MACtE;AAEA,aAAO,EAAE,MAAM,IAAI,MAAM,MAAM,OAAO,QAAQ,WAAW,aAAa;AAAA,IACxE,CAAC;AAAA,EACH;AAEA,QAAM,KAAK,CAAC,GAAG,MAAM,EAAE,MAAM,cAAc,EAAE,KAAK,CAAC;AACnD,SAAO;AACT;AAMO,MAAM,oBAAoB,OAAO,uBAAyD;AA/FjG;AAgGE,QAAM,SAAS,UAAU;AACzB,QAAM,WAAW,MAAM,gBAAgB,IAAI;AAC3C,QAAM,YAA6B,CAAC;AAEpC,aAAW,QAAQ,UAAU;AAC3B,QAAI;AACF,YAAM,UAAU,MAAM,QAAQ,IAAI;AAClC,YAAM,QAAO,wCAAS,QAAT,mBAAc;AAC3B,UAAI,CAAC,KAAM;AAEX,YAAM,aAAa,OAAO,YAAY,IAAmC;AACzE,UAAI,CAAC,WAAY;AAEjB,YAAM,qBAAqB,OAAO,KAAK,WAAW,MAAM,EAAE;AAAA,QACxD,CAAC,MAAM,WAAW,OAAO,CAAC,EAAE,WAAW;AAAA,MACzC;AACA,UAAI,mBAAmB,WAAW,EAAG;AAErC,UAAI,QAAQ;AACZ,iBAAW,YAAY,oBAAoB;AACzC,cAAM,cAAa,wCAAS,WAAT,mBAAkB;AACrC,YAAI,CAAC,WAAY;AAGjB,YAAI,OAAiB,CAAC;AACtB,YAAI,OAAO,eAAe,UAAU;AAClC,cAAI;AACF,kBAAM,SAAS,KAAK,MAAM,UAAU;AACpC,mBAAO,MAAM,QAAQ,MAAM,IAAI,SAAS,CAAC,UAAU;AAAA,UACrD,SAAQ;AACN,mBAAO,CAAC,UAAU;AAAA,UACpB;AAAA,QACF,WAAW,MAAM,QAAQ,UAAU,GAAG;AACpC,iBAAO;AAAA,QACT;AAEA,YAAI,KAAK,SAAS,kBAAkB,GAAG;AACrC,kBAAQ;AACR;AAAA,QACF;AAAA,MACF;AAEA,UAAI,OAAO;AACT,cAAM,oBAAoB,KAAK,QAAQ,GAAG,OAAO,aAAa,KAAK,EAAE,EAAE,QAAQ,SAAS,EAAE;AAC1F,cAAM,QAAQ,kBAAkB,MAAM,GAAG;AACzC,cAAM,KAAK,MAAM,MAAM,SAAS,CAAC;AACjC,cAAM,aAAa,mBAAmB,IAAI;AAC1C,YAAI,QAAQ;AACZ,YAAI,gBAAc,wCAAS,WAAT,mBAAkB,cAAa;AAC/C,kBAAQ,QAAQ,OAAO,UAAU;AAAA,QACnC;AACA,cAAM,WAAsB,wCAAS,QAAT,mBAAc,WAAU;AACpD,kBAAU,KAAK,EAAE,MAAM,IAAI,MAAM,MAAM,OAAO,OAAO,CAAC;AAAA,MACxD;AAAA,IACF,SAAS,IAAI;AAAA,IAEb;AAAA,EACF;AAEA,SAAO;AACT;","names":["collection"]}
1
+ {"version":3,"sources":["../../../admin/actions/entries.ts"],"sourcesContent":["'use server';\n\nimport './registerConfig';\n\nimport fsPromises from 'fs/promises';\nimport path from 'path';\n\nimport { cookies } from 'next/headers';\n\nimport { getConfig } from '../../lib/configStore';\nimport type { Config } from '../types';\nimport type { EntryListItem, EntryStatus } from '../../types';\n\nimport { getContentFiles, getFile, getFileJson } from './files';\nimport { isProductionMode } from '../github';\nimport { getMediaEntries } from './media';\nimport { getEntryTitleField } from './utils';\nimport { getStoredEntryListSnapshot, getStoredEntryReferencePaths } from '../store/contentStore';\n\nexport const getEntryList = async (collection: string = '**'): Promise<EntryListItem[]> => {\n const config = getConfig();\n const isProd = isProductionMode();\n let storedSnapshot: Awaited<ReturnType<typeof getStoredEntryListSnapshot>> = null;\n\n if (isProd) {\n try {\n const activeBranch = (await cookies()).get('cms-active-branch')?.value;\n storedSnapshot = await getStoredEntryListSnapshot(collection, activeBranch);\n } catch {\n // Store unavailable; retain the direct-read recovery path below.\n }\n }\n\n const files = storedSnapshot ? storedSnapshot.entries.map((entry) => entry.path) : await getContentFiles(collection);\n const storedContentByPath = storedSnapshot\n ? new Map(storedSnapshot.entries.map((entry) => [entry.path, entry.content]))\n : null;\n\n const mediaById = new Map<string, { publicUrl: string }>();\n if (storedSnapshot) {\n for (const entry of storedSnapshot.mediaEntries) {\n const sys = entry.content.sys as { id?: unknown } | undefined;\n const fields = entry.content.fields as { extension?: unknown } | undefined;\n if (typeof sys?.id === 'string' && typeof fields?.extension === 'string') {\n mediaById.set(sys.id, { publicUrl: `/media/${sys.id}.${fields.extension}` });\n }\n }\n } else {\n // Direct-read recovery and local development still use the existing media\n // action, with one batched call regardless of entry count.\n const mediaList = await getMediaEntries().catch(() => []);\n for (const media of mediaList) {\n mediaById.set(media.id, { publicUrl: media.publicUrl });\n }\n }\n\n const imageFieldKeyByType = new Map<string, string | null>();\n function firstImageFieldKey(type: string): string | null {\n if (imageFieldKeyByType.has(type)) return imageFieldKeyByType.get(type) ?? null;\n const collection = (config as Config).collections[type as keyof Config['collections']];\n if (!collection) {\n imageFieldKeyByType.set(type, null);\n return null;\n }\n const key = Object.keys(collection.fields).find((k) => collection.fields[k].format === 'image') ?? null;\n imageFieldKeyByType.set(type, key);\n return key;\n }\n\n const items = await Promise.all(\n files.map(async (file) => {\n const nameWithoutFolder = file.replace(`${config.contentFolder}/`, '').replace('.json', '');\n const parts = nameWithoutFolder.split('/');\n const type = parts[0];\n const id = parts[parts.length - 1];\n const titleField = getEntryTitleField(type);\n\n let title = id;\n let status: EntryStatus = 'merged';\n let updatedAt: string | undefined;\n let thumbnailUrl: string | undefined;\n\n const [contentResult, statResult] = await Promise.allSettled([\n storedContentByPath ? Promise.resolve(storedContentByPath.get(file) ?? null) : getFileJson(file),\n isProd ? Promise.resolve(null) : fsPromises.stat(path.join(/*turbopackIgnore: true*/ process.cwd(), file)),\n ]);\n\n if (contentResult.status === 'fulfilled' && contentResult.value) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const content = contentResult.value as any;\n if (titleField && content?.fields?.[titleField]) {\n title = content.fields[titleField];\n }\n const imgKey = firstImageFieldKey(type);\n if (imgKey) {\n const value = content?.fields?.[imgKey];\n if (typeof value === 'string' && value.trim()) {\n const hit = mediaById.get(value.trim());\n if (hit) thumbnailUrl = hit.publicUrl;\n }\n }\n if (content?.sys?.status) {\n status = content.sys.status;\n }\n }\n\n if (!isProd && statResult.status === 'fulfilled' && statResult.value) {\n updatedAt = (statResult.value as { mtime: Date }).mtime.toISOString();\n }\n\n return { type, id, path: file, title, status, updatedAt, thumbnailUrl };\n }),\n );\n\n items.sort((a, b) => a.title.localeCompare(b.title));\n return items;\n};\n\n/**\n * Find all content entries that reference the given entry via reference fields.\n * Returns entries that contain `targetReferenceKey` in any reference field value.\n */\nexport const getEntryBacklinks = async (targetReferenceKey: string): Promise<EntryListItem[]> => {\n const config = getConfig();\n let indexedFiles: string[] | null = null;\n if (isProductionMode()) {\n try {\n const activeBranch = (await cookies()).get('cms-active-branch')?.value;\n indexedFiles = await getStoredEntryReferencePaths(targetReferenceKey, activeBranch);\n } catch {\n // Store unavailable; retain the direct-read recovery path below.\n }\n }\n\n const allFiles = indexedFiles ?? (await getContentFiles('**'));\n const backlinks: EntryListItem[] = [];\n\n for (const file of allFiles) {\n try {\n const content = await getFile(file);\n const type = content?.sys?.type;\n if (!type) continue;\n\n const collection = config.collections[type as keyof Config['collections']];\n if (!collection) continue;\n\n if (indexedFiles === null) {\n const referenceFieldKeys = Object.keys(collection.fields).filter(\n (k) => collection.fields[k].format === 'reference',\n );\n if (referenceFieldKeys.length === 0) continue;\n\n let found = false;\n for (const fieldKey of referenceFieldKeys) {\n const fieldValue = content?.fields?.[fieldKey];\n if (!fieldValue) continue;\n\n let keys: string[] = [];\n if (typeof fieldValue === 'string') {\n try {\n const parsed = JSON.parse(fieldValue);\n keys = Array.isArray(parsed) ? parsed : [fieldValue];\n } catch {\n keys = [fieldValue];\n }\n } else if (Array.isArray(fieldValue)) {\n keys = fieldValue;\n }\n\n if (keys.includes(targetReferenceKey)) {\n found = true;\n break;\n }\n }\n if (!found) continue;\n }\n\n const nameWithoutFolder = file.replace(`${config.contentFolder}/`, '').replace('.json', '');\n const parts = nameWithoutFolder.split('/');\n const id = parts[parts.length - 1];\n const titleField = getEntryTitleField(type);\n let title = id;\n if (titleField && content?.fields?.[titleField]) {\n title = content.fields[titleField];\n }\n const status: EntryStatus = content?.sys?.status || 'merged';\n backlinks.push({ type, id, path: file, title, status });\n } catch (_e) {\n // Skip files that can't be read\n }\n }\n\n return backlinks;\n};\n"],"mappings":"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","names":["_a","collection","_e"]}
@@ -5,7 +5,6 @@ export type { SaveFileResult } from './utils';
5
5
  * In production, reject writes that would target `config.git.baseBranch` (no feature branch cookie).
6
6
  */
7
7
  export declare const assertFeatureBranchForWritesIfRequired: () => Promise<void>;
8
- export declare const waitForPublicReadConsistency: (fileName: string, expectedContent: string, readRef?: string) => Promise<void>;
9
8
  export declare const getContentFiles: (collection?: string) => Promise<string[]>;
10
9
  /**
11
10
  * List media-entry JSON files (e.g. `cms/media/media-<uuid>.json`).
@@ -1 +1 @@
1
- {"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../../admin/actions/files.ts"],"names":[],"mappings":"AAEA,OAAO,kBAAkB,CAAC;AA+B1B,OAAO,EAIL,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,cAAc,EACpB,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAgM9C;;GAEG;AACH,eAAO,MAAM,sCAAsC,QAAa,OAAO,CAAC,IAAI,CAQ3E,CAAC;AAEF,eAAO,MAAM,4BAA4B,GAAU,UAAU,MAAM,EAAE,iBAAiB,MAAM,EAAE,UAAU,MAAM,kBAyB7G,CAAC;AAEF,eAAO,MAAM,eAAe,GAAU,aAAY,MAAa,sBAkC9D,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,QAAa,OAAO,CAAC,MAAM,EAAE,CAiB7D,CAAC;AAEF,eAAO,MAAM,aAAa,GAAU,SAAQ,MAAa,sBAqBxD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAU,UAAU,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAgC1F,CAAC;AAEF,eAAO,MAAM,OAAO,GAAU,UAAU,MAAM,iBAkF7C,CAAC;AAuBF,eAAO,MAAM,QAAQ,GACnB,UAAU,GAAG,EACb,UAAU,MAAM,EAChB,UAAU;IAAE,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAAE,KAC3C,OAAO,CAAC,cAAc,CAoJxB,CAAC;AAEF,eAAO,MAAM,OAAO,GAAU,MAAM,MAAM,KAAG,OAAO,CAAC,aAAa,CAoDjE,CAAC;AAEF,eAAO,MAAM,UAAU,GAAU,UAAU,MAAM,KAAG,OAAO,CAAC,YAAY,CA2DvE,CAAC"}
1
+ {"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../../admin/actions/files.ts"],"names":[],"mappings":"AAEA,OAAO,kBAAkB,CAAC;AAsC1B,OAAO,EAIL,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,cAAc,EACpB,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAiM9C;;GAEG;AACH,eAAO,MAAM,sCAAsC,QAAa,OAAO,CAAC,IAAI,CAqB3E,CAAC;AAEF,eAAO,MAAM,eAAe,GAAU,aAAY,MAAa,sBAkC9D,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,QAAa,OAAO,CAAC,MAAM,EAAE,CAiB7D,CAAC;AAEF,eAAO,MAAM,aAAa,GAAU,SAAQ,MAAa,sBAqBxD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAU,UAAU,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAgC1F,CAAC;AAEF,eAAO,MAAM,OAAO,GAAU,UAAU,MAAM,iBAkF7C,CAAC;AAuBF,eAAO,MAAM,QAAQ,GACnB,UAAU,GAAG,EACb,UAAU,MAAM,EAChB,UAAU;IAAE,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAAE,KAC3C,OAAO,CAAC,cAAc,CAqJxB,CAAC;AAEF,eAAO,MAAM,OAAO,GAAU,MAAM,MAAM,KAAG,OAAO,CAAC,aAAa,CAwDjE,CAAC;AAEF,eAAO,MAAM,UAAU,GAAU,UAAU,MAAM,KAAG,OAAO,CAAC,YAAY,CA0DvE,CAAC"}
@@ -18,15 +18,21 @@ import { persistedFieldsFromFormStrings } from "../../lib/persistedFormFields";
18
18
  import { normalizeStoredSlug } from "../../lib/slugField";
19
19
  import { validateEntryFields } from "../../lib/validateEntryFields";
20
20
  import {
21
- deleteGitHubFile,
21
+ commitMultipleFilesToGitHub,
22
+ GitHubBranchConflictError,
22
23
  getGitHubFile,
23
24
  isProductionMode,
24
25
  listGitHubFiles,
25
26
  listGitHubFilesRecursive,
26
- readGitHubFilePublic,
27
- saveGitHubFile
27
+ readGitHubFilePublic
28
28
  } from "../github";
29
- import { applyMutation, getStoredContentFiles, getStoredFile, getStoredFileSha } from "../store/contentStore";
29
+ import {
30
+ applyCommittedMutations,
31
+ getContentStoreStatus,
32
+ getStoredContentFiles,
33
+ getStoredFile,
34
+ getStoredFileSha
35
+ } from "../store/contentStore";
30
36
  import { mediaContentFolder, mediaEntryPath } from "../../lib/mediaPath";
31
37
  import { buildJsons } from "./build";
32
38
  import {
@@ -49,24 +55,25 @@ async function syncEmbeddingsForRemoveIfEnabled(entryPath, branch, isProduction)
49
55
  if (!agentConfig) return;
50
56
  await syncEmbeddingsAfterRemove({ agentConfig, entryPath, branch, isProduction });
51
57
  }
52
- async function persistBranchHistoryEntryIfNeeded(activeBranch, entryPath) {
58
+ async function getBranchHistoryBatchChange(activeBranch, entryPath) {
53
59
  var _a;
60
+ try {
61
+ const historyFile = await getGitHubFile(BRANCH_HISTORY_FILE_PATH, activeBranch);
62
+ const next = mergeHistoryContentWithAppendedEntry(
63
+ (_a = historyFile == null ? void 0 : historyFile.content) != null ? _a : "",
64
+ activeBranch,
65
+ normalizeContentPath(entryPath)
66
+ );
67
+ return next === null ? null : { kind: "upsert-text", path: BRANCH_HISTORY_FILE_PATH, content: next };
68
+ } catch (e) {
69
+ return null;
70
+ }
71
+ }
72
+ async function persistLocalBranchHistoryEntryIfNeeded(activeBranch, entryPath) {
54
73
  if (!activeBranch) {
55
74
  return;
56
75
  }
57
76
  const normalized = normalizeContentPath(entryPath);
58
- if (isProductionMode()) {
59
- try {
60
- const historyFile = await getGitHubFile(BRANCH_HISTORY_FILE_PATH, activeBranch);
61
- const next = mergeHistoryContentWithAppendedEntry((_a = historyFile == null ? void 0 : historyFile.content) != null ? _a : "", activeBranch, normalized);
62
- if (next == null) {
63
- return;
64
- }
65
- await saveGitHubFile(BRANCH_HISTORY_FILE_PATH, next, "CMS: track entry in branch history", activeBranch);
66
- } catch (e) {
67
- }
68
- return;
69
- }
70
77
  try {
71
78
  const abs = path.join(
72
79
  /*turbopackIgnore: true*/
@@ -190,28 +197,18 @@ const assertFeatureBranchForWritesIfRequired = async () => {
190
197
  if (!isProductionMode()) {
191
198
  return;
192
199
  }
193
- if (!((_a = (await cookies()).get(CMS_ACTIVE_BRANCH_COOKIE)) == null ? void 0 : _a.value)) {
200
+ const activeBranch = (_a = (await cookies()).get(CMS_ACTIVE_BRANCH_COOKIE)) == null ? void 0 : _a.value;
201
+ if (!activeBranch) {
194
202
  throw new Error("Create or select a branch before editing.");
195
203
  }
196
- };
197
- const waitForPublicReadConsistency = async (fileName, expectedContent, readRef) => {
198
- if (!isProductionMode()) {
199
- return;
200
- }
201
- const parsedAttempts = Number.parseInt(process.env.CMS_GITHUB_CONSISTENCY_ATTEMPTS || "8", 10);
202
- const parsedDelayMs = Number.parseInt(process.env.CMS_GITHUB_CONSISTENCY_DELAY_MS || "250", 10);
203
- const maxAttempts = Number.isFinite(parsedAttempts) && parsedAttempts > 0 ? parsedAttempts : 1;
204
- const delayMs = Number.isFinite(parsedDelayMs) && parsedDelayMs >= 0 ? parsedDelayMs : 250;
205
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
206
- try {
207
- const visibleContent = await readGitHubFilePublic(fileName, readRef);
208
- if (visibleContent === expectedContent) {
209
- return;
210
- }
211
- } catch (_e) {
204
+ try {
205
+ const cache = await getContentStoreStatus(activeBranch);
206
+ if (cache.status === "stale") {
207
+ throw new Error("Editing is temporarily disabled because GitHub could not confirm the current branch HEAD.");
212
208
  }
213
- if (attempt < maxAttempts && delayMs > 0) {
214
- await new Promise((resolve) => setTimeout(resolve, delayMs));
209
+ } catch (error) {
210
+ if (error instanceof Error && error.message.includes("temporarily disabled")) {
211
+ throw error;
215
212
  }
216
213
  }
217
214
  };
@@ -485,30 +482,31 @@ const saveFile = async (formData, fileName, options) => {
485
482
  const entryId = ((_h = payload == null ? void 0 : payload.sys) == null ? void 0 : _h.id) || "";
486
483
  const message = `Update ${entryTypeLabel} ${entryId}`;
487
484
  const activeBranch = (_i = (await cookies()).get(CMS_ACTIVE_BRANCH_COOKIE)) == null ? void 0 : _i.value;
488
- const cachedSha = await getStoredFileSha(fileName, activeBranch) || void 0;
489
- await saveGitHubFile(fileName, normalizedData, message, activeBranch, cachedSha);
485
+ if (!activeBranch) throw new Error("Create or select a branch before editing.");
486
+ const changes = [{ kind: "upsert-text", path: fileName, content: normalizedData }];
490
487
  const mdPaths2 = typeof entryType === "string" ? companionMarkdownPathsForEntry(fileName, entryType, config.collections) : {};
491
488
  for (const [fieldName, mdPath] of Object.entries(mdPaths2)) {
492
489
  const mdContent = (_j = markdownContents[fieldName]) != null ? _j : "";
493
- await saveGitHubFile(mdPath, mdContent, `Update ${fieldName} for ${entryType} ${entryId}`, activeBranch);
490
+ changes.push({ kind: "upsert-text", path: mdPath, content: mdContent });
494
491
  }
495
492
  const rtPaths = typeof entryType === "string" ? companionRichTextPathsForEntry(fileName, entryType, config.collections) : {};
496
493
  for (const [fieldName, rtPath] of Object.entries(rtPaths)) {
497
494
  const rtContent = (_k = markdownContents[fieldName]) != null ? _k : "";
498
- await saveGitHubFile(rtPath, rtContent, `Update ${fieldName} for ${entryType} ${entryId}`, activeBranch);
495
+ changes.push({ kind: "upsert-text", path: rtPath, content: rtContent });
499
496
  }
500
- await waitForPublicReadConsistency(fileName, normalizedData);
501
- if (activeBranch) {
502
- applyMutation(activeBranch, {
497
+ const historyChange = await getBranchHistoryBatchChange(activeBranch, fileName);
498
+ if (historyChange) changes.push(historyChange);
499
+ const commit = await commitMultipleFilesToGitHub(changes, message, activeBranch);
500
+ applyCommittedMutations(activeBranch, commit.sha, [
501
+ {
503
502
  type: "upsert",
504
503
  path: fileName,
505
504
  content: payload,
506
505
  sha: "",
507
- // SHA unknown after single-file commit; next tree fetch will correct it
506
+ // Blob SHA is corrected by the next manifest refresh.
508
507
  companions: markdownContents
509
- });
510
- }
511
- await persistBranchHistoryEntryIfNeeded(activeBranch, fileName);
508
+ }
509
+ ]);
512
510
  await syncEmbeddingsForUpsertIfEnabled(fileName, payload, markdownContents, activeBranch, true);
513
511
  const built2 = await buildJsons(fileName);
514
512
  return built2.success ? actionOk() : built2;
@@ -539,11 +537,12 @@ const saveFile = async (formData, fileName, options) => {
539
537
  rtPath
540
538
  ), rtContent, "utf8");
541
539
  }
542
- await persistBranchHistoryEntryIfNeeded(activeBranchDev, fileName);
540
+ await persistLocalBranchHistoryEntryIfNeeded(activeBranchDev, fileName);
543
541
  await syncEmbeddingsForUpsertIfEnabled(fileName, payload, markdownContents, activeBranchDev, false);
544
542
  const built = await buildJsons(fileName);
545
543
  return built.success ? actionOk() : built;
546
544
  } catch (e) {
545
+ if (e instanceof GitHubBranchConflictError) return actionErr(e);
547
546
  return actionErr(new Error(`Failed to save file: ${getErrorMessage(e)}`));
548
547
  }
549
548
  };
@@ -567,17 +566,19 @@ const newFile = async (type) => {
567
566
  if (isProductionMode()) {
568
567
  await assertFeatureBranchForWritesIfRequired();
569
568
  const activeBranch = (_a = (await cookies()).get(CMS_ACTIVE_BRANCH_COOKIE)) == null ? void 0 : _a.value;
570
- await saveGitHubFile(file, normalizedData, `Create new ${type} ${id}`, activeBranch);
571
- await waitForPublicReadConsistency(file, normalizedData);
572
- if (activeBranch) {
573
- applyMutation(activeBranch, {
569
+ if (!activeBranch) throw new Error("Create or select a branch before editing.");
570
+ const changes = [{ kind: "upsert-text", path: file, content: normalizedData }];
571
+ const historyChange = await getBranchHistoryBatchChange(activeBranch, file);
572
+ if (historyChange) changes.push(historyChange);
573
+ const commit = await commitMultipleFilesToGitHub(changes, `Create new ${type} ${id}`, activeBranch);
574
+ applyCommittedMutations(activeBranch, commit.sha, [
575
+ {
574
576
  type: "upsert",
575
577
  path: file,
576
578
  content: values,
577
579
  sha: ""
578
- });
579
- }
580
- await persistBranchHistoryEntryIfNeeded(activeBranch, file);
580
+ }
581
+ ]);
581
582
  await syncEmbeddingsForUpsertIfEnabled(file, values, {}, activeBranch, true);
582
583
  const built2 = await buildJsons(file);
583
584
  return built2.success ? { success: true, path: file } : { success: false, error: built2.error };
@@ -591,12 +592,12 @@ const newFile = async (type) => {
591
592
  );
592
593
  await fsPromises.mkdir(path.dirname(filePath), { recursive: true });
593
594
  await fsPromises.writeFile(filePath, normalizedData, "utf8");
594
- await persistBranchHistoryEntryIfNeeded(activeBranchDev, file);
595
+ await persistLocalBranchHistoryEntryIfNeeded(activeBranchDev, file);
595
596
  await syncEmbeddingsForUpsertIfEnabled(file, values, {}, activeBranchDev, false);
596
597
  const built = await buildJsons(file);
597
598
  return built.success ? { success: true, path: file } : { success: false, error: built.error };
598
599
  } catch (e) {
599
- return { success: false, error: getErrorMessage(e) };
600
+ return e instanceof GitHubBranchConflictError ? actionErr(e) : { success: false, error: getErrorMessage(e) };
600
601
  }
601
602
  };
602
603
  const removeFile = async (fileName) => {
@@ -616,16 +617,16 @@ const removeFile = async (fileName) => {
616
617
  if (isProductionMode()) {
617
618
  await assertFeatureBranchForWritesIfRequired();
618
619
  const activeBranch = (_b = (await cookies()).get(CMS_ACTIVE_BRANCH_COOKIE)) == null ? void 0 : _b.value;
619
- await deleteGitHubFile(fileName, `Remove ${fileName}`, activeBranch);
620
+ if (!activeBranch) throw new Error("Create or select a branch before editing.");
621
+ const changes = [{ kind: "delete", path: fileName }];
620
622
  for (const mdPath of companionPaths) {
621
- try {
622
- await deleteGitHubFile(mdPath, `Remove companion ${mdPath}`, activeBranch);
623
- } catch (e) {
623
+ const cachedSha = await getStoredFileSha(mdPath, activeBranch);
624
+ if (cachedSha !== null || await getGitHubFile(mdPath, activeBranch)) {
625
+ changes.push({ kind: "delete", path: mdPath });
624
626
  }
625
627
  }
626
- if (activeBranch) {
627
- applyMutation(activeBranch, { type: "delete", path: fileName });
628
- }
628
+ const commit = await commitMultipleFilesToGitHub(changes, `Remove ${fileName}`, activeBranch);
629
+ applyCommittedMutations(activeBranch, commit.sha, [{ type: "delete", path: fileName }]);
629
630
  await syncEmbeddingsForRemoveIfEnabled(fileName, activeBranch, true);
630
631
  const built2 = await buildJsons(fileName);
631
632
  return built2.success ? actionOk() : built2;
@@ -662,7 +663,6 @@ export {
662
663
  getMediaFiles,
663
664
  newFile,
664
665
  removeFile,
665
- saveFile,
666
- waitForPublicReadConsistency
666
+ saveFile
667
667
  };
668
668
  //# sourceMappingURL=files.js.map