canopycms 0.0.61-int.75 → 0.0.62

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 (369) hide show
  1. package/dist/ai/handler.js +1 -1
  2. package/dist/ai/handler.js.map +1 -1
  3. package/dist/ai/json-to-markdown.js +1 -2
  4. package/dist/ai/json-to-markdown.js.map +1 -1
  5. package/dist/api/__test__/mock-client.d.ts +5 -1
  6. package/dist/api/__test__/mock-client.d.ts.map +1 -1
  7. package/dist/api/__test__/mock-client.js +9 -2
  8. package/dist/api/__test__/mock-client.js.map +1 -1
  9. package/dist/api/admin-branch-health.d.ts +36 -1
  10. package/dist/api/admin-branch-health.d.ts.map +1 -1
  11. package/dist/api/admin-branch-health.js +176 -2
  12. package/dist/api/admin-branch-health.js.map +1 -1
  13. package/dist/api/admin.d.ts +8 -1
  14. package/dist/api/admin.d.ts.map +1 -1
  15. package/dist/api/admin.js.map +1 -1
  16. package/dist/api/assets.d.ts.map +1 -1
  17. package/dist/api/assets.js +36 -4
  18. package/dist/api/assets.js.map +1 -1
  19. package/dist/api/branch-status.d.ts.map +1 -1
  20. package/dist/api/branch-status.js +56 -0
  21. package/dist/api/branch-status.js.map +1 -1
  22. package/dist/api/branch-withdraw.d.ts.map +1 -1
  23. package/dist/api/branch-withdraw.js +14 -3
  24. package/dist/api/branch-withdraw.js.map +1 -1
  25. package/dist/api/branch.d.ts +31 -2
  26. package/dist/api/branch.d.ts.map +1 -1
  27. package/dist/api/branch.js +268 -15
  28. package/dist/api/branch.js.map +1 -1
  29. package/dist/api/client.d.ts +5 -1
  30. package/dist/api/client.d.ts.map +1 -1
  31. package/dist/api/client.js +6 -0
  32. package/dist/api/client.js.map +1 -1
  33. package/dist/api/comments.d.ts +2 -2
  34. package/dist/api/content.d.ts +11 -5
  35. package/dist/api/content.d.ts.map +1 -1
  36. package/dist/api/content.js +91 -10
  37. package/dist/api/content.js.map +1 -1
  38. package/dist/api/entries.d.ts +10 -0
  39. package/dist/api/entries.d.ts.map +1 -1
  40. package/dist/api/entries.js +51 -24
  41. package/dist/api/entries.js.map +1 -1
  42. package/dist/api/guards.d.ts.map +1 -1
  43. package/dist/api/guards.js +12 -5
  44. package/dist/api/guards.js.map +1 -1
  45. package/dist/api/permissions.d.ts +2 -2
  46. package/dist/api/permissions.d.ts.map +1 -1
  47. package/dist/api/permissions.js +51 -3
  48. package/dist/api/permissions.js.map +1 -1
  49. package/dist/api/reference-options.d.ts.map +1 -1
  50. package/dist/api/reference-options.js +10 -17
  51. package/dist/api/reference-options.js.map +1 -1
  52. package/dist/api/resolve-references.d.ts.map +1 -1
  53. package/dist/api/resolve-references.js +3 -1
  54. package/dist/api/resolve-references.js.map +1 -1
  55. package/dist/api/schema.d.ts +4 -4
  56. package/dist/api/schema.d.ts.map +1 -1
  57. package/dist/api/schema.js +16 -15
  58. package/dist/api/schema.js.map +1 -1
  59. package/dist/api/settings-helpers.d.ts.map +1 -1
  60. package/dist/api/settings-helpers.js +6 -5
  61. package/dist/api/settings-helpers.js.map +1 -1
  62. package/dist/assets/pipeline.d.ts +11 -2
  63. package/dist/assets/pipeline.d.ts.map +1 -1
  64. package/dist/assets/pipeline.js +114 -2
  65. package/dist/assets/pipeline.js.map +1 -1
  66. package/dist/assets/transform-directives.d.ts +18 -0
  67. package/dist/assets/transform-directives.d.ts.map +1 -1
  68. package/dist/assets/transform-directives.js +18 -0
  69. package/dist/assets/transform-directives.js.map +1 -1
  70. package/dist/assets/transform.d.ts.map +1 -1
  71. package/dist/assets/transform.js +20 -16
  72. package/dist/assets/transform.js.map +1 -1
  73. package/dist/auth/types.d.ts +29 -0
  74. package/dist/auth/types.d.ts.map +1 -1
  75. package/dist/authorization/index.d.ts +1 -1
  76. package/dist/authorization/index.d.ts.map +1 -1
  77. package/dist/authorization/index.js +1 -1
  78. package/dist/authorization/index.js.map +1 -1
  79. package/dist/authorization/protected-branch.d.ts +75 -0
  80. package/dist/authorization/protected-branch.d.ts.map +1 -1
  81. package/dist/authorization/protected-branch.js +39 -0
  82. package/dist/authorization/protected-branch.js.map +1 -1
  83. package/dist/branch-health.d.ts +13 -0
  84. package/dist/branch-health.d.ts.map +1 -1
  85. package/dist/branch-health.js +23 -0
  86. package/dist/branch-health.js.map +1 -1
  87. package/dist/branch-registry.d.ts.map +1 -1
  88. package/dist/branch-registry.js +5 -1
  89. package/dist/branch-registry.js.map +1 -1
  90. package/dist/branch-schema-cache.d.ts.map +1 -1
  91. package/dist/branch-schema-cache.js +9 -0
  92. package/dist/branch-schema-cache.js.map +1 -1
  93. package/dist/build/generate-ai-content.js +1 -1
  94. package/dist/build/generate-ai-content.js.map +1 -1
  95. package/dist/cli/cli.js +2215 -1280
  96. package/dist/cli/generate-ai-content.js +1158 -585
  97. package/dist/cli/init.d.ts +8 -2
  98. package/dist/cli/init.d.ts.map +1 -1
  99. package/dist/cli/init.js +459 -152
  100. package/dist/cli/init.js.map +1 -1
  101. package/dist/cli/project-detect.d.ts +89 -0
  102. package/dist/cli/project-detect.d.ts.map +1 -0
  103. package/dist/cli/project-detect.js +240 -0
  104. package/dist/cli/project-detect.js.map +1 -0
  105. package/dist/cli/template-files/Dockerfile.cms.template +30 -7
  106. package/dist/cli/template-files/canopycms.config.ts.template +7 -0
  107. package/dist/cli/template-files/cdk-app.ts.template +82 -0
  108. package/dist/cli/template-files/cdk.json.template +9 -0
  109. package/dist/cli/template-files/cms-stack.ts.template +130 -0
  110. package/dist/cli/template-files/deploy-cms.yml.template +125 -13
  111. package/dist/cli/template-files/dockerignore.template +2 -1
  112. package/dist/cli/templates.d.ts +26 -2
  113. package/dist/cli/templates.d.ts.map +1 -1
  114. package/dist/cli/templates.js +36 -4
  115. package/dist/cli/templates.js.map +1 -1
  116. package/dist/client.d.ts +1 -0
  117. package/dist/client.d.ts.map +1 -1
  118. package/dist/client.js.map +1 -1
  119. package/dist/config/schemas/config.d.ts +2 -3
  120. package/dist/config/schemas/config.d.ts.map +1 -1
  121. package/dist/config/schemas/config.js +21 -1
  122. package/dist/config/schemas/config.js.map +1 -1
  123. package/dist/config/schemas/field.d.ts +15 -15
  124. package/dist/config/types.d.ts +2 -2
  125. package/dist/config/types.d.ts.map +1 -1
  126. package/dist/config/types.js +0 -1
  127. package/dist/config/types.js.map +1 -1
  128. package/dist/config/validation.d.ts +18 -0
  129. package/dist/config/validation.d.ts.map +1 -1
  130. package/dist/config/validation.js +36 -12
  131. package/dist/config/validation.js.map +1 -1
  132. package/dist/content-id-index.d.ts +97 -5
  133. package/dist/content-id-index.d.ts.map +1 -1
  134. package/dist/content-id-index.js +170 -18
  135. package/dist/content-id-index.js.map +1 -1
  136. package/dist/content-reader.js +1 -1
  137. package/dist/content-reader.js.map +1 -1
  138. package/dist/content-store.d.ts +92 -7
  139. package/dist/content-store.d.ts.map +1 -1
  140. package/dist/content-store.js +445 -259
  141. package/dist/content-store.js.map +1 -1
  142. package/dist/dev-content-watcher.d.ts.map +1 -1
  143. package/dist/dev-content-watcher.js +4 -1
  144. package/dist/dev-content-watcher.js.map +1 -1
  145. package/dist/editor/BranchManager.d.ts +15 -0
  146. package/dist/editor/BranchManager.d.ts.map +1 -1
  147. package/dist/editor/BranchManager.js +61 -15
  148. package/dist/editor/BranchManager.js.map +1 -1
  149. package/dist/editor/CanopyEditor.d.ts.map +1 -1
  150. package/dist/editor/CanopyEditor.js +3 -3
  151. package/dist/editor/CanopyEditor.js.map +1 -1
  152. package/dist/editor/CanopyEditorPage.d.ts +9 -1
  153. package/dist/editor/CanopyEditorPage.d.ts.map +1 -1
  154. package/dist/editor/CanopyEditorPage.js +9 -2
  155. package/dist/editor/CanopyEditorPage.js.map +1 -1
  156. package/dist/editor/Editor.d.ts +9 -1
  157. package/dist/editor/Editor.d.ts.map +1 -1
  158. package/dist/editor/Editor.js +156 -60
  159. package/dist/editor/Editor.js.map +1 -1
  160. package/dist/editor/FormRenderer.d.ts.map +1 -1
  161. package/dist/editor/FormRenderer.js +12 -0
  162. package/dist/editor/FormRenderer.js.map +1 -1
  163. package/dist/editor/admin/SystemHealthPanel.d.ts.map +1 -1
  164. package/dist/editor/admin/SystemHealthPanel.js +14 -9
  165. package/dist/editor/admin/SystemHealthPanel.js.map +1 -1
  166. package/dist/editor/components/EditorHeader.d.ts +6 -0
  167. package/dist/editor/components/EditorHeader.d.ts.map +1 -1
  168. package/dist/editor/components/EditorHeader.js +76 -23
  169. package/dist/editor/components/EditorHeader.js.map +1 -1
  170. package/dist/editor/components/EntryCreateModal.d.ts +9 -1
  171. package/dist/editor/components/EntryCreateModal.d.ts.map +1 -1
  172. package/dist/editor/components/EntryCreateModal.js +25 -7
  173. package/dist/editor/components/EntryCreateModal.js.map +1 -1
  174. package/dist/editor/context/SWRProvider.d.ts +31 -0
  175. package/dist/editor/context/SWRProvider.d.ts.map +1 -0
  176. package/dist/editor/context/SWRProvider.js +12 -0
  177. package/dist/editor/context/SWRProvider.js.map +1 -0
  178. package/dist/editor/context/index.d.ts +3 -0
  179. package/dist/editor/context/index.d.ts.map +1 -1
  180. package/dist/editor/context/index.js +3 -0
  181. package/dist/editor/context/index.js.map +1 -1
  182. package/dist/editor/editor-utils.d.ts.map +1 -1
  183. package/dist/editor/editor-utils.js +8 -2
  184. package/dist/editor/editor-utils.js.map +1 -1
  185. package/dist/editor/fields/DateTimeField.d.ts +46 -0
  186. package/dist/editor/fields/DateTimeField.d.ts.map +1 -0
  187. package/dist/editor/fields/DateTimeField.js +66 -0
  188. package/dist/editor/fields/DateTimeField.js.map +1 -0
  189. package/dist/editor/fields/ImageField.d.ts.map +1 -1
  190. package/dist/editor/fields/ImageField.js +27 -3
  191. package/dist/editor/fields/ImageField.js.map +1 -1
  192. package/dist/editor/fields/NumberField.d.ts +34 -0
  193. package/dist/editor/fields/NumberField.d.ts.map +1 -0
  194. package/dist/editor/fields/NumberField.js +53 -0
  195. package/dist/editor/fields/NumberField.js.map +1 -0
  196. package/dist/editor/fields/NumberListField.d.ts +25 -0
  197. package/dist/editor/fields/NumberListField.d.ts.map +1 -0
  198. package/dist/editor/fields/NumberListField.js +53 -0
  199. package/dist/editor/fields/NumberListField.js.map +1 -0
  200. package/dist/editor/fields/ReferenceField.d.ts.map +1 -1
  201. package/dist/editor/fields/ReferenceField.js +52 -31
  202. package/dist/editor/fields/ReferenceField.js.map +1 -1
  203. package/dist/editor/hooks/__test__/test-utils.d.ts +38 -3
  204. package/dist/editor/hooks/__test__/test-utils.d.ts.map +1 -1
  205. package/dist/editor/hooks/__test__/test-utils.js +50 -4
  206. package/dist/editor/hooks/__test__/test-utils.js.map +1 -1
  207. package/dist/editor/hooks/index.d.ts +4 -1
  208. package/dist/editor/hooks/index.d.ts.map +1 -1
  209. package/dist/editor/hooks/index.js +4 -1
  210. package/dist/editor/hooks/index.js.map +1 -1
  211. package/dist/editor/hooks/useBranchManager.d.ts +21 -1
  212. package/dist/editor/hooks/useBranchManager.d.ts.map +1 -1
  213. package/dist/editor/hooks/useBranchManager.js +183 -58
  214. package/dist/editor/hooks/useBranchManager.js.map +1 -1
  215. package/dist/editor/hooks/useBranchesData.d.ts +57 -0
  216. package/dist/editor/hooks/useBranchesData.d.ts.map +1 -0
  217. package/dist/editor/hooks/useBranchesData.js +69 -0
  218. package/dist/editor/hooks/useBranchesData.js.map +1 -0
  219. package/dist/editor/hooks/useCommentSystem.d.ts.map +1 -1
  220. package/dist/editor/hooks/useCommentSystem.js +24 -22
  221. package/dist/editor/hooks/useCommentSystem.js.map +1 -1
  222. package/dist/editor/hooks/useCommentsData.d.ts +29 -0
  223. package/dist/editor/hooks/useCommentsData.d.ts.map +1 -0
  224. package/dist/editor/hooks/useCommentsData.js +40 -0
  225. package/dist/editor/hooks/useCommentsData.js.map +1 -0
  226. package/dist/editor/hooks/useDraftManager.d.ts +8 -0
  227. package/dist/editor/hooks/useDraftManager.d.ts.map +1 -1
  228. package/dist/editor/hooks/useDraftManager.js +290 -36
  229. package/dist/editor/hooks/useDraftManager.js.map +1 -1
  230. package/dist/editor/hooks/useEntriesData.d.ts +35 -0
  231. package/dist/editor/hooks/useEntriesData.d.ts.map +1 -0
  232. package/dist/editor/hooks/useEntriesData.js +96 -0
  233. package/dist/editor/hooks/useEntriesData.js.map +1 -0
  234. package/dist/editor/hooks/useEntryManager.d.ts +29 -15
  235. package/dist/editor/hooks/useEntryManager.d.ts.map +1 -1
  236. package/dist/editor/hooks/useEntryManager.js +284 -119
  237. package/dist/editor/hooks/useEntryManager.js.map +1 -1
  238. package/dist/editor/hooks/usePermissionManager.d.ts +2 -2
  239. package/dist/editor/hooks/usePermissionManager.d.ts.map +1 -1
  240. package/dist/editor/hooks/usePermissionManager.js +11 -9
  241. package/dist/editor/hooks/usePermissionManager.js.map +1 -1
  242. package/dist/editor/hooks/useReferenceResolution.d.ts.map +1 -1
  243. package/dist/editor/hooks/useReferenceResolution.js +24 -1
  244. package/dist/editor/hooks/useReferenceResolution.js.map +1 -1
  245. package/dist/editor/media/MediaLibraryBody.d.ts.map +1 -1
  246. package/dist/editor/media/MediaLibraryBody.js +12 -2
  247. package/dist/editor/media/MediaLibraryBody.js.map +1 -1
  248. package/dist/editor/permission-manager/GroupSelector.d.ts.map +1 -1
  249. package/dist/editor/permission-manager/GroupSelector.js +7 -2
  250. package/dist/editor/permission-manager/GroupSelector.js.map +1 -1
  251. package/dist/editor/permission-manager/hooks/useGroupsAndUsers.d.ts +3 -3
  252. package/dist/editor/permission-manager/hooks/useGroupsAndUsers.d.ts.map +1 -1
  253. package/dist/editor/permission-manager/hooks/useGroupsAndUsers.js +1 -0
  254. package/dist/editor/permission-manager/hooks/useGroupsAndUsers.js.map +1 -1
  255. package/dist/editor/permission-manager/types.d.ts +6 -4
  256. package/dist/editor/permission-manager/types.d.ts.map +1 -1
  257. package/dist/editor/test-setup.js +8 -0
  258. package/dist/editor/test-setup.js.map +1 -1
  259. package/dist/editor/theme.d.ts +8 -8
  260. package/dist/git-manager.d.ts +175 -14
  261. package/dist/git-manager.d.ts.map +1 -1
  262. package/dist/git-manager.js +354 -39
  263. package/dist/git-manager.js.map +1 -1
  264. package/dist/github-service.d.ts.map +1 -1
  265. package/dist/github-service.js +13 -7
  266. package/dist/github-service.js.map +1 -1
  267. package/dist/http/handler.d.ts.map +1 -1
  268. package/dist/http/handler.js +78 -29
  269. package/dist/http/handler.js.map +1 -1
  270. package/dist/http/router.d.ts.map +1 -1
  271. package/dist/http/router.js +40 -3
  272. package/dist/http/router.js.map +1 -1
  273. package/dist/operating-mode/client-unsafe-strategy.d.ts.map +1 -1
  274. package/dist/operating-mode/client-unsafe-strategy.js +7 -8
  275. package/dist/operating-mode/client-unsafe-strategy.js.map +1 -1
  276. package/dist/operating-mode/deployment-name-fixtures.d.ts +29 -0
  277. package/dist/operating-mode/deployment-name-fixtures.d.ts.map +1 -0
  278. package/dist/operating-mode/deployment-name-fixtures.js +55 -0
  279. package/dist/operating-mode/deployment-name-fixtures.js.map +1 -0
  280. package/dist/operating-mode/deployment-name.d.ts +37 -0
  281. package/dist/operating-mode/deployment-name.d.ts.map +1 -0
  282. package/dist/operating-mode/deployment-name.js +86 -0
  283. package/dist/operating-mode/deployment-name.js.map +1 -0
  284. package/dist/operating-mode/index.d.ts +6 -1
  285. package/dist/operating-mode/index.d.ts.map +1 -1
  286. package/dist/operating-mode/index.js +10 -1
  287. package/dist/operating-mode/index.js.map +1 -1
  288. package/dist/operating-mode/mode-env.d.ts +58 -0
  289. package/dist/operating-mode/mode-env.d.ts.map +1 -0
  290. package/dist/operating-mode/mode-env.js +97 -0
  291. package/dist/operating-mode/mode-env.js.map +1 -0
  292. package/dist/operating-mode/types.d.ts +13 -3
  293. package/dist/operating-mode/types.d.ts.map +1 -1
  294. package/dist/paths/branch-name.d.ts +38 -0
  295. package/dist/paths/branch-name.d.ts.map +1 -1
  296. package/dist/paths/branch-name.js +46 -0
  297. package/dist/paths/branch-name.js.map +1 -1
  298. package/dist/paths/index.d.ts +1 -1
  299. package/dist/paths/index.d.ts.map +1 -1
  300. package/dist/paths/index.js +1 -1
  301. package/dist/paths/index.js.map +1 -1
  302. package/dist/reference-resolver.d.ts +6 -2
  303. package/dist/reference-resolver.d.ts.map +1 -1
  304. package/dist/reference-resolver.js +8 -1
  305. package/dist/reference-resolver.js.map +1 -1
  306. package/dist/resolve-canopy-user.d.ts +34 -0
  307. package/dist/resolve-canopy-user.d.ts.map +1 -0
  308. package/dist/resolve-canopy-user.js +55 -0
  309. package/dist/resolve-canopy-user.js.map +1 -0
  310. package/dist/schema/meta-loader.d.ts.map +1 -1
  311. package/dist/schema/meta-loader.js +10 -2
  312. package/dist/schema/meta-loader.js.map +1 -1
  313. package/dist/schema/schema-store.d.ts +64 -4
  314. package/dist/schema/schema-store.d.ts.map +1 -1
  315. package/dist/schema/schema-store.js +113 -30
  316. package/dist/schema/schema-store.js.map +1 -1
  317. package/dist/server.d.ts +9 -0
  318. package/dist/server.d.ts.map +1 -1
  319. package/dist/server.js +9 -0
  320. package/dist/server.js.map +1 -1
  321. package/dist/services.d.ts.map +1 -1
  322. package/dist/services.js +40 -6
  323. package/dist/services.js.map +1 -1
  324. package/dist/settings-workspace.d.ts +27 -2
  325. package/dist/settings-workspace.d.ts.map +1 -1
  326. package/dist/settings-workspace.js +142 -58
  327. package/dist/settings-workspace.js.map +1 -1
  328. package/dist/types.d.ts +73 -2
  329. package/dist/types.d.ts.map +1 -1
  330. package/dist/utils/content-write-lock.d.ts +127 -0
  331. package/dist/utils/content-write-lock.d.ts.map +1 -0
  332. package/dist/utils/content-write-lock.js +170 -0
  333. package/dist/utils/content-write-lock.js.map +1 -0
  334. package/dist/utils/debug.d.ts +11 -0
  335. package/dist/utils/debug.d.ts.map +1 -1
  336. package/dist/utils/debug.js +12 -1
  337. package/dist/utils/debug.js.map +1 -1
  338. package/dist/utils/error.d.ts +1 -1
  339. package/dist/utils/error.js +1 -1
  340. package/dist/utils/git.d.ts +54 -0
  341. package/dist/utils/git.d.ts.map +1 -1
  342. package/dist/utils/git.js +91 -0
  343. package/dist/utils/git.js.map +1 -1
  344. package/dist/utils/logger.d.ts +71 -0
  345. package/dist/utils/logger.d.ts.map +1 -0
  346. package/dist/utils/logger.js +85 -0
  347. package/dist/utils/logger.js.map +1 -0
  348. package/dist/utils/sanitize-href.d.ts +28 -3
  349. package/dist/utils/sanitize-href.d.ts.map +1 -1
  350. package/dist/utils/sanitize-href.js +64 -6
  351. package/dist/utils/sanitize-href.js.map +1 -1
  352. package/dist/validation/entry-link-validator.d.ts +1 -1
  353. package/dist/validation/entry-link-validator.js +3 -3
  354. package/dist/validation/entry-link-validator.js.map +1 -1
  355. package/dist/validation/entry-type-reference-validator.d.ts +33 -0
  356. package/dist/validation/entry-type-reference-validator.d.ts.map +1 -0
  357. package/dist/validation/entry-type-reference-validator.js +110 -0
  358. package/dist/validation/entry-type-reference-validator.js.map +1 -0
  359. package/dist/validation/entry-validator.js +1 -1
  360. package/dist/validation/entry-validator.js.map +1 -1
  361. package/dist/worker/cms-worker.d.ts +245 -3
  362. package/dist/worker/cms-worker.d.ts.map +1 -1
  363. package/dist/worker/cms-worker.js +1084 -253
  364. package/dist/worker/cms-worker.js.map +1 -1
  365. package/dist/worker/log.d.ts +61 -0
  366. package/dist/worker/log.d.ts.map +1 -0
  367. package/dist/worker/log.js +74 -0
  368. package/dist/worker/log.js.map +1 -0
  369. package/package.json +4 -2
@@ -0,0 +1,170 @@
1
+ /**
2
+ * [SYNC-C1] Cross-host mutual exclusion between the worker's rebase loop and
3
+ * content writes against the same branch working tree.
4
+ *
5
+ * Production runs a Lambda (API) and an EC2 worker over one EFS filesystem.
6
+ * `ContentStore`'s write/delete/rename critical sections were guarded only by
7
+ * the in-process mutex (`utils/async-mutex`), which does not cross that
8
+ * boundary, while `CmsWorker.rebaseActiveBranches()` rewrites the very same
9
+ * working tree. A write landing after `git rebase` started is destroyed two
10
+ * ways -- `git checkout --theirs <file>` overwrites it with the branch's
11
+ * committed version (and the rebase then SUCCEEDS, logging nothing), and
12
+ * `git rebase --abort` hard-resets the tree. Either way the editor already got
13
+ * its 200. This lock is what serializes the two.
14
+ *
15
+ * **Asymmetric by design.** The worker retries every branch on its next sync
16
+ * cycle (~5 minutes) while the editor is a person waiting on a save, so the
17
+ * worker yields and the writer waits:
18
+ *
19
+ * - Worker: {@link tryAcquireContentWriteLock} -- zero retries. On contention
20
+ * it skips that branch this cycle (an extension of the existing
21
+ * skip-dirty-branches behavior) and picks it up next cycle.
22
+ * - Writers: {@link withContentWriteLock} -- a short bounded wait, then a
23
+ * retriable failure the API surfaces as a 409.
24
+ *
25
+ * **Reads never take this lock.** An extra EFS round-trip on every read is not
26
+ * an acceptable price, and a read racing a rebase gets a consistent-enough
27
+ * older or newer file, never a destroyed one.
28
+ *
29
+ * ## Why the lock is anchored on `{branchRoot}/.canopy-meta`
30
+ *
31
+ * proper-lockfile keys its module-level `locks{}` bookkeeping (refresh timer,
32
+ * release function) by the **target path** passed to `lock()`, NOT by
33
+ * `lockfilePath`. The provisioning lock targets `path.dirname(branchRoot)` --
34
+ * the shared content-branches directory -- so anchoring here on that same
35
+ * directory with a different lock NAME would land on the same registry key:
36
+ * one process holding both (a Lambda container provisioning a workspace and
37
+ * writing content) would have the second acquisition clobber the first's
38
+ * bookkeeping. Targeting `{branchRoot}/.canopy-meta` instead gives:
39
+ *
40
+ * - a distinct in-process registry key from the provisioning lock (and from
41
+ * every other branch's content lock -- the provisioning locks already share
42
+ * one key across branches);
43
+ * - a distinct on-disk lock directory, so the server-enforced mkdir cannot
44
+ * collide either; and
45
+ * - no deadlock potential: the two locks are never both required, and the only
46
+ * possible acquisition order (provision, then write) is consistent.
47
+ *
48
+ * `.canopy-meta/` is git-excluded in every branch clone (`ensureGitExclude`),
49
+ * so the lock directory can never dirty the working tree or be swept into
50
+ * `git add .` at publish time.
51
+ *
52
+ * ## Honest caveat (do not oversell this)
53
+ *
54
+ * `proper-lockfile` decides a holder is dead by reading the lock directory's
55
+ * mtime with `fs.stat`, which on EFS is served through the NFS attribute
56
+ * cache. A live holder refreshes every `stale/2`, but a waiter can read a
57
+ * cached mtime and conclude the lock is stale when it is not, taking it over.
58
+ * The failure mode of a bad takeover is exactly today's behavior (two
59
+ * unsynchronized writers), so this is a strict improvement -- not a proof of
60
+ * mutual exclusion. See docs/concurrency.md.
61
+ */
62
+ import path from 'node:path';
63
+ import { isNodeError } from './error.js';
64
+ import { tryAcquireProvisioningLock } from './provisioning-lock.js';
65
+ /** Directory the lock marker lives in, relative to the branch root. */
66
+ const META_DIR = '.canopy-meta';
67
+ /** On-disk name of the lock marker (a directory, created by mkdir). */
68
+ export const CONTENT_WRITE_LOCK_NAME = 'content-write.lock';
69
+ /**
70
+ * Default bounded wait for a content write.
71
+ *
72
+ * Deliberately short. A rebase holds this lock for a fetch plus a replay plus
73
+ * N conflict rounds of git subprocesses on EFS -- far longer than any wait an
74
+ * interactive save can absorb, and longer than the API's own latency budget.
75
+ * So the wait is not sized to outlast a rebase (nothing reasonable could); it
76
+ * is sized to absorb lock handoff and the short holds of other writers, and to
77
+ * turn everything longer into a fast, explicit "retry" the editor can act on
78
+ * rather than a request that hangs toward the Lambda timeout. The worker
79
+ * revisits the branch next cycle, so the blocked state is transient.
80
+ *
81
+ * This is ALSO the writer-vs-writer budget, not only the rebase one. The lock
82
+ * is per-branch-root, so every write to one branch now serializes behind it
83
+ * where in-process serialization used to be per-entry -- bounded on Lambda
84
+ * (one invocation per container), but real under `next dev` and build-time
85
+ * provisioning across worker processes. The case to watch is a write whose
86
+ * in-lock path triggers a full `idIndex()` rescan of a large tree over EFS:
87
+ * that can plausibly exceed this budget and start 409ing unrelated saves.
88
+ * Making it configurable for prod tuning is tracked in
89
+ * .claude/future-tasks/content-write-lock-tuning-and-granularity.md.
90
+ */
91
+ export const DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS = 2000;
92
+ /**
93
+ * Thrown when the bounded wait expires with the branch's content lock still
94
+ * held. Retriable -- callers translate this into a 409 with a message that
95
+ * says so.
96
+ *
97
+ * The message says "syncing OR another save" because BOTH produce it. The
98
+ * lock is taken by `write`/`delete`/`renameEntry` and by the admin
99
+ * repair-content-duplicates action as well as by the worker's rebase, so a
100
+ * message naming only the rebase was a confident claim about something that
101
+ * may not be happening. `api/content.ts` routes this error ahead of the
102
+ * generic conflict specifically so the editor sees this wording, which makes
103
+ * the wording load-bearing rather than cosmetic.
104
+ */
105
+ export class ContentWriteLockBusyError extends Error {
106
+ constructor(message = 'This branch is busy (syncing with the base branch, or another save is in flight); the change was not saved. Try again in a moment.') {
107
+ super(message);
108
+ this.name = 'ContentWriteLockBusyError';
109
+ }
110
+ }
111
+ /** The directory proper-lockfile anchors on -- also its in-process registry key. */
112
+ function lockTargetDir(branchRoot) {
113
+ return path.join(path.resolve(branchRoot), META_DIR);
114
+ }
115
+ const sleep = (ms) => new Promise((resolve) => {
116
+ setTimeout(resolve, ms);
117
+ });
118
+ /**
119
+ * Acquire the branch's content-write lock WITHOUT waiting.
120
+ *
121
+ * Throws an error with `code === 'ELOCKED'` when a live holder has it. Used by
122
+ * the worker's rebase loop (which skips the branch and retries next cycle) and
123
+ * by tests standing in for a writer.
124
+ */
125
+ export function tryAcquireContentWriteLock(branchRoot) {
126
+ return tryAcquireProvisioningLock(lockTargetDir(branchRoot), CONTENT_WRITE_LOCK_NAME);
127
+ }
128
+ /**
129
+ * Acquire the branch's content-write lock with a short bounded wait.
130
+ *
131
+ * Retries ONLY on genuine contention (`ELOCKED`), the same discipline
132
+ * `withOccFileLock` uses: proper-lockfile's own retry loop retries blindly on
133
+ * any error, which would burn the whole budget re-hitting e.g. ENOENT after
134
+ * the branch directory was deleted out from under the caller.
135
+ *
136
+ * @throws ContentWriteLockBusyError when the budget expires under contention.
137
+ */
138
+ export async function acquireContentWriteLock(branchRoot, waitMs = DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS) {
139
+ const startedAt = Date.now();
140
+ for (let attempt = 0;; attempt++) {
141
+ try {
142
+ return await tryAcquireContentWriteLock(branchRoot);
143
+ }
144
+ catch (err) {
145
+ if (!isNodeError(err) || err.code !== 'ELOCKED')
146
+ throw err;
147
+ const remaining = waitMs - (Date.now() - startedAt);
148
+ if (remaining <= 0)
149
+ throw new ContentWriteLockBusyError();
150
+ // Exponential backoff with jitter, capped so a long budget still polls
151
+ // often enough to pick the lock up promptly when the holder releases.
152
+ const base = Math.min(40 * 2 ** attempt, 250);
153
+ await sleep(Math.min(base * (0.5 + Math.random()), remaining));
154
+ }
155
+ }
156
+ }
157
+ /**
158
+ * Run `fn` holding the branch's content-write lock, releasing it in a
159
+ * `finally` so a throw can never strand it.
160
+ */
161
+ export async function withContentWriteLock(branchRoot, fn, waitMs = DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS) {
162
+ const release = await acquireContentWriteLock(branchRoot, waitMs);
163
+ try {
164
+ return await fn();
165
+ }
166
+ finally {
167
+ await release();
168
+ }
169
+ }
170
+ //# sourceMappingURL=content-write-lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-write-lock.js","sourceRoot":"","sources":["../../src/utils/content-write-lock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACrC,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAA;AAEhE,uEAAuE;AACvE,MAAM,QAAQ,GAAG,cAAc,CAAA;AAE/B,uEAAuE;AACvE,MAAM,CAAC,MAAM,uBAAuB,GAAG,oBAAoB,CAAA;AAE3D;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,MAAM,kCAAkC,GAAG,IAAI,CAAA;AAEtD;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,yBAA0B,SAAQ,KAAK;IAClD,YACE,OAAO,GAAG,oIAAoI;QAE9I,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAA;IACzC,CAAC;CACF;AAED,oFAAoF;AACpF,SAAS,aAAa,CAAC,UAAkB;IACvC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,CAAA;AACtD,CAAC;AAED,MAAM,KAAK,GAAG,CAAC,EAAU,EAAiB,EAAE,CAC1C,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;IACtB,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAA;AACzB,CAAC,CAAC,CAAA;AAEJ;;;;;;GAMG;AACH,MAAM,UAAU,0BAA0B,CAAC,UAAkB;IAC3D,OAAO,0BAA0B,CAAC,aAAa,CAAC,UAAU,CAAC,EAAE,uBAAuB,CAAC,CAAA;AACvF,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,UAAkB,EAClB,SAAiB,kCAAkC;IAEnD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IAC5B,KAAK,IAAI,OAAO,GAAG,CAAC,GAAI,OAAO,EAAE,EAAE,CAAC;QAClC,IAAI,CAAC;YACH,OAAO,MAAM,0BAA0B,CAAC,UAAU,CAAC,CAAA;QACrD,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS;gBAAE,MAAM,GAAG,CAAA;YAC1D,MAAM,SAAS,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,CAAA;YACnD,IAAI,SAAS,IAAI,CAAC;gBAAE,MAAM,IAAI,yBAAyB,EAAE,CAAA;YACzD,uEAAuE;YACvE,sEAAsE;YACtE,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,IAAI,OAAO,EAAE,GAAG,CAAC,CAAA;YAC7C,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,SAAS,CAAC,CAAC,CAAA;QAChE,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,UAAkB,EAClB,EAAoB,EACpB,SAAiB,kCAAkC;IAEnD,MAAM,OAAO,GAAG,MAAM,uBAAuB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAA;IACjE,IAAI,CAAC;QACH,OAAO,MAAM,EAAE,EAAE,CAAA;IACnB,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,EAAE,CAAA;IACjB,CAAC;AACH,CAAC"}
@@ -14,6 +14,17 @@ export declare class DebugLogger {
14
14
  private timers;
15
15
  constructor(options?: DebugOptions);
16
16
  private shouldLog;
17
+ /**
18
+ * The ISO-8601 timestamp leads the line BARE - not wrapped in brackets as it
19
+ * once was. `CANOPYCMS_DEBUG=true` can be set on the EC2 worker, whose stdout
20
+ * appends to /var/log/canopy-worker/worker.log; the CloudWatch agent's
21
+ * `multi_line_start_pattern` matches the timestamp at the START of a line
22
+ * (see worker/log.ts's INVARIANT and cms-service.ts's agent config), so a
23
+ * leading `[` meant every debug line was silently folded into the previous
24
+ * event instead of starting its own. The bracketed `[prefix:category]` and
25
+ * `[level]` fields still follow, so the human-readable shape is unchanged
26
+ * apart from those two characters.
27
+ */
17
28
  private formatMessage;
18
29
  debug(category: string, message: string, data?: unknown): void;
19
30
  info(category: string, message: string, data?: unknown): void;
@@ -1 +1 @@
1
- {"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../../src/utils/debug.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;AAE1D,MAAM,WAAW,YAAY;IAC3B,kEAAkE;IAClE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,sDAAsD;IACtD,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,sEAAsE;IACtE,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB;AASD,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,MAAM,CAAiC;gBAEnC,OAAO,GAAE,YAAiB;IAItC,OAAO,CAAC,SAAS;IASjB,OAAO,CAAC,aAAa;IAMrB,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO;IAMvD,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO;IAMtD,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO;IAMtD,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO;IAcvD;;OAEG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM;IAIlB;;OAEG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAavC;;OAEG;IACG,KAAK,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAQlF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,YAAY,GAAG,WAAW,CAErE;AAED;;;GAGG;AACH,eAAO,MAAM,UAAU,aAIrB,CAAA"}
1
+ {"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../../src/utils/debug.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;AAE1D,MAAM,WAAW,YAAY;IAC3B,kEAAkE;IAClE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,sDAAsD;IACtD,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,sEAAsE;IACtE,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB;AASD,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,MAAM,CAAiC;gBAEnC,OAAO,GAAE,YAAiB;IAItC,OAAO,CAAC,SAAS;IASjB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,aAAa;IAMrB,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO;IAMvD,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO;IAMtD,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO;IAMtD,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO;IAcvD;;OAEG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM;IAIlB;;OAEG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAavC;;OAEG;IACG,KAAK,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAQlF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,YAAY,GAAG,WAAW,CAErE;AAED;;;GAGG;AACH,eAAO,MAAM,UAAU,aAIrB,CAAA"}
@@ -17,10 +17,21 @@ export class DebugLogger {
17
17
  const minLevel = this.options.minLevel ?? 'DEBUG';
18
18
  return LOG_LEVELS[level] >= LOG_LEVELS[minLevel];
19
19
  }
20
+ /**
21
+ * The ISO-8601 timestamp leads the line BARE - not wrapped in brackets as it
22
+ * once was. `CANOPYCMS_DEBUG=true` can be set on the EC2 worker, whose stdout
23
+ * appends to /var/log/canopy-worker/worker.log; the CloudWatch agent's
24
+ * `multi_line_start_pattern` matches the timestamp at the START of a line
25
+ * (see worker/log.ts's INVARIANT and cms-service.ts's agent config), so a
26
+ * leading `[` meant every debug line was silently folded into the previous
27
+ * event instead of starting its own. The bracketed `[prefix:category]` and
28
+ * `[level]` fields still follow, so the human-readable shape is unchanged
29
+ * apart from those two characters.
30
+ */
20
31
  formatMessage(level, category, message) {
21
32
  const timestamp = new Date().toISOString();
22
33
  const prefix = this.options.prefix ?? 'CanopyCMS';
23
- return `[${timestamp}] [${prefix}:${category}] [${level}] ${message}`;
34
+ return `${timestamp} [${prefix}:${category}] [${level}] ${message}`;
24
35
  }
25
36
  debug(category, message, data) {
26
37
  if (this.shouldLog('DEBUG')) {
@@ -1 +1 @@
1
- {"version":3,"file":"debug.js","sourceRoot":"","sources":["../../src/utils/debug.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"debug.js","sourceRoot":"","sources":["../../src/utils/debug.ts"],"names":[],"mappings":"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"}
@@ -15,7 +15,7 @@
15
15
  * try {
16
16
  * await riskyOperation()
17
17
  * } catch (err: unknown) {
18
- * console.error('Operation failed:', getErrorMessage(err))
18
+ * canopyLogError('Operation failed:', getErrorMessage(err))
19
19
  * }
20
20
  * ```
21
21
  */
@@ -15,7 +15,7 @@
15
15
  * try {
16
16
  * await riskyOperation()
17
17
  * } catch (err: unknown) {
18
- * console.error('Operation failed:', getErrorMessage(err))
18
+ * canopyLogError('Operation failed:', getErrorMessage(err))
19
19
  * }
20
20
  * ```
21
21
  */
@@ -18,6 +18,60 @@ import type { OperatingMode } from '../operating-mode';
18
18
  * `@`-required heuristic for scp-like syntax.
19
19
  */
20
20
  export declare function isNetworkRemoteUrl(url: string): boolean;
21
+ /**
22
+ * Whether a git push failure message is git's non-fast-forward rejection --
23
+ * the remote has diverged (moved ahead with commits this side never fetched),
24
+ * so retrying the IDENTICAL push can never succeed. In CanopyCMS this is the
25
+ * signature of two CanopyCMS deployments sharing one GitHub repo and picking
26
+ * the same content-branch name, or someone pushing directly to GitHub.
27
+ *
28
+ * Deliberately narrow: ordinary transient push failures -- network drops,
29
+ * auth/permission denial, lock contention -- must keep retrying with backoff
30
+ * (see worker/cms-worker.ts's `isPermanentTaskFailure`, which treats git
31
+ * failures as transient precisely so those cases still get retried). Only
32
+ * this specific, structurally-unretryable shape should fail fast (worker) or
33
+ * return 409 (API) instead.
34
+ *
35
+ * Matches git's literal (English) rejection text, which is gettext-
36
+ * translated -- callers MUST run the underlying git command with a
37
+ * locale-pinning env (see `gitChildEnv` in `../git-manager.ts`) or an ambient
38
+ * LANG/LC_ALL could silently turn this into a permanent no-op.
39
+ */
40
+ export declare function isNonFastForwardRejection(message: string): boolean;
41
+ /**
42
+ * Whether a git push failure message is a refused `--force-with-lease` --
43
+ * the remote ref is NOT at the commit the pusher expected, so the forced
44
+ * update was declined and nothing was overwritten.
45
+ *
46
+ * A SEPARATE predicate from `isNonFastForwardRejection` because git's output
47
+ * for the two is disjoint: a refused lease prints
48
+ * ` ! [rejected] branch -> branch (stale info)` and, unlike an
49
+ * ordinary rejection, emits NEITHER `non-fast-forward`/`fetch first` NOR the
50
+ * `Updates were rejected because` hint. Without this predicate every lease
51
+ * refusal would be classified transient and retried -- three identical,
52
+ * guaranteed-to-fail force attempts -- instead of surfacing as the permanent,
53
+ * human-actionable state it is.
54
+ *
55
+ * Same locale caveat as above: the reason text is gettext-translated, so
56
+ * callers MUST pin the locale (`gitChildEnv`/`gitNetworkChildEnv`).
57
+ */
58
+ export declare function isStaleLeaseRejection(message: string): boolean;
59
+ /**
60
+ * Whether a `git fetch <remote> <branch>` failure is specifically "that ref
61
+ * doesn't exist on the remote" -- the ONLY benign fetch outcome, meaning the
62
+ * branch has never been pushed.
63
+ *
64
+ * Narrow on purpose, and the narrowness is the whole point. This predicate
65
+ * exists because a bare `catch` around the fetch classified EVERY failure --
66
+ * unreachable remote, auth denial, permission error on the workspace, corrupt
67
+ * object store -- as "nothing to pull", which callers then logged as normal
68
+ * and proceeded past. Anything this does not recognize must reach the caller
69
+ * as the genuine error it is.
70
+ *
71
+ * Same locale caveat as the predicates above: the text is gettext-translated,
72
+ * so callers MUST run git with a locale-pinning env (`gitChildEnv`).
73
+ */
74
+ export declare function isMissingRemoteRefFailure(message: string): boolean;
21
75
  /**
22
76
  * Detect the current HEAD branch name for a given repository root.
23
77
  * Returns the branch name, or the provided fallback (default 'main')
@@ -1 +1 @@
1
- {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/utils/git.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAmBtD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOvD;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,MAAe,GACxB,OAAO,CAAC,MAAM,CAAC,CAQjB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,IAAI,EAAE,aAAa,CAAA;IACnB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,GAAG,OAAO,CAAC,MAAM,CAAC,CAMlB"}
1
+ {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/utils/git.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAmBtD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOvD;AAyBD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAKlE;AAMD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE9D;AAOD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAElE;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,MAAe,GACxB,OAAO,CAAC,MAAM,CAAC,CAQjB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,IAAI,EAAE,aAAa,CAAA;IACnB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,GAAG,OAAO,CAAC,MAAM,CAAC,CAMlB"}
package/dist/utils/git.js CHANGED
@@ -46,6 +46,97 @@ export function isNetworkRemoteUrl(url) {
46
46
  return false;
47
47
  return COLON_BEFORE_SLASH_PATTERN.test(url);
48
48
  }
49
+ // Matches git's per-ref push-status summary line marking a ref as rejected.
50
+ // Present verbatim in BOTH output shapes CanopyCMS's push call sites produce:
51
+ // the plain CLI form (`GitManager.push()`'s `.raw(['push', ...])`, e.g.
52
+ // ` ! [rejected] branch -> branch (non-fast-forward)`) and the
53
+ // machine-readable `--porcelain` form simple-git's `.push()` wrapper requests
54
+ // (`CmsWorker.pushBranchToGitHub`/`pushSettingsBranches`, e.g.
55
+ // `!\trefs/heads/branch:refs/heads/branch\t[rejected] (non-fast-forward)`) --
56
+ // `--porcelain` only changes the per-ref summary's leading fields, not this
57
+ // trailing human-readable bracket text.
58
+ const REJECTED_MARKER = '[rejected]';
59
+ // git's two names for the SAME underlying condition: `non-fast-forward` fires
60
+ // when the pusher's own remote-tracking ref is present but stale;
61
+ // `fetch first` fires when no local knowledge of the remote ref exists at all
62
+ // (e.g. it was never fetched). Both mean the remote has commits this side
63
+ // doesn't have -- retrying the identical push can never succeed until the
64
+ // caller fetches and integrates (or picks a different branch name).
65
+ const NON_FAST_FORWARD_REASONS = ['non-fast-forward', 'fetch first'];
66
+ // git always prints this stderr hint immediately after a rejected push,
67
+ // regardless of --porcelain (porcelain only affects the machine-readable
68
+ // summary line, not the human hint text that follows it) -- a second,
69
+ // independent signal for the same condition.
70
+ const REJECTION_HINT = 'Updates were rejected because';
71
+ /**
72
+ * Whether a git push failure message is git's non-fast-forward rejection --
73
+ * the remote has diverged (moved ahead with commits this side never fetched),
74
+ * so retrying the IDENTICAL push can never succeed. In CanopyCMS this is the
75
+ * signature of two CanopyCMS deployments sharing one GitHub repo and picking
76
+ * the same content-branch name, or someone pushing directly to GitHub.
77
+ *
78
+ * Deliberately narrow: ordinary transient push failures -- network drops,
79
+ * auth/permission denial, lock contention -- must keep retrying with backoff
80
+ * (see worker/cms-worker.ts's `isPermanentTaskFailure`, which treats git
81
+ * failures as transient precisely so those cases still get retried). Only
82
+ * this specific, structurally-unretryable shape should fail fast (worker) or
83
+ * return 409 (API) instead.
84
+ *
85
+ * Matches git's literal (English) rejection text, which is gettext-
86
+ * translated -- callers MUST run the underlying git command with a
87
+ * locale-pinning env (see `gitChildEnv` in `../git-manager.ts`) or an ambient
88
+ * LANG/LC_ALL could silently turn this into a permanent no-op.
89
+ */
90
+ export function isNonFastForwardRejection(message) {
91
+ const hasNonFastForwardRejection = message.includes(REJECTED_MARKER) &&
92
+ NON_FAST_FORWARD_REASONS.some((reason) => message.includes(reason));
93
+ return hasNonFastForwardRejection || message.includes(REJECTION_HINT);
94
+ }
95
+ // git's reason text when a `--force-with-lease=<ref>:<sha>` push is refused
96
+ // because the remote is not at `<sha>`.
97
+ const STALE_LEASE_REASON = 'stale info';
98
+ /**
99
+ * Whether a git push failure message is a refused `--force-with-lease` --
100
+ * the remote ref is NOT at the commit the pusher expected, so the forced
101
+ * update was declined and nothing was overwritten.
102
+ *
103
+ * A SEPARATE predicate from `isNonFastForwardRejection` because git's output
104
+ * for the two is disjoint: a refused lease prints
105
+ * ` ! [rejected] branch -> branch (stale info)` and, unlike an
106
+ * ordinary rejection, emits NEITHER `non-fast-forward`/`fetch first` NOR the
107
+ * `Updates were rejected because` hint. Without this predicate every lease
108
+ * refusal would be classified transient and retried -- three identical,
109
+ * guaranteed-to-fail force attempts -- instead of surfacing as the permanent,
110
+ * human-actionable state it is.
111
+ *
112
+ * Same locale caveat as above: the reason text is gettext-translated, so
113
+ * callers MUST pin the locale (`gitChildEnv`/`gitNetworkChildEnv`).
114
+ */
115
+ export function isStaleLeaseRejection(message) {
116
+ return message.includes(REJECTED_MARKER) && message.includes(STALE_LEASE_REASON);
117
+ }
118
+ // git's message when `git fetch <remote> <branch>` names a ref the remote does
119
+ // not have. Both spellings occur: modern git prints the lowercase form, older
120
+ // versions and some transports capitalize it.
121
+ const MISSING_REMOTE_REF_REASONS = ["couldn't find remote ref", "Couldn't find remote ref"];
122
+ /**
123
+ * Whether a `git fetch <remote> <branch>` failure is specifically "that ref
124
+ * doesn't exist on the remote" -- the ONLY benign fetch outcome, meaning the
125
+ * branch has never been pushed.
126
+ *
127
+ * Narrow on purpose, and the narrowness is the whole point. This predicate
128
+ * exists because a bare `catch` around the fetch classified EVERY failure --
129
+ * unreachable remote, auth denial, permission error on the workspace, corrupt
130
+ * object store -- as "nothing to pull", which callers then logged as normal
131
+ * and proceeded past. Anything this does not recognize must reach the caller
132
+ * as the genuine error it is.
133
+ *
134
+ * Same locale caveat as the predicates above: the text is gettext-translated,
135
+ * so callers MUST run git with a locale-pinning env (`gitChildEnv`).
136
+ */
137
+ export function isMissingRemoteRefFailure(message) {
138
+ return MISSING_REMOTE_REF_REASONS.some((reason) => message.includes(reason));
139
+ }
49
140
  /**
50
141
  * Detect the current HEAD branch name for a given repository root.
51
142
  * Returns the branch name, or the provided fallback (default 'main')
@@ -1 +1 @@
1
- {"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/utils/git.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAItC,wEAAwE;AACxE,MAAM,sBAAsB,GAAG,yBAAyB,CAAA;AACxD,6EAA6E;AAC7E,8EAA8E;AAC9E,6DAA6D;AAC7D,MAAM,wBAAwB,GAAG,uBAAuB,CAAA;AACxD,4EAA4E;AAC5E,4EAA4E;AAC5E,MAAM,sBAAsB,GAAG,IAAI,CAAA;AACnC,uEAAuE;AACvE,6EAA6E;AAC7E,8EAA8E;AAC9E,uEAAuE;AACvE,8BAA8B;AAC9B,MAAM,qBAAqB,GAAG,iBAAiB,CAAA;AAC/C,MAAM,0BAA0B,GAAG,aAAa,CAAA;AAEhD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAW;IAC5C,IAAI,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAA;IACzC,IAAI,sBAAsB,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IACjD,IAAI,wBAAwB,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IACnD,IAAI,sBAAsB,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IACjD,IAAI,qBAAqB,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAA;IACjD,OAAO,0BAA0B,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAC7C,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,QAAgB,EAChB,WAAmB,MAAM;IAEzB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,SAAS,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAA;QAC5C,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,QAAQ,CAAC,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;QAClE,OAAO,IAAI,IAAI,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAA;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,QAAQ,CAAA;IACjB,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,OAKvC;IACC,IAAI,OAAO,CAAC,iBAAiB;QAAE,OAAO,OAAO,CAAC,iBAAiB,CAAA;IAC/D,IAAI,OAAO,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QAC3B,OAAO,gBAAgB,CAAC,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;IAC9D,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC"}
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/utils/git.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Process-scoped logger indirection for modules that run in BOTH the worker
3
+ * daemon and the Lambda/dev server.
4
+ *
5
+ * ## Why this exists
6
+ *
7
+ * `worker/log.ts` documents a hard invariant: every line written to
8
+ * `/var/log/canopy-worker/worker.log` must start with an ISO-8601 timestamp,
9
+ * because the CloudWatch agent's `multi_line_start_pattern` is keyed on that
10
+ * prefix (see `canopycms-cdk/src/constructs/cms-service.ts`). A line WITHOUT
11
+ * the prefix is not a new event - it is appended to the PREVIOUS one,
12
+ * inheriting that event's timestamp and carrying none of its own severity tag.
13
+ *
14
+ * The worker's own code honoured that by calling `workerLog*`. But the worker
15
+ * process also executes shared modules that were never worker-specific and
16
+ * called bare `console.warn`: `github-service.ts` (the throttling plugin's
17
+ * rate-limit callbacks fire on the worker's own Octokit, and the PR
18
+ * create/update path runs on the worker's submit task) and `branch-registry.ts`
19
+ * (registry regeneration, reached from every worker `meta.save()`). Their
20
+ * output landed in worker.log unprefixed, so an operationally interesting
21
+ * warning - "GitHub throttled us" - was silently folded into a minutes-old
22
+ * event and became invisible to `filter @message like /WARN/`. Invisible
23
+ * locally too, since vitest intercepts console.
24
+ *
25
+ * ## Why process-scoped rather than a threaded parameter
26
+ *
27
+ * The invariant is a property of the PROCESS's stdout/stderr, not of any
28
+ * particular call: the same `branch-registry.ts` line is correct as bare
29
+ * console under Lambda and wrong under the worker. Threading a logger down to
30
+ * it would mean touching every signature between `CmsWorker.rebase*` and
31
+ * `BranchMetadataFileManager.loadOnly` to carry a logging concern. One
32
+ * process-wide switch, set once at the worker entrypoint, expresses exactly
33
+ * the thing that is actually true.
34
+ *
35
+ * Lambda and the dev server install nothing and keep plain `console`, which is
36
+ * correct there - CloudWatch's Lambda integration stamps and delimits events
37
+ * on its own.
38
+ *
39
+ * ## Usage
40
+ *
41
+ * Shared server-side modules call `canopyLogWarn`/`canopyLogError`/`canopyLog`
42
+ * instead of `console.*`. The worker entrypoint calls `installWorkerLogger()`
43
+ * (`worker/log.ts`, re-exported from `worker/cms-worker.ts`) once, before doing
44
+ * any work.
45
+ *
46
+ * Not for browser/editor code: those keep `console` directly. This module is
47
+ * dependency-free so importing it can never drag a server-only dependency into
48
+ * a client bundle, but there is nothing for it to do in a browser.
49
+ */
50
+ /** The three levels the shared modules use. Structurally satisfied by `console`. */
51
+ export interface CanopyLogger {
52
+ log(...args: unknown[]): void;
53
+ warn(...args: unknown[]): void;
54
+ error(...args: unknown[]): void;
55
+ }
56
+ /**
57
+ * Route subsequent `canopyLog*` calls through `logger`. Idempotent, and safe to
58
+ * call after the modules that use the helpers have already been imported.
59
+ */
60
+ export declare function setCanopyLogger(logger: CanopyLogger): void;
61
+ /** Restore the default (`console`). Exists for tests; production installs once and never reverts. */
62
+ export declare function resetCanopyLogger(): void;
63
+ /** The logger currently installed. Exported for assertions; prefer the helpers below. */
64
+ export declare function getCanopyLogger(): CanopyLogger;
65
+ /** Informational. Routes to stdout (or the worker's timestamped stdout). */
66
+ export declare function canopyLog(...args: unknown[]): void;
67
+ /** Recoverable problem - the caller carries on. Routes to stderr. */
68
+ export declare function canopyLogWarn(...args: unknown[]): void;
69
+ /** Failure worth alerting on. Routes to stderr. */
70
+ export declare function canopyLogError(...args: unknown[]): void;
71
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAEH,oFAAoF;AACpF,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;IAC7B,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;IAC9B,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;CAChC;AAWD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAE1D;AAED,qGAAqG;AACrG,wBAAgB,iBAAiB,IAAI,IAAI,CAExC;AAED,yFAAyF;AACzF,wBAAgB,eAAe,IAAI,YAAY,CAE9C;AAED,4EAA4E;AAC5E,wBAAgB,SAAS,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAElD;AAED,qEAAqE;AACrE,wBAAgB,aAAa,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAEtD;AAED,mDAAmD;AACnD,wBAAgB,cAAc,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAEvD"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Process-scoped logger indirection for modules that run in BOTH the worker
3
+ * daemon and the Lambda/dev server.
4
+ *
5
+ * ## Why this exists
6
+ *
7
+ * `worker/log.ts` documents a hard invariant: every line written to
8
+ * `/var/log/canopy-worker/worker.log` must start with an ISO-8601 timestamp,
9
+ * because the CloudWatch agent's `multi_line_start_pattern` is keyed on that
10
+ * prefix (see `canopycms-cdk/src/constructs/cms-service.ts`). A line WITHOUT
11
+ * the prefix is not a new event - it is appended to the PREVIOUS one,
12
+ * inheriting that event's timestamp and carrying none of its own severity tag.
13
+ *
14
+ * The worker's own code honoured that by calling `workerLog*`. But the worker
15
+ * process also executes shared modules that were never worker-specific and
16
+ * called bare `console.warn`: `github-service.ts` (the throttling plugin's
17
+ * rate-limit callbacks fire on the worker's own Octokit, and the PR
18
+ * create/update path runs on the worker's submit task) and `branch-registry.ts`
19
+ * (registry regeneration, reached from every worker `meta.save()`). Their
20
+ * output landed in worker.log unprefixed, so an operationally interesting
21
+ * warning - "GitHub throttled us" - was silently folded into a minutes-old
22
+ * event and became invisible to `filter @message like /WARN/`. Invisible
23
+ * locally too, since vitest intercepts console.
24
+ *
25
+ * ## Why process-scoped rather than a threaded parameter
26
+ *
27
+ * The invariant is a property of the PROCESS's stdout/stderr, not of any
28
+ * particular call: the same `branch-registry.ts` line is correct as bare
29
+ * console under Lambda and wrong under the worker. Threading a logger down to
30
+ * it would mean touching every signature between `CmsWorker.rebase*` and
31
+ * `BranchMetadataFileManager.loadOnly` to carry a logging concern. One
32
+ * process-wide switch, set once at the worker entrypoint, expresses exactly
33
+ * the thing that is actually true.
34
+ *
35
+ * Lambda and the dev server install nothing and keep plain `console`, which is
36
+ * correct there - CloudWatch's Lambda integration stamps and delimits events
37
+ * on its own.
38
+ *
39
+ * ## Usage
40
+ *
41
+ * Shared server-side modules call `canopyLogWarn`/`canopyLogError`/`canopyLog`
42
+ * instead of `console.*`. The worker entrypoint calls `installWorkerLogger()`
43
+ * (`worker/log.ts`, re-exported from `worker/cms-worker.ts`) once, before doing
44
+ * any work.
45
+ *
46
+ * Not for browser/editor code: those keep `console` directly. This module is
47
+ * dependency-free so importing it can never drag a server-only dependency into
48
+ * a client bundle, but there is nothing for it to do in a browser.
49
+ */
50
+ /**
51
+ * Deliberately mutable module state, read at CALL time by the helpers below
52
+ * rather than captured at import time. Module import order is not something a
53
+ * call site controls, so a captured reference would mean whether a line got its
54
+ * prefix depended on whether `github-service.ts` happened to be imported before
55
+ * or after the entrypoint installed the worker logger.
56
+ */
57
+ let active = console;
58
+ /**
59
+ * Route subsequent `canopyLog*` calls through `logger`. Idempotent, and safe to
60
+ * call after the modules that use the helpers have already been imported.
61
+ */
62
+ export function setCanopyLogger(logger) {
63
+ active = logger;
64
+ }
65
+ /** Restore the default (`console`). Exists for tests; production installs once and never reverts. */
66
+ export function resetCanopyLogger() {
67
+ active = console;
68
+ }
69
+ /** The logger currently installed. Exported for assertions; prefer the helpers below. */
70
+ export function getCanopyLogger() {
71
+ return active;
72
+ }
73
+ /** Informational. Routes to stdout (or the worker's timestamped stdout). */
74
+ export function canopyLog(...args) {
75
+ active.log(...args);
76
+ }
77
+ /** Recoverable problem - the caller carries on. Routes to stderr. */
78
+ export function canopyLogWarn(...args) {
79
+ active.warn(...args);
80
+ }
81
+ /** Failure worth alerting on. Routes to stderr. */
82
+ export function canopyLogError(...args) {
83
+ active.error(...args);
84
+ }
85
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AASH;;;;;;GAMG;AACH,IAAI,MAAM,GAAiB,OAAO,CAAA;AAElC;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,MAAoB;IAClD,MAAM,GAAG,MAAM,CAAA;AACjB,CAAC;AAED,qGAAqG;AACrG,MAAM,UAAU,iBAAiB;IAC/B,MAAM,GAAG,OAAO,CAAA;AAClB,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,eAAe;IAC7B,OAAO,MAAM,CAAA;AACf,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,SAAS,CAAC,GAAG,IAAe;IAC1C,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAA;AACrB,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,aAAa,CAAC,GAAG,IAAe;IAC9C,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAA;AACtB,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,cAAc,CAAC,GAAG,IAAe;IAC/C,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAA;AACvB,CAAC"}