canopycms 0.0.68-int.98 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (688) hide show
  1. package/CHANGELOG.md +2131 -0
  2. package/README.md +47 -63
  3. package/dist/ai/generate.d.ts +5 -0
  4. package/dist/ai/generate.js +244 -115
  5. package/dist/ai/index.d.ts +1 -1
  6. package/dist/ai/json-to-markdown.d.ts +21 -1
  7. package/dist/ai/json-to-markdown.js +120 -57
  8. package/dist/ai/references.d.ts +72 -0
  9. package/dist/ai/references.js +223 -0
  10. package/dist/ai/resolve-branch.js +9 -2
  11. package/dist/ai/to-plain-text.d.ts +17 -8
  12. package/dist/ai/to-plain-text.js +18 -15
  13. package/dist/ai/types.d.ts +84 -3
  14. package/dist/api/admin-branch-health.d.ts +24 -1
  15. package/dist/api/admin-branch-health.js +52 -20
  16. package/dist/api/admin.d.ts +23 -5
  17. package/dist/api/admin.js +36 -1
  18. package/dist/api/assets.js +88 -78
  19. package/dist/api/branch-create-window.d.ts +12 -0
  20. package/dist/api/branch-create-window.js +13 -0
  21. package/dist/api/branch-merge.d.ts +1 -1
  22. package/dist/api/branch-merge.js +1 -1
  23. package/dist/api/branch-review.d.ts +3 -3
  24. package/dist/api/branch-review.js +16 -7
  25. package/dist/api/branch-status.d.ts +16 -6
  26. package/dist/api/branch-status.js +57 -12
  27. package/dist/api/branch-withdraw.d.ts +2 -2
  28. package/dist/api/branch-withdraw.js +8 -3
  29. package/dist/api/branch.d.ts +25 -12
  30. package/dist/api/branch.js +164 -103
  31. package/dist/api/client.d.ts +78 -58
  32. package/dist/api/client.js +150 -117
  33. package/dist/api/comments.d.ts +12 -3
  34. package/dist/api/comments.js +118 -9
  35. package/dist/api/content.d.ts +9 -9
  36. package/dist/api/content.js +131 -22
  37. package/dist/api/entries-constants.d.ts +14 -3
  38. package/dist/api/entries-constants.js +14 -3
  39. package/dist/api/entries.d.ts +40 -2
  40. package/dist/api/entries.js +102 -6
  41. package/dist/api/github-sync.d.ts +19 -0
  42. package/dist/api/github-sync.js +50 -3
  43. package/dist/api/groups.d.ts +4 -4
  44. package/dist/api/groups.js +2 -1
  45. package/dist/api/guards.js +23 -3
  46. package/dist/api/index.d.ts +3 -1
  47. package/dist/api/index.js +1 -0
  48. package/dist/api/permissions.d.ts +21 -2
  49. package/dist/api/permissions.js +50 -3
  50. package/dist/api/request-deadline.d.ts +59 -0
  51. package/dist/api/request-deadline.js +73 -0
  52. package/dist/api/resolve-references.js +8 -7
  53. package/dist/api/route-builder.d.ts +14 -0
  54. package/dist/api/route-builder.js +14 -1
  55. package/dist/api/schema.d.ts +11 -9
  56. package/dist/api/schema.js +2 -0
  57. package/dist/api/settings-helpers.d.ts +5 -1
  58. package/dist/api/settings-helpers.js +10 -2
  59. package/dist/api/types.d.ts +18 -0
  60. package/dist/api/users-constants.d.ts +6 -0
  61. package/dist/api/users-constants.js +6 -0
  62. package/dist/assets/asset-url.d.ts +28 -16
  63. package/dist/assets/asset-url.js +51 -25
  64. package/dist/assets/finalize.js +3 -0
  65. package/dist/assets/index.d.ts +1 -1
  66. package/dist/assets/materialize.d.ts +153 -0
  67. package/dist/assets/materialize.js +402 -0
  68. package/dist/assets/store-local.d.ts +19 -5
  69. package/dist/assets/store-local.js +69 -10
  70. package/dist/assets/store-s3.d.ts +38 -7
  71. package/dist/assets/store-s3.js +128 -22
  72. package/dist/assets/transform-directives.d.ts +52 -13
  73. package/dist/assets/transform-directives.js +87 -27
  74. package/dist/assets/transform.d.ts +3 -3
  75. package/dist/assets/transform.js +13 -8
  76. package/dist/assets/types.d.ts +31 -4
  77. package/dist/auth/cache.d.ts +1 -1
  78. package/dist/auth/cache.js +1 -1
  79. package/dist/auth/caching-auth-plugin.d.ts +2 -0
  80. package/dist/auth/caching-auth-plugin.js +12 -2
  81. package/dist/auth/file-based-auth-cache.d.ts +8 -0
  82. package/dist/auth/file-based-auth-cache.js +18 -8
  83. package/dist/auth/person-name.d.ts +5 -0
  84. package/dist/auth/person-name.js +8 -0
  85. package/dist/auth/plugin.d.ts +10 -1
  86. package/dist/auth/types.d.ts +2 -0
  87. package/dist/auth/user-metadata-lookup.d.ts +21 -0
  88. package/dist/auth/user-metadata-lookup.js +116 -0
  89. package/dist/authorization/branch.d.ts +1 -1
  90. package/dist/authorization/branch.js +12 -13
  91. package/dist/authorization/groups/schema.d.ts +4 -4
  92. package/dist/authorization/helpers.d.ts +8 -0
  93. package/dist/authorization/helpers.js +8 -0
  94. package/dist/authorization/index.d.ts +1 -1
  95. package/dist/authorization/index.js +1 -1
  96. package/dist/authorization/protected-branch.d.ts +5 -4
  97. package/dist/authorization/protected-branch.js +7 -5
  98. package/dist/branch-health.d.ts +35 -8
  99. package/dist/branch-health.js +54 -18
  100. package/dist/branch-metadata-error.d.ts +27 -0
  101. package/dist/branch-metadata-error.js +23 -0
  102. package/dist/branch-metadata-file.d.ts +205 -24
  103. package/dist/branch-metadata-file.js +126 -25
  104. package/dist/branch-metadata.d.ts +45 -6
  105. package/dist/branch-metadata.js +142 -50
  106. package/dist/branch-provisioning.d.ts +167 -0
  107. package/dist/branch-provisioning.js +501 -0
  108. package/dist/branch-registry.d.ts +8 -0
  109. package/dist/branch-registry.js +61 -9
  110. package/dist/branch-schema-cache.d.ts +29 -12
  111. package/dist/branch-schema-cache.js +64 -21
  112. package/dist/branch-sparse.d.ts +23 -0
  113. package/dist/branch-sparse.js +118 -0
  114. package/dist/branch-workspace.d.ts +42 -3
  115. package/dist/branch-workspace.js +239 -100
  116. package/dist/build/asset-refs.d.ts +94 -0
  117. package/dist/build/asset-refs.js +267 -0
  118. package/dist/build/generate-ai-content.js +5 -0
  119. package/dist/build/inline-flight.d.ts +37 -0
  120. package/dist/build/inline-flight.js +142 -0
  121. package/dist/cli/asset-refs.d.ts +52 -0
  122. package/dist/cli/asset-refs.js +194 -0
  123. package/dist/cli/cli.d.ts +15 -1
  124. package/dist/cli/cli.js +8543 -3193
  125. package/dist/cli/configured-asset-store.d.ts +12 -0
  126. package/dist/cli/configured-asset-store.js +46 -0
  127. package/dist/cli/generate-ai-content.js +4244 -1072
  128. package/dist/cli/init.js +217 -126
  129. package/dist/cli/project-detect.d.ts +2 -0
  130. package/dist/cli/project-detect.js +2 -0
  131. package/dist/cli/template-files/Dockerfile.cms.template +13 -5
  132. package/dist/cli/template-files/cms-stack.ts.template +16 -13
  133. package/dist/client.d.ts +1 -2
  134. package/dist/client.js +1 -2
  135. package/dist/comment-store.d.ts +25 -0
  136. package/dist/comment-store.js +43 -8
  137. package/dist/config/flatten.js +2 -0
  138. package/dist/config/helpers.js +2 -2
  139. package/dist/config/index.d.ts +1 -1
  140. package/dist/config/schemas/config.d.ts +12 -29
  141. package/dist/config/schemas/config.js +9 -12
  142. package/dist/config/schemas/field.d.ts +3 -0
  143. package/dist/config/schemas/field.js +58 -4
  144. package/dist/config/schemas/media.d.ts +4 -13
  145. package/dist/config/schemas/media.js +5 -8
  146. package/dist/config/schemas/url.d.ts +2 -20
  147. package/dist/config/schemas/url.js +2 -25
  148. package/dist/config/types.d.ts +116 -22
  149. package/dist/config/types.js +3 -9
  150. package/dist/config/validation.d.ts +25 -4
  151. package/dist/config/validation.js +116 -3
  152. package/dist/content-id-index.js +2 -2
  153. package/dist/content-index-generation.d.ts +1 -1
  154. package/dist/content-index-generation.js +1 -1
  155. package/dist/content-listing.d.ts +10 -1
  156. package/dist/content-listing.js +22 -5
  157. package/dist/content-reader.d.ts +9 -0
  158. package/dist/content-reader.js +16 -11
  159. package/dist/content-store.d.ts +75 -12
  160. package/dist/content-store.js +249 -86
  161. package/dist/content-tree.js +2 -2
  162. package/dist/context.js +5 -2
  163. package/dist/dangling-reference-log.d.ts +17 -0
  164. package/dist/dangling-reference-log.js +29 -0
  165. package/dist/dev-content-watcher.d.ts +1 -1
  166. package/dist/dev-content-watcher.js +1 -1
  167. package/dist/editor/BranchManager.d.ts +23 -7
  168. package/dist/editor/BranchManager.js +119 -54
  169. package/dist/editor/CanopyEditor.d.ts +1 -1
  170. package/dist/editor/CanopyEditor.js +1 -2
  171. package/dist/editor/CommentsPanel.d.ts +14 -0
  172. package/dist/editor/CommentsPanel.js +68 -43
  173. package/dist/editor/Editor.d.ts +11 -5
  174. package/dist/editor/Editor.js +891 -218
  175. package/dist/editor/EditorAuthGate.d.ts +7 -1
  176. package/dist/editor/EditorAuthGate.js +41 -9
  177. package/dist/editor/EntryNavigator.d.ts +22 -0
  178. package/dist/editor/EntryNavigator.js +353 -105
  179. package/dist/editor/FormHeader.d.ts +25 -0
  180. package/dist/editor/FormHeader.js +82 -0
  181. package/dist/editor/FormRenderer.d.ts +27 -3
  182. package/dist/editor/FormRenderer.js +171 -76
  183. package/dist/editor/PreviewFrame.d.ts +40 -0
  184. package/dist/editor/PreviewFrame.js +215 -0
  185. package/dist/editor/PreviewPane.d.ts +47 -0
  186. package/dist/editor/PreviewPane.js +147 -0
  187. package/dist/editor/PreviewToolbarTools.d.ts +22 -0
  188. package/dist/editor/PreviewToolbarTools.js +73 -0
  189. package/dist/editor/admin/SystemHealthPanel.d.ts +2 -1
  190. package/dist/editor/admin/SystemHealthPanel.js +163 -33
  191. package/dist/editor/admin/useSystemHealth.d.ts +18 -0
  192. package/dist/editor/admin/useSystemHealth.js +70 -1
  193. package/dist/editor/branch-status.d.ts +17 -0
  194. package/dist/editor/branch-status.js +29 -0
  195. package/dist/editor/canopy-path.d.ts +9 -6
  196. package/dist/editor/canopy-path.js +8 -11
  197. package/dist/editor/client-reference-resolver.d.ts +43 -16
  198. package/dist/editor/client-reference-resolver.js +155 -64
  199. package/dist/editor/comments/BranchComments.d.ts +1 -1
  200. package/dist/editor/comments/BranchComments.js +2 -2
  201. package/dist/editor/comments/CloseRefusedNotice.d.ts +4 -0
  202. package/dist/editor/comments/CloseRefusedNotice.js +7 -0
  203. package/dist/editor/comments/CommentThreadsPopover.d.ts +47 -0
  204. package/dist/editor/comments/CommentThreadsPopover.js +274 -0
  205. package/dist/editor/comments/CommentWriteAlert.d.ts +11 -0
  206. package/dist/editor/comments/CommentWriteAlert.js +13 -0
  207. package/dist/editor/comments/EntryComments.d.ts +10 -4
  208. package/dist/editor/comments/EntryComments.js +14 -17
  209. package/dist/editor/comments/FieldWrapper.d.ts +18 -4
  210. package/dist/editor/comments/FieldWrapper.js +62 -28
  211. package/dist/editor/comments/InlineCommentThread.d.ts +9 -2
  212. package/dist/editor/comments/InlineCommentThread.js +11 -47
  213. package/dist/editor/comments/ThreadCarousel.d.ts +1 -1
  214. package/dist/editor/comments/ThreadCarousel.js +18 -21
  215. package/dist/editor/comments/ThreadReplyControls.d.ts +28 -0
  216. package/dist/editor/comments/ThreadReplyControls.js +92 -0
  217. package/dist/editor/comments/field-comment-context.d.ts +14 -0
  218. package/dist/editor/comments/field-comment-context.js +14 -0
  219. package/dist/editor/comments/form-comments.d.ts +48 -0
  220. package/dist/editor/comments/form-comments.js +178 -0
  221. package/dist/editor/comments/item-anchor-copy.d.ts +6 -0
  222. package/dist/editor/comments/item-anchor-copy.js +6 -0
  223. package/dist/editor/comments/move-focus-if-unmoved.d.ts +6 -0
  224. package/dist/editor/comments/move-focus-if-unmoved.js +11 -0
  225. package/dist/editor/comments/popover-placement.d.ts +18 -0
  226. package/dist/editor/comments/popover-placement.js +58 -0
  227. package/dist/editor/comments/use-tracked-writes.d.ts +16 -0
  228. package/dist/editor/comments/use-tracked-writes.js +53 -0
  229. package/dist/editor/components/BranchAccessList.d.ts +15 -0
  230. package/dist/editor/components/BranchAccessList.js +52 -0
  231. package/dist/editor/components/BranchLockBanner.d.ts +22 -0
  232. package/dist/editor/components/BranchLockBanner.js +39 -0
  233. package/dist/editor/components/BranchPill.d.ts +32 -0
  234. package/dist/editor/components/BranchPill.js +33 -0
  235. package/dist/editor/components/BranchesDrawer.d.ts +11 -0
  236. package/dist/editor/components/BranchesDrawer.js +5 -0
  237. package/dist/editor/components/ConfirmDeleteModal.d.ts +6 -1
  238. package/dist/editor/components/ConfirmDeleteModal.js +3 -2
  239. package/dist/editor/components/DestructiveMenuItem.d.ts +7 -0
  240. package/dist/editor/components/DestructiveMenuItem.js +8 -0
  241. package/dist/editor/components/EditorCrashScreen.d.ts +17 -0
  242. package/dist/editor/components/EditorCrashScreen.js +22 -0
  243. package/dist/editor/components/EditorErrorBoundary.d.ts +38 -0
  244. package/dist/editor/components/EditorErrorBoundary.js +40 -0
  245. package/dist/editor/components/EditorHeader.d.ts +56 -106
  246. package/dist/editor/components/EditorHeader.js +130 -131
  247. package/dist/editor/components/EditorRail.d.ts +49 -0
  248. package/dist/editor/components/EditorRail.js +65 -0
  249. package/dist/editor/components/EntryBreadcrumb.d.ts +33 -0
  250. package/dist/editor/components/EntryBreadcrumb.js +56 -0
  251. package/dist/editor/components/EntryCreateModal.d.ts +12 -7
  252. package/dist/editor/components/EntryCreateModal.js +51 -57
  253. package/dist/editor/components/NoEditPermissionNotice.d.ts +6 -5
  254. package/dist/editor/components/NoEditPermissionNotice.js +5 -4
  255. package/dist/editor/components/QuickOpenPalette.d.ts +22 -0
  256. package/dist/editor/components/QuickOpenPalette.js +66 -0
  257. package/dist/editor/components/ReadOnlyDraftNotice.d.ts +11 -0
  258. package/dist/editor/components/ReadOnlyDraftNotice.js +12 -0
  259. package/dist/editor/components/ReasonedMenuItem.d.ts +15 -0
  260. package/dist/editor/components/ReasonedMenuItem.js +15 -0
  261. package/dist/editor/components/ReferencedByList.d.ts +11 -0
  262. package/dist/editor/components/ReferencedByList.js +28 -0
  263. package/dist/editor/components/RelativeTime.d.ts +10 -0
  264. package/dist/editor/components/RelativeTime.js +10 -0
  265. package/dist/editor/components/RenameEntryModal.d.ts +9 -1
  266. package/dist/editor/components/RenameEntryModal.js +19 -31
  267. package/dist/editor/components/RetryToastMessage.d.ts +9 -0
  268. package/dist/editor/components/RetryToastMessage.js +6 -0
  269. package/dist/editor/components/SaveAllResult.d.ts +25 -0
  270. package/dist/editor/components/SaveAllResult.js +86 -0
  271. package/dist/editor/components/SaveSplitButton.d.ts +30 -0
  272. package/dist/editor/components/SaveSplitButton.js +108 -0
  273. package/dist/editor/components/StagedChangesDrawer.d.ts +39 -0
  274. package/dist/editor/components/StagedChangesDrawer.js +92 -0
  275. package/dist/editor/components/UnavailableEntryNotice.d.ts +6 -0
  276. package/dist/editor/components/UnavailableEntryNotice.js +9 -0
  277. package/dist/editor/components/UnavailableTypeMessage.d.ts +6 -0
  278. package/dist/editor/components/UnavailableTypeMessage.js +9 -0
  279. package/dist/editor/components/UndoToastMessage.d.ts +18 -0
  280. package/dist/editor/components/UndoToastMessage.js +10 -0
  281. package/dist/editor/components/UserBadge.d.ts +2 -5
  282. package/dist/editor/components/UserBadge.js +62 -33
  283. package/dist/editor/components/ValidationSummary.d.ts +14 -0
  284. package/dist/editor/components/ValidationSummary.js +24 -0
  285. package/dist/editor/components/index.d.ts +2 -2
  286. package/dist/editor/components/index.js +2 -2
  287. package/dist/editor/context/ApiClientContext.d.ts +8 -2
  288. package/dist/editor/context/ApiClientContext.js +19 -8
  289. package/dist/editor/context/AssetContext.d.ts +13 -35
  290. package/dist/editor/context/AssetContext.js +12 -31
  291. package/dist/editor/context/EditorStateContext.d.ts +4 -4
  292. package/dist/editor/context/EditorStateContext.js +4 -4
  293. package/dist/editor/context/SWRProvider.d.ts +3 -3
  294. package/dist/editor/context/SiteMdxAllowContext.d.ts +4 -0
  295. package/dist/editor/context/SiteMdxAllowContext.js +7 -0
  296. package/dist/editor/context/index.d.ts +2 -1
  297. package/dist/editor/context/index.js +2 -1
  298. package/dist/editor/contrast.d.ts +19 -0
  299. package/dist/editor/contrast.js +41 -0
  300. package/dist/editor/copy.d.ts +18 -0
  301. package/dist/editor/copy.js +18 -0
  302. package/dist/editor/editor-config.js +16 -0
  303. package/dist/editor/editor-utils.d.ts +34 -8
  304. package/dist/editor/editor-utils.js +42 -20
  305. package/dist/editor/entry-search.d.ts +20 -0
  306. package/dist/editor/entry-search.js +34 -0
  307. package/dist/editor/field-breadcrumb.d.ts +12 -0
  308. package/dist/editor/field-breadcrumb.js +85 -0
  309. package/dist/editor/field-props.d.ts +86 -0
  310. package/dist/editor/field-props.js +27 -0
  311. package/dist/editor/field-target.d.ts +30 -0
  312. package/dist/editor/field-target.js +77 -0
  313. package/dist/editor/fields/BlockField.d.ts +12 -0
  314. package/dist/editor/fields/BlockField.js +89 -73
  315. package/dist/editor/fields/CodeField.d.ts +2 -0
  316. package/dist/editor/fields/CodeField.js +6 -2
  317. package/dist/editor/fields/DateTimeField.d.ts +4 -29
  318. package/dist/editor/fields/DateTimeField.js +79 -41
  319. package/dist/editor/fields/ExpandAllButton.d.ts +6 -0
  320. package/dist/editor/fields/ExpandAllButton.js +4 -0
  321. package/dist/editor/fields/FieldCrashFallback.d.ts +17 -0
  322. package/dist/editor/fields/FieldCrashFallback.js +30 -0
  323. package/dist/editor/fields/FieldLabel.d.ts +37 -0
  324. package/dist/editor/fields/FieldLabel.js +19 -0
  325. package/dist/editor/fields/ImageField.d.ts +4 -0
  326. package/dist/editor/fields/ImageField.js +29 -14
  327. package/dist/editor/fields/InlineGroupField.js +4 -3
  328. package/dist/editor/fields/ItemList.d.ts +42 -0
  329. package/dist/editor/fields/ItemList.js +130 -0
  330. package/dist/editor/fields/MarkdownField.d.ts +32 -0
  331. package/dist/editor/fields/MarkdownField.js +223 -46
  332. package/dist/editor/fields/MdxImageDialog.js +1 -1
  333. package/dist/editor/fields/NumberField.d.ts +2 -0
  334. package/dist/editor/fields/NumberField.js +8 -5
  335. package/dist/editor/fields/NumberListField.d.ts +2 -0
  336. package/dist/editor/fields/NumberListField.js +19 -16
  337. package/dist/editor/fields/ObjectField.d.ts +2 -7
  338. package/dist/editor/fields/ObjectField.js +15 -5
  339. package/dist/editor/fields/ObjectListField.d.ts +29 -0
  340. package/dist/editor/fields/ObjectListField.js +57 -0
  341. package/dist/editor/fields/ReferenceField.d.ts +2 -0
  342. package/dist/editor/fields/ReferenceField.js +34 -9
  343. package/dist/editor/fields/SelectField.d.ts +2 -0
  344. package/dist/editor/fields/SelectField.js +11 -2
  345. package/dist/editor/fields/StringListField.d.ts +4 -1
  346. package/dist/editor/fields/StringListField.js +22 -8
  347. package/dist/editor/fields/TextField.d.ts +2 -0
  348. package/dist/editor/fields/TextField.js +6 -2
  349. package/dist/editor/fields/ToggleField.d.ts +2 -0
  350. package/dist/editor/fields/ToggleField.js +8 -3
  351. package/dist/editor/fields/entry-link/InsertEntryLink.d.ts +1 -1
  352. package/dist/editor/fields/entry-link/InsertEntryLink.js +1 -1
  353. package/dist/editor/fields/form-expansion.d.ts +9 -0
  354. package/dist/editor/fields/form-expansion.js +2 -0
  355. package/dist/editor/fields/item-expansion.d.ts +15 -0
  356. package/dist/editor/fields/item-expansion.js +60 -0
  357. package/dist/editor/fields/list-item-keys.d.ts +8 -0
  358. package/dist/editor/fields/list-item-keys.js +50 -0
  359. package/dist/editor/fields/list-item-title.d.ts +20 -0
  360. package/dist/editor/fields/list-item-title.js +85 -0
  361. package/dist/editor/fields/markdown-export-options.d.ts +2 -0
  362. package/dist/editor/fields/markdown-export-options.js +8 -0
  363. package/dist/editor/fields/markdown-fidelity-visitors.d.ts +11 -0
  364. package/dist/editor/fields/markdown-fidelity-visitors.js +160 -0
  365. package/dist/editor/fields/mdx-jsx-support.d.ts +5 -1
  366. package/dist/editor/fields/mdx-jsx-support.js +157 -69
  367. package/dist/editor/fields/rich-text-failures.d.ts +5 -0
  368. package/dist/editor/fields/rich-text-failures.js +26 -0
  369. package/dist/editor/fields/toolbar-wrap.d.ts +7 -0
  370. package/dist/editor/fields/toolbar-wrap.js +35 -0
  371. package/dist/editor/form-outline.d.ts +13 -0
  372. package/dist/editor/form-outline.js +70 -0
  373. package/dist/editor/group-manager/GroupCard.d.ts +4 -1
  374. package/dist/editor/group-manager/GroupCard.js +12 -3
  375. package/dist/editor/group-manager/GroupForm.js +1 -1
  376. package/dist/editor/group-manager/InternalGroupsTab.d.ts +2 -1
  377. package/dist/editor/group-manager/InternalGroupsTab.js +4 -4
  378. package/dist/editor/group-manager/MemberList.d.ts +2 -1
  379. package/dist/editor/group-manager/MemberList.js +15 -4
  380. package/dist/editor/group-manager/hooks/useGroupState.d.ts +5 -0
  381. package/dist/editor/group-manager/hooks/useGroupState.js +30 -7
  382. package/dist/editor/group-manager/index.js +34 -5
  383. package/dist/editor/group-manager/types.d.ts +5 -2
  384. package/dist/editor/hooks/create-branch-request.d.ts +20 -0
  385. package/dist/editor/hooks/create-branch-request.js +54 -0
  386. package/dist/editor/hooks/save-all.d.ts +68 -0
  387. package/dist/editor/hooks/save-all.js +61 -0
  388. package/dist/editor/hooks/useBranchActions.d.ts +11 -1
  389. package/dist/editor/hooks/useBranchActions.js +42 -19
  390. package/dist/editor/hooks/useBranchManager.d.ts +21 -0
  391. package/dist/editor/hooks/useBranchManager.js +321 -125
  392. package/dist/editor/hooks/useBranchesData.d.ts +8 -4
  393. package/dist/editor/hooks/useBranchesData.js +9 -5
  394. package/dist/editor/hooks/useCommentSystem.d.ts +14 -2
  395. package/dist/editor/hooks/useCommentSystem.js +79 -55
  396. package/dist/editor/hooks/useCommentsData.d.ts +11 -7
  397. package/dist/editor/hooks/useCommentsData.js +20 -19
  398. package/dist/editor/hooks/useDraftManager.d.ts +77 -3
  399. package/dist/editor/hooks/useDraftManager.js +887 -94
  400. package/dist/editor/hooks/useEditorLayout.d.ts +53 -7
  401. package/dist/editor/hooks/useEditorLayout.js +98 -20
  402. package/dist/editor/hooks/useEntriesData.d.ts +2 -1
  403. package/dist/editor/hooks/useEntriesData.js +18 -4
  404. package/dist/editor/hooks/useEntryManager.d.ts +41 -2
  405. package/dist/editor/hooks/useEntryManager.js +227 -41
  406. package/dist/editor/hooks/useGroupManager.js +13 -16
  407. package/dist/editor/hooks/usePermissionManager.js +14 -6
  408. package/dist/editor/hooks/usePreviewMarks.d.ts +21 -0
  409. package/dist/editor/hooks/usePreviewMarks.js +33 -0
  410. package/dist/editor/hooks/useReferenceResolution.d.ts +10 -3
  411. package/dist/editor/hooks/useReferenceResolution.js +34 -177
  412. package/dist/editor/hooks/useSchemaManager.d.ts +25 -6
  413. package/dist/editor/hooks/useSchemaManager.js +97 -39
  414. package/dist/editor/hooks/useUserMetadata.d.ts +14 -4
  415. package/dist/editor/hooks/useUserMetadata.js +76 -47
  416. package/dist/editor/hooks/user-metadata-batcher.d.ts +8 -0
  417. package/dist/editor/hooks/user-metadata-batcher.js +33 -0
  418. package/dist/editor/media/AssetCard.d.ts +6 -7
  419. package/dist/editor/media/AssetCard.js +24 -31
  420. package/dist/editor/media/AssetListRow.d.ts +16 -0
  421. package/dist/editor/media/AssetListRow.js +41 -0
  422. package/dist/editor/media/CropStep.js +6 -1
  423. package/dist/editor/media/MediaLibrary.d.ts +4 -3
  424. package/dist/editor/media/MediaLibrary.js +11 -4
  425. package/dist/editor/media/MediaLibraryBody.d.ts +3 -3
  426. package/dist/editor/media/MediaLibraryBody.js +195 -80
  427. package/dist/editor/media/MediaLibrarySkeleton.d.ts +6 -0
  428. package/dist/editor/media/MediaLibrarySkeleton.js +14 -0
  429. package/dist/editor/media/crop-math.d.ts +3 -4
  430. package/dist/editor/media/crop-math.js +11 -22
  431. package/dist/editor/media/editor-image-src.d.ts +8 -0
  432. package/dist/editor/media/editor-image-src.js +10 -0
  433. package/dist/editor/media/format-bytes.d.ts +2 -0
  434. package/dist/editor/media/format-bytes.js +23 -0
  435. package/dist/editor/media/media-library-prefs.d.ts +23 -0
  436. package/dist/editor/media/media-library-prefs.js +49 -0
  437. package/dist/editor/media/media-library-view.d.ts +34 -0
  438. package/dist/editor/media/media-library-view.js +40 -0
  439. package/dist/editor/media/useAssetLibrary.d.ts +30 -0
  440. package/dist/editor/media/useAssetLibrary.js +126 -0
  441. package/dist/editor/media/useStableAssetOrder.d.ts +11 -0
  442. package/dist/editor/media/useStableAssetOrder.js +37 -0
  443. package/dist/editor/media/useThumbnailFailure.d.ts +6 -0
  444. package/dist/editor/media/useThumbnailFailure.js +16 -0
  445. package/dist/editor/permission-manager/GroupSelector.js +2 -2
  446. package/dist/editor/permission-manager/PermissionEditor.d.ts +1 -1
  447. package/dist/editor/permission-manager/PermissionEditor.js +15 -4
  448. package/dist/editor/permission-manager/PermissionLevelBadge.js +1 -1
  449. package/dist/editor/permission-manager/PermissionTree.d.ts +1 -1
  450. package/dist/editor/permission-manager/UserSelector.d.ts +1 -1
  451. package/dist/editor/permission-manager/UserSelector.js +3 -3
  452. package/dist/editor/permission-manager/index.js +12 -3
  453. package/dist/editor/permission-manager/types.d.ts +5 -2
  454. package/dist/editor/platform.d.ts +3 -0
  455. package/dist/editor/platform.js +9 -0
  456. package/dist/editor/preview-asset-base.d.ts +9 -0
  457. package/dist/editor/preview-asset-base.js +17 -0
  458. package/dist/editor/preview-bridge.d.ts +51 -27
  459. package/dist/editor/preview-bridge.js +103 -144
  460. package/dist/editor/preview-marks.d.ts +15 -0
  461. package/dist/editor/preview-marks.js +74 -0
  462. package/dist/editor/preview-path.d.ts +12 -0
  463. package/dist/editor/preview-path.js +45 -1
  464. package/dist/editor/preview-scale.d.ts +27 -0
  465. package/dist/editor/preview-scale.js +34 -0
  466. package/dist/editor/raw-asset-base.d.ts +5 -0
  467. package/dist/editor/raw-asset-base.js +14 -0
  468. package/dist/editor/relative-time.d.ts +4 -4
  469. package/dist/editor/relative-time.js +16 -8
  470. package/dist/editor/schema-editor/CollectionEditor.d.ts +20 -3
  471. package/dist/editor/schema-editor/CollectionEditor.js +361 -62
  472. package/dist/editor/schema-editor/EntryTypeEditor.d.ts +5 -1
  473. package/dist/editor/schema-editor/EntryTypeEditor.js +11 -6
  474. package/dist/editor/schema-editor/collection-draft.d.ts +22 -0
  475. package/dist/editor/schema-editor/collection-draft.js +40 -0
  476. package/dist/editor/schema-editor/index.d.ts +1 -1
  477. package/dist/editor/schema-label.d.ts +10 -0
  478. package/dist/editor/schema-label.js +17 -0
  479. package/dist/editor/settings-conflict.d.ts +5 -0
  480. package/dist/editor/settings-conflict.js +8 -0
  481. package/dist/editor/shell/ActivityRail.d.ts +37 -0
  482. package/dist/editor/shell/ActivityRail.js +91 -0
  483. package/dist/editor/shell/EditorShell.d.ts +17 -0
  484. package/dist/editor/shell/EditorShell.js +31 -0
  485. package/dist/editor/shell/PaneLayout.d.ts +28 -0
  486. package/dist/editor/shell/PaneLayout.js +116 -0
  487. package/dist/editor/shell/SidePanelHost.d.ts +35 -0
  488. package/dist/editor/shell/SidePanelHost.js +184 -0
  489. package/dist/editor/shell/TopBar.d.ts +12 -0
  490. package/dist/editor/shell/TopBar.js +14 -0
  491. package/dist/editor/shell/host-chrome.d.ts +16 -0
  492. package/dist/editor/shell/host-chrome.js +69 -0
  493. package/dist/editor/slug.d.ts +16 -0
  494. package/dist/editor/slug.js +43 -0
  495. package/dist/editor/theme.d.ts +2 -1
  496. package/dist/editor/theme.js +314 -13
  497. package/dist/editor/unanswered.d.ts +17 -0
  498. package/dist/editor/unanswered.js +26 -0
  499. package/dist/editor/unavailable-entry-type.d.ts +20 -0
  500. package/dist/editor/unavailable-entry-type.js +28 -0
  501. package/dist/editor/undo-removal.d.ts +57 -0
  502. package/dist/editor/undo-removal.js +163 -0
  503. package/dist/editor/user-display.d.ts +4 -0
  504. package/dist/editor/user-display.js +5 -0
  505. package/dist/editor/utils/confirm-modal.d.ts +8 -0
  506. package/dist/editor/utils/confirm-modal.js +12 -0
  507. package/dist/editor/utils/editor-errors.d.ts +19 -0
  508. package/dist/editor/utils/editor-errors.js +32 -0
  509. package/dist/editor/utils/focus-return.d.ts +56 -0
  510. package/dist/editor/utils/focus-return.js +106 -0
  511. package/dist/entry-link-resolver.d.ts +11 -1
  512. package/dist/entry-link-resolver.js +65 -5
  513. package/dist/entry-schema-registry.d.ts +3 -3
  514. package/dist/entry-schema-registry.js +7 -4
  515. package/dist/entry-schema.d.ts +49 -3
  516. package/dist/entry-schema.js +18 -0
  517. package/dist/git-manager.d.ts +172 -10
  518. package/dist/git-manager.js +449 -107
  519. package/dist/github-service.d.ts +39 -2
  520. package/dist/github-service.js +101 -7
  521. package/dist/http/handler.js +74 -9
  522. package/dist/http/types.d.ts +2 -0
  523. package/dist/http/worker-not-ready.d.ts +9 -4
  524. package/dist/http/worker-not-ready.js +59 -6
  525. package/dist/index.d.ts +2 -0
  526. package/dist/index.js +3 -0
  527. package/dist/item-keys.d.ts +46 -0
  528. package/dist/item-keys.js +235 -0
  529. package/dist/operating-mode/editor-mode-check.d.ts +15 -0
  530. package/dist/operating-mode/editor-mode-check.js +24 -0
  531. package/dist/operating-mode/mode-env.d.ts +13 -14
  532. package/dist/operating-mode/mode-env.js +13 -14
  533. package/dist/operating-mode/types.d.ts +2 -2
  534. package/dist/paths/branch.d.ts +0 -1
  535. package/dist/paths/branch.js +1 -8
  536. package/dist/paths/index.d.ts +1 -1
  537. package/dist/paths/index.js +1 -1
  538. package/dist/paths/validation.js +0 -2
  539. package/dist/preview.d.ts +8 -0
  540. package/dist/preview.js +7 -0
  541. package/dist/reference-resolver.js +1 -0
  542. package/dist/resolve-canopy-user.d.ts +1 -1
  543. package/dist/resolve-canopy-user.js +1 -1
  544. package/dist/schema/meta-loader.d.ts +18 -8
  545. package/dist/schema/meta-loader.js +42 -9
  546. package/dist/schema/registry-fingerprint.d.ts +14 -0
  547. package/dist/schema/registry-fingerprint.js +63 -0
  548. package/dist/schema/resolver.d.ts +7 -4
  549. package/dist/schema/resolver.js +7 -6
  550. package/dist/schema/schema-store.d.ts +7 -10
  551. package/dist/schema/schema-store.js +13 -11
  552. package/dist/schema/schema-unavailable-error.d.ts +11 -0
  553. package/dist/schema/schema-unavailable-error.js +15 -0
  554. package/dist/schema/types.d.ts +22 -0
  555. package/dist/schema-registry-record.d.ts +35 -0
  556. package/dist/schema-registry-record.js +134 -0
  557. package/dist/server.d.ts +79 -9
  558. package/dist/server.js +62 -8
  559. package/dist/services.d.ts +33 -5
  560. package/dist/services.js +155 -37
  561. package/dist/settings-workspace.d.ts +23 -3
  562. package/dist/settings-workspace.js +159 -45
  563. package/dist/stamp-on-publish.d.ts +36 -0
  564. package/dist/stamp-on-publish.js +71 -0
  565. package/dist/static/index.d.ts +58 -5
  566. package/dist/static/index.js +133 -3
  567. package/dist/static/seo.d.ts +5 -5
  568. package/dist/static/seo.js +5 -5
  569. package/dist/submission-attribution.d.ts +8 -0
  570. package/dist/submission-attribution.js +22 -0
  571. package/dist/system-users.d.ts +12 -0
  572. package/dist/system-users.js +12 -0
  573. package/dist/task-queue/cms-task-queue.d.ts +1 -1
  574. package/dist/task-queue/cms-task-queue.js +1 -1
  575. package/dist/task-queue/index.d.ts +1 -1
  576. package/dist/task-queue/index.js +1 -1
  577. package/dist/task-queue/task-queue.d.ts +6 -0
  578. package/dist/task-queue/task-queue.js +29 -0
  579. package/dist/task-queue/worker-status.d.ts +35 -9
  580. package/dist/task-queue/worker-status.js +84 -17
  581. package/dist/types.d.ts +90 -9
  582. package/dist/url-collision.js +3 -3
  583. package/dist/url-exclusivity-fixtures.d.ts +8 -1
  584. package/dist/url-exclusivity-fixtures.js +11 -1
  585. package/dist/user.js +2 -1
  586. package/dist/utils/atomic-write.js +2 -1
  587. package/dist/utils/base-branch.d.ts +13 -0
  588. package/dist/utils/base-branch.js +23 -0
  589. package/dist/utils/content-serialize.d.ts +15 -7
  590. package/dist/utils/content-serialize.js +353 -51
  591. package/dist/utils/content-write-lock.d.ts +6 -1
  592. package/dist/utils/content-write-lock.js +21 -6
  593. package/dist/utils/debug.d.ts +1 -1
  594. package/dist/utils/debug.js +5 -3
  595. package/dist/utils/error.js +13 -2
  596. package/dist/utils/escape-text.d.ts +7 -0
  597. package/dist/utils/escape-text.js +27 -0
  598. package/dist/utils/fs.d.ts +12 -4
  599. package/dist/utils/fs.js +18 -5
  600. package/dist/utils/git.d.ts +21 -1
  601. package/dist/utils/git.js +63 -8
  602. package/dist/utils/json-source-splice.d.ts +14 -0
  603. package/dist/utils/json-source-splice.js +120 -0
  604. package/dist/utils/markdown-body-splice.d.ts +39 -0
  605. package/dist/utils/markdown-body-splice.js +421 -0
  606. package/dist/utils/occ-json-write.js +18 -12
  607. package/dist/utils/provision-log.d.ts +25 -0
  608. package/dist/utils/provision-log.js +49 -0
  609. package/dist/utils/provisioning-lock.d.ts +32 -1
  610. package/dist/utils/provisioning-lock.js +78 -32
  611. package/dist/utils/sanitize-href.d.ts +9 -23
  612. package/dist/utils/sanitize-href.js +14 -28
  613. package/dist/utils/title-field.d.ts +10 -0
  614. package/dist/utils/title-field.js +47 -0
  615. package/dist/utils/yaml-source-splice.d.ts +58 -0
  616. package/dist/utils/yaml-source-splice.js +515 -0
  617. package/dist/validation/block-structural-keys.d.ts +1 -1
  618. package/dist/validation/block-structural-keys.js +1 -1
  619. package/dist/validation/deletion-checker.d.ts +21 -29
  620. package/dist/validation/deletion-checker.js +41 -117
  621. package/dist/validation/entry-validator.d.ts +6 -10
  622. package/dist/validation/entry-validator.js +19 -26
  623. package/dist/validation/field-traversal.d.ts +22 -0
  624. package/dist/validation/field-traversal.js +39 -8
  625. package/dist/validation/markdown-safety.d.ts +68 -0
  626. package/dist/validation/markdown-safety.js +596 -0
  627. package/dist/validation/mdx-allowlist.d.ts +55 -0
  628. package/dist/validation/mdx-allowlist.js +183 -0
  629. package/dist/validation/reference-validator.d.ts +2 -0
  630. package/dist/validation/reference-validator.js +3 -0
  631. package/dist/version.d.ts +1 -1
  632. package/dist/version.js +1 -1
  633. package/dist/worker/branch-policy.d.ts +124 -0
  634. package/dist/worker/branch-policy.js +243 -0
  635. package/dist/worker/bundle-header.d.ts +117 -0
  636. package/dist/worker/bundle-header.js +253 -0
  637. package/dist/worker/cms-worker.d.ts +236 -142
  638. package/dist/worker/cms-worker.js +772 -285
  639. package/dist/worker/gateway-egress.d.ts +43 -0
  640. package/dist/worker/gateway-egress.js +209 -0
  641. package/dist/worker/gateway-instance.d.ts +42 -0
  642. package/dist/worker/gateway-instance.js +129 -0
  643. package/dist/worker/git-process.d.ts +81 -0
  644. package/dist/worker/git-process.js +234 -0
  645. package/dist/worker/git-sync.d.ts +34 -17
  646. package/dist/worker/git-sync.js +394 -131
  647. package/dist/worker/github-auth.d.ts +9 -9
  648. package/dist/worker/github-auth.js +13 -15
  649. package/dist/worker/github-gateway-http.d.ts +62 -0
  650. package/dist/worker/github-gateway-http.js +977 -0
  651. package/dist/worker/github-gateway-wire.d.ts +590 -0
  652. package/dist/worker/github-gateway-wire.js +488 -0
  653. package/dist/worker/github-gateway.d.ts +238 -0
  654. package/dist/worker/github-gateway.js +726 -0
  655. package/dist/worker/github-mirror-upkeep.d.ts +75 -0
  656. package/dist/worker/github-mirror-upkeep.js +266 -0
  657. package/dist/worker/github-mirror.d.ts +364 -0
  658. package/dist/worker/github-mirror.js +1030 -0
  659. package/dist/worker/github-transfer.d.ts +56 -0
  660. package/dist/worker/github-transfer.js +444 -0
  661. package/dist/worker/history-rewrite.d.ts +4 -5
  662. package/dist/worker/history-rewrite.js +12 -17
  663. package/dist/worker/provisioned-workspace.d.ts +5 -5
  664. package/dist/worker/provisioned-workspace.js +5 -5
  665. package/dist/worker/rebase.d.ts +1 -1
  666. package/dist/worker/rebase.js +58 -54
  667. package/dist/worker/remote-git-maintenance.d.ts +3 -0
  668. package/dist/worker/remote-git-maintenance.js +12 -0
  669. package/dist/worker/schema-gate.d.ts +49 -0
  670. package/dist/worker/schema-gate.js +226 -0
  671. package/dist/worker/self-checks.d.ts +107 -0
  672. package/dist/worker/self-checks.js +358 -0
  673. package/dist/worker/shared-repo-git.d.ts +96 -0
  674. package/dist/worker/shared-repo-git.js +459 -0
  675. package/dist/worker/sparse-cone.d.ts +20 -0
  676. package/dist/worker/sparse-cone.js +136 -0
  677. package/dist/worker/stale-mirror.d.ts +82 -0
  678. package/dist/worker/stale-mirror.js +130 -0
  679. package/dist/worker/task-runner.d.ts +5 -3
  680. package/dist/worker/task-runner.js +287 -186
  681. package/dist/worker/worker-context.d.ts +36 -37
  682. package/package.json +35 -7
  683. package/dist/editor/EditorPanes.d.ts +0 -10
  684. package/dist/editor/EditorPanes.js +0 -98
  685. package/dist/editor/components/EditorFooter.d.ts +0 -11
  686. package/dist/editor/components/EditorFooter.js +0 -9
  687. package/dist/editor/components/EditorSidebar.d.ts +0 -67
  688. package/dist/editor/components/EditorSidebar.js +0 -22
@@ -0,0 +1,459 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { simpleGit } from 'simple-git';
4
+ import { gitChildEnv } from '../git-manager.js';
5
+ import { getErrorMessage } from '../utils/error.js';
6
+ /**
7
+ * How the worker runs git in a repository the CMS Lambda can also write: `remote.git` and every
8
+ * branch clone on the shared filesystem. Their `.git/config` and hooks are Lambda-writable, and
9
+ * the worker runs as the user that holds the GitHub credential, so git there must neither run
10
+ * anything those files name nor reach anywhere they point. The credential itself never enters
11
+ * these repositories; worker/github-mirror.ts holds it.
12
+ *
13
+ * Two layers. The `-c` pins below override repository config for every key they name, which
14
+ * holds against a concurrent writer. Keys whose names the attacker chooses cannot be pinned
15
+ * (`filter.<driver>.*`, `merge.<driver>.driver`, `url.<base>.insteadOf`, `http.<url>.*`), so
16
+ * {@link assertSharedRepoConfig} refuses a repository whose own config holds anything outside
17
+ * {@link ALLOWED_REPO_CONFIG_KEYS}, or a submodule with a repository in it. That check reads
18
+ * before git does, so a writer racing it still runs code in a working-tree operation (rebase,
19
+ * merge, checkout) through a driver it plants in between, as does one that edits a rebase the
20
+ * worker has stopped at a conflict (an `exec` line in `git-rebase-todo`, the `strategy` file). Only
21
+ * moving that git into a process without the credential closes those:
22
+ * .claude/future-tasks/resolved/worker-shared-repo-git-process-split.md.
23
+ */
24
+ /**
25
+ * Every hook event in githooks(5) at git 2.55 except git-p4's, which only git-p4 fires and the worker
26
+ * never runs. `hook.<event>.enabled=false` is what stops a
27
+ * config-defined hook (`hook.<name>.command` + `.event`), which `core.hooksPath` does not; a git
28
+ * without config hooks ignores the key.
29
+ */
30
+ const HOOK_EVENTS = [
31
+ 'applypatch-msg',
32
+ 'pre-applypatch',
33
+ 'post-applypatch',
34
+ 'pre-commit',
35
+ 'pre-merge-commit',
36
+ 'prepare-commit-msg',
37
+ 'commit-msg',
38
+ 'post-commit',
39
+ 'pre-rebase',
40
+ 'post-checkout',
41
+ 'post-merge',
42
+ 'pre-push',
43
+ 'pre-receive',
44
+ 'update',
45
+ 'proc-receive',
46
+ 'post-receive',
47
+ 'post-update',
48
+ 'reference-transaction',
49
+ 'push-to-checkout',
50
+ 'pre-auto-gc',
51
+ 'post-rewrite',
52
+ 'sendemail-validate',
53
+ 'fsmonitor-watchman',
54
+ 'post-index-change',
55
+ ];
56
+ /**
57
+ * Transports git can reach without a helper on PATH. A `protocol.<name>.allow` in repo config
58
+ * outranks the generic `protocol.allow`, so each is pinned by name.
59
+ */
60
+ const DENIED_TRANSPORTS = ['ext', 'fd', 'ssh', 'git', 'http', 'https', 'ftp', 'ftps'];
61
+ /**
62
+ * `-c` settings for every worker git process in a shared repository, and for each process those
63
+ * spawn: git passes them on through `GIT_CONFIG_PARAMETERS`, except to the `upload-pack` or
64
+ * `receive-pack` of a local fetch or push, which {@link pinnedUploadPack} and
65
+ * {@link pinnedReceivePack} pin on their own command line. Each entry is shell-safe, because
66
+ * those two commands run through `sh -c`.
67
+ */
68
+ const SHARED_REPO_PINS = [
69
+ 'core.hooksPath=/dev/null',
70
+ ...HOOK_EVENTS.map((event) => `hook.${event}.enabled=false`),
71
+ 'core.fsmonitor=false',
72
+ // Advertising an alternate's refs runs this through the shell, or spawns git in the alternate
73
+ // directory; the alternates file is Lambda-writable too.
74
+ 'core.alternateRefsCommand=true',
75
+ 'core.editor=true',
76
+ 'sequence.editor=true',
77
+ 'credential.helper=',
78
+ 'core.askPass=',
79
+ 'protocol.allow=never',
80
+ 'protocol.file.allow=always',
81
+ ...DENIED_TRANSPORTS.map((transport) => `protocol.${transport}.allow=never`),
82
+ 'commit.gpgSign=false',
83
+ 'tag.gpgSign=false',
84
+ 'push.gpgSign=false',
85
+ // A merge that verifies signatures runs the signing program on whatever signature a commit in
86
+ // remote.git carries; the programs are pinned too, so nothing else that verifies runs one.
87
+ 'merge.verifySignatures=false',
88
+ 'pull.verifySignatures=false',
89
+ 'log.showSignature=false',
90
+ 'gpg.program=false',
91
+ 'gpg.ssh.program=false',
92
+ 'gpg.x509.program=false',
93
+ // Negotiating a push runs the destination's upload-pack, unpinned.
94
+ 'push.negotiate=false',
95
+ 'submodule.recurse=false',
96
+ 'diff.ignoreSubmodules=all',
97
+ // The commit `rebase --continue` makes would otherwise summarise status, descending into a
98
+ // submodule under that submodule's own config.
99
+ 'commit.status=false',
100
+ 'fetch.recurseSubmodules=false',
101
+ 'push.recurseSubmodules=no',
102
+ 'gc.auto=0',
103
+ 'maintenance.auto=false',
104
+ ];
105
+ /** What a bare shared repository adds: never a working tree, so never a checkout. */
106
+ const BARE_PINS = ['core.bare=true'];
107
+ /**
108
+ * What the worker's private GitHub mirror runs with (worker/github-mirror.ts). Nothing the Lambda
109
+ * writes reaches that repository's config, so these are depth, not the boundary: the same pins,
110
+ * HTTPS allowed for GitHub, and no credential helper ever handed the token. Pushed objects are
111
+ * checked where a quarantine takes them in (worker/github-mirror.ts).
112
+ */
113
+ const MIRROR_PINS = [
114
+ ...SHARED_REPO_PINS.filter((pin) => pin !== 'protocol.https.allow=never'),
115
+ 'protocol.https.allow=always',
116
+ // A host's own core.sharedRepository would make `init` create it group-writable.
117
+ 'core.sharedRepository=umask',
118
+ ...BARE_PINS,
119
+ ];
120
+ /** simple-git refuses each of these pinned keys, and the pack-command options, unless opted in. */
121
+ const PIN_OPT_INS = {
122
+ allowUnsafeHooksPath: true,
123
+ allowUnsafeFsMonitor: true,
124
+ allowUnsafeEditor: true,
125
+ allowUnsafeCredentialHelper: true,
126
+ allowUnsafeAskPass: true,
127
+ allowUnsafeProtocolOverride: true,
128
+ allowUnsafeGpgProgram: true,
129
+ allowUnsafePack: true,
130
+ };
131
+ function pinsFor(kind) {
132
+ return kind === 'bare' ? [...SHARED_REPO_PINS, ...BARE_PINS] : [...SHARED_REPO_PINS];
133
+ }
134
+ /**
135
+ * The pins alone, without the environment {@link sharedRepoGit} adds (an explicit `GIT_DIR`, no
136
+ * lazy fetches), which every worker git in a shared repository needs too.
137
+ * @internal Exported for tests.
138
+ */
139
+ export function sharedRepoGitOptions(kind) {
140
+ return { config: pinsFor(kind), unsafe: { ...PIN_OPT_INS } };
141
+ }
142
+ /** The git directory of a shared repository: itself when bare, its `.git` otherwise. */
143
+ function gitDirOf(repoPath, kind) {
144
+ return kind === 'bare' ? repoPath : path.join(repoPath, '.git');
145
+ }
146
+ /**
147
+ * A worker git instance in the shared repository at `repoPath` (the bare repository itself, or a
148
+ * clone's working tree), with the pins applied. The repository is named outright, as
149
+ * {@link assertSharedRepoConfig} names it, so both read the same config: discovery would take a
150
+ * planted `remote.git/.git`, or a `.git` above a clone whose own was removed. A clone's working
151
+ * tree is named too, which also overrides `core.worktree`.
152
+ *
153
+ * `options` are the caller's own (`timeout`, `abort`), applied under the pins. The child env is
154
+ * `gitChildEnv`'s: every shared-repository operation is local.
155
+ */
156
+ export function sharedRepoGit(repoPath, kind, options = {}) {
157
+ const absolute = path.resolve(repoPath);
158
+ return simpleGit({ ...options, baseDir: absolute, ...sharedRepoGitOptions(kind) }).env(sharedRepoEnv(absolute, kind));
159
+ }
160
+ /** simple-git options for the worker's private GitHub mirror. */
161
+ export function mirrorGitOptions() {
162
+ return { config: [...MIRROR_PINS], unsafe: { ...PIN_OPT_INS } };
163
+ }
164
+ /** {@link mirrorGitOptions}'s pins, for a git the mirror runs without simple-git (worker/git-process.ts). */
165
+ export function mirrorPins() {
166
+ return [...MIRROR_PINS];
167
+ }
168
+ /**
169
+ * The pins of a quarantine repository, where the GitHub gateway takes in a pushed bundle
170
+ * (worker/github-mirror.ts): the mirror's, without `core.alternateRefsCommand=true`. That pin
171
+ * guards an alternates file the Lambda can write; a quarantine's names the mirror and is written
172
+ * by the gateway. With it, the fetch's connectivity check could not stop at the mirror's heads and
173
+ * would walk the mirror's whole history for every push.
174
+ */
175
+ export function quarantinePins() {
176
+ return MIRROR_PINS.filter((pin) => pin !== 'core.alternateRefsCommand=true');
177
+ }
178
+ /**
179
+ * {@link sharedRepoGit}'s pins, environment and working directory, for a git run through
180
+ * worker/git-process.ts: one whose stdin or stdout is a stream.
181
+ */
182
+ export function sharedRepoProcessOptions(repoPath, kind) {
183
+ const absolute = path.resolve(repoPath);
184
+ return {
185
+ cwd: absolute,
186
+ env: sharedRepoEnv(absolute, kind),
187
+ config: pinsFor(kind),
188
+ };
189
+ }
190
+ function sharedRepoEnv(absolute, kind) {
191
+ return gitChildEnv({
192
+ GIT_DIR: gitDirOf(absolute, kind),
193
+ ...(kind === 'worktree' ? { GIT_WORK_TREE: absolute } : {}),
194
+ // A missing object would otherwise be fetched from a promisor remote the config names, by
195
+ // that remote's own upload-pack command (git 2.45+; older git ignores it).
196
+ GIT_NO_LAZY_FETCH: '1',
197
+ });
198
+ }
199
+ function packCommand(program, extra, flags = []) {
200
+ return [
201
+ 'git',
202
+ ...[...pinsFor('bare'), ...extra].flatMap((pin) => ['-c', pin]),
203
+ program,
204
+ ...flags,
205
+ ].join(' ');
206
+ }
207
+ /**
208
+ * `--upload-pack` for a fetch FROM `remote.git` into a clone.
209
+ * @internal Exported for tests.
210
+ */
211
+ export function pinnedUploadPack() {
212
+ // --strict: never `<path>/.git` in place of the repository named.
213
+ return packCommand('upload-pack', [], ['--strict']);
214
+ }
215
+ /**
216
+ * `--receive-pack` for a push INTO `remote.git`. Without it the push runs remote.git's own hooks
217
+ * as the worker. The `receive.deny*` pins keep a planted setting from blocking the forced update
218
+ * that publishes a rewritten history (worker/history-rewrite.ts).
219
+ */
220
+ export function pinnedReceivePack() {
221
+ return packCommand('receive-pack', [
222
+ 'receive.autogc=false',
223
+ 'receive.denyDeletes=false',
224
+ 'receive.denyNonFastForwards=false',
225
+ ]);
226
+ }
227
+ /**
228
+ * Fetch `branch` from `remote.git` into the clone `git` runs in, through
229
+ * {@link pinnedUploadPack}. The result is in FETCH_HEAD only.
230
+ */
231
+ export async function fetchFromRemoteGit(git, remoteGitPath, branch) {
232
+ await git.raw([
233
+ 'fetch',
234
+ `--upload-pack=${pinnedUploadPack()}`,
235
+ '--end-of-options',
236
+ remoteGitPath,
237
+ branch,
238
+ ]);
239
+ }
240
+ /**
241
+ * Repository config keys CanopyCMS or git itself writes into a shared repository, in the form
242
+ * `git config --list` prints them (section and key lowercased, subsection verbatim). Covers every
243
+ * writer in this repository's history: clone (`core.*`, `remote.*`, `branch.*`, plus the `-c`
244
+ * settings it persists: `managedWorkspaceConfig`'s and gc's), `ensureAuthor`, `ensureRemote`,
245
+ * `push --set-upstream`, `sparse-checkout set` (`extensions.worktreeConfig` and the cone keys in
246
+ * `config.worktree`), `REMOTE_GIT_CONFIG`, and the `remote.origin.url` a pre-scrub bare clone of
247
+ * GitHub left in `remote.git`. A named remote's `url` is inert here because the worker addresses
248
+ * remotes by absolute path, which git never reads as a remote's name, and the transport pins hold
249
+ * whatever it says.
250
+ */
251
+ const ALLOWED_REPO_CONFIG_KEYS = [
252
+ /^core\.(repositoryformatversion|filemode|bare|logallrefupdates|ignorecase|precomposeunicode|symlinks|sparsecheckout|sparsecheckoutcone)$/,
253
+ /^extensions\.(worktreeconfig|objectformat|refstorage)$/,
254
+ /^index\.sparse$/,
255
+ /^remote\..+\.(url|fetch)$/,
256
+ /^branch\..+\.(remote|merge)$/,
257
+ /^user\.(name|email)$/,
258
+ /^canopycms\.managed$/,
259
+ /^gc\.auto$/,
260
+ /^maintenance\.auto$/,
261
+ /^receive\.autogc$/,
262
+ /^transfer\.unpacklimit$/,
263
+ ];
264
+ /**
265
+ * How a refusal's fix is to be run. Run as root, or as an unsandboxed worker user, a command on EFS
266
+ * can be made to run what the CMS Lambda wrote there (a repository's config, a symbolic link), and
267
+ * root writes into EFS follow its links; `canopy-inspect` (the AWS worker instance's
268
+ * /opt/canopy-worker/canopy-inspect) runs each one as the worker does, inside its sandbox.
269
+ */
270
+ const RUN_FIX_AS_THE_WORKER = "as the worker's user and never as root: on the AWS worker instance, run each command below " +
271
+ "on its own as /opt/canopy-worker/canopy-inspect <command>; '; then ' separates them";
272
+ /** Every refusal to run git in a shared repository, so a caller can tell one from git's own errors. */
273
+ export class SharedRepoRefusalError extends Error {
274
+ constructor(message) {
275
+ super(message);
276
+ this.name = 'SharedRepoRefusalError';
277
+ }
278
+ }
279
+ /**
280
+ * A shared repository whose own config holds a key CanopyCMS never writes.
281
+ * @internal Exported for tests.
282
+ */
283
+ export class UntrustedRepoConfigError extends SharedRepoRefusalError {
284
+ constructor(repoPath, keys) {
285
+ // Keys and files only: a value can be a credential.
286
+ super(`Refusing to run git in ${repoPath}: its git config holds ` +
287
+ `${keys.length === 1 ? 'a setting' : `${keys.length} settings`} CanopyCMS never writes, ` +
288
+ `which can make the worker run a command or send its GitHub credential elsewhere ` +
289
+ `(${keys.map((k) => `${k.key} in ${k.file}`).join('; ')}). Find out how ` +
290
+ `${keys.length === 1 ? 'it' : 'they'} got there, then remove ` +
291
+ `${keys.length === 1 ? 'it' : 'them'}, ${RUN_FIX_AS_THE_WORKER}: ` +
292
+ keys
293
+ .map((k) => `git config --file ${shellQuote(k.file)} --unset-all ${shellQuote(k.key)}`)
294
+ .join('; then '));
295
+ this.repoPath = repoPath;
296
+ this.keys = keys;
297
+ this.name = 'UntrustedRepoConfigError';
298
+ }
299
+ }
300
+ /** A shared repository whose config git could not read, or did not finish reading. */
301
+ function unreadableConfig(repoPath, configFile, err) {
302
+ const cause = getErrorMessage(err).trim();
303
+ const timedOut = /block timeout/i.test(cause);
304
+ return new SharedRepoRefusalError(`Refusing to run git in ${repoPath}: ` +
305
+ (timedOut
306
+ ? `reading its git config did not finish, which an include.path naming a pipe or device ` +
307
+ `causes. Check ${configFile} and every file it includes.`
308
+ : `git could not read its config (${cause}). Check ${configFile} and every file it ` +
309
+ `includes.`));
310
+ }
311
+ /** A clone whose index git could not list. */
312
+ function unreadableIndex(clone, err) {
313
+ return new SharedRepoRefusalError(`Refusing to run git in ${clone}: git could not list its index (${getErrorMessage(err).trim()}).`);
314
+ }
315
+ async function pathExists(file) {
316
+ return fs.lstat(file).then(() => true, () => false);
317
+ }
318
+ function shellQuote(value) {
319
+ return `'${value.replace(/'/g, `'\\''`)}'`;
320
+ }
321
+ /**
322
+ * An `include.path` naming a pipe or `/dev/stdin` blocks every read of the config for ever; the
323
+ * timeout turns the check's read into a refusal. sparse-cone.ts's earlier read has its own.
324
+ */
325
+ const CHECK_TIMEOUT_MS = 30_000;
326
+ /** Scopes the repository itself supplies; `system`, `global` and `command` are the worker's. */
327
+ const REPO_SCOPES = new Set(['local', 'worktree']);
328
+ /**
329
+ * Refuse a shared repository whose own config (with its includes, and `config.worktree`) holds a
330
+ * key outside {@link ALLOWED_REPO_CONFIG_KEYS}, or a submodule with a repository in it. Throws
331
+ * {@link UntrustedRepoConfigError} for a key. A config git cannot read, or does not finish reading,
332
+ * is also refused, with git's own message for a bad line, which names the line but not its content.
333
+ *
334
+ * `repoPath` is the bare repository itself for `bare`, the working tree for `worktree`.
335
+ */
336
+ export async function assertSharedRepoConfig(repoPath, kind, options = {}) {
337
+ const timeout = {
338
+ timeout: { block: options.timeoutMs ?? CHECK_TIMEOUT_MS },
339
+ };
340
+ const absolute = path.resolve(repoPath);
341
+ const gitDir = gitDirOf(absolute, kind);
342
+ let listing;
343
+ try {
344
+ // `git config --list` runs outside a repository too, reading no repository config at all. A
345
+ // symlink, or a `commondir` file, would aim the operation at another repository, such as the
346
+ // worker's own mirror.
347
+ for (const entry of kind === 'bare' ? [gitDir] : [absolute, gitDir]) {
348
+ const stat = await fs.lstat(entry);
349
+ if (!stat.isDirectory())
350
+ throw new Error(`${entry} is not a directory`);
351
+ }
352
+ if (await pathExists(path.join(gitDir, 'commondir'))) {
353
+ throw new Error(`${path.join(gitDir, 'commondir')} exists, and git would use another repository`);
354
+ }
355
+ // receive-pack, given a bare repository's path, uses `<path>/.git` when there is one.
356
+ if (kind === 'bare' && (await pathExists(path.join(gitDir, '.git')))) {
357
+ throw new Error(`${path.join(gitDir, '.git')} exists, and git would use it instead`);
358
+ }
359
+ listing = await sharedRepoGit(absolute, kind, timeout).raw([
360
+ 'config',
361
+ '--list',
362
+ '--show-scope',
363
+ '--show-origin',
364
+ '-z',
365
+ ]);
366
+ }
367
+ catch (err) {
368
+ throw unreadableConfig(absolute, path.join(gitDir, 'config'), err);
369
+ }
370
+ const unexpected = [];
371
+ // -z prints each entry as `scope NUL origin NUL key LF value NUL`.
372
+ const fields = listing.split('\0');
373
+ for (let i = 0; i + 2 < fields.length; i += 3) {
374
+ const [scope, origin, entry] = [fields[i], fields[i + 1], fields[i + 2]];
375
+ if (!REPO_SCOPES.has(scope))
376
+ continue;
377
+ const newline = entry.indexOf('\n');
378
+ const key = newline === -1 ? entry : entry.slice(0, newline);
379
+ if (ALLOWED_REPO_CONFIG_KEYS.some((allowed) => allowed.test(key)))
380
+ continue;
381
+ const file = origin.startsWith('file:') ? origin.slice('file:'.length) : origin;
382
+ unexpected.push({ key, file });
383
+ }
384
+ if (unexpected.length > 0)
385
+ throw new UntrustedRepoConfigError(absolute, unexpected);
386
+ if (kind === 'worktree')
387
+ await assertNoSubmodules(absolute, timeout);
388
+ }
389
+ /**
390
+ * Refuse a clone whose index holds a submodule with a repository in it. Git runs inside one, under
391
+ * that repository's own config, which no allowlist here reads. `commit.status=false` and
392
+ * {@link SHARED_REPO_STATUS_ARGS} keep status, and the commit a continued rebase makes, out of one
393
+ * planted later; this refuses the plant itself.
394
+ * An unpopulated submodule, which a non-recursive clone of a repository that has one leaves, is
395
+ * harmless and allowed.
396
+ */
397
+ async function assertNoSubmodules(clone, timeout) {
398
+ let listing;
399
+ try {
400
+ // --sparse: a sparse index stays collapsed rather than being expanded for every clone.
401
+ listing = await sharedRepoGit(clone, 'worktree', timeout).raw([
402
+ 'ls-files',
403
+ '--stage',
404
+ '--sparse',
405
+ '-z',
406
+ ]);
407
+ }
408
+ catch (err) {
409
+ throw unreadableIndex(clone, err);
410
+ }
411
+ const gitlinks = listing
412
+ .split('\0')
413
+ .filter((entry) => entry.startsWith('160000 '))
414
+ .map((entry) => entry.slice(entry.indexOf('\t') + 1));
415
+ await refusePopulated(clone, gitlinks);
416
+ }
417
+ /**
418
+ * Refuse a rebase or fast-forward from `from` to `to` in `clone` that would bring in a submodule
419
+ * at a path where a repository already sits, as one planted ahead of it would.
420
+ */
421
+ export async function assertNoIncomingSubmodules(git, clone, from, to) {
422
+ // Raw diff-tree records: `:oldmode newmode oldsha newsha status` NUL path NUL.
423
+ const fields = (await git.raw(['diff-tree', '-r', '-z', '--no-renames', '--end-of-options', from, to])).split('\0');
424
+ const incoming = [];
425
+ for (let i = 0; i + 1 < fields.length; i += 2) {
426
+ const newMode = fields[i].split(' ')[1];
427
+ if (newMode === '160000')
428
+ incoming.push(fields[i + 1]);
429
+ }
430
+ await refusePopulated(clone, incoming);
431
+ }
432
+ async function refusePopulated(clone, gitlinks) {
433
+ const populated = [];
434
+ // A conflicted gitlink is listed once per stage.
435
+ for (const gitlink of new Set(gitlinks)) {
436
+ if (await pathExists(path.join(clone, gitlink, '.git')))
437
+ populated.push(gitlink);
438
+ }
439
+ if (populated.length === 0)
440
+ return;
441
+ const removals = [];
442
+ for (const gitlink of populated) {
443
+ const planted = path.join(clone, gitlink, '.git');
444
+ // A symbolic link on the way would make `rm -rf` remove whatever it names instead.
445
+ const real = await fs.realpath(planted).catch(() => planted);
446
+ removals.push(real === planted
447
+ ? `rm -rf ${shellQuote(planted)}`
448
+ : `remove the symbolic link that makes ${shellQuote(planted)} resolve to ${shellQuote(real)}`);
449
+ }
450
+ throw new SharedRepoRefusalError(`Refusing to run git in ${clone}: it has a submodule with a repository in it at ` +
451
+ `${populated.map((p) => JSON.stringify(p)).join(', ')}, and git runs inside one under its ` +
452
+ `own config. CanopyCMS never populates a submodule. Find out how it got there, then, ` +
453
+ `${RUN_FIX_AS_THE_WORKER}: ${removals.join('; then ')}`);
454
+ }
455
+ /**
456
+ * Arguments for every worker `git status` in a clone. On the command line, unlike in config, the
457
+ * setting outranks a `.gitmodules` entry's own `ignore`.
458
+ */
459
+ export const SHARED_REPO_STATUS_ARGS = ['--ignore-submodules=all'];
@@ -0,0 +1,20 @@
1
+ export interface SparseConeReport {
2
+ reapplied: string[];
3
+ failed: {
4
+ branch: string;
5
+ error: string;
6
+ }[];
7
+ }
8
+ /**
9
+ * Move each provisioned sparse clone to the cone the deployment's config now asks for, as
10
+ * recorded beside the branch directories by the Lambda (branch-sparse.ts): a changed content
11
+ * root would otherwise leave every existing clone without its content. A full clone stays
12
+ * full. Best-effort per clone, like [SYNC-M2].
13
+ *
14
+ * The cone is compared without a lock, and changed only under the clone's provisioning lock
15
+ * and content-write lock, both zero-retry, outside a rebase: `sparse-checkout set` rewrites the
16
+ * working tree. A clone that is busy, or holds an unpublished deletion, is retried next cycle.
17
+ */
18
+ export declare function reapplySparseCones(ctx: {
19
+ contentBranchesPath: string;
20
+ }): Promise<SparseConeReport>;
@@ -0,0 +1,136 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { readRecordedSparseCone, sameCone } from '../branch-sparse.js';
4
+ import { invalidateBranchContentCaches } from '../content-index-generation.js';
5
+ import { tryAcquireContentWriteLock } from '../utils/content-write-lock.js';
6
+ import { getErrorMessage, isNodeError } from '../utils/error.js';
7
+ import { isRebaseInProgress } from '../utils/git.js';
8
+ import { workerLog, workerLogWarn } from './log.js';
9
+ import { holdProvisionedWorkspace, releaseProvisionedWorkspace } from './provisioned-workspace.js';
10
+ import { assertSharedRepoConfig, sharedRepoGit } from './shared-repo-git.js';
11
+ /** A clone's cone directories, or null when it is not sparse (or cannot be read). */
12
+ async function currentCone(git) {
13
+ try {
14
+ const listed = (await git.raw(['sparse-checkout', 'list'])).trim();
15
+ return listed ? listed.split('\n') : null;
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ }
21
+ /**
22
+ * Move each provisioned sparse clone to the cone the deployment's config now asks for, as
23
+ * recorded beside the branch directories by the Lambda (branch-sparse.ts): a changed content
24
+ * root would otherwise leave every existing clone without its content. A full clone stays
25
+ * full. Best-effort per clone, like [SYNC-M2].
26
+ *
27
+ * The cone is compared without a lock, and changed only under the clone's provisioning lock
28
+ * and content-write lock, both zero-retry, outside a rebase: `sparse-checkout set` rewrites the
29
+ * working tree. A clone that is busy, or holds an unpublished deletion, is retried next cycle.
30
+ */
31
+ export async function reapplySparseCones(ctx) {
32
+ const report = { reapplied: [], failed: [] };
33
+ const recorded = await readRecordedSparseCone(ctx.contentBranchesPath);
34
+ if (!recorded)
35
+ return report;
36
+ let entries;
37
+ try {
38
+ entries = await fs.readdir(ctx.contentBranchesPath, { withFileTypes: true });
39
+ }
40
+ catch (err) {
41
+ if (isNodeError(err) && err.code === 'ENOENT')
42
+ return report;
43
+ throw err;
44
+ }
45
+ const dirNames = entries
46
+ .filter((entry) => entry.isDirectory() && !entry.name.startsWith('.'))
47
+ .map((entry) => entry.name)
48
+ .sort();
49
+ for (const dirName of dirNames) {
50
+ const branchPath = path.join(ctx.contentBranchesPath, dirName);
51
+ // Without its own .git, git would answer for a repository above the branches root.
52
+ const isClone = await fs.stat(path.join(branchPath, '.git')).then((stat) => stat.isDirectory(), () => false);
53
+ if (!isClone)
54
+ continue;
55
+ const git = sharedRepoGit(branchPath, 'worktree');
56
+ // Read before this clone's config check, so a read that never finishes (an include naming a
57
+ // pipe) must not hold the sync cycle.
58
+ const cone = await currentCone(sharedRepoGit(branchPath, 'worktree', { timeout: { block: 30_000 } }));
59
+ if (cone === null || sameCone(cone, recorded.cone))
60
+ continue;
61
+ try {
62
+ // `sparse-checkout set` checks files out, which runs any filter driver the config names.
63
+ await assertSharedRepoConfig(branchPath, 'worktree');
64
+ const outcome = await reapplyOne(ctx.contentBranchesPath, dirName, git, recorded.cone);
65
+ if (outcome === 'applied') {
66
+ workerLog(` ${dirName}: sparse-checkout cone ${[...cone].sort().join(',')} -> ` +
67
+ (recorded.cone ? [...recorded.cone].sort().join(',') : 'full checkout'));
68
+ report.reapplied.push(dirName);
69
+ }
70
+ else if (typeof outcome === 'number') {
71
+ workerLog(` ${dirName}: sparse-checkout cone waits for ${outcome} unpublished ` +
72
+ `deletion${outcome === 1 ? '' : 's'} to be published`);
73
+ }
74
+ }
75
+ catch (err) {
76
+ const error = getErrorMessage(err);
77
+ workerLogWarn(` ${dirName}: could not change the sparse-checkout cone: ${error}`);
78
+ report.failed.push({ branch: dirName, error });
79
+ }
80
+ }
81
+ return report;
82
+ }
83
+ /**
84
+ * Apply `target` under both locks: `'applied'`, `'skipped'` when the clone is busy or no longer
85
+ * differs, or the count of its unpublished deletions, which hold the cone where it is.
86
+ */
87
+ async function reapplyOne(contentBranchesPath, dirName, git, target) {
88
+ const branchPath = path.join(contentBranchesPath, dirName);
89
+ const hold = await holdProvisionedWorkspace(contentBranchesPath, dirName);
90
+ if (hold.kind !== 'held')
91
+ return 'skipped';
92
+ try {
93
+ let releaseContent;
94
+ let contentLost = false;
95
+ try {
96
+ releaseContent = await tryAcquireContentWriteLock(branchPath, () => {
97
+ contentLost = true;
98
+ });
99
+ }
100
+ catch (err) {
101
+ if (isNodeError(err) && err.code === 'ELOCKED')
102
+ return 'skipped';
103
+ throw err;
104
+ }
105
+ try {
106
+ if (await isRebaseInProgress(branchPath))
107
+ return 'skipped';
108
+ const cone = await currentCone(git);
109
+ if (cone === null || sameCone(cone, target))
110
+ return 'skipped';
111
+ // A tracked file missing from the working tree is the one edit a cone change loses: leaving
112
+ // the cone marks it skip-worktree, so the deletion drops out of `status` and `add -A`, and a
113
+ // wider cone checks the file out again. Modified, untracked and staged changes all survive.
114
+ const deleted = (await git.raw(['ls-files', '--deleted', '-z'])).split('\0').filter(Boolean);
115
+ if (deleted.length > 0)
116
+ return deleted.length;
117
+ if (hold.isCompromised() || contentLost)
118
+ return 'skipped';
119
+ try {
120
+ await git.raw(target
121
+ ? ['sparse-checkout', 'set', '--cone', '--', ...target]
122
+ : ['sparse-checkout', 'disable']);
123
+ }
124
+ finally {
125
+ await invalidateBranchContentCaches(branchPath);
126
+ }
127
+ return 'applied';
128
+ }
129
+ finally {
130
+ await releaseContent().catch(() => { });
131
+ }
132
+ }
133
+ finally {
134
+ await releaseProvisionedWorkspace(hold.release, dirName);
135
+ }
136
+ }