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,726 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { createCanopyOctokit, createOrUpdatePullRequest, } from '../github-service.js';
4
+ import { getErrorMessage } from '../utils/error.js';
5
+ import { isStaleLeaseRejection, workflowPushRefusalFile } from '../utils/git.js';
6
+ import { isTransientAuthFailure } from './github-auth.js';
7
+ import { GitHubMirror, GitHubTransientError, RefusedPushError, assertPlainBranchName, isObjectId, } from './github-mirror.js';
8
+ import { BranchPolicy, GatewayRefusalError, assertPullRequestText } from './branch-policy.js';
9
+ import { gatewayLog, gatewayLogError, gatewayLogWarn, redactEgress, redactError, redactValue, registerLiveSecret, } from './gateway-egress.js';
10
+ import { GatewayInstance, untilAborted } from './gateway-instance.js';
11
+ /** The most `have` IDs one fetch takes. */
12
+ export const MAX_FETCH_HAVE = 10_000;
13
+ /** How long a completed GitHub fetch is reused: one sync cycle's fetch, reconcile and settings push. */
14
+ const FETCH_COALESCE_MS = 30_000;
15
+ /**
16
+ * How long a fetched auth snapshot is answered again. Well under the default 15-minute auth-cache
17
+ * refresh interval, and a worker asking in a loop starts no fetch within it of a success.
18
+ */
19
+ const AUTH_SNAPSHOT_REUSE_MS = 60_000;
20
+ /**
21
+ * The longest an operation waits for a credential re-read in flight before it goes on with the
22
+ * credential it has. A provider can take 87 s for one `getSecret`, and an operation that waited it
23
+ * out would die at the task deadline for nothing; the task's retry still sees a rotated token, and
24
+ * a re-read a hostile worker armed cannot hold every push for its length.
25
+ */
26
+ const REFRESH_JOIN_CAP_MS = 15_000;
27
+ /** The most prerequisites one push names. */
28
+ const MAX_PUSH_PREREQUISITES = 3;
29
+ /** The default cap on a pushed bundle, in bytes. */
30
+ const DEFAULT_MAX_BUNDLE_BYTES = 1024 * 1024 * 1024;
31
+ /**
32
+ * A push to GitHub failed: `rejected` on the first attempt, `rejected-after-stale-lease` on the
33
+ * plain retry after GitHub refused a stale lease. The message is the failure's own: git's output,
34
+ * for a refusal, which the task runner classifies. A {@link RefusedPushError} from the first
35
+ * attempt is thrown as itself.
36
+ */
37
+ export class GitHubPushError extends Error {
38
+ constructor(kind, message) {
39
+ super(message);
40
+ this.kind = kind;
41
+ this.name = 'GitHubPushError';
42
+ }
43
+ }
44
+ /** A moved ref for a log line: `<old7>..<new7>`, or `new branch at <new7>`. */
45
+ export function describeMove(update) {
46
+ const to = update.to.slice(0, 7);
47
+ return update.from === null ? `new branch at ${to}` : `${update.from.slice(0, 7)}..${to}`;
48
+ }
49
+ /** The in-process {@link GitHubGateway}. */
50
+ export function createLocalGitHubGateway(options) {
51
+ return new LocalGitHubGateway(options);
52
+ }
53
+ class LocalGitHubGateway {
54
+ constructor(options) {
55
+ this.options = options;
56
+ /** The credential re-read in flight, which every credentialed operation joins. */
57
+ this.refreshing = null;
58
+ /** The GitHub fetch that operations join: in flight, or completed and still reusable. */
59
+ this.githubFetch = null;
60
+ /** Pushes about to run and branch deletions settled at GitHub: each ends reuse of an earlier fetch. */
61
+ this.pushes = 0;
62
+ this.preparing = null;
63
+ /** The auth snapshot being fetched, which every concurrent caller joins. */
64
+ this.snapshotFetch = null;
65
+ /** The last snapshot fetched, and when (`performance.now()`). */
66
+ this.lastSnapshot = null;
67
+ this.preflight = 'pending';
68
+ this.mirror = new GitHubMirror(options.stateDirectory, options.timeoutMs);
69
+ this.instance = new GatewayInstance(options);
70
+ this.octokit =
71
+ options.octokit ??
72
+ createCanopyOctokit(options.auth.octokitAuth, { warn: gatewayLogWarn, error: gatewayLogError },
73
+ // A token the credential provider rotated or an App minted is scrubbed from the first
74
+ // request that carries it, before any `credential()` call has registered it.
75
+ {
76
+ onAuthorization: (value) => registerLiveSecret(value.replace(/^(?:token|bearer)\s+/i, '')),
77
+ });
78
+ this.policy = new BranchPolicy({
79
+ baseBranch: options.baseBranch,
80
+ protectedBranches: options.protectedBranches,
81
+ });
82
+ }
83
+ get repo() {
84
+ return { owner: this.options.githubOwner, repo: this.options.githubRepo };
85
+ }
86
+ /**
87
+ * Run one operation that uses the credential.
88
+ *
89
+ * It first JOINS a re-read in flight, bounded by its own `signal` and by
90
+ * {@link REFRESH_JOIN_CAP_MS}, so the 5s/10s/20s task retries see a rotated token. Then, if the
91
+ * operation fails after any of its GitHub-bound git commands or Octokit calls failed, it ARMS a
92
+ * re-read. Not gated on the failure looking
93
+ * auth-shaped: a dead token's git failure is a plain exit 128 with no HTTP status, and a token
94
+ * that lost access fails as a 404 or a 403. What never arms it: a refusal of the gateway's own
95
+ * (`GatewayRefusalError`, whatever came before it; an invalid object ID), anything else local, a lock file the mirror's own
96
+ * housekeeping holds (`isOwnLockFailure`), and an Octokit 422 (`api`). A 404 does arm it, the
97
+ * branch-already-gone one `deleteBranch` callers tolerate included. What bounds a caller that
98
+ * fails on purpose is the floors behind `auth.refreshCredential`:
99
+ * `refreshGitHubTokenMinIntervalMs` (github-auth.ts) and the provider's own. On the GitHub App
100
+ * path a re-read is a no-op.
101
+ *
102
+ * The re-read starts detached, after the failure has been delivered: a provider can take 87s for
103
+ * one `getSecret`, longer than a task's whole deadline.
104
+ *
105
+ * What it returns or throws leaves the gateway, so it is scrubbed here (worker/gateway-egress.ts).
106
+ */
107
+ async credentialed(signal, operation) {
108
+ try {
109
+ await this.joinRefresh(signal);
110
+ const reach = { failed: false };
111
+ try {
112
+ return redactValue(await operation(reach));
113
+ }
114
+ catch (err) {
115
+ if (reach.failed && !(err instanceof GatewayRefusalError))
116
+ this.armRefresh();
117
+ throw err;
118
+ }
119
+ }
120
+ catch (err) {
121
+ throw redactError(err);
122
+ }
123
+ }
124
+ /**
125
+ * One Octokit call inside {@link credentialed}, whose failure arms a re-read unless it is a 422.
126
+ * A 422 is GitHub validating the request, never judging the credential, and the worker treats
127
+ * two as success: a head branch GitHub already deleted after a merge (`isRefAlreadyGoneError`),
128
+ * and a pull request with no commits between (`isNoCommitsBetweenError`). Arming on them would
129
+ * spend the provider's floor on routine traffic and hold off the re-read a real rotation needs.
130
+ */
131
+ async api(reach, call) {
132
+ try {
133
+ return await call();
134
+ }
135
+ catch (err) {
136
+ if (!(typeof err === 'object' && err !== null && 'status' in err && err.status === 422)) {
137
+ reach.failed = true;
138
+ }
139
+ throw err;
140
+ }
141
+ }
142
+ /**
143
+ * The credential for one operation's GitHub-bound git, resolved ONCE per operation, so every
144
+ * command in it carries the same token and a resolution failure cannot replace a git failure
145
+ * being classified. Async because a GitHub App's installation token is minted on demand and
146
+ * lasts about an hour, so nothing may cache it. A mint failure propagates as thrown, carrying
147
+ * the `.status` `isPermanentTaskFailure` classifies on.
148
+ */
149
+ async credential(reach) {
150
+ const { remoteUrl, githubOwner, githubRepo } = this.options;
151
+ const url = remoteUrl === undefined
152
+ ? `https://github.com/${githubOwner}/${githubRepo}.git`
153
+ : typeof remoteUrl === 'string'
154
+ ? remoteUrl
155
+ : await remoteUrl();
156
+ const token = await this.api(reach, () => this.options.auth.resolveGitToken());
157
+ registerLiveSecret(token);
158
+ return {
159
+ url,
160
+ token,
161
+ onFailure: () => {
162
+ reach.failed = true;
163
+ },
164
+ };
165
+ }
166
+ /**
167
+ * Wait for a re-read in flight, for at most {@link REFRESH_JOIN_CAP_MS}, unless `signal` aborts
168
+ * first; past the cap the operation goes on with the credential as it stands.
169
+ */
170
+ async joinRefresh(signal) {
171
+ const inFlight = this.refreshing;
172
+ if (inFlight === null)
173
+ return;
174
+ let timer;
175
+ const capped = new Promise((resolve) => {
176
+ timer = setTimeout(resolve, this.options.refreshJoinCapMs ?? REFRESH_JOIN_CAP_MS);
177
+ });
178
+ try {
179
+ await untilAborted(Promise.race([inFlight, capped]), signal);
180
+ }
181
+ finally {
182
+ clearTimeout(timer);
183
+ }
184
+ }
185
+ /**
186
+ * Bring the mirror up to GitHub, coalesced: join the GitHub fetch in flight, or reuse one that
187
+ * completed less than {@link FETCH_COALESCE_MS} ago and is still current ({@link isCurrent}), or
188
+ * else start one with the credential `resolve` gives, resolved only then, so a joining operation
189
+ * mints no token. A failed fetch is never joined or reused. The fetch is
190
+ * detached from every caller's `signal`, which ends only that caller's wait: a fetch of a whole
191
+ * repository can outlast any one caller, and killing it on an abort would start it over each
192
+ * time. Its own bound is the mirror's inactivity timeout. It runs in a mirror session of its
193
+ * own, which repairs a corrupt mirror first ({@link GitHubMirror.heal}), and its failure re-reads
194
+ * the credential when it reached GitHub, whoever started it.
195
+ */
196
+ async joinGitHubFetch(resolve, signal) {
197
+ const current = this.githubFetch;
198
+ const window = this.options.fetchCoalesceMs ?? FETCH_COALESCE_MS;
199
+ const reusable = current !== null &&
200
+ !current.failed &&
201
+ (current.completedAt === null ||
202
+ (Date.now() - current.completedAt < window && this.isCurrent(current.snapshot)));
203
+ const job = reusable ? current : this.startGitHubFetch(resolve);
204
+ return untilAborted(job.done, signal);
205
+ }
206
+ /** Whether the mirror still holds GitHub as the fetch that saw `snapshot` left it. */
207
+ isCurrent(snapshot) {
208
+ return (snapshot !== null &&
209
+ snapshot.generation === this.mirror.generation &&
210
+ snapshot.pushes === this.pushes);
211
+ }
212
+ /**
213
+ * Run `fn` in a mirror session over what a GitHub fetch put in the mirror: `join` one, and
214
+ * check inside the session that nothing has changed the mirror or GitHub since
215
+ * ({@link isCurrent}). A session between the two may have re-created the mirror, leaving no
216
+ * heads, which would read as GitHub having none, or pushed. When something has, `join` again,
217
+ * which then fetches afresh; a second miss is transient.
218
+ */
219
+ async afterGitHubFetch(join, fn) {
220
+ for (let attempt = 1;; attempt++) {
221
+ const snapshot = await join();
222
+ const result = await this.mirror.exclusive(async (session) => this.isCurrent(snapshot) ? { value: await fn(session) } : null);
223
+ if (result !== null)
224
+ return result.value;
225
+ if (attempt === 2) {
226
+ throw new GitHubTransientError('github-fetch-failed', 'The GitHub mirror or GitHub changed after each of two GitHub fetches');
227
+ }
228
+ }
229
+ }
230
+ /** Start a GitHub fetch, taking its place at once, before the credential resolves. */
231
+ startGitHubFetch(resolve) {
232
+ let reachedGitHub = false;
233
+ const run = async () => {
234
+ const credential = await resolve();
235
+ const own = {
236
+ url: credential.url,
237
+ token: credential.token,
238
+ onFailure: () => {
239
+ reachedGitHub = true;
240
+ credential.onFailure();
241
+ },
242
+ };
243
+ const snapshot = await this.mirror.exclusive(async (mirror) => {
244
+ const pushes = this.pushes;
245
+ await this.mirror.heal(mirror, own);
246
+ await this.mirror.fetchFromGitHub(mirror, own);
247
+ return { generation: this.mirror.generation, pushes };
248
+ });
249
+ await this.mirror.afterFetch();
250
+ return snapshot;
251
+ };
252
+ const job = { failed: false, completedAt: null, snapshot: null, done: run() };
253
+ job.done.then((snapshot) => {
254
+ job.completedAt = Date.now();
255
+ job.snapshot = snapshot;
256
+ }, () => {
257
+ job.failed = true;
258
+ if (reachedGitHub)
259
+ this.armRefresh();
260
+ });
261
+ this.githubFetch = job;
262
+ return job;
263
+ }
264
+ /** A push or branch deletion at GitHub: every later read must come from a fetch after it. */
265
+ invalidateGitHubFetch() {
266
+ this.pushes++;
267
+ this.githubFetch = null;
268
+ }
269
+ /** Start a re-read on the next turn of the event loop, unless one is already in flight. */
270
+ armRefresh() {
271
+ if (this.refreshing !== null)
272
+ return;
273
+ const refreshing = new Promise((resolve) => setImmediate(resolve))
274
+ .then(() => this.refreshCredential())
275
+ .finally(() => {
276
+ if (this.refreshing === refreshing)
277
+ this.refreshing = null;
278
+ });
279
+ this.refreshing = refreshing;
280
+ }
281
+ prepare() {
282
+ return (this.preparing ??= this.prepareOnce());
283
+ }
284
+ /**
285
+ * One fetch serves every caller: concurrent callers join it, and its result is answered again
286
+ * for {@link AUTH_SNAPSHOT_REUSE_MS}. A fetch is a request per user, so a worker asking in a loop
287
+ * would otherwise hold the provider's API at its rate limit. The fetch runs apart from every
288
+ * caller's signal, which ends only that caller's wait, so a retry after a timed-out wait joins it.
289
+ */
290
+ async authSnapshot(signal) {
291
+ const provider = this.options.authSnapshot;
292
+ if (provider === undefined) {
293
+ throw new Error('The GitHub gateway has no auth provider key configured');
294
+ }
295
+ // Before a fetch starts: one no caller waits on would leave its failure unhandled.
296
+ signal?.throwIfAborted();
297
+ const reuseMs = this.options.authSnapshotReuseMs ?? AUTH_SNAPSHOT_REUSE_MS;
298
+ const last = this.lastSnapshot;
299
+ if (last !== null && performance.now() - last.at < reuseMs)
300
+ return last.snapshot;
301
+ // Not `credentialed()`: this never touches GitHub, and a provider failure must not arm the
302
+ // GitHub credential re-read (the provider's own 401/403 re-read lives with the provider).
303
+ const fetching = (this.snapshotFetch ??= (async () => provider())()
304
+ .then((snapshot) => {
305
+ const scrubbed = redactValue(snapshot);
306
+ this.lastSnapshot = { snapshot: scrubbed, at: performance.now() };
307
+ return scrubbed;
308
+ })
309
+ .finally(() => {
310
+ this.snapshotFetch = null;
311
+ }));
312
+ try {
313
+ return await untilAborted(fetching, signal);
314
+ }
315
+ catch (err) {
316
+ throw redactError(err);
317
+ }
318
+ }
319
+ termination(signal) {
320
+ return this.instance.termination(signal);
321
+ }
322
+ completeLifecycle(signal) {
323
+ return this.instance.completeLifecycle(signal);
324
+ }
325
+ async health(signal) {
326
+ let ready = false;
327
+ let fatalError = null;
328
+ if (this.preparing !== null) {
329
+ try {
330
+ await untilAborted(this.preparing, signal);
331
+ ready = true;
332
+ }
333
+ catch (err) {
334
+ if (signal?.aborted)
335
+ throw err;
336
+ fatalError = redactEgress(getErrorMessage(err));
337
+ }
338
+ }
339
+ return {
340
+ ready,
341
+ fatalError,
342
+ preflight: this.preflight,
343
+ clerkConfigured: this.options.authSnapshot !== undefined,
344
+ protectedBranches: this.policy.describe(),
345
+ };
346
+ }
347
+ /**
348
+ * Fatal: a failed App preflight (unless it looks transient), a state directory inside the
349
+ * workspace, and one this process cannot create or does not own outright, since no later session
350
+ * could clear those and the credential is used in the mirror. Not fatal: creating the mirror,
351
+ * which every session retries ({@link GitHubMirror.exclusive}) while a failure memoized here
352
+ * would outlast a transient cause (a full disk, a failed git spawn) until the process restarted.
353
+ */
354
+ async prepareOnce() {
355
+ try {
356
+ // Before the mirror's first fetch, whose failure would otherwise blame the repository.
357
+ await this.preflightGitHubAppAuth();
358
+ if (this.options.workspacePath !== undefined) {
359
+ await this.ensureStateDirectoryIsPrivate(this.options.workspacePath);
360
+ }
361
+ await this.mirror.assertStateDirectory();
362
+ }
363
+ catch (err) {
364
+ throw redactError(err);
365
+ }
366
+ try {
367
+ await this.mirror.ensure();
368
+ }
369
+ catch (err) {
370
+ gatewayLogWarn(`Could not create the GitHub mirror at startup (${redactEgress(getErrorMessage(err))}); ` +
371
+ 'the next operation tries again');
372
+ }
373
+ // The first GitHub fetch starts now, detached, so the first operation that needs one joins
374
+ // it under way. Its failure surfaces to whoever joins it.
375
+ if (this.options.prefetch === false)
376
+ return;
377
+ void this.credentialed(undefined, (reach) => this.joinGitHubFetch(() => this.credential(reach))).catch(() => undefined);
378
+ }
379
+ /**
380
+ * Prove a GitHub App credential works, failing startup UNLESS THE FAILURE POSITIVELY LOOKS
381
+ * TRANSIENT. Not always fatal, so the two credential paths degrade alike: on the token path a
382
+ * GitHub 502 at boot is absorbed and the loops retry, where rethrowing would crash-loop an App
383
+ * worker until GitHub recovered, each time telling the operator to check the key. Fail CLOSED,
384
+ * through `isTransientAuthFailure` rather than the inverse of `isPermanentTaskFailure`, which
385
+ * defaults a status-less error to transient: a key too mangled to sign with fails locally and
386
+ * status-lessly, and would otherwise boot a worker on a dead credential with no
387
+ * `lastFatalError`. No-op on the token path, where the first real request checks as much.
388
+ */
389
+ async preflightGitHubAppAuth() {
390
+ if (!this.options.githubApp) {
391
+ this.preflight = 'not-an-app';
392
+ return;
393
+ }
394
+ try {
395
+ registerLiveSecret(await this.options.auth.resolveGitToken());
396
+ }
397
+ catch (err) {
398
+ // [REDACT] Both messages below reach worker-status.json and the browser.
399
+ const detail = redactEgress(getErrorMessage(err));
400
+ if (isTransientAuthFailure(err)) {
401
+ this.preflight = 'unverified';
402
+ gatewayLogWarn(`Could not verify GitHub App authentication at startup: ${detail}. ` +
403
+ 'Continuing — this looks transient, and the credential is minted again on first use.');
404
+ return;
405
+ }
406
+ // Re-thrown with context, unlike credential(): nothing classifies a startup failure.
407
+ throw new Error(`GitHub App authentication failed: ${detail}. ` +
408
+ 'Check the app id, the installation id, and that the private key belongs to that app.');
409
+ }
410
+ this.preflight = 'verified';
411
+ gatewayLog('GitHub App authentication verified');
412
+ }
413
+ /**
414
+ * Refuse a state directory on the shared filesystem: the mirror there would be as writable by
415
+ * the Lambda as remote.git is.
416
+ */
417
+ async ensureStateDirectoryIsPrivate(workspacePath) {
418
+ const workspace = await realpathOfNearest(workspacePath);
419
+ const state = await realpathOfNearest(this.options.stateDirectory);
420
+ const relative = path.relative(workspace, state);
421
+ if (relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative))) {
422
+ throw new Error(`The worker's state directory (${this.options.stateDirectory}) is inside its shared workspace ` +
423
+ `(${workspacePath}). It holds the repository the GitHub credential is used ` +
424
+ `in, so it must be somewhere the CMS Lambda cannot write.`);
425
+ }
426
+ }
427
+ defaultBranch(signal) {
428
+ return this.credentialed(signal, async (reach) => {
429
+ const name = await this.policy.recordGitHubDefault(() => this.readDefaultBranch(reach, signal));
430
+ if (name === null)
431
+ throw new Error('GitHub reported no default branch');
432
+ return name;
433
+ });
434
+ }
435
+ /** Reads GitHub's default branch with git, in a mirror session, never through Octokit. */
436
+ isProtectedBranch(branch, signal) {
437
+ return this.credentialed(signal, (reach) => this.policy.isProtected(branch, async () => {
438
+ const credential = await this.credential(reach);
439
+ // A session can wait behind a whole-repository fetch; an abort ends only this wait.
440
+ return untilAborted(this.mirror.exclusive((session) => session.githubDefaultBranch(credential, signal)), signal);
441
+ }));
442
+ }
443
+ /**
444
+ * GitHub's default branch, through the API, for the policy's operations outside a mirror
445
+ * session; a push reads it with git, inside its session (`MirrorSession.pushToGitHub`).
446
+ */
447
+ async readDefaultBranch(reach, signal) {
448
+ const { data } = await this.api(reach, () => this.octokit.repos.get({ ...this.repo, ...(signal ? { request: { signal } } : {}) }));
449
+ return data.default_branch;
450
+ }
451
+ /** Validate a pull request's head and base, and apply the policy to its head and text. */
452
+ async assertPullRequestAllowed(reach, request, signal) {
453
+ const git = await this.mirror.checkGit();
454
+ await assertPlainBranchName(request.head, git, 'pull-request');
455
+ // The base is GitHub's to validate and the policy's to leave alone (BranchPolicy), and may be
456
+ // a configured name outside branchNameProblem's narrower set: git's rules, the length cap and
457
+ // no leading `-` only.
458
+ await assertPlainBranchName(request.base, git, 'pull-request', false);
459
+ await this.policy.assertPullRequestAllowed(request, () => this.readDefaultBranch(reach, signal));
460
+ }
461
+ async fetch(request, signal, onProgress) {
462
+ const stopWatching = onProgress === undefined ? null : this.mirror.watchActivity(onProgress);
463
+ try {
464
+ return await this.fetchOnce(request, signal);
465
+ }
466
+ finally {
467
+ stopWatching?.();
468
+ }
469
+ }
470
+ fetchOnce(request, signal) {
471
+ const have = [...new Set(request.have)];
472
+ if (have.length > MAX_FETCH_HAVE) {
473
+ throw new Error(`A fetch takes at most ${MAX_FETCH_HAVE} have IDs, not ${have.length}`);
474
+ }
475
+ for (const id of have)
476
+ assertObjectId(id);
477
+ return this.credentialed(signal, (reach) => this.afterGitHubFetch(() => this.joinGitHubFetch(() => this.credential(reach), signal), async (mirror) => {
478
+ this.mirror.assertUsable();
479
+ const refs = await mirror.heads();
480
+ const file = await this.mirror.outgoingBundlePath();
481
+ const created = await mirror.createBundle(have, file, signal);
482
+ const bundleId = await this.mirror.setPendingBundle(created ? file : null);
483
+ return { refs, bundleId };
484
+ }));
485
+ }
486
+ openBundle(bundleId) {
487
+ return this.mirror.openBundle(bundleId);
488
+ }
489
+ push(request, bundle, signal) {
490
+ return this.credentialed(signal, (reach) => this.pushOnce(request, bundle, reach, signal)).finally(
491
+ // A refusal can come before the body is read: never leave its writer blocked.
492
+ () => bundle?.destroy());
493
+ }
494
+ async pushOnce(request, bundle, reach, signal) {
495
+ assertObjectId(request.sha, 'a commit ID');
496
+ if (request.lease !== undefined)
497
+ assertObjectId(request.lease, 'a commit ID');
498
+ if (request.prerequisites.length > MAX_PUSH_PREREQUISITES) {
499
+ throw new Error(`A push names at most ${MAX_PUSH_PREREQUISITES} prerequisites`);
500
+ }
501
+ for (const id of request.prerequisites)
502
+ assertObjectId(id, 'a commit ID');
503
+ // Before `branch` reaches anything else: the bundle's expected head, a refspec.
504
+ await assertPlainBranchName(request.branch, await this.mirror.checkGit());
505
+ // Every attempt, and a GitHub fetch this push starts, carries this one credential (see
506
+ // credential()), resolved once and only when first needed.
507
+ let resolved = null;
508
+ const credential = () => (resolved ??= this.credential(reach));
509
+ // The join comes before the session, never inside it: the fetch takes a session of its own,
510
+ // and sessions are not re-entrant. So does resolving the credential, so the session never
511
+ // waits on GitHub's API. A body is read first, so its writer is not left waiting.
512
+ const session = (received) => this.afterGitHubFetch(async () => {
513
+ const snapshot = await this.joinGitHubFetchForPush(credential, request.branch, signal);
514
+ await credential();
515
+ return snapshot;
516
+ }, async (mirror) => this.pushInSession(mirror, await credential(), request, received, signal));
517
+ return bundle === undefined
518
+ ? session(null)
519
+ : this.mirror.ingest(bundle, request, this.maxBundleBytes, session);
520
+ }
521
+ /**
522
+ * The plan's step 2 (§3.2): the push goes ahead only once a GitHub fetch has SUCCEEDED, fresh or
523
+ * reused, so the mirror's heads are GitHub's for the reachability test and the prerequisite
524
+ * check; otherwise it is transient. A token mint GitHub refuses is the exception: it carries its HTTP
525
+ * status through, so the task runner classifies it. That is the whole clause: a mirror with no heads after a
526
+ * successful fetch means GitHub has none, a first push.
527
+ */
528
+ async joinGitHubFetchForPush(credential, branch, signal) {
529
+ try {
530
+ return await this.joinGitHubFetch(credential, signal);
531
+ }
532
+ catch (err) {
533
+ // An HTTP failure is the credential's own (a mint refused), classified by its status.
534
+ if (err instanceof GitHubTransientError || hasHttpStatus(err))
535
+ throw err;
536
+ // [REDACT] The message reaches the task's lastError.
537
+ throw new GitHubTransientError('github-fetch-failed', `Could not fetch from GitHub before pushing ${branch}: ` +
538
+ redactEgress(getErrorMessage(err)).trim());
539
+ }
540
+ }
541
+ get maxBundleBytes() {
542
+ return this.options.maxBundleBytes ?? DEFAULT_MAX_BUNDLE_BYTES;
543
+ }
544
+ /**
545
+ * The push, in one mirror session, every call in it a plain call (the session is not
546
+ * re-entrant), over the mirror a GitHub fetch just filled ({@link afterGitHubFetch}), so its
547
+ * heads are GitHub's for the reachability test and the prerequisite check: find `sha`, in the
548
+ * mirror or in the quarantine `received` goes into; push from there, which refuses a protected
549
+ * branch first. The mirror kills its git when `signal` aborts, and GitHub moves the ref only
550
+ * after receiving the whole pack, so a killed push changes nothing or is found already done by
551
+ * the re-run, unless git's transport child outlives it
552
+ * (safeinsights/canopycms#907).
553
+ */
554
+ async pushInSession(mirror, credential, request, received, signal) {
555
+ const { branch, sha } = request;
556
+ // Never fires: finding corruption changes the generation, so afterGitHubFetch never runs this
557
+ // over a mirror found corrupt after its fetch. It refuses rather than push from a corrupt one.
558
+ this.mirror.assertUsable();
559
+ const heads = await mirror.heads();
560
+ let repo;
561
+ if (received === null) {
562
+ const type = (await mirror.objectTypes([sha])).get(sha);
563
+ if (type === 'missing')
564
+ return this.needObjects(mirror, request);
565
+ if (type !== 'commit')
566
+ throw new RefusedPushError(branch, 'not-a-commit', sha);
567
+ if (!(await mirror.reachableFromHeads(sha)))
568
+ return this.needObjects(mirror, request);
569
+ }
570
+ else {
571
+ repo = await mirror.quarantine(received, request, heads, signal);
572
+ }
573
+ const attempt = (lease) => mirror.pushToGitHub(credential, branch, sha, {
574
+ lease,
575
+ signal,
576
+ repo,
577
+ policy: this.policy,
578
+ // Whatever the push does to GitHub, a fetch reused from before it may not show it.
579
+ onPushing: () => this.invalidateGitHubFetch(),
580
+ });
581
+ try {
582
+ return { kind: 'pushed', ...(await attempt(request.lease)), pastStaleLease: false };
583
+ }
584
+ catch (err) {
585
+ if (err instanceof RefusedPushError || err instanceof GitHubTransientError)
586
+ throw err;
587
+ const message = getErrorMessage(err);
588
+ // A refused lease means GitHub is not at the commit the lease names, which is routine: a
589
+ // task re-runs after a crash, and the marker it came from survives any failure to clear
590
+ // it. So retry PLAIN and let git adjudicate: a non-forced push succeeds only as a
591
+ // fast-forward, so it can destroy nothing. git evaluates a lease only when it has an
592
+ // update to apply, so an up-to-date ref never gets here. A workflow refusal is final
593
+ // whatever the lease, so it is not retried.
594
+ if (request.lease &&
595
+ isStaleLeaseRejection(message) &&
596
+ workflowPushRefusalFile(message) === null) {
597
+ let update;
598
+ try {
599
+ update = await attempt();
600
+ }
601
+ catch (retryErr) {
602
+ throw new GitHubPushError('rejected-after-stale-lease', getErrorMessage(retryErr));
603
+ }
604
+ return { kind: 'pushed', ...update, pastStaleLease: true };
605
+ }
606
+ throw new GitHubPushError('rejected', message);
607
+ }
608
+ }
609
+ async needObjects(mirror, request) {
610
+ return { kind: 'need-objects', usable: await mirror.usablePrerequisites(request.prerequisites) };
611
+ }
612
+ createPullRequest(request, signal) {
613
+ return this.credentialed(signal, async (reach) => {
614
+ await this.assertPullRequestAllowed(reach, request, signal);
615
+ const { data } = await this.api(reach, () => this.octokit.pulls.create({ ...this.repo, ...request, request: { signal } }));
616
+ return { number: data.number, url: data.html_url };
617
+ });
618
+ }
619
+ updatePullRequest(number, update, signal) {
620
+ return this.credentialed(signal, async (reach) => {
621
+ assertPullRequestText(`Refusing to update pull request #${number}`, `#${number}`, update);
622
+ await this.api(reach, () => this.octokit.pulls.update({
623
+ ...this.repo,
624
+ pull_number: number,
625
+ ...update,
626
+ request: { signal },
627
+ }));
628
+ });
629
+ }
630
+ getPullRequest(number, signal) {
631
+ return this.credentialed(signal, (reach) => this.pullRequest(reach, number, signal));
632
+ }
633
+ async pullRequest(reach, number, signal) {
634
+ const { data } = await this.api(reach, () => this.octokit.pulls.get({ ...this.repo, pull_number: number, request: { signal } }));
635
+ return data;
636
+ }
637
+ createOrUpdatePullRequest(request, signal) {
638
+ return this.credentialed(signal, async (reach) => {
639
+ await this.assertPullRequestAllowed(reach, request, signal);
640
+ return this.api(reach, () => createOrUpdatePullRequest({
641
+ octokit: this.octokit,
642
+ ...this.repo,
643
+ ...request,
644
+ signal,
645
+ warn: gatewayLogWarn,
646
+ }));
647
+ });
648
+ }
649
+ convertPullRequestToDraft(number, signal) {
650
+ return this.credentialed(signal, async (reach) => {
651
+ // The REST API cannot convert a PR to a draft; GraphQL can.
652
+ const pr = await this.pullRequest(reach, number, signal);
653
+ await this.api(reach, () => this.octokit.graphql(`mutation($id: ID!) { convertPullRequestToDraft(input: { pullRequestId: $id }) { pullRequest { isDraft } } }`, { id: pr.node_id, request: { signal } }));
654
+ });
655
+ }
656
+ deleteBranch(branch, signal) {
657
+ return this.credentialed(signal, async (reach) => {
658
+ await assertPlainBranchName(branch, await this.mirror.checkGit(), 'delete');
659
+ await this.policy.assertDeleteAllowed(branch, () => this.readDefaultBranch(reach, signal));
660
+ // Once the call has settled, when GitHub may have acted: any fetch before then, a fetch whose
661
+ // session ran during the call included, may still show the branch.
662
+ try {
663
+ await this.api(reach, () => this.octokit.git.deleteRef({ ...this.repo, ref: `heads/${branch}`, request: { signal } }));
664
+ }
665
+ finally {
666
+ this.invalidateGitHubFetch();
667
+ }
668
+ });
669
+ }
670
+ /**
671
+ * Re-read the credential, for {@link armRefresh}. Every consumer reads the credential per use,
672
+ * so a re-read repairs all of them for their next use. Never throws: the failure that armed it
673
+ * has already been reported. Bounded by the timeout, because every credentialed operation joins
674
+ * it and a provider may have no bound of its own. A losing read is not cancelled, but
675
+ * `auth.refreshCredential` discards a result older than one already applied.
676
+ */
677
+ async refreshCredential() {
678
+ let timer;
679
+ const timedOut = new Promise((_, reject) => {
680
+ timer = setTimeout(() => reject(new Error(`the re-read did not settle within ${this.options.timeoutMs}ms`)), this.options.timeoutMs);
681
+ });
682
+ try {
683
+ await Promise.race([this.options.auth.refreshCredential(), timedOut]);
684
+ // The token path's rotated token is scrubbed before any operation uses it. On the App path
685
+ // the re-read is a no-op, so there is nothing new to scrub.
686
+ if (!this.options.githubApp)
687
+ registerLiveSecret(await this.options.auth.resolveGitToken());
688
+ }
689
+ catch (err) {
690
+ logFailedCredentialRefresh(err);
691
+ }
692
+ finally {
693
+ clearTimeout(timer);
694
+ }
695
+ }
696
+ }
697
+ function logFailedCredentialRefresh(err) {
698
+ // [REDACT] The message can name the secret and, on a malformed-secret path, quote what was read.
699
+ gatewayLogError('Failed to re-read the GitHub credential after a failure:', redactEgress(getErrorMessage(err)));
700
+ }
701
+ /** Refuse anything but a full object ID, before it can reach git. */
702
+ function assertObjectId(value, what = 'an object ID') {
703
+ if (!isObjectId(value))
704
+ throw new Error(`Not ${what}: ${JSON.stringify(value.slice(0, 80))}`);
705
+ }
706
+ /** `target` with symlinks resolved as far as it exists, so a link cannot hide where it lands. */
707
+ async function realpathOfNearest(target) {
708
+ const missing = [];
709
+ let current = path.resolve(target);
710
+ for (;;) {
711
+ try {
712
+ return path.join(await fs.realpath(current), ...missing.reverse());
713
+ }
714
+ catch {
715
+ const parent = path.dirname(current);
716
+ if (parent === current)
717
+ return path.resolve(target);
718
+ missing.push(path.basename(current));
719
+ current = parent;
720
+ }
721
+ }
722
+ }
723
+ /** Whether `err` carries an HTTP status, as an Octokit or a token mint's failure does. */
724
+ function hasHttpStatus(err) {
725
+ return (typeof err === 'object' && err !== null && 'status' in err && typeof err.status === 'number');
726
+ }