octocms 0.4.4 → 0.4.7

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 (184) hide show
  1. package/dist/admin/actions/agent.d.ts +41 -0
  2. package/dist/admin/actions/agent.d.ts.map +1 -1
  3. package/dist/admin/actions/agent.js +36 -1
  4. package/dist/admin/actions/agent.js.map +1 -1
  5. package/dist/admin/actions/build.d.ts +9 -5
  6. package/dist/admin/actions/build.d.ts.map +1 -1
  7. package/dist/admin/actions/build.js +6 -111
  8. package/dist/admin/actions/build.js.map +1 -1
  9. package/dist/admin/actions/diff.d.ts +1 -0
  10. package/dist/admin/actions/diff.d.ts.map +1 -1
  11. package/dist/admin/actions/diff.js +1 -0
  12. package/dist/admin/actions/diff.js.map +1 -1
  13. package/dist/admin/actions/entries.d.ts +1 -0
  14. package/dist/admin/actions/entries.d.ts.map +1 -1
  15. package/dist/admin/actions/entries.js +1 -0
  16. package/dist/admin/actions/entries.js.map +1 -1
  17. package/dist/admin/actions/files.d.ts +1 -0
  18. package/dist/admin/actions/files.d.ts.map +1 -1
  19. package/dist/admin/actions/files.js +42 -37
  20. package/dist/admin/actions/files.js.map +1 -1
  21. package/dist/admin/actions/git.d.ts +7 -6
  22. package/dist/admin/actions/git.d.ts.map +1 -1
  23. package/dist/admin/actions/git.js +26 -14
  24. package/dist/admin/actions/git.js.map +1 -1
  25. package/dist/admin/actions/media.d.ts +1 -0
  26. package/dist/admin/actions/media.d.ts.map +1 -1
  27. package/dist/admin/actions/media.js +1 -0
  28. package/dist/admin/actions/media.js.map +1 -1
  29. package/dist/admin/actions/registerConfig.d.ts +11 -0
  30. package/dist/admin/actions/registerConfig.d.ts.map +1 -0
  31. package/dist/admin/actions/registerConfig.js +2 -0
  32. package/dist/admin/actions/registerConfig.js.map +1 -0
  33. package/dist/admin/actions/schema.d.ts +1 -0
  34. package/dist/admin/actions/schema.d.ts.map +1 -1
  35. package/dist/admin/actions/schema.js +1 -0
  36. package/dist/admin/actions/schema.js.map +1 -1
  37. package/dist/admin/actions/search.d.ts +1 -0
  38. package/dist/admin/actions/search.d.ts.map +1 -1
  39. package/dist/admin/actions/search.js +1 -0
  40. package/dist/admin/actions/search.js.map +1 -1
  41. package/dist/admin/actions/status.d.ts +1 -0
  42. package/dist/admin/actions/status.d.ts.map +1 -1
  43. package/dist/admin/actions/status.js +1 -0
  44. package/dist/admin/actions/status.js.map +1 -1
  45. package/dist/admin/github.d.ts +7 -9
  46. package/dist/admin/github.d.ts.map +1 -1
  47. package/dist/admin/github.js +7 -37
  48. package/dist/admin/github.js.map +1 -1
  49. package/dist/admin/mediaRoute.d.ts +27 -0
  50. package/dist/admin/mediaRoute.d.ts.map +1 -0
  51. package/dist/admin/mediaRoute.js +52 -0
  52. package/dist/admin/mediaRoute.js.map +1 -0
  53. package/dist/admin/query/hooks/useBranchMutations.js +4 -4
  54. package/dist/admin/query/hooks/useBranchMutations.js.map +1 -1
  55. package/dist/admin/query/hooks/useEntryMutations.js +4 -4
  56. package/dist/admin/query/hooks/useEntryMutations.js.map +1 -1
  57. package/dist/admin/query/hooks/useMediaMutations.js +2 -2
  58. package/dist/admin/query/hooks/useMediaMutations.js.map +1 -1
  59. package/dist/admin/query/hooks/useNewFile.d.ts.map +1 -1
  60. package/dist/admin/query/hooks/useNewFile.js +2 -4
  61. package/dist/admin/query/hooks/useNewFile.js.map +1 -1
  62. package/dist/admin/query/hooks/useSaveSchema.js +2 -2
  63. package/dist/admin/query/hooks/useSaveSchema.js.map +1 -1
  64. package/dist/admin/query/invalidate.d.ts +4 -5
  65. package/dist/admin/query/invalidate.d.ts.map +1 -1
  66. package/dist/admin/query/invalidate.js +1 -2
  67. package/dist/admin/query/invalidate.js.map +1 -1
  68. package/dist/admin/searchRoute.d.ts +17 -0
  69. package/dist/admin/searchRoute.d.ts.map +1 -0
  70. package/dist/admin/searchRoute.js +60 -0
  71. package/dist/admin/searchRoute.js.map +1 -0
  72. package/dist/agent/chatApi.d.ts +14 -0
  73. package/dist/agent/chatApi.d.ts.map +1 -0
  74. package/dist/agent/defaults.d.ts +16 -0
  75. package/dist/agent/defaults.d.ts.map +1 -0
  76. package/dist/agent/embedder.d.ts +1 -0
  77. package/dist/agent/embedder.d.ts.map +1 -1
  78. package/dist/agent/embedder.js +31 -2
  79. package/dist/agent/embedder.js.map +1 -1
  80. package/dist/agent/index.cjs +366 -524
  81. package/dist/agent/index.cjs.map +1 -1
  82. package/dist/agent/index.d.ts +21 -0
  83. package/dist/agent/index.d.ts.map +1 -0
  84. package/dist/agent/index.js +0 -4
  85. package/dist/agent/index.js.map +1 -1
  86. package/dist/agent/proposals.d.ts +6 -0
  87. package/dist/agent/proposals.d.ts.map +1 -1
  88. package/dist/agent/proposals.js +11 -0
  89. package/dist/agent/proposals.js.map +1 -1
  90. package/dist/agent/providers/anthropic.d.ts +18 -0
  91. package/dist/agent/providers/anthropic.d.ts.map +1 -0
  92. package/dist/agent/providers/index.d.ts +11 -0
  93. package/dist/agent/providers/index.d.ts.map +1 -0
  94. package/dist/agent/providers/openai.d.ts +21 -0
  95. package/dist/agent/providers/openai.d.ts.map +1 -0
  96. package/dist/{agentDocs-JZRZW7XI.js → agentDocs-LCTKE4NJ.js} +2 -2
  97. package/dist/{chunk-6D5NU3XR.js → chunk-37T54ASM.js} +56 -16
  98. package/dist/chunk-37T54ASM.js.map +1 -0
  99. package/dist/chunk-PFWKOLV4.js +7 -0
  100. package/dist/chunk-PFWKOLV4.js.map +1 -0
  101. package/dist/cli/index.js +7 -7
  102. package/dist/cli/index.js.map +1 -1
  103. package/dist/cli/lib/schemaDocs.d.ts.map +1 -1
  104. package/dist/components/Chat/useChatStream.d.ts.map +1 -1
  105. package/dist/components/Chat/useChatStream.js +11 -23
  106. package/dist/components/Chat/useChatStream.js.map +1 -1
  107. package/dist/components/EditPost/EditPost.d.ts.map +1 -1
  108. package/dist/components/EditPost/EditPost.js +8 -27
  109. package/dist/components/EditPost/EditPost.js.map +1 -1
  110. package/dist/components/InlineEntryEditor/InlineEntryEditor.d.ts.map +1 -1
  111. package/dist/components/InlineEntryEditor/InlineEntryEditor.js +10 -25
  112. package/dist/components/InlineEntryEditor/InlineEntryEditor.js.map +1 -1
  113. package/dist/components/Layout/TopHeader.d.ts.map +1 -1
  114. package/dist/components/Layout/TopHeader.js +21 -23
  115. package/dist/components/Layout/TopHeader.js.map +1 -1
  116. package/dist/components/ui/branch-chip.d.ts +5 -0
  117. package/dist/components/ui/branch-chip.d.ts.map +1 -1
  118. package/dist/components/ui/branch-chip.js +2 -2
  119. package/dist/components/ui/branch-chip.js.map +1 -1
  120. package/dist/{embeddingsGen-775PXWJY.js → embeddingsGen-NZQ2RXXP.js} +33 -4
  121. package/dist/{embeddingsGen-775PXWJY.js.map → embeddingsGen-NZQ2RXXP.js.map} +1 -1
  122. package/dist/{github-WDLK3J4R.js → github-RGFCTMMT.js} +160 -48
  123. package/dist/github-RGFCTMMT.js.map +1 -0
  124. package/dist/github-public.d.ts +24 -6
  125. package/dist/github-public.d.ts.map +1 -1
  126. package/dist/github-public.js +144 -12
  127. package/dist/github-public.js.map +1 -1
  128. package/dist/index.cjs +92 -12
  129. package/dist/index.cjs.map +1 -1
  130. package/dist/index.d.ts +1 -0
  131. package/dist/index.d.ts.map +1 -1
  132. package/dist/index.js +2 -0
  133. package/dist/index.js.map +1 -1
  134. package/dist/{init-DTJPSWLO.js → init-SGG3EI5G.js} +60 -28
  135. package/dist/init-SGG3EI5G.js.map +1 -0
  136. package/dist/lib/configStore.cjs +1 -1
  137. package/dist/lib/configStore.cjs.map +1 -1
  138. package/dist/lib/configStore.d.ts.map +1 -1
  139. package/dist/lib/configStore.js +1 -1
  140. package/dist/lib/configStore.js.map +1 -1
  141. package/dist/lib/contentBranch.d.ts +20 -0
  142. package/dist/lib/contentBranch.d.ts.map +1 -0
  143. package/dist/lib/contentBranch.js +35 -0
  144. package/dist/lib/contentBranch.js.map +1 -0
  145. package/dist/lib/publicContentCacheTag.d.ts +9 -0
  146. package/dist/lib/publicContentCacheTag.d.ts.map +1 -0
  147. package/dist/lib/publicContentCacheTag.js +6 -0
  148. package/dist/lib/publicContentCacheTag.js.map +1 -0
  149. package/dist/lib/publicSearchIndex.d.ts +5 -0
  150. package/dist/lib/publicSearchIndex.d.ts.map +1 -0
  151. package/dist/lib/publicSearchIndex.js +144 -0
  152. package/dist/lib/publicSearchIndex.js.map +1 -0
  153. package/dist/lib/searchIndex.d.ts +1 -1
  154. package/dist/lib/searchIndex.d.ts.map +1 -1
  155. package/dist/lib/searchIndex.js.map +1 -1
  156. package/dist/query.cjs +87 -12
  157. package/dist/query.cjs.map +1 -1
  158. package/dist/query.js +2 -2
  159. package/dist/query.js.map +1 -1
  160. package/dist/schema/diffSchema.js +119 -0
  161. package/dist/schema/diffSchema.js.map +1 -0
  162. package/dist/schema/fieldFormats.js +253 -0
  163. package/dist/schema/fieldFormats.js.map +1 -0
  164. package/dist/schema/index.d.ts +11 -0
  165. package/dist/schema/index.d.ts.map +1 -0
  166. package/dist/schema/index.js +14 -0
  167. package/dist/schema/index.js.map +1 -0
  168. package/dist/schema/migrateContent.js +306 -0
  169. package/dist/schema/migrateContent.js.map +1 -0
  170. package/dist/schema/types.js +1 -0
  171. package/dist/schema/types.js.map +1 -0
  172. package/dist/types.cjs.map +1 -1
  173. package/dist/types.d.ts +1 -1
  174. package/dist/{update-CJ3XVFNU.js → update-6ZBUX6O4.js} +45 -26
  175. package/dist/update-6ZBUX6O4.js.map +1 -0
  176. package/docs/editing-schema.md +2 -2
  177. package/package.json +9 -2
  178. package/dist/agent/proposalsApi.js +0 -83
  179. package/dist/agent/proposalsApi.js.map +0 -1
  180. package/dist/chunk-6D5NU3XR.js.map +0 -1
  181. package/dist/github-WDLK3J4R.js.map +0 -1
  182. package/dist/init-DTJPSWLO.js.map +0 -1
  183. package/dist/update-CJ3XVFNU.js.map +0 -1
  184. /package/dist/{agentDocs-JZRZW7XI.js.map → agentDocs-LCTKE4NJ.js.map} +0 -0
@@ -1,3 +1,4 @@
1
+ import './registerConfig';
1
2
  export type AgentClientStatus = {
2
3
  enabled: false;
3
4
  } | {
@@ -13,4 +14,44 @@ export type AgentClientStatus = {
13
14
  * mirroring the route-handler 404.
14
15
  */
15
16
  export declare function getAgentClientStatus(): Promise<AgentClientStatus>;
17
+ export type AcceptProposalActionResult = {
18
+ ok: true;
19
+ entryPath: string;
20
+ } | {
21
+ ok: false;
22
+ error: string;
23
+ fieldErrors?: Record<string, string>;
24
+ };
25
+ /**
26
+ * Server action — apply a chat-agent edit/create proposal.
27
+ *
28
+ * Replaces the previous `POST /api/agent/proposals/accept` Route Handler. The
29
+ * client (`useChatStream`) calls this directly via the Server Action transport
30
+ * — no public endpoint, no thin re-export file.
31
+ *
32
+ * Stateless by design: the entire proposal payload arrives over the wire and
33
+ * is re-validated here (and again inside `acceptProposal` via the schema
34
+ * validator + `saveFile`).
35
+ *
36
+ * Returns `{ ok: false, error }` on validation / write failures so the client
37
+ * can render an inline error without try/catch around the action call.
38
+ * Throws only when the agent is disabled or the caller is unauthenticated —
39
+ * those are caller-bug shapes, not user-facing flow.
40
+ */
41
+ export declare function acceptProposalAction(proposal: unknown): Promise<AcceptProposalActionResult>;
42
+ /**
43
+ * Server action — record that the user dismissed a proposal.
44
+ *
45
+ * Replaces the previous `POST /api/agent/proposals/reject` Route Handler.
46
+ * There's no server-side proposal record to mark rejected; this just
47
+ * acknowledges the click. The client reflects rejection in its own state and
48
+ * feeds the rejection back to the model on the next chat turn as a synthetic
49
+ * system note. The `reason` argument is currently ignored — kept for forward
50
+ * compatibility (e.g. analytics / per-rejection telemetry).
51
+ *
52
+ * Auth-gated to keep the surface consistent with `acceptProposalAction`.
53
+ */
54
+ export declare function rejectProposalAction(_reason?: string | null): Promise<{
55
+ ok: true;
56
+ }>;
16
57
  //# sourceMappingURL=agent.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../../admin/actions/agent.ts"],"names":[],"mappings":"AAKA,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"}
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,7 +1,11 @@
1
1
  "use server";
2
2
  import "../../chunk-B5LE2OEC.js";
3
+ import "./registerConfig";
4
+ import { getServerSession } from "next-auth/next";
3
5
  import { getAgentConfig } from "../../agent/configStore";
4
6
  import { getAgentStatus, isAgentEnabled } from "../../agent/featureFlag";
7
+ import { acceptProposal, isProposal } from "../../agent/proposals";
8
+ import { authOptions } from "../auth";
5
9
  async function getAgentClientStatus() {
6
10
  const cfg = getAgentConfig();
7
11
  if (!cfg || !isAgentEnabled(cfg)) return { enabled: false };
@@ -9,7 +13,38 @@ async function getAgentClientStatus() {
9
13
  if (!status.enabled) return { enabled: false };
10
14
  return { enabled: true, provider: cfg.provider.type, model: cfg.provider.model };
11
15
  }
16
+ async function acceptProposalAction(proposal) {
17
+ const cfg = getAgentConfig();
18
+ if (!cfg || !isAgentEnabled(cfg)) {
19
+ throw new Error("Chat agent is disabled.");
20
+ }
21
+ const session = await getServerSession(authOptions);
22
+ if (!session) {
23
+ throw new Error("Unauthorized.");
24
+ }
25
+ if (!isProposal(proposal)) {
26
+ return { ok: false, error: "Invalid proposal payload." };
27
+ }
28
+ const result = await acceptProposal(proposal);
29
+ if (!result.ok) {
30
+ return result.fieldErrors ? { ok: false, error: result.error, fieldErrors: result.fieldErrors } : { ok: false, error: result.error };
31
+ }
32
+ return { ok: true, entryPath: result.entryPath };
33
+ }
34
+ async function rejectProposalAction(_reason) {
35
+ const cfg = getAgentConfig();
36
+ if (!cfg || !isAgentEnabled(cfg)) {
37
+ throw new Error("Chat agent is disabled.");
38
+ }
39
+ const session = await getServerSession(authOptions);
40
+ if (!session) {
41
+ throw new Error("Unauthorized.");
42
+ }
43
+ return { ok: true };
44
+ }
12
45
  export {
13
- getAgentClientStatus
46
+ acceptProposalAction,
47
+ getAgentClientStatus,
48
+ rejectProposalAction
14
49
  };
15
50
  //# sourceMappingURL=agent.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../admin/actions/agent.ts"],"sourcesContent":["'use server';\n\nimport { getAgentConfig } from '../../agent/configStore';\nimport { getAgentStatus, isAgentEnabled } from '../../agent/featureFlag';\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 is\n * disabled, so the client can hide the nav link without learning *why* —\n * mirroring the route-handler 404.\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"],"mappings":";;AAEA,SAAS,sBAAsB;AAC/B,SAAS,gBAAgB,sBAAsB;AAiB/C,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;","names":[]}
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 is\n * disabled, so the client can hide the nav link without learning *why* —\n * mirroring the route-handler 404.\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,7 +1,11 @@
1
+ import './registerConfig';
1
2
  import { type ActionResult } from './utils';
2
- export type BuildJsonsOptions = {
3
- /** Slug-based `/blog/...` paths to revalidate in addition to the dynamic route segment. */
4
- blogPaths?: string[];
5
- };
6
- export declare const buildJsons: (_editedFileName?: string, options?: BuildJsonsOptions) => Promise<ActionResult>;
3
+ /**
4
+ * Invalidate public caches after content writes: one shared `"use cache"` tag for
5
+ * all OctoCMS-driven public reads, plus root layout revalidation so App Router pages
6
+ * under `/` pick up fresh RSC payloads without per-route config.
7
+ *
8
+ * Public site search (`GET /api/search`) is separate — built on demand from Git.
9
+ */
10
+ export declare const buildJsons: (_editedFileName?: string) => Promise<ActionResult>;
7
11
  //# sourceMappingURL=build.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../admin/actions/build.ts"],"names":[],"mappings":"AAaA,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC;AAOjE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,2FAA2F;IAC3F,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB,CAAC;AAuFF,eAAO,MAAM,UAAU,GAAU,kBAAkB,MAAM,EAAE,UAAU,iBAAiB,KAAG,OAAO,CAAC,YAAY,CAmC5G,CAAC"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../admin/actions/build.ts"],"names":[],"mappings":"AAEA,OAAO,kBAAkB,CAAC;AAM1B,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC;AAEjE;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,GAAU,kBAAkB,MAAM,KAAG,OAAO,CAAC,YAAY,CAc/E,CAAC"}
@@ -1,122 +1,17 @@
1
1
  "use server";
2
2
  import "../../chunk-B5LE2OEC.js";
3
- import fsPromises from "fs/promises";
4
- import path from "path";
5
- import { glob } from "glob";
3
+ import "./registerConfig";
6
4
  import { revalidatePath, revalidateTag, updateTag } from "next/cache";
7
- import { getConfig } from "../../lib/configStore";
8
- import { companionMarkdownPathsForEntry, companionRichTextPathsForEntry } from "../../lib/companionMarkdown";
9
- import { buildSearchIndex } from "../../lib/searchIndex";
10
- import { isProductionMode, saveGitHubFile } from "../github";
5
+ import { OCTOCMS_PUBLIC_CONTENT_CACHE_TAG } from "../../lib/publicContentCacheTag";
11
6
  import { actionErr, actionOk } from "./utils";
12
- const PUBLIC_CACHE_TAGS = ["homePage", "blog"];
13
- const SEARCH_INDEX_FILE_PATH = "cms/__generated__/search-index.json";
14
- async function getEntriesForPublicSearch() {
15
- var _a;
16
- const config = getConfig();
17
- const publicCollections = (_a = config.search) == null ? void 0 : _a.publicCollections;
18
- if (!publicCollections || Object.keys(publicCollections).length === 0) {
19
- return [];
20
- }
21
- const files = await glob(`${config.contentFolder}/**/*.json`);
22
- const entries = [];
23
- for (const file of files) {
24
- const normalized = file.replace(/\\/g, "/");
7
+ const buildJsons = async (_editedFileName) => {
8
+ try {
25
9
  try {
26
- const filePath = path.join(
27
- /*turbopackIgnore: true*/
28
- process.cwd(),
29
- normalized
30
- );
31
- const data = await fsPromises.readFile(filePath, { encoding: "utf8" });
32
- const content = JSON.parse(data);
33
- const sys = content.sys;
34
- const type = sys == null ? void 0 : sys.type;
35
- if (!type || !(type in publicCollections)) continue;
36
- const companions = {};
37
- if (type) {
38
- const mdPaths = companionMarkdownPathsForEntry(normalized, type, config.collections);
39
- for (const [fieldName, mdPath] of Object.entries(mdPaths)) {
40
- try {
41
- const mdFilePath = path.join(
42
- /*turbopackIgnore: true*/
43
- process.cwd(),
44
- mdPath
45
- );
46
- companions[fieldName] = await fsPromises.readFile(mdFilePath, { encoding: "utf8" });
47
- } catch (e) {
48
- companions[fieldName] = "";
49
- }
50
- }
51
- const rtPaths = companionRichTextPathsForEntry(normalized, type, config.collections);
52
- for (const [fieldName, rtPath] of Object.entries(rtPaths)) {
53
- try {
54
- const rtFilePath = path.join(
55
- /*turbopackIgnore: true*/
56
- process.cwd(),
57
- rtPath
58
- );
59
- companions[fieldName] = await fsPromises.readFile(rtFilePath, { encoding: "utf8" });
60
- } catch (e) {
61
- companions[fieldName] = "";
62
- }
63
- }
64
- }
65
- entries.push({
66
- path: normalized.replace(`${config.contentFolder}/`, ""),
67
- content,
68
- companionContent: companions
69
- });
10
+ updateTag(OCTOCMS_PUBLIC_CONTENT_CACHE_TAG);
70
11
  } catch (e) {
71
- }
72
- }
73
- return entries;
74
- }
75
- async function buildAndWriteSearchIndex() {
76
- var _a;
77
- const config = getConfig();
78
- const publicCollections = (_a = config.search) == null ? void 0 : _a.publicCollections;
79
- if (!publicCollections || Object.keys(publicCollections).length === 0) {
80
- return;
81
- }
82
- try {
83
- const entries = await getEntriesForPublicSearch();
84
- const indexJson = buildSearchIndex(entries, config, Object.keys(publicCollections));
85
- if (isProductionMode()) {
86
- await saveGitHubFile(SEARCH_INDEX_FILE_PATH, indexJson, "CMS: update search index");
87
- } else {
88
- const filePath = path.join(
89
- /*turbopackIgnore: true*/
90
- process.cwd(),
91
- SEARCH_INDEX_FILE_PATH
92
- );
93
- await fsPromises.mkdir(path.dirname(filePath), { recursive: true });
94
- await fsPromises.writeFile(filePath, indexJson, "utf8");
95
- }
96
- } catch (e) {
97
- }
98
- }
99
- const buildJsons = async (_editedFileName, options) => {
100
- var _a;
101
- try {
102
- for (const tag of PUBLIC_CACHE_TAGS) {
103
- try {
104
- updateTag(tag);
105
- } catch (e) {
106
- revalidateTag(tag, { expire: 0 });
107
- }
12
+ revalidateTag(OCTOCMS_PUBLIC_CONTENT_CACHE_TAG, { expire: 0 });
108
13
  }
109
14
  revalidatePath("/", "layout");
110
- revalidatePath("/blog", "page");
111
- revalidatePath("/blog/[slug]", "page");
112
- const seen = /* @__PURE__ */ new Set();
113
- for (const p of (_a = options == null ? void 0 : options.blogPaths) != null ? _a : []) {
114
- if (typeof p === "string" && p.startsWith("/blog/") && !seen.has(p)) {
115
- seen.add(p);
116
- revalidatePath(p);
117
- }
118
- }
119
- await buildAndWriteSearchIndex();
120
15
  return actionOk();
121
16
  } catch (e) {
122
17
  return actionErr(e);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../admin/actions/build.ts"],"sourcesContent":["'use server';\n\nimport fsPromises from 'fs/promises';\nimport path from 'path';\n\nimport { glob } from 'glob';\nimport { revalidatePath, revalidateTag, updateTag } from 'next/cache';\n\nimport { getConfig } from '../../lib/configStore';\nimport { companionMarkdownPathsForEntry, companionRichTextPathsForEntry } from '../../lib/companionMarkdown';\nimport { buildSearchIndex, type EntryForSearch } from '../../lib/searchIndex';\n\nimport { isProductionMode, saveGitHubFile } from '../github';\nimport { actionErr, actionOk, type ActionResult } from './utils';\n\n/** Cache tags used by `getHomePage` / `getBlog` / `getPublishedPosts` in `src/app/cms/ssr/getPageContent.ts`. */\nconst PUBLIC_CACHE_TAGS = ['homePage', 'blog'] as const;\n\nconst SEARCH_INDEX_FILE_PATH = 'cms/__generated__/search-index.json';\n\nexport type BuildJsonsOptions = {\n /** Slug-based `/blog/...` paths to revalidate in addition to the dynamic route segment. */\n blogPaths?: string[];\n};\n\n/** Helper: gather all searchable entries from the filesystem. */\nasync function getEntriesForPublicSearch(): Promise<EntryForSearch[]> {\n const config = getConfig();\n const publicCollections = config.search?.publicCollections;\n if (!publicCollections || Object.keys(publicCollections).length === 0) {\n return [];\n }\n\n const files = await glob(`${config.contentFolder}/**/*.json`);\n const entries: EntryForSearch[] = [];\n\n for (const file of files) {\n const normalized = file.replace(/\\\\/g, '/');\n try {\n const filePath = path.join(/*turbopackIgnore: true*/ process.cwd(), normalized);\n const data = await fsPromises.readFile(filePath, { encoding: 'utf8' });\n const content = JSON.parse(data) as Record<string, unknown>;\n const sys = content.sys as { type?: string } | undefined;\n const type = sys?.type;\n\n // Only include entries from publicCollections\n if (!type || !(type in publicCollections)) continue;\n\n // Read companion markdown/richtext files\n const companions: Record<string, string> = {};\n if (type) {\n const mdPaths = companionMarkdownPathsForEntry(normalized, type, config.collections);\n for (const [fieldName, mdPath] of Object.entries(mdPaths)) {\n try {\n const mdFilePath = path.join(/*turbopackIgnore: true*/ process.cwd(), mdPath);\n companions[fieldName] = await fsPromises.readFile(mdFilePath, { encoding: 'utf8' });\n } catch {\n companions[fieldName] = '';\n }\n }\n const rtPaths = companionRichTextPathsForEntry(normalized, type, config.collections);\n for (const [fieldName, rtPath] of Object.entries(rtPaths)) {\n try {\n const rtFilePath = path.join(/*turbopackIgnore: true*/ process.cwd(), rtPath);\n companions[fieldName] = await fsPromises.readFile(rtFilePath, { encoding: 'utf8' });\n } catch {\n companions[fieldName] = '';\n }\n }\n }\n\n entries.push({\n path: normalized.replace(`${config.contentFolder}/`, ''),\n content,\n companionContent: companions,\n });\n } catch {\n // Skip unreadable files\n }\n }\n\n return entries;\n}\n\n/** Build and write the public search index. */\nasync function buildAndWriteSearchIndex(): Promise<void> {\n const config = getConfig();\n const publicCollections = config.search?.publicCollections;\n if (!publicCollections || Object.keys(publicCollections).length === 0) {\n return;\n }\n\n try {\n const entries = await getEntriesForPublicSearch();\n const indexJson = buildSearchIndex(entries, config, Object.keys(publicCollections));\n\n if (isProductionMode()) {\n // Write to GitHub\n await saveGitHubFile(SEARCH_INDEX_FILE_PATH, indexJson, 'CMS: update search index');\n } else {\n // Write to local filesystem\n const filePath = path.join(/*turbopackIgnore: true*/ process.cwd(), SEARCH_INDEX_FILE_PATH);\n await fsPromises.mkdir(path.dirname(filePath), { recursive: true });\n await fsPromises.writeFile(filePath, indexJson, 'utf8');\n }\n } catch {\n // Silently fail search index generation to not block the build\n }\n}\n\nexport const buildJsons = async (_editedFileName?: string, options?: BuildJsonsOptions): Promise<ActionResult> => {\n try {\n for (const tag of PUBLIC_CACHE_TAGS) {\n // `updateTag` gives read-your-own-writes inside a Server Action (the\n // editor save path). When called from a Route Handler — e.g. the\n // proposal accept endpoint at `/api/agent/proposals/accept` — the\n // runtime throws (\"updateTag can only be called from within a Server\n // Action\"); fall back to `revalidateTag` with immediate expiry, which\n // is allowed everywhere and produces the same fresh-data outcome.\n try {\n updateTag(tag);\n } catch {\n revalidateTag(tag, { expire: 0 });\n }\n }\n\n revalidatePath('/', 'layout');\n revalidatePath('/blog', 'page');\n revalidatePath('/blog/[slug]', 'page');\n\n const seen = new Set<string>();\n for (const p of options?.blogPaths ?? []) {\n if (typeof p === 'string' && p.startsWith('/blog/') && !seen.has(p)) {\n seen.add(p);\n revalidatePath(p);\n }\n }\n\n // Build and write the public search index\n await buildAndWriteSearchIndex();\n\n return actionOk();\n } catch (e) {\n return actionErr(e);\n }\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../admin/actions/build.ts"],"sourcesContent":["'use server';\n\nimport './registerConfig';\n\nimport { revalidatePath, revalidateTag, updateTag } from 'next/cache';\n\nimport { OCTOCMS_PUBLIC_CONTENT_CACHE_TAG } from '../../lib/publicContentCacheTag';\n\nimport { actionErr, actionOk, type ActionResult } from './utils';\n\n/**\n * Invalidate public caches after content writes: one shared `\"use cache\"` tag for\n * all OctoCMS-driven public reads, plus root layout revalidation so App Router pages\n * under `/` pick up fresh RSC payloads without per-route config.\n *\n * Public site search (`GET /api/search`) is separate built on demand from Git.\n */\nexport const buildJsons = async (_editedFileName?: string): Promise<ActionResult> => {\n try {\n try {\n updateTag(OCTOCMS_PUBLIC_CONTENT_CACHE_TAG);\n } catch {\n revalidateTag(OCTOCMS_PUBLIC_CONTENT_CACHE_TAG, { expire: 0 });\n }\n\n revalidatePath('/', 'layout');\n\n return actionOk();\n } catch (e) {\n return actionErr(e);\n }\n};\n"],"mappings":";;AAEA,OAAO;AAEP,SAAS,gBAAgB,eAAe,iBAAiB;AAEzD,SAAS,wCAAwC;AAEjD,SAAS,WAAW,gBAAmC;AAShD,MAAM,aAAa,OAAO,oBAAoD;AACnF,MAAI;AACF,QAAI;AACF,gBAAU,gCAAgC;AAAA,IAC5C,SAAQ;AACN,oBAAc,kCAAkC,EAAE,QAAQ,EAAE,CAAC;AAAA,IAC/D;AAEA,mBAAe,KAAK,QAAQ;AAE5B,WAAO,SAAS;AAAA,EAClB,SAAS,GAAG;AACV,WAAO,UAAU,CAAC;AAAA,EACpB;AACF;","names":[]}
@@ -1,3 +1,4 @@
1
+ import './registerConfig';
1
2
  import { type FieldDiff } from '../../lib/entryDiff';
2
3
  export type EntryDiff = {
3
4
  changed: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../../admin/actions/diff.ts"],"names":[],"mappings":"AAOA,OAAO,EAAmC,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAQtF,MAAM,MAAM,SAAS,GAAG;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAClC,wFAAwF;IACxF,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IAC5E,gGAAgG;IAChG,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC,CAAC;AA8CF;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAU,UAAU,MAAM,KAAG,OAAO,CAAC,SAAS,CA0GtE,CAAC"}
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../../admin/actions/diff.ts"],"names":[],"mappings":"AAEA,OAAO,kBAAkB,CAAC;AAO1B,OAAO,EAAmC,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAQtF,MAAM,MAAM,SAAS,GAAG;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAClC,wFAAwF;IACxF,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IAC5E,gGAAgG;IAChG,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC,CAAC;AA8CF;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAU,UAAU,MAAM,KAAG,OAAO,CAAC,SAAS,CA0GtE,CAAC"}
@@ -1,5 +1,6 @@
1
1
  "use server";
2
2
  import "../../chunk-B5LE2OEC.js";
3
+ import "./registerConfig";
3
4
  import { execFile } from "node:child_process";
4
5
  import fsPromises from "node:fs/promises";
5
6
  import { getConfig } from "../../lib/configStore";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../admin/actions/diff.ts"],"sourcesContent":["'use server';\n\nimport { execFile } from 'node:child_process';\nimport fsPromises from 'node:fs/promises';\n\nimport { getConfig } from '../../lib/configStore';\nimport { companionFilePathsForEntry } from '../../lib/companionMarkdown';\nimport { diffEntryFields, safeParseEntry, type FieldDiff } from '../../lib/entryDiff';\nimport { logCmsServerError } from '../../lib/cmsServerLog';\nimport { mediaContentFolder, mediaEntryPath } from '../../lib/mediaPath';\nimport { parseFileName } from '../../utils/parseFileName';\nimport { getGitHubFile, isProductionMode } from '../github';\nimport { getBranch } from './git';\nimport { getErrorMessage } from './utils';\n\nexport type EntryDiff = {\n changed: boolean;\n activeBranch: string;\n baseBranch: string;\n /** Per-field diffs derived from the entry JSON `fields` object. */\n fields: Record<string, FieldDiff>;\n /** Raw before/after text for companion markdown/richtext files, keyed by field name. */\n companions: Record<string, { before: string | null; after: string | null }>;\n /** Resolved `/media/<uuid>.<ext>` URLs for image UUIDs appearing on either side of the diff. */\n imageUrls: Record<string, string>;\n};\n\nconst emptyDiff = (activeBranch = '', baseBranch = ''): EntryDiff => ({\n changed: false,\n activeBranch,\n baseBranch,\n fields: {},\n companions: {},\n imageUrls: {},\n});\n\n/**\n * Read a file at a specific git ref. Returns `null` if the file doesn't exist on that ref.\n * - Production: GitHub API via {@link getGitHubFile}.\n * - Dev: `git show <ref>:<path>` for committed content, or the working-tree file when the ref\n * matches the currently checked-out branch.\n */\nasync function readFileAtRef(filePath: string, ref: string, isProd: boolean): Promise<string | null> {\n if (isProd) {\n const result = await getGitHubFile(filePath, ref);\n return result ? result.content : null;\n }\n return await readFileAtGitRef(filePath, ref);\n}\n\nfunction readFileAtGitRef(filePath: string, ref: string): Promise<string | null> {\n return new Promise((resolve) => {\n execFile('git', ['show', `${ref}:${filePath}`], { maxBuffer: 10 * 1024 * 1024 }, (error, stdout) => {\n if (error) {\n // `git show` exits non-zero if the path doesn't exist on that ref.\n resolve(null);\n return;\n }\n resolve(stdout.toString());\n });\n });\n}\n\nasync function readWorkingTreeFile(filePath: string): Promise<string | null> {\n try {\n return await fsPromises.readFile(filePath, 'utf-8');\n } catch {\n return null;\n }\n}\n\n/**\n * Compute the diff between the currently active feature branch and the base branch for a\n * single entry's files (JSON + companion `.md` / `.mdx`). Read-only, best-effort: any failure\n * resolves to an empty diff so the UI never throws.\n */\nexport const getEntryDiff = async (filePath: string): Promise<EntryDiff> => {\n if (!filePath) return emptyDiff();\n\n try {\n const config = getConfig();\n const baseBranch = config.git.baseBranch;\n const activeBranch = await getBranch();\n const isProd = isProductionMode();\n\n // Same branch on both sides → no unmerged changes.\n if (!activeBranch || activeBranch === baseBranch) {\n return emptyDiff(activeBranch, baseBranch);\n }\n\n const { type: collectionType } = parseFileName(filePath);\n\n // --- Entry JSON ---------------------------------------------------------\n const [afterJson, beforeJson] = await Promise.all([\n isProd ? readFileAtRef(filePath, activeBranch, true) : readWorkingTreeFile(filePath),\n readFileAtRef(filePath, baseBranch, isProd),\n ]);\n\n const fields = diffEntryFields(safeParseEntry(beforeJson), safeParseEntry(afterJson));\n\n // --- Companion files ----------------------------------------------------\n const companionPaths = companionFilePathsForEntry(filePath, collectionType, config.collections);\n const companionEntries = Object.entries(companionPaths);\n\n const companionResults = await Promise.all(\n companionEntries.map(async ([fieldName, compPath]) => {\n const [after, before] = await Promise.all([\n isProd ? readFileAtRef(compPath, activeBranch, true) : readWorkingTreeFile(compPath),\n readFileAtRef(compPath, baseBranch, isProd),\n ]);\n return [fieldName, { before, after }] as const;\n }),\n );\n\n const companions: EntryDiff['companions'] = {};\n for (const [fieldName, payload] of companionResults) {\n companions[fieldName] = payload;\n }\n\n const hasFieldChange = Object.values(fields).some((d) => d.kind !== 'unchanged');\n const hasCompanionChange = Object.values(companions).some((c) => (c.before ?? '') !== (c.after ?? ''));\n\n // Resolve image UUIDs that changed on either side to `/media/<uuid>.<ext>` URLs.\n const collectionDef = (config.collections as Record<string, { fields: Record<string, { format?: string }> }>)[\n collectionType\n ];\n const imageFieldNames = collectionDef\n ? Object.entries(collectionDef.fields)\n .filter(([, f]) => f.format === 'image')\n .map(([name]) => name)\n : [];\n\n const imageUuids = new Set<string>();\n for (const name of imageFieldNames) {\n const d = fields[name];\n if (!d) continue;\n if (d.kind === 'added' && typeof d.after === 'string') imageUuids.add(d.after);\n else if (d.kind === 'removed' && typeof d.before === 'string') imageUuids.add(d.before);\n else if (d.kind === 'changed') {\n if (typeof d.before === 'string') imageUuids.add(d.before);\n if (typeof d.after === 'string') imageUuids.add(d.after);\n }\n }\n\n const imageUrls: Record<string, string> = {};\n if (imageUuids.size > 0) {\n const mediaDir = mediaContentFolder();\n const resolvePairs = await Promise.all(\n Array.from(imageUuids).map(async (uuid) => {\n if (!uuid || uuid.startsWith('/')) return [uuid, uuid] as const;\n // Prefer the active-branch media entry; fall back to base branch.\n let raw =\n (await readFileAtRef(mediaEntryPath(uuid), activeBranch, isProd)) ??\n (await readFileAtRef(mediaEntryPath(uuid), baseBranch, isProd));\n if (!raw) {\n // Legacy path: {uuid}.json (pre-`media-` prefix)\n raw =\n (await readFileAtRef(`${mediaDir}/${uuid}.json`, activeBranch, isProd)) ??\n (await readFileAtRef(`${mediaDir}/${uuid}.json`, baseBranch, isProd));\n }\n const parsed = safeParseEntry(raw);\n const ext = parsed && typeof parsed.fields?.extension === 'string' ? (parsed.fields.extension as string) : '';\n return [uuid, ext ? `/media/${uuid}.${ext}` : ''] as const;\n }),\n );\n for (const [uuid, url] of resolvePairs) {\n if (url) imageUrls[uuid] = url;\n }\n }\n\n return {\n changed: hasFieldChange || hasCompanionChange,\n activeBranch,\n baseBranch,\n fields,\n companions,\n imageUrls,\n };\n } catch (e) {\n logCmsServerError({ operation: 'getEntryDiff', message: getErrorMessage(e) });\n return emptyDiff();\n }\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../admin/actions/diff.ts"],"sourcesContent":["'use server';\n\nimport './registerConfig';\n\nimport { execFile } from 'node:child_process';\nimport fsPromises from 'node:fs/promises';\n\nimport { getConfig } from '../../lib/configStore';\nimport { companionFilePathsForEntry } from '../../lib/companionMarkdown';\nimport { diffEntryFields, safeParseEntry, type FieldDiff } from '../../lib/entryDiff';\nimport { logCmsServerError } from '../../lib/cmsServerLog';\nimport { mediaContentFolder, mediaEntryPath } from '../../lib/mediaPath';\nimport { parseFileName } from '../../utils/parseFileName';\nimport { getGitHubFile, isProductionMode } from '../github';\nimport { getBranch } from './git';\nimport { getErrorMessage } from './utils';\n\nexport type EntryDiff = {\n changed: boolean;\n activeBranch: string;\n baseBranch: string;\n /** Per-field diffs derived from the entry JSON `fields` object. */\n fields: Record<string, FieldDiff>;\n /** Raw before/after text for companion markdown/richtext files, keyed by field name. */\n companions: Record<string, { before: string | null; after: string | null }>;\n /** Resolved `/media/<uuid>.<ext>` URLs for image UUIDs appearing on either side of the diff. */\n imageUrls: Record<string, string>;\n};\n\nconst emptyDiff = (activeBranch = '', baseBranch = ''): EntryDiff => ({\n changed: false,\n activeBranch,\n baseBranch,\n fields: {},\n companions: {},\n imageUrls: {},\n});\n\n/**\n * Read a file at a specific git ref. Returns `null` if the file doesn't exist on that ref.\n * - Production: GitHub API via {@link getGitHubFile}.\n * - Dev: `git show <ref>:<path>` for committed content, or the working-tree file when the ref\n * matches the currently checked-out branch.\n */\nasync function readFileAtRef(filePath: string, ref: string, isProd: boolean): Promise<string | null> {\n if (isProd) {\n const result = await getGitHubFile(filePath, ref);\n return result ? result.content : null;\n }\n return await readFileAtGitRef(filePath, ref);\n}\n\nfunction readFileAtGitRef(filePath: string, ref: string): Promise<string | null> {\n return new Promise((resolve) => {\n execFile('git', ['show', `${ref}:${filePath}`], { maxBuffer: 10 * 1024 * 1024 }, (error, stdout) => {\n if (error) {\n // `git show` exits non-zero if the path doesn't exist on that ref.\n resolve(null);\n return;\n }\n resolve(stdout.toString());\n });\n });\n}\n\nasync function readWorkingTreeFile(filePath: string): Promise<string | null> {\n try {\n return await fsPromises.readFile(filePath, 'utf-8');\n } catch {\n return null;\n }\n}\n\n/**\n * Compute the diff between the currently active feature branch and the base branch for a\n * single entry's files (JSON + companion `.md` / `.mdx`). Read-only, best-effort: any failure\n * resolves to an empty diff so the UI never throws.\n */\nexport const getEntryDiff = async (filePath: string): Promise<EntryDiff> => {\n if (!filePath) return emptyDiff();\n\n try {\n const config = getConfig();\n const baseBranch = config.git.baseBranch;\n const activeBranch = await getBranch();\n const isProd = isProductionMode();\n\n // Same branch on both sides → no unmerged changes.\n if (!activeBranch || activeBranch === baseBranch) {\n return emptyDiff(activeBranch, baseBranch);\n }\n\n const { type: collectionType } = parseFileName(filePath);\n\n // --- Entry JSON ---------------------------------------------------------\n const [afterJson, beforeJson] = await Promise.all([\n isProd ? readFileAtRef(filePath, activeBranch, true) : readWorkingTreeFile(filePath),\n readFileAtRef(filePath, baseBranch, isProd),\n ]);\n\n const fields = diffEntryFields(safeParseEntry(beforeJson), safeParseEntry(afterJson));\n\n // --- Companion files ----------------------------------------------------\n const companionPaths = companionFilePathsForEntry(filePath, collectionType, config.collections);\n const companionEntries = Object.entries(companionPaths);\n\n const companionResults = await Promise.all(\n companionEntries.map(async ([fieldName, compPath]) => {\n const [after, before] = await Promise.all([\n isProd ? readFileAtRef(compPath, activeBranch, true) : readWorkingTreeFile(compPath),\n readFileAtRef(compPath, baseBranch, isProd),\n ]);\n return [fieldName, { before, after }] as const;\n }),\n );\n\n const companions: EntryDiff['companions'] = {};\n for (const [fieldName, payload] of companionResults) {\n companions[fieldName] = payload;\n }\n\n const hasFieldChange = Object.values(fields).some((d) => d.kind !== 'unchanged');\n const hasCompanionChange = Object.values(companions).some((c) => (c.before ?? '') !== (c.after ?? ''));\n\n // Resolve image UUIDs that changed on either side to `/media/<uuid>.<ext>` URLs.\n const collectionDef = (config.collections as Record<string, { fields: Record<string, { format?: string }> }>)[\n collectionType\n ];\n const imageFieldNames = collectionDef\n ? Object.entries(collectionDef.fields)\n .filter(([, f]) => f.format === 'image')\n .map(([name]) => name)\n : [];\n\n const imageUuids = new Set<string>();\n for (const name of imageFieldNames) {\n const d = fields[name];\n if (!d) continue;\n if (d.kind === 'added' && typeof d.after === 'string') imageUuids.add(d.after);\n else if (d.kind === 'removed' && typeof d.before === 'string') imageUuids.add(d.before);\n else if (d.kind === 'changed') {\n if (typeof d.before === 'string') imageUuids.add(d.before);\n if (typeof d.after === 'string') imageUuids.add(d.after);\n }\n }\n\n const imageUrls: Record<string, string> = {};\n if (imageUuids.size > 0) {\n const mediaDir = mediaContentFolder();\n const resolvePairs = await Promise.all(\n Array.from(imageUuids).map(async (uuid) => {\n if (!uuid || uuid.startsWith('/')) return [uuid, uuid] as const;\n // Prefer the active-branch media entry; fall back to base branch.\n let raw =\n (await readFileAtRef(mediaEntryPath(uuid), activeBranch, isProd)) ??\n (await readFileAtRef(mediaEntryPath(uuid), baseBranch, isProd));\n if (!raw) {\n // Legacy path: {uuid}.json (pre-`media-` prefix)\n raw =\n (await readFileAtRef(`${mediaDir}/${uuid}.json`, activeBranch, isProd)) ??\n (await readFileAtRef(`${mediaDir}/${uuid}.json`, baseBranch, isProd));\n }\n const parsed = safeParseEntry(raw);\n const ext = parsed && typeof parsed.fields?.extension === 'string' ? (parsed.fields.extension as string) : '';\n return [uuid, ext ? `/media/${uuid}.${ext}` : ''] as const;\n }),\n );\n for (const [uuid, url] of resolvePairs) {\n if (url) imageUrls[uuid] = url;\n }\n }\n\n return {\n changed: hasFieldChange || hasCompanionChange,\n activeBranch,\n baseBranch,\n fields,\n companions,\n imageUrls,\n };\n } catch (e) {\n logCmsServerError({ operation: 'getEntryDiff', message: getErrorMessage(e) });\n return emptyDiff();\n }\n};\n"],"mappings":"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","names":[]}
@@ -1,3 +1,4 @@
1
+ import './registerConfig';
1
2
  import type { EntryListItem } from '../../types';
2
3
  export declare const getEntryList: (collection?: string) => Promise<EntryListItem[]>;
3
4
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"entries.d.ts","sourceRoot":"","sources":["../../../admin/actions/entries.ts"],"names":[],"mappings":"AAOA,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;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,5 +1,6 @@
1
1
  "use server";
2
2
  import "../../chunk-B5LE2OEC.js";
3
+ import "./registerConfig";
3
4
  import fsPromises from "fs/promises";
4
5
  import path from "path";
5
6
  import { getConfig } from "../../lib/configStore";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../admin/actions/entries.ts"],"sourcesContent":["'use server';\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 } 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 const entries: EntryListItem[] = [];\n\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 for (const file of files) {\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 try {\n const content = await getFile(file);\n\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 } catch (_e) {\n // Fall back to id as title\n }\n\n if (!isProductionMode()) {\n try {\n const stat = await fsPromises.stat(path.join(/*turbopackIgnore: true*/ process.cwd(), file));\n updatedAt = stat.mtime.toISOString();\n } catch {\n // ignore\n }\n }\n\n entries.push({ type, id, path: file, title, status, updatedAt, thumbnailUrl });\n }\n\n entries.sort((a, b) => a.title.localeCompare(b.title));\n return entries;\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":"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","names":["_a","_b","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 { getConfig } from '../../lib/configStore';\nimport type { Config } from '../types';\nimport type { EntryListItem, EntryStatus } from '../../types';\n\nimport { getContentFiles, getFile } 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 const entries: EntryListItem[] = [];\n\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 for (const file of files) {\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 try {\n const content = await getFile(file);\n\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 } catch (_e) {\n // Fall back to id as title\n }\n\n if (!isProductionMode()) {\n try {\n const stat = await fsPromises.stat(path.join(/*turbopackIgnore: true*/ process.cwd(), file));\n updatedAt = stat.mtime.toISOString();\n } catch {\n // ignore\n }\n }\n\n entries.push({ type, id, path: file, title, status, updatedAt, thumbnailUrl });\n }\n\n entries.sort((a, b) => a.title.localeCompare(b.title));\n return entries;\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":"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","names":["_a","_b","collection"]}
@@ -1,3 +1,4 @@
1
+ import './registerConfig';
1
2
  import { type ActionResult, type NewFileResult, type SaveFileResult } from './utils';
2
3
  export type { SaveFileResult } from './utils';
3
4
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../../admin/actions/files.ts"],"names":[],"mappings":"AAgCA,OAAO,EAIL,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,cAAc,EACpB,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AA6L9C;;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,sBA8B9D,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,QAAa,OAAO,CAAC,MAAM,EAAE,CAkB7D,CAAC;AAEF,eAAO,MAAM,aAAa,GAAU,SAAQ,MAAa,sBAqBxD,CAAC;AAEF,eAAO,MAAM,OAAO,GAAU,UAAU,MAAM,iBAgE7C,CAAC;AAuBF,eAAO,MAAM,QAAQ,GACnB,UAAU,GAAG,EACb,UAAU,MAAM,EAChB,UAAU;IAAE,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAAE,KAC3C,OAAO,CAAC,cAAc,CA4JxB,CAAC;AAEF,eAAO,MAAM,OAAO,GAAU,MAAM,MAAM,KAAG,OAAO,CAAC,aAAa,CAmDjE,CAAC;AAEF,eAAO,MAAM,UAAU,GAAU,UAAU,MAAM,KAAG,OAAO,CAAC,YAAY,CAgEvE,CAAC"}
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,sBAgC9D,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,QAAa,OAAO,CAAC,MAAM,EAAE,CAkB7D,CAAC;AAEF,eAAO,MAAM,aAAa,GAAU,SAAQ,MAAa,sBAqBxD,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,CAmDjE,CAAC;AAEF,eAAO,MAAM,UAAU,GAAU,UAAU,MAAM,KAAG,OAAO,CAAC,YAAY,CA2DvE,CAAC"}