@astralyn/sash 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 (339) hide show
  1. package/CHANGELOG.md +184 -0
  2. package/LICENSE +21 -0
  3. package/README.md +71 -0
  4. package/THIRD_PARTY_NOTICES.md +54 -0
  5. package/dist/api.js +103 -0
  6. package/dist/browser.js +25 -0
  7. package/dist/cli-errors.js +15 -0
  8. package/dist/cli.js +122 -0
  9. package/dist/commands/lifecycle.js +50 -0
  10. package/dist/commands/logs.js +54 -0
  11. package/dist/commands/shared.js +8 -0
  12. package/dist/commands/status.js +39 -0
  13. package/dist/commands/update.js +5 -0
  14. package/dist/commands/upgrade.js +43 -0
  15. package/dist/commands/web.js +33 -0
  16. package/dist/contracts.js +412 -0
  17. package/dist/core-config-validation.js +72 -0
  18. package/dist/core-install-record.js +55 -0
  19. package/dist/core-install-transaction.js +114 -0
  20. package/dist/core-update-coordination.js +105 -0
  21. package/dist/core-update-service.js +245 -0
  22. package/dist/core-update.js +804 -0
  23. package/dist/core-version.js +18 -0
  24. package/dist/core.js +206 -0
  25. package/dist/daemon/app.js +157 -0
  26. package/dist/daemon/context.js +40 -0
  27. package/dist/daemon/entry.js +67 -0
  28. package/dist/daemon/errors.js +47 -0
  29. package/dist/daemon/handlers/core.js +17 -0
  30. package/dist/daemon/handlers/daemon.js +66 -0
  31. package/dist/daemon/handlers/profiles.js +57 -0
  32. package/dist/daemon/handlers/proxy.js +12 -0
  33. package/dist/daemon/handlers/settings.js +48 -0
  34. package/dist/daemon/router.js +312 -0
  35. package/dist/daemon/scheduler.js +33 -0
  36. package/dist/daemon/server.js +121 -0
  37. package/dist/daemon-auth.js +71 -0
  38. package/dist/daemon-client.js +55 -0
  39. package/dist/daemon-entry.js +13 -0
  40. package/dist/daemon-http.js +93 -0
  41. package/dist/daemon-lifecycle.js +378 -0
  42. package/dist/daemon-proxy.js +176 -0
  43. package/dist/daemon-static.js +106 -0
  44. package/dist/daemon.js +3 -0
  45. package/dist/error-utils.js +14 -0
  46. package/dist/fs-atomic.js +123 -0
  47. package/dist/github.js +187 -0
  48. package/dist/http.js +282 -0
  49. package/dist/json-shape.js +17 -0
  50. package/dist/log-follow.js +280 -0
  51. package/dist/log.js +17 -0
  52. package/dist/managed-state-transaction.js +377 -0
  53. package/dist/mihomo-config.js +334 -0
  54. package/dist/node-version-guard.js +8 -0
  55. package/dist/offline-mutation.js +58 -0
  56. package/dist/paths.js +57 -0
  57. package/dist/process.js +572 -0
  58. package/dist/profile-migration.js +101 -0
  59. package/dist/profile-service.js +667 -0
  60. package/dist/profiles.js +182 -0
  61. package/dist/runtime-lifecycle.js +205 -0
  62. package/dist/runtime-owner.js +87 -0
  63. package/dist/runtime-recovery.js +31 -0
  64. package/dist/sash-client.js +163 -0
  65. package/dist/settings-service.js +242 -0
  66. package/dist/settings.js +314 -0
  67. package/dist/state-lock.js +331 -0
  68. package/dist/status.js +279 -0
  69. package/dist/supervisor.js +301 -0
  70. package/dist/sysproxy/common.js +99 -0
  71. package/dist/sysproxy/darwin.js +287 -0
  72. package/dist/sysproxy/factory.js +71 -0
  73. package/dist/sysproxy/gnome.js +181 -0
  74. package/dist/sysproxy/legacy.js +58 -0
  75. package/dist/sysproxy/snapshot.js +284 -0
  76. package/dist/sysproxy/types.js +24 -0
  77. package/dist/sysproxy/windows.js +216 -0
  78. package/dist/sysproxy.js +8 -0
  79. package/dist/system-proxy-manager.js +460 -0
  80. package/dist/tun-guidance.js +11 -0
  81. package/dist/ui/assets/05129d5ad91a6181d272cc3d031363d6-DJkDXMrK.woff2 +0 -0
  82. package/dist/ui/assets/0526ebf94418638dfd5b6cd9b06c07d4-BzWe4Lji.woff2 +0 -0
  83. package/dist/ui/assets/053f2647a78f659f6f8a3ae8436bd265-BocXYDl5.woff2 +0 -0
  84. package/dist/ui/assets/078f666863424d3fd2d98e3ea528704c-DQ_QCvOt.woff2 +0 -0
  85. package/dist/ui/assets/07f3ff379663ffa0222c12f241421dd9-Cma7oDlr.woff2 +0 -0
  86. package/dist/ui/assets/08427b8d865853bbd7c439321b26495f-CJmC8fWx.woff2 +0 -0
  87. package/dist/ui/assets/095224e4db29d40c54098da02b266b0a-DjsP0nQn.woff2 +0 -0
  88. package/dist/ui/assets/0ada18ca45940b08accde5c61ee871e3-E1BZD3RX.woff2 +0 -0
  89. package/dist/ui/assets/0be242294f7d791af850c6df38ac78a0-2cL6Ntwf.woff2 +0 -0
  90. package/dist/ui/assets/0bf18c938d0c27cce06c39f855405fdb-CruLZZZJ.woff2 +0 -0
  91. package/dist/ui/assets/0cfb3e9f4edf60839c37dc83e6f75ea3-DzAtRHvi.woff2 +0 -0
  92. package/dist/ui/assets/0fbbd340d0467dfe3101d58846f30cb0-gtFcO250.woff2 +0 -0
  93. package/dist/ui/assets/1063fa2dc7334d1f383e30824e016eae-B7U-kD-0.woff2 +0 -0
  94. package/dist/ui/assets/109e26e62ddc85d946e699464afbece7-BLnUjAEV.woff2 +0 -0
  95. package/dist/ui/assets/12a443b0ae9748a993b5395f8fad412b-Btjff2Lz.woff2 +0 -0
  96. package/dist/ui/assets/135c4621de7a4d85d852e03d416f13ee-DdSuW36e.woff2 +0 -0
  97. package/dist/ui/assets/13aa415469ee761c968749b1c0c56ec6-Dwe-Wa_w.woff2 +0 -0
  98. package/dist/ui/assets/1435b1ad447bd9c0e8bc52035c9dcfa8-C05ldXtw.woff2 +0 -0
  99. package/dist/ui/assets/175e0915aa874e7446b79b6bc9b00721-Dp0-l77L.woff2 +0 -0
  100. package/dist/ui/assets/1876e7d02187f006e2b7f769391c3089-CNjpRdap.woff2 +0 -0
  101. package/dist/ui/assets/1886dc284b3b845e3b5fe04aa2107276-Bh_wHwL1.woff2 +0 -0
  102. package/dist/ui/assets/19a3dda6611aaf5d787403bd1159fe15-zoEBvdpA.woff2 +0 -0
  103. package/dist/ui/assets/1afa74e14da193125e45d4783d358a4f-BgjSTENX.woff2 +0 -0
  104. package/dist/ui/assets/1cd26a6a6d784720b4a43053e9058bcf-TAP3dvNi.woff2 +0 -0
  105. package/dist/ui/assets/1ce19bddc3beab385a863b1e05685678-BCdXitZw.woff2 +0 -0
  106. package/dist/ui/assets/1d3459ac6578c80502bdb876a4824a93-CSwdnEGP.woff2 +0 -0
  107. package/dist/ui/assets/1d479e364dab4af01c539f0a6a7ac036-ZFI-djGt.woff2 +0 -0
  108. package/dist/ui/assets/1dcea2408d17fcec4197adce50f8b32d-CnK2xXR-.woff2 +0 -0
  109. package/dist/ui/assets/1e855385699999e24a33f7df8aed0489-DGFHkDVQ.woff2 +0 -0
  110. package/dist/ui/assets/1f55944b8f7972580e3029694462a6b0-CB7ogVJ9.woff2 +0 -0
  111. package/dist/ui/assets/1f96eda70929089762dc3c4feb33f9c8-DtSQ6YOf.woff2 +0 -0
  112. package/dist/ui/assets/1fb275532caadc746455881b31da27c4-BHFTsg74.woff2 +0 -0
  113. package/dist/ui/assets/20e679bdc00d06f802ea71fd9154b284-D9gRjq_v.woff2 +0 -0
  114. package/dist/ui/assets/25aff66c4a9f8cb801cbd525f4a923e4-Dgi87hP7.woff2 +0 -0
  115. package/dist/ui/assets/28e6700380b023b23e77f2d75aee624f-b19D855Q.woff2 +0 -0
  116. package/dist/ui/assets/2a1c50aa9cd99ce3a0197f5d49948e63-DrztdXCB.woff2 +0 -0
  117. package/dist/ui/assets/2b4e0ae0bf3af610b97717dfd66a012d-CBoapvha.woff2 +0 -0
  118. package/dist/ui/assets/2be2b7a672b616bc2f07059a67bfaad8-BUR0aNML.woff2 +0 -0
  119. package/dist/ui/assets/2e0a358ada78ea98ec8ba86002a27eac-DvgbSr9v.woff2 +0 -0
  120. package/dist/ui/assets/2e1cf63a2a22a50516c49b1dc521ea8a-CmltIxig.woff2 +0 -0
  121. package/dist/ui/assets/2e4481801925fcb6573c4337c73f15d7-DYOoCk-m.woff2 +0 -0
  122. package/dist/ui/assets/2fe5e634c568cd874f94d24b8f02bfd1-DcEHY35G.woff2 +0 -0
  123. package/dist/ui/assets/309c33f63f29887fb557115aa24d4bb5-CUgqrXIj.woff2 +0 -0
  124. package/dist/ui/assets/3158f45c959e1b346b0bbd6f3193dbf0-kimcClqp.woff2 +0 -0
  125. package/dist/ui/assets/316adbc8037a36f8d804637b053bbe5e-CjbZmJf9.woff2 +0 -0
  126. package/dist/ui/assets/319e9631bc29fc7235b70fc9aac43cd9-IV6yZuHZ.woff2 +0 -0
  127. package/dist/ui/assets/32054f9bb88d9431b1cdf023f770e614-CYrpCgqK.woff2 +0 -0
  128. package/dist/ui/assets/3245fa1aac6338c1d9d04befb7c346f8-CzvPhVPA.woff2 +0 -0
  129. package/dist/ui/assets/326c730f98afa476057957f2743c81e2-GPHJ3Fpu.woff2 +0 -0
  130. package/dist/ui/assets/33885558dc783bf9bae6c5d4668fef07-D33us0yR.woff2 +0 -0
  131. package/dist/ui/assets/33cd11f5e61556e4618801c8231f2744-CoR_Tv3X.woff2 +0 -0
  132. package/dist/ui/assets/34a170749cb7e6c5851acc510882af6b-BCZwsihk.woff2 +0 -0
  133. package/dist/ui/assets/3538de2d387591258d7e9908aad803b9-CewOkERZ.woff2 +0 -0
  134. package/dist/ui/assets/38d7b029c036080067a2e94211098171-CettR5Qv.woff2 +0 -0
  135. package/dist/ui/assets/393f47efeefc9f9e9cca83eb10a82c69-Brhz4z1y.woff2 +0 -0
  136. package/dist/ui/assets/396a51f2b274d01f6ba606a94f3e5fe2-DgRk-jtI.woff2 +0 -0
  137. package/dist/ui/assets/3991bf955d4c925e84b4a6542760d4d5-II4HtRkQ.woff2 +0 -0
  138. package/dist/ui/assets/39b6cb6ae8d1ea0e9832b88d2c21ef99-CbJm-q6b.woff2 +0 -0
  139. package/dist/ui/assets/3a1133157fbd259f1a51aaa9f8418a26-3FmUjjvi.woff2 +0 -0
  140. package/dist/ui/assets/3a34cff07dca7a65ed2486be19229290-B7ml2yBN.woff2 +0 -0
  141. package/dist/ui/assets/3c339dd7c27e6b8a06eea431733e076f-dGQqXaEj.woff2 +0 -0
  142. package/dist/ui/assets/3d074ea67f60953b816ce7cf5d87a980-B5VkPOmg.woff2 +0 -0
  143. package/dist/ui/assets/3d38f54dcf4a9f4a50165d5849becf71-CVDtFSZn.woff2 +0 -0
  144. package/dist/ui/assets/3e3fc9a19a27f94e4ff1b815b55b2671-Ci736GLi.woff2 +0 -0
  145. package/dist/ui/assets/3e8abb1f606f29e2f0a8a8a6b702d263-C5sOSf5L.woff2 +0 -0
  146. package/dist/ui/assets/3f0ab4e1cd99bdbfdf9acbc8f4bcb9ce-0DcdTLxV.woff2 +0 -0
  147. package/dist/ui/assets/3f30c0482650345c981d66920f56ab61-CTD2CsZI.woff2 +0 -0
  148. package/dist/ui/assets/41a9aa12ad9a60758b304c1a998fb781-BTQUDLf1.woff2 +0 -0
  149. package/dist/ui/assets/424010b2bc5cee540b92b28da49b61bf-DT8upW2M.woff2 +0 -0
  150. package/dist/ui/assets/42fbdafff2d44b76c6665d51bb2cee9d-_UrojhXD.woff2 +0 -0
  151. package/dist/ui/assets/4341db13c311b3cb0eaaf6b4c71b89e5-CzjA7MzG.woff2 +0 -0
  152. package/dist/ui/assets/434db5a796128131fde78d4389d14b71-AeNdWKua.woff2 +0 -0
  153. package/dist/ui/assets/43cb51fc70a15274d49e2d956c5ec3b4-BQbufd96.woff2 +0 -0
  154. package/dist/ui/assets/43e79de1c7815f21efbc966ff96fcbd6-DlCQvOsQ.woff2 +0 -0
  155. package/dist/ui/assets/43faf6a8a3cde1088bd5f7c830890a89-SvDzF3nA.woff2 +0 -0
  156. package/dist/ui/assets/44731ae21d7690ce538d0f8242755833-CEmu9Z88.woff2 +0 -0
  157. package/dist/ui/assets/45324b2a64cc0c0db797d5c09e239d34-BuFudtvM.woff2 +0 -0
  158. package/dist/ui/assets/4584df05d00aa75ae6e1f850fdc9df1d-GNHt6xwM.woff2 +0 -0
  159. package/dist/ui/assets/48d539539e98481fb511a761031c59fd-Cd9gdi8B.woff2 +0 -0
  160. package/dist/ui/assets/49a1fb9fc6a35796b2e0d3539088ede8-CD1cRDOy.woff2 +0 -0
  161. package/dist/ui/assets/4bec02eb0c2371e59bfb7d41a141ea1e-CmBnNPo0.woff2 +0 -0
  162. package/dist/ui/assets/4c5a81ef5683f6fbf9935e640de804e7-B4wQxzVs.woff2 +0 -0
  163. package/dist/ui/assets/4f11c9c75e86214b7f69ddee3a9f8076-DHsQ2xpE.woff2 +0 -0
  164. package/dist/ui/assets/50434b8ff88e27101d1c67ba953ee873-PH_hIqxu.woff2 +0 -0
  165. package/dist/ui/assets/5107fd5153d52c6a3f8d43ff214dd6c2-ls3eo5Qd.woff2 +0 -0
  166. package/dist/ui/assets/520cb7d3683dee99e767650efc0aa5b1-Lkd4z-9S.woff2 +0 -0
  167. package/dist/ui/assets/5455087df2f091b7bf3d38c5a102a2e7-QNkiOxwe.woff2 +0 -0
  168. package/dist/ui/assets/556450fdf0978d329cf8277e3b71dcdb-CtypGAPw.woff2 +0 -0
  169. package/dist/ui/assets/5a1b5c1b71cad50c51a79a7f4825e6e6-DQA407Ec.woff2 +0 -0
  170. package/dist/ui/assets/5ba02e809d2ef4f7639178e7a7d654c0-Dty0omtl.woff2 +0 -0
  171. package/dist/ui/assets/5bd654afe48e094f95e5142e4eaadc75-D9pUq71R.woff2 +0 -0
  172. package/dist/ui/assets/5c4c3ca0a3f4115879a61a96d02e3efc-RcVLBj31.woff2 +0 -0
  173. package/dist/ui/assets/5c737d740638e8d764f12c1e5c1cd144-D2Npjq-N.woff2 +0 -0
  174. package/dist/ui/assets/5e78019102102aa5741aa591313fd5b5-V7Azrw-2.woff2 +0 -0
  175. package/dist/ui/assets/6287cacac64e1849941f7b7d7e6316c5-DzkbPOID.woff2 +0 -0
  176. package/dist/ui/assets/643df7194160d54edc48d16d76e5ba74-BKmmIRjw.woff2 +0 -0
  177. package/dist/ui/assets/65d3b214c69ff6786fd1cab06f8394b7-B0cRUJAr.woff2 +0 -0
  178. package/dist/ui/assets/6698103c11bfe260d44fcf00c6d8b623-DVWYOeyb.woff2 +0 -0
  179. package/dist/ui/assets/692e07b3a609a8f1a21c15c45631f348-DJW5LcZH.woff2 +0 -0
  180. package/dist/ui/assets/694e87456af181279c2bfd551aa3c6e3-Cu7kRuJB.woff2 +0 -0
  181. package/dist/ui/assets/6b88066bdc7a6e8bc823128dbd0f6f3b-BG38UZSq.woff2 +0 -0
  182. package/dist/ui/assets/6d6f36f15381826d113832bfdd41b021-Bgh7YPiq.woff2 +0 -0
  183. package/dist/ui/assets/6e22361162bd0a5cd189ca3c98d23d52-DqnptHqF.woff2 +0 -0
  184. package/dist/ui/assets/6e884dd501b8da24ecd37425a9b3d51d-BVNqra8s.woff2 +0 -0
  185. package/dist/ui/assets/71200dbc7497fbafa28db5cfeae74acb-B6xMqoC6.woff2 +0 -0
  186. package/dist/ui/assets/72a85441a9757146440e3985f3b02075-CzTSlCPZ.woff2 +0 -0
  187. package/dist/ui/assets/7472d7e02045dea3ff288eac850e38eb-CboJEy4X.woff2 +0 -0
  188. package/dist/ui/assets/795ffa953bea666763ae79ac88aa7e5a-C6UsoDmi.woff2 +0 -0
  189. package/dist/ui/assets/7aa8647d6c52d249169b73417aa8e444-kJejhQ0-.woff2 +0 -0
  190. package/dist/ui/assets/7be6b794fc233ccd8b5769496301313a-Ba2iZC0a.woff2 +0 -0
  191. package/dist/ui/assets/7c0748f04d52086809c9f551c5aaded7-DSzO_ybF.woff2 +0 -0
  192. package/dist/ui/assets/7c858bb53a7652227fab8cf9753d3e1f-Bi9fbvIF.woff2 +0 -0
  193. package/dist/ui/assets/7e7026091b4e6061b05963bdb3a7f944-CrmRDJpc.woff2 +0 -0
  194. package/dist/ui/assets/7f80369a7479d568ab8bf492000282bd-CqcI5ZOp.woff2 +0 -0
  195. package/dist/ui/assets/834ce2f2571aaa26e47a618c4ad55e58-DZfWJrxo.woff2 +0 -0
  196. package/dist/ui/assets/834faab602d9c795341bc7dfd6578fe5-B1VeBVWh.woff2 +0 -0
  197. package/dist/ui/assets/8513a8eea35b460a047a16a87c36f8bb-H259w_6b.woff2 +0 -0
  198. package/dist/ui/assets/8699e8a36465bf46671dc28ce24e188c-YlkPpQ6h.woff2 +0 -0
  199. package/dist/ui/assets/86ae8881eae9eac7e9c0182c8ce6c627-Ch2r3LNs.woff2 +0 -0
  200. package/dist/ui/assets/86b3ae1be58d76f7147793f09264451b-CpgTfcn-.woff2 +0 -0
  201. package/dist/ui/assets/8769f8d7d45adcfcbd3f80460d22720d-DV_yqcTo.woff2 +0 -0
  202. package/dist/ui/assets/87bb0ceea9f5515490c832bdf6ebf641-CJpxd8e6.woff2 +0 -0
  203. package/dist/ui/assets/8928b9a50b5efbaaafa039674a3c7301-C-42WRD6.woff2 +0 -0
  204. package/dist/ui/assets/899f2c2a36cbeac473a7caeeaabc46b1-xrIcXJXU.woff2 +0 -0
  205. package/dist/ui/assets/8a075d4043e6bf66740808b492186438-Dq0346Qm.woff2 +0 -0
  206. package/dist/ui/assets/8b86676936792154c53205ab5d90b797-BGH_CGsg.woff2 +0 -0
  207. package/dist/ui/assets/8c63ca479653200a0e12f428242e1171-CsBTcPwY.woff2 +0 -0
  208. package/dist/ui/assets/8c9c7ff8342f5fed60e73c4a7ad67325-CGSYpt52.woff2 +0 -0
  209. package/dist/ui/assets/8ca655fcc5d9cf33d49815e231f3f356-b8PyEeZg.woff2 +0 -0
  210. package/dist/ui/assets/8e96f0f9385ef826f900587d0fc18b3d-D7GIG6Uq.woff2 +0 -0
  211. package/dist/ui/assets/8ea917252cf2c1e664f06ee5badffeea-CDZ1AFr0.woff2 +0 -0
  212. package/dist/ui/assets/8f42f890fc37e292017446af85af2745-Qx6oGI0z.woff2 +0 -0
  213. package/dist/ui/assets/8f9589e71c53bb753749a9c412f15c01-BVR5mPJl.woff2 +0 -0
  214. package/dist/ui/assets/90e70b52a3cc2491bbb2b4c0caf0ae7e-BdwPu67j.woff2 +0 -0
  215. package/dist/ui/assets/92ac4ca42fe050c1e680c585551abd9c-CXLyuj7l.woff2 +0 -0
  216. package/dist/ui/assets/92c12bb794c491930fd06f3f97299807-u_0ognJ0.woff2 +0 -0
  217. package/dist/ui/assets/92f0b4d2bb274a801aac10fc34c1e0cc-DFHB6dyu.woff2 +0 -0
  218. package/dist/ui/assets/93247aebd628367f5518eafdb2770ce9-o1N4zvTO.woff2 +0 -0
  219. package/dist/ui/assets/940446ea160995af66a9ee223cc3d4d1-C-_7aPYO.woff2 +0 -0
  220. package/dist/ui/assets/94be430de618453b6ae27bd366827ba2-1YYf1n5N.woff2 +0 -0
  221. package/dist/ui/assets/98cd7d62095e466e55885b34f63c86f1-z33YCk0X.woff2 +0 -0
  222. package/dist/ui/assets/99049fd5cec5894cafe2a1e5e13d1e03-DNrg8nIk.woff2 +0 -0
  223. package/dist/ui/assets/99e2752b7351aaa7681b4d91db80e89a-b3hi7miY.woff2 +0 -0
  224. package/dist/ui/assets/9d1b670cbfd64716ac685dea26237d95-DAZ9U6es.woff2 +0 -0
  225. package/dist/ui/assets/CodeEditorModal-6m0TJWyF.css +1 -0
  226. package/dist/ui/assets/CodeEditorModal-CFEnWsyh.js +14 -0
  227. package/dist/ui/assets/ConnectionsView-DHo6yvU6.css +1 -0
  228. package/dist/ui/assets/ConnectionsView-DNGmZBSU.js +1 -0
  229. package/dist/ui/assets/LogsView-BoeE92VF.css +1 -0
  230. package/dist/ui/assets/LogsView-fSiaxQ13.js +1 -0
  231. package/dist/ui/assets/PaginationFooter-B3kHzRfB.js +1 -0
  232. package/dist/ui/assets/PaginationFooter-BNkpcNoB.css +1 -0
  233. package/dist/ui/assets/ProfileEditorDialog-BydoZthX.js +1 -0
  234. package/dist/ui/assets/ProfilesView-Btc1DOxE.js +2 -0
  235. package/dist/ui/assets/ProfilesView-ByqdFNHN.css +1 -0
  236. package/dist/ui/assets/RulesView-CHyynVqA.css +1 -0
  237. package/dist/ui/assets/RulesView-D9vZBiJ1.js +1 -0
  238. package/dist/ui/assets/SettingsFileDialog-CNFEAVs4.js +1 -0
  239. package/dist/ui/assets/SettingsView-BlDhZkXQ.js +2 -0
  240. package/dist/ui/assets/SettingsView-CngS3vBM.css +1 -0
  241. package/dist/ui/assets/a0f2d41645a113d821e7ff0891daad1e-XK0rDAci.woff2 +0 -0
  242. package/dist/ui/assets/a1d1345d8407dd6169f39c28ff6b1708-Bn7hMmv8.woff2 +0 -0
  243. package/dist/ui/assets/a29f51b4ebb6776e43e7492a036be28e-CrAKKz8u.woff2 +0 -0
  244. package/dist/ui/assets/a3252ac86b2c4e88bfb9baecabcad817-qnA_3LNE.woff2 +0 -0
  245. package/dist/ui/assets/a3d63a61e7eb2bb497dbc2da65bd0873-BRMJAm0m.woff2 +0 -0
  246. package/dist/ui/assets/a45eea933252dbe3529f30a0323c2661-D4kBOrXR.woff2 +0 -0
  247. package/dist/ui/assets/a4886c4ebed8c8b1f8a87b5b491a39aa-CBQdRqpF.woff2 +0 -0
  248. package/dist/ui/assets/a50b4ab745c7676d9d3ceb3c0a023a3d-CW990pgI.woff2 +0 -0
  249. package/dist/ui/assets/a5851ae4d7e58996d8bd681f80d4727c-BBytdb0t.woff2 +0 -0
  250. package/dist/ui/assets/a5cdaa96bf7d1db6c671432f9ab7bccb-slKJjgfX.woff2 +0 -0
  251. package/dist/ui/assets/a640dd2e962cf72c7fcf76234c973303-CUCWUDBY.woff2 +0 -0
  252. package/dist/ui/assets/a67e457bbd366aef5104ce743a2cf6d4-DkE8fLlE.woff2 +0 -0
  253. package/dist/ui/assets/a6c3f2257315d45aa212bd2e86a02852-CxDbXnZV.woff2 +0 -0
  254. package/dist/ui/assets/a73399a40a5f81fb7314b8b9a10cd221-DRAvJPKI.woff2 +0 -0
  255. package/dist/ui/assets/a73a64b0941803d9d807b9ec082df641-BbPpPyy5.woff2 +0 -0
  256. package/dist/ui/assets/a73d1a0a1dfd4f4234f7a0649ece4da4-BHeqktY9.woff2 +0 -0
  257. package/dist/ui/assets/a740044e77a400a450e71fcdea55523f-Co5hpBNK.woff2 +0 -0
  258. package/dist/ui/assets/a7669b29f404662c179c71b87e67c8d2-CVnuBgHy.woff2 +0 -0
  259. package/dist/ui/assets/a964a4def15811e91b05a91740a1cf37-CzcDfCRn.woff2 +0 -0
  260. package/dist/ui/assets/aac3f69132da164921fda75d2d90bfb7-B6rUZQ4_.woff2 +0 -0
  261. package/dist/ui/assets/aacd263c3bb4ba163a4d5079c159680a-DtrDLFlk.woff2 +0 -0
  262. package/dist/ui/assets/ac62ec881ded2cdb450e542b396e24d0-CRlVJvPa.woff2 +0 -0
  263. package/dist/ui/assets/aebcb9b7d4b174ab5a4b911206f24036-BuleSpgP.woff2 +0 -0
  264. package/dist/ui/assets/af70712dadc05c7b18d248996a5ab0a9-gCZSZGzb.woff2 +0 -0
  265. package/dist/ui/assets/afbd3a8a4cfaa4561b7848c28bc6c4f8-DgnqceEw.woff2 +0 -0
  266. package/dist/ui/assets/b02d3867e6e2ea921d2845b44c4a5a31-C9v4FrqD.woff2 +0 -0
  267. package/dist/ui/assets/b1b22825b930688403337d4fd7bbba62-vmMs2pJf.woff2 +0 -0
  268. package/dist/ui/assets/b3206c979bfb28d12d7ad6df58e74b9f-X1PqLaCR.woff2 +0 -0
  269. package/dist/ui/assets/b6280e42d4f11dcab70594f9d6774936-CtezImLG.woff2 +0 -0
  270. package/dist/ui/assets/b71b8cd1a70405c3dbc0e7a44055ca9a-Cd6cIOEr.woff2 +0 -0
  271. package/dist/ui/assets/b9198c4f220a4ca075f8daa4db529e50-Bgc7zlEt.woff2 +0 -0
  272. package/dist/ui/assets/baad304a58d7d42d0af29235c5e9490b-BQZYwARB.woff2 +0 -0
  273. package/dist/ui/assets/bb99e34f8784cc44f0eeadbe735dd238-UHFYI827.woff2 +0 -0
  274. package/dist/ui/assets/bc1cb0f523c8a2ff566a047aeddf90fd-BSlXQn97.woff2 +0 -0
  275. package/dist/ui/assets/bd3171e975d62612e50148bf38b2158f-CtZKzCzQ.woff2 +0 -0
  276. package/dist/ui/assets/be10640c403263dd5917ad65c4d453a6-B-rbuuja.woff2 +0 -0
  277. package/dist/ui/assets/be764e101e78b50b749aa1414d4810a7-yIPaQQih.woff2 +0 -0
  278. package/dist/ui/assets/bfa682a7f5f12945b41230c35eb859f6-CzagcI5f.woff2 +0 -0
  279. package/dist/ui/assets/bfa9753ed26f2e1f81ab7020167bd1a0-C6ZNQQ2p.woff2 +0 -0
  280. package/dist/ui/assets/branding/sash-cat.png +0 -0
  281. package/dist/ui/assets/c0e78c6ad709b19ce437677c91033489-BtwtlApg.woff2 +0 -0
  282. package/dist/ui/assets/c5a59409c610cc8c265d1691eb3f71eb-DmI_ky2X.woff2 +0 -0
  283. package/dist/ui/assets/c5ff66a1a57f2a29424e8de5d4ef3756-C9pOsaV1.woff2 +0 -0
  284. package/dist/ui/assets/c607bdee84a0e5b69721367ccc0c9c31-eoHbdXeg.woff2 +0 -0
  285. package/dist/ui/assets/c8095033941d18db730496eb4c863757-DoexTYks.woff2 +0 -0
  286. package/dist/ui/assets/c8295519e16f4cf3a350e9729f07653a-C8bAtIO_.woff2 +0 -0
  287. package/dist/ui/assets/c91f9c9f4db81afeba37c8e73afb0bfd-BGIl2iKW.woff2 +0 -0
  288. package/dist/ui/assets/c9ad9caa4afdd40766add91d321b0350-HfuiRq1k.woff2 +0 -0
  289. package/dist/ui/assets/cc4f271c90f9ea5157db9c0a4edc976b-MAjuDvHE.woff2 +0 -0
  290. package/dist/ui/assets/cdf7c3d96ba172214d7e1e9fe5e6ba8b-CUx4fWQE.woff2 +0 -0
  291. package/dist/ui/assets/cf70d5821ea7c3ca34653169ac763606-CE0T0Svc.woff2 +0 -0
  292. package/dist/ui/assets/d0d5efe92496b1ceea38e180b0ac97d3-7G71Z6v3.woff2 +0 -0
  293. package/dist/ui/assets/d1977142eceb878b06440375e64c4372-mr-7LWm-.woff2 +0 -0
  294. package/dist/ui/assets/d42feb13392b4dd9214c61b61a6e5ae8-DjnKADf_.woff2 +0 -0
  295. package/dist/ui/assets/d46a696ec6a077c6873fc39043c3c082-C62E-13k.woff2 +0 -0
  296. package/dist/ui/assets/d4c0bbf1605a8a9c36fff5a2ead81bb5-C2F6tzOj.woff2 +0 -0
  297. package/dist/ui/assets/d918d4e47697b7c752ea223e0d0d4c81-twovU2ty.woff2 +0 -0
  298. package/dist/ui/assets/d988c05da0b5624267dd825fedd133d1-Bgbc9SVH.woff2 +0 -0
  299. package/dist/ui/assets/da3a071df79531f4f033b7c8499651d0-0tgZJL_N.woff2 +0 -0
  300. package/dist/ui/assets/da8eba4acbf93048645043006de1c5f0-Ce0dmHn_.woff2 +0 -0
  301. package/dist/ui/assets/dc3f16892e20a85a8fcad2d3097e622b-8PRrFPJt.woff2 +0 -0
  302. package/dist/ui/assets/dd5f808ec691f911483a2eb60b5049bd-BhVupSbT.woff2 +0 -0
  303. package/dist/ui/assets/e2b04534cc697a76afd70e885119cb8c-DO303iCb.woff2 +0 -0
  304. package/dist/ui/assets/e307280eb2fb7809adee14ff0d247d32-DHMLXvSs.woff2 +0 -0
  305. package/dist/ui/assets/e3b484bec850c669094f713570870b3a-BI_9kRw8.woff2 +0 -0
  306. package/dist/ui/assets/e7b1d196949b7530cbda0ad956fbb2cc-i2m2Id0q.woff2 +0 -0
  307. package/dist/ui/assets/e7c2e5fb387880508811cadc2926e13a-BfROXX4U.woff2 +0 -0
  308. package/dist/ui/assets/e8a5fe283c8b9e173097bee0b5757ab6-BvfjDjYP.woff2 +0 -0
  309. package/dist/ui/assets/e8e3465d48435612e9c3509be3984188-BeUPU5-9.woff2 +0 -0
  310. package/dist/ui/assets/e93903f4604b007fc1b7134a74f5c534-B1M4h46T.woff2 +0 -0
  311. package/dist/ui/assets/eb0994906efd969102d522b6d8b3aff2-i9SHU5CJ.woff2 +0 -0
  312. package/dist/ui/assets/eb32eee23ceadd3fc4eb5763d3573f57-B3h9kgf3.woff2 +0 -0
  313. package/dist/ui/assets/ebadbc005027fb3e2b1c2d15be38c66b-qVQSBwBu.woff2 +0 -0
  314. package/dist/ui/assets/ecbbb464ff6934b5012cf33c80c7a907-CBEPqZfS.woff2 +0 -0
  315. package/dist/ui/assets/ed79ac25c014b6434f22fe1e2549bc26-mWhz7hkU.woff2 +0 -0
  316. package/dist/ui/assets/eed64f40443554cb9f65edaca176abb0-Cqw29XTm.woff2 +0 -0
  317. package/dist/ui/assets/efc749471476cce09e12e7038c757950-BhSWPf1Q.woff2 +0 -0
  318. package/dist/ui/assets/f2a7c2cb375662aa93d8f31116d39c20-D1zsMYCW.woff2 +0 -0
  319. package/dist/ui/assets/f5ba0fb4332ccceadc481e47134023d3-CAKMPiu5.woff2 +0 -0
  320. package/dist/ui/assets/f6326eb5031e18478aa4588598f96965-Dj3uu55n.woff2 +0 -0
  321. package/dist/ui/assets/f8678cbaab9caef95e1986731dcbd92f-CnIuR3EN.woff2 +0 -0
  322. package/dist/ui/assets/f8d9d55e2a811e9839c9af5f8f2d62ac-DIUQRBsH.woff2 +0 -0
  323. package/dist/ui/assets/f98b117e363eb51f4d7c7d1056da7688-O9MtqjIW.woff2 +0 -0
  324. package/dist/ui/assets/f9ab5d9163148ceb211e5b4f15a3d8e0-Dtqse-AT.woff2 +0 -0
  325. package/dist/ui/assets/fa69ce224ff0a6c0ff97b10a829779c2-pX45dCNY.woff2 +0 -0
  326. package/dist/ui/assets/fc38d1da3930d39dd8e286f708105765-DmbOHwTI.woff2 +0 -0
  327. package/dist/ui/assets/fe03c1dfaffc1cad3521fe55f1541d6b-BW8aEqCj.woff2 +0 -0
  328. package/dist/ui/assets/fe0b2421eb4d1f4f8949c602871a317c-BsBLV6jH.woff2 +0 -0
  329. package/dist/ui/assets/fe9c75d0cb3d808023412d26dfc9d388-eXjcgUPI.woff2 +0 -0
  330. package/dist/ui/assets/index-B61V60w_.js +19 -0
  331. package/dist/ui/assets/index-bkyxJG8J.css +1 -0
  332. package/dist/ui/assets/theme-BNq4FkXS.js +1 -0
  333. package/dist/ui/index.html +52 -0
  334. package/dist/webui.js +21 -0
  335. package/docs/backend.md +260 -0
  336. package/docs/frontend.md +125 -0
  337. package/docs/remix-icon-license.txt +186 -0
  338. package/docs/usage.md +227 -0
  339. package/package.json +82 -0
@@ -0,0 +1,804 @@
1
+ import fs from "node:fs";
2
+ import { verifyCoreExecutable } from "./core.js";
3
+ import { installRecordsEqual, parseInstallRecord, readInstallRecord, validateCoreReleaseTag, writeInstallRecord, } from "./core-install-record.js";
4
+ import { atomicWriteFileSync, durableRemoveFileSync, durableRenameSync, pathEntryExists, } from "./fs-atomic.js";
5
+ import { hasExactOwnKeys, isCanonicalIsoTimestamp, isPlainObject } from "./json-shape.js";
6
+ import { waitForBinaryUnlocked } from "./process.js";
7
+ const UPDATE_TRANSACTION_SIZE_LIMIT = 16 * 1024;
8
+ function parseTargetRecord(value, createdAt) {
9
+ const targetRecord = parseInstallRecord(value);
10
+ return targetRecord?.installedAt === createdAt ? targetRecord : undefined;
11
+ }
12
+ function parseNormalTransaction(value) {
13
+ if (!hasExactOwnKeys(value, [
14
+ "createdAt",
15
+ "deferredHealth",
16
+ "phase",
17
+ "previousRecord",
18
+ "targetRecord",
19
+ "version",
20
+ ]) ||
21
+ value.version !== 1 ||
22
+ (value.phase !== "prepared" &&
23
+ value.phase !== "swapped" &&
24
+ value.phase !== "health-verified") ||
25
+ !isCanonicalIsoTimestamp(value.createdAt) ||
26
+ typeof value.deferredHealth !== "boolean") {
27
+ return undefined;
28
+ }
29
+ let previousRecord = null;
30
+ if (value.previousRecord !== null) {
31
+ const parsedPrevious = parseInstallRecord(value.previousRecord);
32
+ if (!parsedPrevious)
33
+ return undefined;
34
+ previousRecord = parsedPrevious;
35
+ }
36
+ const targetRecord = parseTargetRecord(value.targetRecord, value.createdAt);
37
+ if (!targetRecord)
38
+ return undefined;
39
+ return {
40
+ version: 1,
41
+ phase: value.phase,
42
+ createdAt: value.createdAt,
43
+ previousRecord,
44
+ targetRecord,
45
+ deferredHealth: value.deferredHealth,
46
+ };
47
+ }
48
+ function parseRepairTransaction(value) {
49
+ if (!hasExactOwnKeys(value, [
50
+ "createdAt",
51
+ "deferredHealth",
52
+ "phase",
53
+ "previousRecord",
54
+ "repair",
55
+ "targetRecord",
56
+ "version",
57
+ ]) ||
58
+ value.version !== 2 ||
59
+ (value.phase !== "repair-prepared" &&
60
+ value.phase !== "repair-restoring" &&
61
+ value.phase !== "prepared" &&
62
+ value.phase !== "swapped" &&
63
+ value.phase !== "health-verified") ||
64
+ !isCanonicalIsoTimestamp(value.createdAt) ||
65
+ typeof value.deferredHealth !== "boolean" ||
66
+ value.previousRecord !== null ||
67
+ !isPlainObject(value.repair) ||
68
+ !hasExactOwnKeys(value.repair, ["binaryExisted", "installRecordExisted"]) ||
69
+ typeof value.repair.binaryExisted !== "boolean" ||
70
+ typeof value.repair.installRecordExisted !== "boolean" ||
71
+ (!value.repair.binaryExisted && !value.repair.installRecordExisted)) {
72
+ return undefined;
73
+ }
74
+ const targetRecord = parseTargetRecord(value.targetRecord, value.createdAt);
75
+ if (!targetRecord)
76
+ return undefined;
77
+ return {
78
+ version: 2,
79
+ phase: value.phase,
80
+ createdAt: value.createdAt,
81
+ previousRecord: null,
82
+ targetRecord,
83
+ deferredHealth: value.deferredHealth,
84
+ repair: {
85
+ binaryExisted: value.repair.binaryExisted,
86
+ installRecordExisted: value.repair.installRecordExisted,
87
+ },
88
+ };
89
+ }
90
+ function parseTransaction(value) {
91
+ if (!isPlainObject(value))
92
+ return undefined;
93
+ return value.version === 1
94
+ ? parseNormalTransaction(value)
95
+ : value.version === 2
96
+ ? parseRepairTransaction(value)
97
+ : undefined;
98
+ }
99
+ function backupPath(layout) {
100
+ return `${layout.coreExe}.bak`;
101
+ }
102
+ function repairBinaryBackupPath(layout) {
103
+ return `${layout.coreExe}.repair.bak`;
104
+ }
105
+ function repairInstallBackupPath(layout) {
106
+ return `${layout.installFile}.repair.bak`;
107
+ }
108
+ function defaultVerifier(exe, expectedVersion) {
109
+ verifyCoreExecutable(exe, 5000, expectedVersion);
110
+ }
111
+ function verifyTransactionBinary(file, expectedVersion, verifier, role) {
112
+ let stat;
113
+ try {
114
+ stat = fs.lstatSync(file);
115
+ }
116
+ catch (err) {
117
+ throw new Error(`${role} is missing: ${file}: ${err.message}`);
118
+ }
119
+ if (!stat.isFile())
120
+ throw new Error(`${role} is not a regular file: ${file}`);
121
+ try {
122
+ verifier(file, expectedVersion);
123
+ }
124
+ catch (err) {
125
+ throw new Error(`${role} does not match expected version ${expectedVersion}: ${file}: ${err.message}`);
126
+ }
127
+ }
128
+ function writeTransaction(transaction, layout) {
129
+ atomicWriteFileSync(layout.coreUpdateTransactionFile, `${JSON.stringify(transaction, null, 2)}\n`);
130
+ }
131
+ export function readCoreUpdateTransaction(layout) {
132
+ let stat;
133
+ try {
134
+ stat = fs.lstatSync(layout.coreUpdateTransactionFile);
135
+ }
136
+ catch (err) {
137
+ if (err.code === "ENOENT")
138
+ return undefined;
139
+ throw new Error(`Cannot inspect Core update transaction ${layout.coreUpdateTransactionFile}: ${err.message}`);
140
+ }
141
+ if (!stat.isFile()) {
142
+ throw new Error(`Core update transaction is not a regular file: ${layout.coreUpdateTransactionFile}`);
143
+ }
144
+ if (stat.size > UPDATE_TRANSACTION_SIZE_LIMIT) {
145
+ throw new Error(`Core update transaction exceeds ${UPDATE_TRANSACTION_SIZE_LIMIT} bytes: ${layout.coreUpdateTransactionFile}`);
146
+ }
147
+ let parsed;
148
+ try {
149
+ parsed = JSON.parse(fs.readFileSync(layout.coreUpdateTransactionFile, "utf8"));
150
+ }
151
+ catch (err) {
152
+ throw new Error(`Core update transaction is not valid JSON: ${layout.coreUpdateTransactionFile} (${err.message})`);
153
+ }
154
+ const transaction = parseTransaction(parsed);
155
+ if (!transaction) {
156
+ throw new Error(`Core update transaction has an invalid version, phase, timestamp, records, repair snapshot, or shape: ${layout.coreUpdateTransactionFile}`);
157
+ }
158
+ return transaction;
159
+ }
160
+ function assertRollbackSlotsClear(layout) {
161
+ for (const file of [
162
+ backupPath(layout),
163
+ repairBinaryBackupPath(layout),
164
+ repairInstallBackupPath(layout),
165
+ ]) {
166
+ if (pathEntryExists(file))
167
+ throw new Error(`Previous Core rollback state still exists: ${file}`);
168
+ }
169
+ }
170
+ export function beginCoreUpdateTransaction(layout, targetVersion, previousRecord, deferredHealth, createdAt = new Date().toISOString()) {
171
+ const targetRecord = {
172
+ coreVersion: validateCoreReleaseTag(targetVersion),
173
+ installedAt: createdAt,
174
+ };
175
+ if (!isCanonicalIsoTimestamp(createdAt)) {
176
+ throw new Error(`Invalid Core update timestamp: ${createdAt}`);
177
+ }
178
+ if (readCoreUpdateTransaction(layout)) {
179
+ throw new Error(`A Core update transaction is already pending: ${layout.coreUpdateTransactionFile}`);
180
+ }
181
+ assertRollbackSlotsClear(layout);
182
+ const currentExists = pathEntryExists(layout.coreExe);
183
+ const installEntryExists = pathEntryExists(layout.installFile);
184
+ if (currentExists !== Boolean(previousRecord) || installEntryExists !== Boolean(previousRecord)) {
185
+ throw new Error("Cannot begin Core update with inconsistent binary/install metadata state");
186
+ }
187
+ const transaction = {
188
+ version: 1,
189
+ phase: "prepared",
190
+ createdAt,
191
+ previousRecord: previousRecord ?? null,
192
+ targetRecord,
193
+ deferredHealth,
194
+ };
195
+ writeTransaction(transaction, layout);
196
+ return transaction;
197
+ }
198
+ function isRegularFile(file) {
199
+ try {
200
+ return fs.lstatSync(file).isFile();
201
+ }
202
+ catch {
203
+ return false;
204
+ }
205
+ }
206
+ function assertRepairableCoreEntry(file, role) {
207
+ if (!pathEntryExists(file))
208
+ return;
209
+ if (fs.lstatSync(file).isDirectory()) {
210
+ throw new Error(`Cannot repair Core installation: ${role} path is a directory: ${file}`);
211
+ }
212
+ }
213
+ export function beginCoreRepairUpdateTransaction(layout, targetVersion, deferredHealth, createdAt = new Date().toISOString()) {
214
+ const targetRecord = {
215
+ coreVersion: validateCoreReleaseTag(targetVersion),
216
+ installedAt: createdAt,
217
+ };
218
+ if (!isCanonicalIsoTimestamp(createdAt)) {
219
+ throw new Error(`Invalid Core update timestamp: ${createdAt}`);
220
+ }
221
+ if (readCoreUpdateTransaction(layout)) {
222
+ throw new Error(`A Core update transaction is already pending: ${layout.coreUpdateTransactionFile}`);
223
+ }
224
+ assertRollbackSlotsClear(layout);
225
+ const binaryExisted = pathEntryExists(layout.coreExe);
226
+ const installRecordExisted = pathEntryExists(layout.installFile);
227
+ const previousRecord = readInstallRecord(layout);
228
+ if ((!binaryExisted && !installRecordExisted) ||
229
+ (binaryExisted && installRecordExisted && isRegularFile(layout.coreExe) && previousRecord)) {
230
+ throw new Error("Core installation does not require forced repair");
231
+ }
232
+ assertRepairableCoreEntry(layout.coreExe, "executable");
233
+ assertRepairableCoreEntry(layout.installFile, "install metadata");
234
+ const transaction = {
235
+ version: 2,
236
+ phase: "repair-prepared",
237
+ createdAt,
238
+ previousRecord: null,
239
+ targetRecord,
240
+ deferredHealth,
241
+ repair: { binaryExisted, installRecordExisted },
242
+ };
243
+ writeTransaction(transaction, layout);
244
+ return transaction;
245
+ }
246
+ function assertSameTransaction(expected, current) {
247
+ const sameRepair = expected.version === 1
248
+ ? current?.version === 1
249
+ : current?.version === 2 &&
250
+ current.repair.binaryExisted === expected.repair.binaryExisted &&
251
+ current.repair.installRecordExisted === expected.repair.installRecordExisted;
252
+ if (!current ||
253
+ current.version !== expected.version ||
254
+ current.createdAt !== expected.createdAt ||
255
+ current.targetRecord.coreVersion !== expected.targetRecord.coreVersion ||
256
+ current.targetRecord.installedAt !== expected.targetRecord.installedAt ||
257
+ !sameRepair) {
258
+ throw new Error("Core update transaction changed during publication");
259
+ }
260
+ }
261
+ export function quarantineCoreInstallationForUpdate(transaction, layout) {
262
+ const current = readCoreUpdateTransaction(layout);
263
+ assertSameTransaction(transaction, current);
264
+ if (current.version !== 2 || current.phase !== "repair-prepared") {
265
+ throw new Error("Core repair transaction is not ready for quarantine");
266
+ }
267
+ if (current.repair.binaryExisted) {
268
+ if (!pathEntryExists(layout.coreExe)) {
269
+ throw new Error("Core repair executable disappeared before quarantine");
270
+ }
271
+ durableRenameSync(layout.coreExe, repairBinaryBackupPath(layout));
272
+ }
273
+ else if (pathEntryExists(layout.coreExe)) {
274
+ throw new Error("Core executable appeared before repair quarantine");
275
+ }
276
+ if (current.repair.installRecordExisted) {
277
+ if (!pathEntryExists(layout.installFile)) {
278
+ throw new Error("Core install metadata disappeared before quarantine");
279
+ }
280
+ durableRenameSync(layout.installFile, repairInstallBackupPath(layout));
281
+ }
282
+ else if (pathEntryExists(layout.installFile)) {
283
+ throw new Error("Core install metadata appeared before repair quarantine");
284
+ }
285
+ if (pathEntryExists(layout.coreExe) ||
286
+ pathEntryExists(layout.installFile) ||
287
+ pathEntryExists(repairBinaryBackupPath(layout)) !== current.repair.binaryExisted ||
288
+ pathEntryExists(repairInstallBackupPath(layout)) !== current.repair.installRecordExisted) {
289
+ throw new Error("Core repair quarantine could not be verified");
290
+ }
291
+ const prepared = { ...current, phase: "prepared" };
292
+ writeTransaction(prepared, layout);
293
+ return prepared;
294
+ }
295
+ export function markCoreUpdateSwapped(transaction, layout) {
296
+ const current = readCoreUpdateTransaction(layout);
297
+ assertSameTransaction(transaction, current);
298
+ if (current.phase !== "prepared")
299
+ throw new Error("Core update is not ready to mark swapped");
300
+ const swapped = { ...current, phase: "swapped" };
301
+ writeTransaction(swapped, layout);
302
+ return swapped;
303
+ }
304
+ export function markCoreUpdateHealthVerified(transaction, layout) {
305
+ const current = readCoreUpdateTransaction(layout);
306
+ assertSameTransaction(transaction, current);
307
+ if (current.phase !== "swapped") {
308
+ throw new Error("Core update is not ready to mark health-verified");
309
+ }
310
+ const verified = { ...current, phase: "health-verified" };
311
+ writeTransaction(verified, layout);
312
+ return verified;
313
+ }
314
+ function markCoreRepairRestoring(transaction, layout) {
315
+ const current = readCoreUpdateTransaction(layout);
316
+ assertSameTransaction(transaction, current);
317
+ if (current.version !== 2)
318
+ throw new Error("Core update is not a repair transaction");
319
+ if (current.phase === "repair-restoring")
320
+ return current;
321
+ const restoring = { ...current, phase: "repair-restoring" };
322
+ writeTransaction(restoring, layout);
323
+ return restoring;
324
+ }
325
+ export function clearCoreUpdateTransaction(layout) {
326
+ durableRemoveFileSync(layout.coreUpdateTransactionFile);
327
+ }
328
+ function restoreInstallRecord(record, layout) {
329
+ if (record)
330
+ writeInstallRecord(record, layout);
331
+ else
332
+ durableRemoveFileSync(layout.installFile);
333
+ }
334
+ function rollbackNormalCoreUpdate(transaction, layout, verifier) {
335
+ const backup = backupPath(layout);
336
+ const currentExists = pathEntryExists(layout.coreExe);
337
+ const backupExists = pathEntryExists(backup);
338
+ if (transaction.previousRecord) {
339
+ if (backupExists) {
340
+ verifyTransactionBinary(backup, transaction.previousRecord.coreVersion, verifier, "Core rollback backup");
341
+ if (currentExists) {
342
+ verifyTransactionBinary(layout.coreExe, transaction.targetRecord.coreVersion, verifier, "Core update candidate");
343
+ durableRemoveFileSync(layout.coreExe);
344
+ }
345
+ durableRenameSync(backup, layout.coreExe);
346
+ }
347
+ else {
348
+ if (!currentExists) {
349
+ throw new Error("Core update rollback has neither the previous binary nor its backup");
350
+ }
351
+ verifyTransactionBinary(layout.coreExe, transaction.previousRecord.coreVersion, verifier, "Previous Core binary");
352
+ }
353
+ }
354
+ else {
355
+ if (backupExists) {
356
+ throw new Error(`Unexpected rollback backup for an update without a previous Core: ${backup}`);
357
+ }
358
+ if (currentExists) {
359
+ verifyTransactionBinary(layout.coreExe, transaction.targetRecord.coreVersion, verifier, "Core update candidate");
360
+ durableRemoveFileSync(layout.coreExe);
361
+ }
362
+ }
363
+ restoreInstallRecord(transaction.previousRecord, layout);
364
+ clearCoreUpdateTransaction(layout);
365
+ return transaction.previousRecord;
366
+ }
367
+ function removeRepairCandidateBinary(transaction, layout, verifier) {
368
+ if (!pathEntryExists(layout.coreExe))
369
+ return;
370
+ verifyTransactionBinary(layout.coreExe, transaction.targetRecord.coreVersion, verifier, "Core repair candidate");
371
+ durableRemoveFileSync(layout.coreExe);
372
+ }
373
+ function verifyRepairCandidateRecord(transaction, layout) {
374
+ const current = readInstallRecord(layout);
375
+ if (!installRecordsEqual(current, transaction.targetRecord)) {
376
+ throw new Error("Core repair candidate install metadata does not match its journal");
377
+ }
378
+ }
379
+ function removeRepairCandidateRecord(transaction, layout) {
380
+ if (!pathEntryExists(layout.installFile))
381
+ return;
382
+ verifyRepairCandidateRecord(transaction, layout);
383
+ durableRemoveFileSync(layout.installFile);
384
+ }
385
+ function assertRepairRollbackOwnership(transaction, layout, verifier) {
386
+ if (pathEntryExists(backupPath(layout))) {
387
+ throw new Error(`Core repair has an unexpected normal rollback slot: ${backupPath(layout)}`);
388
+ }
389
+ if (transaction.phase === "repair-restoring")
390
+ return;
391
+ const binaryCurrent = pathEntryExists(layout.coreExe);
392
+ const binaryBackup = pathEntryExists(repairBinaryBackupPath(layout));
393
+ if (transaction.repair.binaryExisted) {
394
+ if (transaction.phase === "repair-prepared") {
395
+ if (binaryCurrent === binaryBackup) {
396
+ throw new Error("Core repair cannot identify the original executable during rollback");
397
+ }
398
+ }
399
+ else if (!binaryBackup) {
400
+ throw new Error("Core repair rollback lost the quarantined executable");
401
+ }
402
+ }
403
+ else if (binaryBackup) {
404
+ throw new Error(`Core repair has an unexpected executable backup: ${repairBinaryBackupPath(layout)}`);
405
+ }
406
+ if (transaction.phase === "repair-prepared" &&
407
+ !transaction.repair.binaryExisted &&
408
+ binaryCurrent) {
409
+ throw new Error("Core executable appeared during repair preparation");
410
+ }
411
+ if (transaction.phase !== "repair-prepared" && binaryCurrent) {
412
+ verifyTransactionBinary(layout.coreExe, transaction.targetRecord.coreVersion, verifier, "Core repair candidate");
413
+ }
414
+ if ((transaction.phase === "swapped" || transaction.phase === "health-verified") &&
415
+ !binaryCurrent) {
416
+ throw new Error("Core repair candidate disappeared before rollback");
417
+ }
418
+ const installCurrent = pathEntryExists(layout.installFile);
419
+ const installBackup = pathEntryExists(repairInstallBackupPath(layout));
420
+ if (transaction.repair.installRecordExisted) {
421
+ if (transaction.phase === "repair-prepared") {
422
+ if (installCurrent === installBackup) {
423
+ throw new Error("Core repair cannot identify the original install metadata during rollback");
424
+ }
425
+ }
426
+ else if (!installBackup) {
427
+ throw new Error("Core repair rollback lost the quarantined install metadata");
428
+ }
429
+ }
430
+ else if (installBackup) {
431
+ throw new Error(`Core repair has an unexpected install metadata backup: ${repairInstallBackupPath(layout)}`);
432
+ }
433
+ if (transaction.phase === "repair-prepared") {
434
+ if (!transaction.repair.installRecordExisted && installCurrent) {
435
+ throw new Error("Core install metadata appeared during repair preparation");
436
+ }
437
+ return;
438
+ }
439
+ if (transaction.phase === "prepared") {
440
+ if (installCurrent) {
441
+ throw new Error("Core install metadata appeared before the candidate swap was journaled");
442
+ }
443
+ return;
444
+ }
445
+ const shouldHaveTargetRecord = transaction.phase === "health-verified" || transaction.deferredHealth;
446
+ if (shouldHaveTargetRecord !== installCurrent) {
447
+ throw new Error("Core repair candidate install metadata does not match its journal phase");
448
+ }
449
+ if (installCurrent)
450
+ verifyRepairCandidateRecord(transaction, layout);
451
+ }
452
+ function rollbackRepairCoreUpdate(transaction, layout, verifier) {
453
+ assertRepairRollbackOwnership(transaction, layout, verifier);
454
+ const restoring = markCoreRepairRestoring(transaction, layout);
455
+ const binaryBackup = repairBinaryBackupPath(layout);
456
+ const installBackup = repairInstallBackupPath(layout);
457
+ if (restoring.repair.binaryExisted) {
458
+ if (pathEntryExists(binaryBackup)) {
459
+ removeRepairCandidateBinary(restoring, layout, verifier);
460
+ durableRenameSync(binaryBackup, layout.coreExe);
461
+ }
462
+ else if (!pathEntryExists(layout.coreExe)) {
463
+ throw new Error("Core repair rollback lost the quarantined executable");
464
+ }
465
+ }
466
+ else {
467
+ if (pathEntryExists(binaryBackup)) {
468
+ throw new Error(`Core repair has an unexpected executable backup: ${binaryBackup}`);
469
+ }
470
+ removeRepairCandidateBinary(restoring, layout, verifier);
471
+ }
472
+ if (restoring.repair.installRecordExisted) {
473
+ if (pathEntryExists(installBackup)) {
474
+ removeRepairCandidateRecord(restoring, layout);
475
+ durableRenameSync(installBackup, layout.installFile);
476
+ }
477
+ else if (!pathEntryExists(layout.installFile)) {
478
+ throw new Error("Core repair rollback lost the quarantined install metadata");
479
+ }
480
+ }
481
+ else {
482
+ if (pathEntryExists(installBackup)) {
483
+ throw new Error(`Core repair has an unexpected install metadata backup: ${installBackup}`);
484
+ }
485
+ removeRepairCandidateRecord(restoring, layout);
486
+ }
487
+ if (pathEntryExists(binaryBackup) ||
488
+ pathEntryExists(installBackup) ||
489
+ pathEntryExists(layout.coreExe) !== restoring.repair.binaryExisted ||
490
+ pathEntryExists(layout.installFile) !== restoring.repair.installRecordExisted) {
491
+ throw new Error("Core repair rollback could not restore the exact prior entry presence");
492
+ }
493
+ clearCoreUpdateTransaction(layout);
494
+ return null;
495
+ }
496
+ export function rollbackCoreUpdateTransaction(layout, verifier = defaultVerifier) {
497
+ const transaction = readCoreUpdateTransaction(layout);
498
+ if (!transaction)
499
+ return undefined;
500
+ return transaction.version === 1
501
+ ? rollbackNormalCoreUpdate(transaction, layout, verifier)
502
+ : rollbackRepairCoreUpdate(transaction, layout, verifier);
503
+ }
504
+ function assertRepairBackupEntry(file, role) {
505
+ if (!pathEntryExists(file))
506
+ return;
507
+ if (fs.lstatSync(file).isDirectory()) {
508
+ throw new Error(`Pending Core repair ${role} backup is a directory: ${file}`);
509
+ }
510
+ }
511
+ function validatePendingRepairAssets(transaction, layout) {
512
+ if (pathEntryExists(backupPath(layout))) {
513
+ throw new Error(`Pending Core repair has an unexpected normal backup: ${backupPath(layout)}`);
514
+ }
515
+ const allowRemoved = transaction.phase === "health-verified";
516
+ const binaryBackup = repairBinaryBackupPath(layout);
517
+ const installBackup = repairInstallBackupPath(layout);
518
+ assertRepairBackupEntry(binaryBackup, "executable");
519
+ assertRepairBackupEntry(installBackup, "install metadata");
520
+ if (transaction.repair.binaryExisted && !allowRemoved && !pathEntryExists(binaryBackup)) {
521
+ throw new Error(`Pending Core repair is missing its executable backup: ${binaryBackup}`);
522
+ }
523
+ if (!transaction.repair.binaryExisted && pathEntryExists(binaryBackup)) {
524
+ throw new Error(`Pending Core repair has an unexpected executable backup: ${binaryBackup}`);
525
+ }
526
+ if (transaction.repair.installRecordExisted && !allowRemoved && !pathEntryExists(installBackup)) {
527
+ throw new Error(`Pending Core repair is missing its install metadata backup: ${installBackup}`);
528
+ }
529
+ if (!transaction.repair.installRecordExisted && pathEntryExists(installBackup)) {
530
+ throw new Error(`Pending Core repair has an unexpected install metadata backup: ${installBackup}`);
531
+ }
532
+ }
533
+ function validatePendingTransaction(transaction, layout, verifier) {
534
+ if (transaction.phase !== "swapped" && transaction.phase !== "health-verified") {
535
+ throw new Error("Core update is not pending a managed health decision");
536
+ }
537
+ verifyTransactionBinary(layout.coreExe, transaction.targetRecord.coreVersion, verifier, "Pending Core update candidate");
538
+ if (transaction.version === 1) {
539
+ const backup = backupPath(layout);
540
+ if (transaction.previousRecord) {
541
+ if (pathEntryExists(backup)) {
542
+ verifyTransactionBinary(backup, transaction.previousRecord.coreVersion, verifier, "Pending Core rollback backup");
543
+ }
544
+ else if (transaction.phase !== "health-verified") {
545
+ throw new Error(`Pending Core update is missing its rollback backup: ${backup}`);
546
+ }
547
+ }
548
+ else if (pathEntryExists(backup)) {
549
+ throw new Error(`Pending first Core update has an unexpected rollback backup: ${backup}`);
550
+ }
551
+ }
552
+ else {
553
+ validatePendingRepairAssets(transaction, layout);
554
+ }
555
+ const currentRecord = readInstallRecord(layout);
556
+ if (!currentRecord && pathEntryExists(layout.installFile)) {
557
+ throw new Error("Pending Core update install metadata is malformed or non-regular");
558
+ }
559
+ const recordMatchesPhase = transaction.version === 2
560
+ ? transaction.phase === "health-verified" || transaction.deferredHealth
561
+ ? installRecordsEqual(currentRecord, transaction.targetRecord)
562
+ : currentRecord === undefined
563
+ : transaction.phase === "health-verified"
564
+ ? installRecordsEqual(currentRecord, transaction.targetRecord) ||
565
+ installRecordsEqual(currentRecord, transaction.previousRecord)
566
+ : installRecordsEqual(currentRecord, transaction.previousRecord ?? transaction.targetRecord);
567
+ if (!recordMatchesPhase) {
568
+ throw new Error("Pending Core update install metadata does not match its journal phase");
569
+ }
570
+ }
571
+ function rollbackRequired(transaction) {
572
+ return (transaction.phase === "repair-prepared" ||
573
+ transaction.phase === "repair-restoring" ||
574
+ transaction.phase === "prepared" ||
575
+ (!transaction.deferredHealth && transaction.phase === "swapped"));
576
+ }
577
+ export function recoverCoreUpdateTransaction(layout, verifier = defaultVerifier) {
578
+ const transaction = readCoreUpdateTransaction(layout);
579
+ if (!transaction) {
580
+ recoverLegacyInterruptedCoreUpdate(layout, verifier);
581
+ return undefined;
582
+ }
583
+ if (rollbackRequired(transaction)) {
584
+ rollbackCoreUpdateTransaction(layout, verifier);
585
+ return undefined;
586
+ }
587
+ validatePendingTransaction(transaction, layout, verifier);
588
+ if (transaction.phase === "health-verified") {
589
+ writeInstallRecord(transaction.targetRecord, layout);
590
+ }
591
+ return transaction;
592
+ }
593
+ export function pendingCoreUpdateVersion(layout) {
594
+ const transaction = readCoreUpdateTransaction(layout);
595
+ return transaction?.phase === "swapped" || transaction?.phase === "health-verified"
596
+ ? transaction.targetRecord.coreVersion
597
+ : undefined;
598
+ }
599
+ function removeCommittedRollbackAssets(transaction, layout) {
600
+ if (transaction.version === 1) {
601
+ durableRemoveFileSync(backupPath(layout));
602
+ return;
603
+ }
604
+ durableRemoveFileSync(repairBinaryBackupPath(layout));
605
+ durableRemoveFileSync(repairInstallBackupPath(layout));
606
+ }
607
+ function rollbackAssetsExist(transaction, layout) {
608
+ return transaction.version === 1
609
+ ? pathEntryExists(backupPath(layout))
610
+ : pathEntryExists(repairBinaryBackupPath(layout)) ||
611
+ pathEntryExists(repairInstallBackupPath(layout));
612
+ }
613
+ export function completePendingCoreUpdateAfterStart(layout, verifier = defaultVerifier) {
614
+ let transaction = readCoreUpdateTransaction(layout);
615
+ if (!transaction)
616
+ return undefined;
617
+ if (transaction.phase !== "swapped" && transaction.phase !== "health-verified") {
618
+ throw new Error("Cannot complete a Core update which has not been swapped");
619
+ }
620
+ validatePendingTransaction(transaction, layout, verifier);
621
+ if (transaction.phase === "swapped") {
622
+ if (!transaction.deferredHealth) {
623
+ throw new Error("Cannot complete a Core update whose in-command health check was interrupted");
624
+ }
625
+ transaction = markCoreUpdateHealthVerified(transaction, layout);
626
+ }
627
+ writeInstallRecord(transaction.targetRecord, layout);
628
+ removeCommittedRollbackAssets(transaction, layout);
629
+ clearCoreUpdateTransaction(layout);
630
+ return transaction.targetRecord.coreVersion;
631
+ }
632
+ export function finalizeCoreUpdateTransaction(layout, verifier = defaultVerifier) {
633
+ const transaction = readCoreUpdateTransaction(layout);
634
+ if (!transaction) {
635
+ recoverLegacyInterruptedCoreUpdate(layout, verifier, true);
636
+ return;
637
+ }
638
+ if (transaction.phase !== "health-verified") {
639
+ throw new Error(`Core update still requires a health check: ${transaction.targetRecord.coreVersion}`);
640
+ }
641
+ validatePendingTransaction(transaction, layout, verifier);
642
+ writeInstallRecord(transaction.targetRecord, layout);
643
+ removeCommittedRollbackAssets(transaction, layout);
644
+ clearCoreUpdateTransaction(layout);
645
+ }
646
+ export async function commitCoreUpdate(opts) {
647
+ try {
648
+ return await commitCoreUpdateUnlocked(opts);
649
+ }
650
+ finally {
651
+ fs.rmSync(opts.staged.exe, { force: true });
652
+ }
653
+ }
654
+ async function commitCoreUpdateUnlocked(opts) {
655
+ const { layout, staged, runtime } = opts;
656
+ const verifier = opts.verifyExecutable ?? defaultVerifier;
657
+ let transaction;
658
+ let transactionStarted = false;
659
+ let swapped = false;
660
+ let runtimeStopped = false;
661
+ try {
662
+ if (readCoreUpdateTransaction(layout))
663
+ recoverCoreUpdateTransaction(layout, verifier);
664
+ else
665
+ recoverLegacyInterruptedCoreUpdate(layout, verifier, true);
666
+ if (readCoreUpdateTransaction(layout)) {
667
+ throw new Error(`A Core update is pending managed startup/finalization: ${layout.coreUpdateTransactionFile}`);
668
+ }
669
+ let previousRecord = readInstallRecord(layout);
670
+ const binaryExists = pathEntryExists(layout.coreExe);
671
+ const installEntryExists = pathEntryExists(layout.installFile);
672
+ const consistentEmpty = !binaryExists && !installEntryExists;
673
+ const consistentInstalled = binaryExists && installEntryExists && isRegularFile(layout.coreExe) && previousRecord;
674
+ const repairRequired = !consistentEmpty && !consistentInstalled;
675
+ if (repairRequired && !opts.forceRepair) {
676
+ throw new Error("Cannot update inconsistent Core binary/install metadata state");
677
+ }
678
+ const deferredHealth = runtime?.verifyRuntime !== true;
679
+ if (repairRequired) {
680
+ transaction = beginCoreRepairUpdateTransaction(layout, staged.version, deferredHealth);
681
+ previousRecord = undefined;
682
+ }
683
+ else {
684
+ transaction = beginCoreUpdateTransaction(layout, staged.version, previousRecord, deferredHealth);
685
+ }
686
+ transactionStarted = true;
687
+ if (runtime?.verifyRuntime) {
688
+ await runtime.stop();
689
+ runtimeStopped = true;
690
+ }
691
+ if (transaction.version === 2) {
692
+ transaction = quarantineCoreInstallationForUpdate(transaction, layout);
693
+ }
694
+ else if (previousRecord) {
695
+ await waitForBinaryUnlocked(layout.coreExe);
696
+ durableRenameSync(layout.coreExe, backupPath(layout));
697
+ }
698
+ durableRenameSync(staged.exe, layout.coreExe);
699
+ swapped = true;
700
+ transaction = markCoreUpdateSwapped(transaction, layout);
701
+ if (deferredHealth && !previousRecord)
702
+ writeInstallRecord(transaction.targetRecord, layout);
703
+ if (runtime?.verifyRuntime) {
704
+ await runtime.startAndVerify(staged.version);
705
+ transaction = markCoreUpdateHealthVerified(transaction, layout);
706
+ writeInstallRecord(transaction.targetRecord, layout);
707
+ }
708
+ return {
709
+ version: staged.version,
710
+ backupRemoved: !rollbackAssetsExist(transaction, layout),
711
+ pendingStartupValidation: deferredHealth,
712
+ };
713
+ }
714
+ catch (err) {
715
+ const rollbackErrors = [];
716
+ let mayRestoreFiles = true;
717
+ if (runtime?.verifyRuntime && swapped) {
718
+ try {
719
+ await runtime.stop();
720
+ runtimeStopped = true;
721
+ }
722
+ catch (rollbackErr) {
723
+ mayRestoreFiles = false;
724
+ rollbackErrors.push(rollbackErr.message);
725
+ }
726
+ }
727
+ let restored;
728
+ if (mayRestoreFiles) {
729
+ try {
730
+ await opts.beforeRollback?.();
731
+ }
732
+ catch (rollbackErr) {
733
+ rollbackErrors.push(`managed state: ${rollbackErr.message}`);
734
+ }
735
+ if (transactionStarted) {
736
+ try {
737
+ restored = rollbackCoreUpdateTransaction(layout, verifier);
738
+ }
739
+ catch (rollbackErr) {
740
+ rollbackErrors.push(`Core binary: ${rollbackErr.message}`);
741
+ }
742
+ }
743
+ if (runtime?.verifyRuntime && runtimeStopped && restored) {
744
+ try {
745
+ await runtime.startAndVerify(restored.coreVersion);
746
+ }
747
+ catch (rollbackErr) {
748
+ rollbackErrors.push(`previous runtime: ${rollbackErr.message}`);
749
+ }
750
+ }
751
+ }
752
+ const message = err.message;
753
+ if (rollbackErrors.length) {
754
+ throw new Error(`${message}; rollback also failed: ${rollbackErrors.join("; ")}`);
755
+ }
756
+ throw err;
757
+ }
758
+ }
759
+ function recoverLegacyInterruptedCoreUpdate(layout, verifier = defaultVerifier, finalizeCommittedBackup = false) {
760
+ for (const repairBackup of [repairBinaryBackupPath(layout), repairInstallBackupPath(layout)]) {
761
+ if (pathEntryExists(repairBackup)) {
762
+ throw new Error(`Cannot recover Core repair backup without its transaction journal: ${repairBackup}`);
763
+ }
764
+ }
765
+ const backup = backupPath(layout);
766
+ if (!pathEntryExists(backup))
767
+ return;
768
+ const previousRecord = readInstallRecord(layout);
769
+ if (!previousRecord) {
770
+ throw new Error(`Cannot recover ambiguous Core backup without install metadata: ${backup}`);
771
+ }
772
+ if (!pathEntryExists(layout.coreExe)) {
773
+ verifyTransactionBinary(backup, previousRecord.coreVersion, verifier, "Core backup");
774
+ durableRenameSync(backup, layout.coreExe);
775
+ return;
776
+ }
777
+ let currentMatches = false;
778
+ let backupMatches = false;
779
+ try {
780
+ verifier(layout.coreExe, previousRecord.coreVersion);
781
+ currentMatches = true;
782
+ }
783
+ catch {
784
+ }
785
+ if (!currentMatches) {
786
+ try {
787
+ verifier(backup, previousRecord.coreVersion);
788
+ backupMatches = true;
789
+ }
790
+ catch {
791
+ }
792
+ }
793
+ if (currentMatches) {
794
+ if (finalizeCommittedBackup)
795
+ durableRemoveFileSync(backup);
796
+ }
797
+ else if (backupMatches) {
798
+ durableRemoveFileSync(layout.coreExe);
799
+ durableRenameSync(backup, layout.coreExe);
800
+ }
801
+ else {
802
+ throw new Error(`Cannot determine a safe Core after an interrupted update; preserved ${layout.coreExe} and ${backup}`);
803
+ }
804
+ }