@hot-updater/console 1.1.4 → 1.2.0-rc.1

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 (273) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/-native-builds-search-C-TGpFAm.js +1 -0
  3. package/.output/public/assets/{ConsoleFeatureUnavailable-C0KfsKWL.js → ConsoleFeatureUnavailable-CUtcXuY2.js} +1 -1
  4. package/.output/public/assets/EventDetails-Dl2RvPAk.js +1 -0
  5. package/.output/public/assets/HashValueDisplay-6lNRxDJX.js +1 -0
  6. package/.output/public/assets/InsightsPageHeader-DHhqY3DT.js +1 -0
  7. package/.output/public/assets/NotFoundPage-DZ3DxyRA.js +1 -0
  8. package/.output/public/assets/PlatformIcon-BBWQlWm9.js +1 -0
  9. package/.output/public/assets/RolloutPercentageBadge-Cxogpi8F.js +1 -0
  10. package/.output/public/assets/StoreSubmission-JpngSvHS.js +1 -0
  11. package/.output/public/assets/alert-dialog-BqYScr9y.js +1 -0
  12. package/.output/public/assets/api-P2g8dCNg.js +1 -0
  13. package/.output/public/assets/api-keys-DWQC2q6y.js +1 -0
  14. package/.output/public/assets/api-keys-FueRkF9q.js +1 -0
  15. package/.output/public/assets/arrow-left-g9cKqXuC.js +1 -0
  16. package/.output/public/assets/arrow-right-CNII8TYn.js +1 -0
  17. package/.output/public/assets/arrow-up-right-DK8Zdg40.js +1 -0
  18. package/.output/public/assets/badge-DWFcDWho.js +1 -0
  19. package/.output/public/assets/check-CXVR3s3X.js +1 -0
  20. package/.output/public/assets/chevron-right-BoUXo82O.js +1 -0
  21. package/.output/public/assets/circle-off-C7Muo0ss.js +1 -0
  22. package/.output/public/assets/{clipboard-DTkrlIMn.js → clipboard-DQbOcrL7.js} +1 -1
  23. package/.output/public/assets/console-features-api-DEXSwFtc.js +1 -0
  24. package/.output/public/assets/copy-DsuLj3oj.js +1 -0
  25. package/.output/public/assets/dialog-CNUPxNxH.js +1 -0
  26. package/.output/public/assets/{dist-dDj-R6uh.js → dist-CPlO6ko9.js} +1 -1
  27. package/.output/public/assets/download-Cf24LBde.js +1 -0
  28. package/.output/public/assets/{empty-Bf4DanOL.js → empty-B3Uwdd6k.js} +1 -1
  29. package/.output/public/assets/{field-GxF9SyUo.js → field-D-2-mnNh.js} +1 -1
  30. package/.output/public/assets/history-DqHlgSuV.js +1 -0
  31. package/.output/public/assets/index-BWU6owTG.js +10 -0
  32. package/.output/public/assets/{input-group-B4nrqIh0.js → input-group-D3OUBk4n.js} +1 -1
  33. package/.output/public/assets/insights-B-0odqpt.js +42 -0
  34. package/.output/public/assets/insights-BSVGfxSL.js +1 -0
  35. package/.output/public/assets/insights-NK5B6ReS.js +2 -0
  36. package/.output/public/assets/insights-api-BVCXJsoV.js +1 -0
  37. package/.output/public/assets/insights-search-DghCbEnk.js +1 -0
  38. package/.output/public/assets/insights-usage-rpc-CKQH0pUS.js +1 -0
  39. package/.output/public/assets/insights_.distribution-CJvgwcVZ.js +2 -0
  40. package/.output/public/assets/insights_.distribution-DblZM0mO.js +1 -0
  41. package/.output/public/assets/insights_.distribution-dvk6vYDM.js +1 -0
  42. package/.output/public/assets/installations-B674P5Z5.js +2 -0
  43. package/.output/public/assets/installations-BWxczZ2t.js +1 -0
  44. package/.output/public/assets/installations-Dv4kWQYz.js +1 -0
  45. package/.output/public/assets/{key-round-B4fI1psK.js → key-round-DwNKkHhy.js} +1 -1
  46. package/.output/public/assets/link-DTEyW70e.js +2 -0
  47. package/.output/public/assets/{list-Dtj7TtzV.js → list--NIee1FZ.js} +1 -1
  48. package/.output/public/assets/native-builds-BOC71grI.js +1 -0
  49. package/.output/public/assets/native-builds-BswZazmc.js +1 -0
  50. package/.output/public/assets/native-builds-c4BpOLAe.js +2 -0
  51. package/.output/public/assets/native-builds_._buildId-D_Spjl10.js +1 -0
  52. package/.output/public/assets/native-builds_._buildId-DtoXD8rz.js +2 -0
  53. package/.output/public/assets/plus-BReVKct-.js +1 -0
  54. package/.output/public/assets/preload-helper-B44EvKhG.js +1 -0
  55. package/.output/public/assets/{refresh-cw-DjIKjmrS.js → refresh-cw-B9HHjLej.js} +1 -1
  56. package/.output/public/assets/relativeTime-YQt84dYr.js +1 -0
  57. package/.output/public/assets/remote-config-B7ujqhLa.js +6 -0
  58. package/.output/public/assets/remote-config-WjpozBG5.js +1 -0
  59. package/.output/public/assets/remote-config-gXeH6PBW.js +1 -0
  60. package/.output/public/assets/rotate-cw-Cv2aVbYr.js +1 -0
  61. package/.output/public/assets/routes-BDdBIxYS.js +1 -0
  62. package/.output/public/assets/routes-CCD33Azv.js +2 -0
  63. package/.output/public/assets/search-BCel4Oy6.js +1 -0
  64. package/.output/public/assets/select-CBIU0_M1.js +1 -0
  65. package/.output/public/assets/{sidebar-BqX460IW.js → sidebar-Di9SN47G.js} +1 -1
  66. package/.output/public/assets/smartphone-CMaSy_ir.js +1 -0
  67. package/.output/public/assets/styles-Ccxthuz1.css +2 -0
  68. package/.output/public/assets/switch-BpdiHL_t.js +1 -0
  69. package/.output/public/assets/{table-ek1DmRHq.js → table-Bpy3SvSo.js} +1 -1
  70. package/.output/public/assets/{tabs-CgEH_fMT.js → tabs-Xmt7tZeG.js} +1 -1
  71. package/.output/public/assets/{undo-2-DxXx9Zvr.js → undo-2-CUX96_BG.js} +1 -1
  72. package/.output/public/assets/useCompositeListItem-JmyDfo3-.js +1 -0
  73. package/.output/public/assets/useInfiniteQuery-DX4WpBRG.js +1 -0
  74. package/.output/public/assets/useMutation-DbYw7boA.js +1 -0
  75. package/.output/public/assets/uuidv7-zXMtwR9Q.js +1 -0
  76. package/.output/public/assets/{valueToPercent-B44vDssN.js → valueToPercent-BLwGy_xK.js} +1 -1
  77. package/.output/server/{__23tanstack-start-server-fn-resolver-KpQ5Z_24.mjs → __23tanstack-start-server-fn-resolver-CygMPYHZ.mjs} +64 -40
  78. package/.output/server/_chunks/ssr-renderer.mjs +2 -2
  79. package/.output/server/_libs/@tanstack/react-router+[...].mjs +1 -1
  80. package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +1 -1
  81. package/.output/server/_libs/lucide-react.mjs +88 -1
  82. package/.output/server/_libs/uqr.mjs +858 -0
  83. package/.output/server/_ssr/-native-builds-search-CwNcoXf4.mjs +37 -0
  84. package/.output/server/_ssr/{BundleIdDisplay-CjSnY65J.mjs → BundleIdDisplay-DOPz4pUS.mjs} +2 -2
  85. package/.output/server/_ssr/{ConsoleFeatureUnavailable-CHxn9f-w.mjs → ConsoleFeatureUnavailable-Xb1JKqcc.mjs} +7 -7
  86. package/.output/server/_ssr/{EventDetails-l6dF54zp.mjs → EventDetails-D7MejFCN.mjs} +8 -8
  87. package/.output/server/_ssr/{HashValueDisplay-Zs7ABoOi.mjs → HashValueDisplay-Dd-wu5rY.mjs} +2 -2
  88. package/.output/server/_ssr/{InsightsPageHeader-ZlAlDxQy.mjs → InsightsPageHeader-CWf1n4Vt.mjs} +13 -11
  89. package/.output/server/_ssr/{NotFoundPage-u28ndvp1.mjs → NotFoundPage-C5rFdUpg.mjs} +2 -2
  90. package/.output/server/_ssr/PlatformIcon-CFLiTjh1.mjs +29 -0
  91. package/.output/server/_ssr/RolloutPercentageBadge-g0Bhfl3M.mjs +21 -0
  92. package/.output/server/_ssr/StoreSubmission-JZ2Zltak.mjs +139 -0
  93. package/.output/server/_ssr/{alert-dialog-B8AqlJwo.mjs → alert-dialog-CvNvTvIJ.mjs} +1 -1
  94. package/.output/server/_ssr/{api-C_0B7NXM.mjs → api-0vtwA5vY.mjs} +126 -7
  95. package/.output/server/_ssr/{api-keys-pRNvn2-9.mjs → api-keys-D5dyA4D1.mjs} +10 -10
  96. package/.output/server/_ssr/{api-keys-rpc-wecWs0nA.mjs → api-keys-rpc-BOIUHhDi.mjs} +5 -5
  97. package/.output/server/_ssr/{api-keys-CLH38u9B.mjs → api-keys-yFlQDwQF.mjs} +2 -2
  98. package/.output/server/_ssr/{api-rpc-DWN8eOHS.mjs → api-rpc-COeFOpYi.mjs} +79 -12
  99. package/.output/server/_ssr/{auth-UP0wXs4i.mjs → auth-Z-wLddzh.mjs} +1 -1
  100. package/.output/server/_ssr/{auth-rpc-CLRArbIl.mjs → auth-rpc-D1ss2GUI.mjs} +6 -6
  101. package/.output/server/_ssr/{auth.server-DR9zc0EU.mjs → auth.server-DYS1OJ6P.mjs} +771 -641
  102. package/.output/server/_ssr/{badge-BpT7r-ng.mjs → badge-CEdVRgyl.mjs} +1 -1
  103. package/.output/server/_ssr/{bundleActivity-0qFIcvqt.mjs → bundleActivity-MLrjNJCT.mjs} +1 -1
  104. package/.output/server/_ssr/{config-WVX4IxSR.mjs → config-CjIBFhJX.mjs} +1 -1
  105. package/.output/server/_ssr/config.server-NnMFjMFX.mjs +43 -0
  106. package/.output/server/_ssr/{console-access-aUpQs49I.mjs → console-access-33BNlH97.mjs} +3 -3
  107. package/.output/server/_ssr/{console-features-4xAGfZHw.mjs → console-features-BTV-XuhQ.mjs} +1 -1
  108. package/.output/server/_ssr/{console-features-api-DSSgCV3v.mjs → console-features-api-v1fSycue.mjs} +6 -6
  109. package/.output/server/_ssr/{console-features-rpc-D6kMlR2A.mjs → console-features-rpc-C7XNVqm0.mjs} +5 -5
  110. package/.output/server/_ssr/createCsrfMiddleware-BwHbC8n-.mjs +159 -0
  111. package/.output/server/_ssr/createMiddleware-C3ACsfuG.mjs +24 -0
  112. package/.output/server/_ssr/{createMiddleware-B1iPez7u.mjs → createServerFn-AQvBMBjJ.mjs} +2 -23
  113. package/.output/server/_ssr/{createServerRpc-BaDJLGBx.mjs → createServerRpc-BVvRVOz5.mjs} +2 -2
  114. package/.output/server/_ssr/{databaseConfig-C5SkAyUy.mjs → databaseConfig-B_Bg7qS5.mjs} +1 -1
  115. package/.output/server/_ssr/{databaseJsonValue-Csbuzc9Q.mjs → databaseJsonValue-kS7cvEtz.mjs} +1 -1
  116. package/.output/server/_ssr/{databaseRows-CPphvbag.mjs → databaseRows-B6H8Cmgq.mjs} +3 -3
  117. package/.output/server/_ssr/{dialog-B4ALf7NJ.mjs → dialog-C8CS8RMt.mjs} +1 -1
  118. package/.output/server/_ssr/{dist-CbQbBIw-.mjs → dist-TEpM_UAD.mjs} +125 -125
  119. package/.output/server/_ssr/{downloadBundle-94jPBGSO.mjs → downloadBundle-DCmr7vsj.mjs} +1 -1
  120. package/.output/server/_ssr/{empty-Bh1FEeYF.mjs → empty-CkdEAvCY.mjs} +2 -2
  121. package/.output/server/_ssr/{empty-plugin-adapters-CpCHi1TV.mjs → empty-plugin-adapters-zrZ0xs6n.mjs} +1 -1
  122. package/.output/server/_ssr/{failureReports-Bh22wois.mjs → failureReports-Cn_zU9S8.mjs} +3 -3
  123. package/.output/server/_ssr/{field-HKCaPH3e.mjs → field-CW08sZXd.mjs} +2 -2
  124. package/.output/server/_ssr/{getBundleChildren-DfxB2K8D.mjs → getBundleChildren-rtkEEJw5.mjs} +2 -2
  125. package/.output/server/_ssr/git-CTA8Aqr0.mjs +56 -0
  126. package/.output/server/_ssr/{input-group-xm3LzDuq.mjs → input-group-D-TpFPuG.mjs} +1 -1
  127. package/.output/server/_ssr/{insights-BKZpJ_K4.mjs → insights-CMyzPs8l.mjs} +28 -27
  128. package/.output/server/_ssr/{insights-CCyTACIw.mjs → insights-CYeBl8N9.mjs} +5 -5
  129. package/.output/server/_ssr/{insights-CNqWQLJR.mjs → insights-DAi9KknT.mjs} +2 -2
  130. package/.output/server/_ssr/{insights-api-DtRFz5X3.mjs → insights-api-BFARQ5ct.mjs} +6 -6
  131. package/.output/server/_ssr/{insights-errors-Iy-lBaM7.mjs → insights-errors-7BCwFybo.mjs} +2 -2
  132. package/.output/server/_ssr/{insights-failures-BJN3RfLu.mjs → insights-failures-B9VPkzZE.mjs} +2 -2
  133. package/.output/server/_ssr/{insights-failures-rpc-DLZ91uOG.mjs → insights-failures-rpc-Q4WNNJkU.mjs} +12 -12
  134. package/.output/server/_ssr/{insights-recovery-BUuQdlA3.mjs → insights-recovery-BtXVU3ro.mjs} +1 -1
  135. package/.output/server/_ssr/{insights-recovery-rpc-BQHUhPi7.mjs → insights-recovery-rpc-CY5RYJKa.mjs} +8 -8
  136. package/.output/server/_ssr/{insights-rpc-C6Vukyn7.mjs → insights-rpc-BO7_evvs.mjs} +5 -5
  137. package/.output/server/_ssr/{insights-search-CBYoxT7v.mjs → insights-search-DKfxgHD3.mjs} +5 -5
  138. package/.output/server/_ssr/{insights-usage-CHqvtehH.mjs → insights-usage-DiGnuxW8.mjs} +1 -1
  139. package/.output/server/_ssr/{insights-usage-rpc-BJ7a-xco.mjs → insights-usage-rpc-B4czku50.mjs} +8 -8
  140. package/.output/server/_ssr/{insights-usage-rpc-C-lNUIsD.mjs → insights-usage-rpc-CUCTFska.mjs} +5 -5
  141. package/.output/server/_ssr/{insightsUsage-COUG-_-U.mjs → insightsUsage-ClAmoUz8.mjs} +3 -3
  142. package/.output/server/_ssr/{insights_.distribution-DmLW8I8a.mjs → insights_.distribution-BDeUhZGj.mjs} +2 -2
  143. package/.output/server/_ssr/{insights_.distribution-CTwilMPm.mjs → insights_.distribution-BRrbZN1X.mjs} +5 -5
  144. package/.output/server/_ssr/{insights_.distribution-DztDyrBj.mjs → insights_.distribution-C2MXpzKd.mjs} +15 -14
  145. package/.output/server/_ssr/{installations-Bct7ag5y.mjs → installations-BVk7okiY.mjs} +4 -4
  146. package/.output/server/_ssr/{installations-B6MK5iA8.mjs → installations-BjLMgBoH.mjs} +2 -2
  147. package/.output/server/_ssr/{installations-_N4PtYFl.mjs → installations-DxGZEC5G.mjs} +12 -12
  148. package/.output/server/_ssr/{listReleases-BfUBlCHo.mjs → listReleases-D_GY00SQ.mjs} +1 -1
  149. package/.output/server/_ssr/native-builds-CSCgFB21.mjs +10 -0
  150. package/.output/server/_ssr/native-builds-CdlyAldW.mjs +569 -0
  151. package/.output/server/_ssr/native-builds-DhXL5zf_.mjs +137 -0
  152. package/.output/server/_ssr/native-builds_._buildId-CMp1h8dT.mjs +10 -0
  153. package/.output/server/_ssr/native-builds_._buildId-GHpGaYyE.mjs +813 -0
  154. package/.output/server/_ssr/nativeBuildQr-84cnFQPX.mjs +59 -0
  155. package/.output/server/_ssr/nativeBuilds-B_7BPjt_.mjs +133 -0
  156. package/.output/server/_ssr/{plugins-BaD1slHx.mjs → plugins-RIryUNWj.mjs} +6 -5
  157. package/.output/server/_ssr/qr-code-C7kNRIDX.mjs +22 -0
  158. package/.output/server/_ssr/{release-adoption-DuYJTGuX.mjs → release-adoption-DoAtNPED.mjs} +2 -2
  159. package/.output/server/_ssr/{release-adoption-rpc-CHl7G_Zy.mjs → release-adoption-rpc-BJXZvtoQ.mjs} +10 -10
  160. package/.output/server/_ssr/{releaseAdoption-B39cpvfo.mjs → releaseAdoption-a2byXMfz.mjs} +4 -4
  161. package/.output/server/_ssr/releaseCatalogCompiler-DLcean2k.mjs +364 -0
  162. package/.output/server/_ssr/{releaseManagement-DFMUnDF5.mjs → releaseManagement-DHUux2az.mjs} +1 -1
  163. package/.output/server/_ssr/{releaseReachability-bFsuElSS.mjs → releaseReachability-DZOK_dH-.mjs} +2 -2
  164. package/.output/server/_ssr/{remote-config-Mj05qVYZ.mjs → remote-config-DjsEl270.mjs} +20 -19
  165. package/.output/server/_ssr/{remote-config-10-obSZ0.mjs → remote-config-DlyyohL2.mjs} +2 -2
  166. package/.output/server/_ssr/{remote-config-Dj1eTYi4.mjs → remote-config-XdYuByHz.mjs} +2 -2
  167. package/.output/server/_ssr/{remote-config-rpc-lteqDSLn.mjs → remote-config-rpc-GMLPb-90.mjs} +6 -6
  168. package/.output/server/_ssr/request-guard-CPdRBuGW.mjs +108 -0
  169. package/.output/server/_ssr/{router-Dr4jjjhx.mjs → router-vpxd56RW.mjs} +317 -71
  170. package/.output/server/_ssr/{routes-BZbrY_qw.mjs → routes-DyEM4b3x.mjs} +14 -14
  171. package/.output/server/_ssr/{routes-B5KrCDwk.mjs → routes-dQe3XhiS.mjs} +99 -81
  172. package/.output/server/_ssr/{runtime.server-DsHqVqTr.mjs → runtime.server-BdMY1blX.mjs} +4 -4
  173. package/.output/server/_ssr/schema-ChlRtZIo.mjs +23 -0
  174. package/.output/server/_ssr/schema-migration-rpc-BfGzEK_o.mjs +65 -0
  175. package/.output/server/_ssr/{select-BtdGNSmp.mjs → select-DM1uwtk9.mjs} +3 -27
  176. package/.output/server/_ssr/{server-BxaHSwHh.mjs → server-BUUKNSPI.mjs} +7 -161
  177. package/.output/server/_ssr/{sidebar-CVsgosaD.mjs → sidebar-DB2YQlok.mjs} +4 -4
  178. package/.output/server/_ssr/ssr.mjs +1 -1
  179. package/.output/server/_ssr/start-BvDsQehg.mjs +49 -0
  180. package/.output/server/_ssr/{switch-BmARgkUP.mjs → switch-DLD58zLy.mjs} +2 -2
  181. package/.output/server/_ssr/{table-M5P3YUfY.mjs → table-P4TnAZhl.mjs} +1 -1
  182. package/.output/server/_ssr/{tabs-CrmfOlJj.mjs → tabs-B7ZeLicF.mjs} +2 -2
  183. package/.output/server/_ssr/{updateFailures-z1IQy_4q.mjs → updateFailures-n-tGv90S.mjs} +3 -3
  184. package/.output/server/_ssr/{uuidv7-C88s9jWF.mjs → uuidv7-BIF4nb_q.mjs} +2 -2
  185. package/.output/server/_ssr/{schema-BiAVyEj2.mjs → version-CQ98ZBpL-CWSkTT9J.mjs} +2 -22
  186. package/.output/server/_tanstack-start-manifest_v-C-KC63Zq.mjs +184 -0
  187. package/.output/server/index.mjs +432 -271
  188. package/lib/vite-internal.mjs +32 -1
  189. package/package.json +8 -7
  190. package/src/components/AppSidebar.tsx +12 -1
  191. package/src/components/ConsoleAccessPage.tsx +49 -3
  192. package/src/components/QrCode.tsx +35 -0
  193. package/src/components/SchemaMigrationRequired.tsx +139 -0
  194. package/src/components/features/insights/BuiltinBundle.tsx +5 -3
  195. package/src/components/features/native-builds/BuiltInBundleCard.tsx +163 -0
  196. package/src/components/features/native-builds/CommitLink.tsx +30 -0
  197. package/src/components/features/native-builds/CopyableText.tsx +48 -0
  198. package/src/components/features/native-builds/ImportBuild.tsx +91 -0
  199. package/src/components/features/native-builds/InstallCard.tsx +205 -0
  200. package/src/components/features/native-builds/NativeBuildDetailPage.tsx +175 -0
  201. package/src/components/features/native-builds/NativeBuildsPage.tsx +151 -0
  202. package/src/components/features/native-builds/NativeBuildsTable.tsx +426 -0
  203. package/src/components/features/native-builds/PatchesCard.tsx +222 -0
  204. package/src/components/features/native-builds/ReleaseNativeBuildLink.tsx +101 -0
  205. package/src/components/features/native-builds/ServedNowCard.tsx +214 -0
  206. package/src/components/features/native-builds/ShortHash.tsx +41 -0
  207. package/src/components/features/native-builds/StoreSubmission.tsx +73 -0
  208. package/src/components/features/releases/ReleaseEditorSheet.tsx +8 -0
  209. package/src/lib/api-rpc.ts +98 -0
  210. package/src/lib/api.ts +120 -0
  211. package/src/lib/native-builds.ts +162 -0
  212. package/src/lib/qr-code.ts +16 -0
  213. package/src/lib/schema-migration-api.ts +54 -0
  214. package/src/lib/schema-migration-rpc.ts +26 -0
  215. package/src/lib/schema-migration.ts +101 -0
  216. package/src/lib/server/config.server.ts +23 -14
  217. package/src/lib/server/console-runtime.server.ts +4 -6
  218. package/src/lib/server/local-console.ts +164 -0
  219. package/src/lib/server/nativeBuildQr.ts +73 -0
  220. package/src/lib/server/nativeBuilds.ts +288 -0
  221. package/src/lib/shell.ts +9 -0
  222. package/src/routeTree.gen.ts +88 -1
  223. package/src/router.tsx +11 -1
  224. package/src/routes/-native-builds-search.ts +51 -0
  225. package/src/routes/[_]hot-updater/native-builds/qr.ts +14 -0
  226. package/src/routes/__root.tsx +11 -1
  227. package/src/routes/api/native-builds/$buildId/download.ts +50 -0
  228. package/src/routes/native-builds.tsx +21 -0
  229. package/src/routes/native-builds_.$buildId.tsx +23 -0
  230. package/src/start.ts +33 -0
  231. package/src/virtual-modules.d.ts +18 -0
  232. package/src/vite-internal.ts +57 -1
  233. package/.output/public/assets/EventDetails-gbD_fCqc.js +0 -1
  234. package/.output/public/assets/HashValueDisplay-BGkDPaYi.js +0 -1
  235. package/.output/public/assets/InsightsPageHeader-BRw8XkJW.js +0 -1
  236. package/.output/public/assets/NotFoundPage-Cei6W2lm.js +0 -1
  237. package/.output/public/assets/alert-dialog-BtVg50WQ.js +0 -1
  238. package/.output/public/assets/api-DoicFJfd.js +0 -1
  239. package/.output/public/assets/api-keys-D81VHHNM.js +0 -1
  240. package/.output/public/assets/api-keys-Dia6i8K1.js +0 -1
  241. package/.output/public/assets/arrow-left-DsivSewE.js +0 -1
  242. package/.output/public/assets/arrow-up-right-B5aM0F7f.js +0 -1
  243. package/.output/public/assets/badge-Bqf3UDee.js +0 -1
  244. package/.output/public/assets/circle-off-BsbZQtsF.js +0 -1
  245. package/.output/public/assets/console-features-api-DPE4C06Y.js +0 -2
  246. package/.output/public/assets/dialog-CGKPxz3n.js +0 -1
  247. package/.output/public/assets/index-DrkdiRIn.js +0 -10
  248. package/.output/public/assets/insights-C-5t8wh2.js +0 -2
  249. package/.output/public/assets/insights-D_ngQdiz.js +0 -1
  250. package/.output/public/assets/insights-KigRBDgC.js +0 -42
  251. package/.output/public/assets/insights-api-1ZjVu3Sb.js +0 -1
  252. package/.output/public/assets/insights-search-aT8BVCci.js +0 -1
  253. package/.output/public/assets/insights-usage-rpc-DqTiXI1_.js +0 -1
  254. package/.output/public/assets/insights_.distribution-Bzl6RFBR.js +0 -2
  255. package/.output/public/assets/insights_.distribution-Cthgttqb.js +0 -1
  256. package/.output/public/assets/insights_.distribution-D2c2v1bu.js +0 -1
  257. package/.output/public/assets/installations-2Nx8Htf-.js +0 -1
  258. package/.output/public/assets/installations-BskJ1xQr.js +0 -2
  259. package/.output/public/assets/installations-K2iJ_r0h.js +0 -1
  260. package/.output/public/assets/remote-config-D9X3NiwV.js +0 -6
  261. package/.output/public/assets/remote-config-DzOOiBIO.js +0 -1
  262. package/.output/public/assets/remote-config-ikg7kak0.js +0 -1
  263. package/.output/public/assets/routes-9rb9P5Y7.js +0 -2
  264. package/.output/public/assets/routes-P7683vKQ.js +0 -1
  265. package/.output/public/assets/search-DuI1FBpg.js +0 -1
  266. package/.output/public/assets/select-M3H2gxCc.js +0 -1
  267. package/.output/public/assets/smartphone-CtsHuiIC.js +0 -1
  268. package/.output/public/assets/styles-CpiW_YdW.css +0 -2
  269. package/.output/public/assets/switch-By_vfm8I.js +0 -1
  270. package/.output/public/assets/useCompositeListItem-B-UkrDlu.js +0 -1
  271. package/.output/server/_ssr/config.server-DTdsG47T.mjs +0 -35
  272. package/.output/server/_ssr/start-B6ECIE_7.mjs +0 -4
  273. package/.output/server/_tanstack-start-manifest_v-_BY9hYTm.mjs +0 -136
@@ -0,0 +1,164 @@
1
+ /**
2
+ * The variable `hot-updater console` passes the console it starts its
3
+ * per-run session secret in. Without it, the local console serves no page
4
+ * and no server function.
5
+ */
6
+ export const LOCAL_CONSOLE_SESSION_ENV = "HOT_UPDATER_CONSOLE_SESSION";
7
+
8
+ /** The query parameter of the link `hot-updater console` prints. */
9
+ export const LOCAL_CONSOLE_SESSION_PARAM = "hot-updater-session";
10
+
11
+ const LOOPBACK_HOSTNAMES = new Set(["localhost", "127.0.0.1", "[::1]"]);
12
+
13
+ const SAFE_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
14
+
15
+ export interface LocalConsoleGuardOptions {
16
+ /** The run's session secret; without one, every page is refused. */
17
+ readonly session: string | undefined;
18
+ /** The port the console listens on; any loopback port when unknown. */
19
+ readonly port: string | undefined;
20
+ /**
21
+ * Whether to check the `Host` header. Vite's dev server checks it
22
+ * itself, against its `server.allowedHosts`.
23
+ */
24
+ readonly checkHost: boolean;
25
+ }
26
+
27
+ /** Compares in time that depends only on the lengths. */
28
+ const sameSecret = (given: string, expected: string): boolean => {
29
+ if (given.length !== expected.length) return false;
30
+ let difference = 0;
31
+ for (let index = 0; index < given.length; index += 1) {
32
+ difference |= given.charCodeAt(index) ^ expected.charCodeAt(index);
33
+ }
34
+ return difference === 0;
35
+ };
36
+
37
+ /**
38
+ * The session cookie's name. Browsers send a cookie to every port of a
39
+ * host, so the port keeps two local consoles' sessions apart.
40
+ */
41
+ const cookieName = (port: string | undefined) =>
42
+ port === undefined
43
+ ? "hot_updater_console_session"
44
+ : `hot_updater_console_session_${port}`;
45
+
46
+ const cookieOf = (request: Request, name: string): string | undefined => {
47
+ for (const part of request.headers.get("cookie")?.split(";") ?? []) {
48
+ const separator = part.indexOf("=");
49
+ if (separator !== -1 && part.slice(0, separator).trim() === name) {
50
+ return part.slice(separator + 1).trim();
51
+ }
52
+ }
53
+ return undefined;
54
+ };
55
+
56
+ interface HostParts {
57
+ readonly hostname: string;
58
+ /** The port, `80` when the host names none, as HTTP's default. */
59
+ readonly port: string;
60
+ }
61
+
62
+ /** A port as digits without leading zeros, so `80` and `080` agree. */
63
+ const normalPort = (port: string) => String(Number(port));
64
+
65
+ /** A `Host` header's name and port, or undefined when it is not one. */
66
+ const hostPartsOf = (host: string): HostParts | undefined => {
67
+ const match = /^(\[[0-9a-f:.]+\]|[a-z0-9.-]+)(?::(\d{1,5}))?$/iu.exec(host);
68
+ if (!match) return undefined;
69
+ return {
70
+ hostname: match[1]!.toLowerCase(),
71
+ port: normalPort(match[2] ?? "80"),
72
+ };
73
+ };
74
+
75
+ /** Whether `origin` is the plain-HTTP origin of `host`. */
76
+ const isOriginOf = (origin: string, host: HostParts | undefined) => {
77
+ if (host === undefined) return false;
78
+ let parsed: URL;
79
+ try {
80
+ parsed = new URL(origin);
81
+ } catch {
82
+ return false;
83
+ }
84
+ return (
85
+ parsed.protocol === "http:" &&
86
+ parsed.hostname === host.hostname &&
87
+ normalPort(parsed.port || "80") === host.port
88
+ );
89
+ };
90
+
91
+ const refuse = (status: 401 | 403, message: string) =>
92
+ new Response(message, {
93
+ status,
94
+ headers: {
95
+ "cache-control": "no-store",
96
+ "content-type": "text/plain; charset=utf-8",
97
+ },
98
+ });
99
+
100
+ /**
101
+ * What the local console answers before a request reaches a page, a server
102
+ * function, or a route, or undefined to let it through:
103
+ *
104
+ * - a `Host` that is not this machine on the console's port is refused, so
105
+ * a site that rebinds its name to 127.0.0.1 reaches nothing;
106
+ * - a request that changes something from another origin is refused;
107
+ * - the link `hot-updater console` prints carries the run's session secret,
108
+ * which becomes an HttpOnly, SameSite=Strict cookie, and every other page
109
+ * and server function needs that cookie, so another user of the machine
110
+ * or a local process without the secret reaches nothing.
111
+ */
112
+ export const guardLocalConsoleRequest = (
113
+ request: Request,
114
+ { session, port, checkHost }: LocalConsoleGuardOptions,
115
+ ): Response | undefined => {
116
+ const header = request.headers.get("host");
117
+ const host = header === null ? undefined : hostPartsOf(header);
118
+ if (
119
+ checkHost &&
120
+ (host === undefined ||
121
+ !LOOPBACK_HOSTNAMES.has(host.hostname) ||
122
+ (port !== undefined && host.port !== normalPort(port)))
123
+ ) {
124
+ return refuse(
125
+ 403,
126
+ `This console answers only at http://localhost${port === undefined ? "" : `:${port}`}.`,
127
+ );
128
+ }
129
+ if (!SAFE_METHODS.has(request.method.toUpperCase())) {
130
+ const origin = request.headers.get("origin");
131
+ const site = request.headers.get("sec-fetch-site");
132
+ if (
133
+ (origin !== null && !isOriginOf(origin, host)) ||
134
+ (site !== null && site !== "same-origin" && site !== "none")
135
+ ) {
136
+ return refuse(403, "This console takes changes only from its own pages.");
137
+ }
138
+ }
139
+
140
+ const url = new URL(request.url);
141
+ const name = cookieName(port);
142
+ const given = url.searchParams.get(LOCAL_CONSOLE_SESSION_PARAM);
143
+ if (session && given !== null && sameSecret(given, session)) {
144
+ url.searchParams.delete(LOCAL_CONSOLE_SESSION_PARAM);
145
+ return new Response(null, {
146
+ status: 303,
147
+ headers: {
148
+ "cache-control": "no-store",
149
+ location: `${url.pathname}${url.search}${url.hash}`,
150
+ "set-cookie": `${name}=${session}; Path=/; HttpOnly; SameSite=Strict`,
151
+ },
152
+ });
153
+ }
154
+ const cookie = cookieOf(request, name);
155
+ if (session && cookie !== undefined && sameSecret(cookie, session)) {
156
+ return undefined;
157
+ }
158
+ return refuse(
159
+ 401,
160
+ session
161
+ ? "Open this console with the link hot-updater console printed."
162
+ : "Start this console with hot-updater console, and open the link it prints.",
163
+ );
164
+ };
@@ -0,0 +1,73 @@
1
+ import { isUUIDv7 } from "@hot-updater/protocol";
2
+
3
+ import { nativeBuildDownloadPath } from "../native-builds";
4
+ import { qrCodePath } from "../qr-code";
5
+
6
+ /** The header that carries the download URL the QR code holds. */
7
+ export const DOWNLOAD_URL_HEADER = "x-hot-updater-download-url";
8
+
9
+ /** The route that serves the QR code, under the console's own path. */
10
+ export const NATIVE_BUILD_QR_PATH = "/_hot-updater/native-builds/qr";
11
+
12
+ /** The pixel size of one module, so the image is legible at its own size. */
13
+ const MODULE_PX = 6;
14
+
15
+ const refusal = (status: number, message: string) =>
16
+ new Response(message, {
17
+ status,
18
+ headers: {
19
+ "cache-control": "no-store",
20
+ "content-type": "text/plain; charset=utf-8",
21
+ },
22
+ });
23
+
24
+ /**
25
+ * The URL of a native build's console download, under the console path the
26
+ * QR request came to: the console answers it with a fresh storage URL on
27
+ * each request, so it never expires.
28
+ */
29
+ export const nativeBuildDownloadUrl = (request: Request, id: string) => {
30
+ const url = new URL(request.url);
31
+ const base = url.pathname.endsWith(NATIVE_BUILD_QR_PATH)
32
+ ? url.pathname.slice(0, -NATIVE_BUILD_QR_PATH.length)
33
+ : "";
34
+ return `${url.origin}${base}${nativeBuildDownloadPath(id)}`;
35
+ };
36
+
37
+ /**
38
+ * The QR code of a native build's download URL, `<console>/api/native-builds/
39
+ * <id>/download`, as an SVG image, served at
40
+ * `/_hot-updater/native-builds/qr?build=<id>` without a session: a pull
41
+ * request comment shows it, and GitHub fetches a comment's images
42
+ * anonymously. It reads nothing, no database and no storage, so it says
43
+ * nothing about whether the build exists; the download itself asks for the
44
+ * session. The response's `x-hot-updater-download-url` header is the URL.
45
+ *
46
+ * - 400: `build` is missing or not a UUIDv7;
47
+ * - 405: a method other than GET or HEAD.
48
+ */
49
+ export const serveNativeBuildQr = (request: Request): Response => {
50
+ if (request.method !== "GET" && request.method !== "HEAD") {
51
+ const response = refusal(405, "Method Not Allowed");
52
+ response.headers.set("allow", "GET, HEAD");
53
+ return response;
54
+ }
55
+ const id = new URL(request.url).searchParams.get("build");
56
+ if (id === null || !isUUIDv7(id)) {
57
+ return refusal(400, "Pass ?build=<native build ID>.");
58
+ }
59
+ const downloadUrl = nativeBuildDownloadUrl(request, id);
60
+ const { size, path } = qrCodePath(downloadUrl);
61
+ const pixels = size * MODULE_PX;
62
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${pixels}" height="${pixels}" viewBox="0 0 ${size} ${size}" shape-rendering="crispEdges"><rect fill="#fff" width="${size}" height="${size}"/><path fill="#000" d="${path}"/></svg>`;
63
+ return new Response(request.method === "HEAD" ? null : svg, {
64
+ headers: {
65
+ // The URL depends only on the build ID and the console's own address.
66
+ "cache-control": "public, max-age=86400",
67
+ "content-type": "image/svg+xml",
68
+ "content-security-policy": "default-src 'none'",
69
+ "x-content-type-options": "nosniff",
70
+ [DOWNLOAD_URL_HEADER]: downloadUrl,
71
+ },
72
+ });
73
+ };
@@ -0,0 +1,288 @@
1
+ import {
2
+ type CompiledReleaseCatalog,
3
+ type HotUpdaterCoreApi,
4
+ type KeysetInput,
5
+ type NativeBuildRow,
6
+ projectCompiledCatalog,
7
+ type StorageAdapter,
8
+ } from "@hot-updater/plugin-core";
9
+ import type { ReleaseCatalogDescriptor } from "@hot-updater/protocol";
10
+
11
+ import {
12
+ nativeBuildCatalogScopeKey,
13
+ nativeBuildChannel,
14
+ nativeBuildScope,
15
+ } from "../native-builds";
16
+
17
+ export interface KeysetPageInput {
18
+ /** Rows older than this key: the next page. */
19
+ readonly before?: string;
20
+ /** Rows newer than this key: the previous page. */
21
+ readonly after?: string;
22
+ readonly limit: number;
23
+ }
24
+
25
+ export interface KeysetPage<T> {
26
+ /** Newest first. */
27
+ readonly data: readonly T[];
28
+ /** `before` of the next page, when there may be older rows. */
29
+ readonly next?: string;
30
+ /** `after` of the previous page, when there may be newer rows. */
31
+ readonly previous?: string;
32
+ }
33
+
34
+ /**
35
+ * One page newest first, from one keyset read: newest first for the next
36
+ * page, oldest first (then reversed) for the previous one. `next` is set
37
+ * for a full page, so the last page may lead to an empty one.
38
+ */
39
+ async function keysetPage<T>(
40
+ read: (input: KeysetInput) => Promise<readonly T[]>,
41
+ keyOf: (row: T) => string,
42
+ { before, after, limit }: KeysetPageInput,
43
+ ): Promise<KeysetPage<T>> {
44
+ const data =
45
+ after === undefined
46
+ ? await read({
47
+ order: "desc",
48
+ limit,
49
+ ...(before === undefined ? {} : { after: before }),
50
+ })
51
+ : [...(await read({ order: "asc", after, limit }))].reverse();
52
+ const first = data[0];
53
+ const last = data.at(-1);
54
+ const full = data.length === limit;
55
+ return {
56
+ data,
57
+ ...(last !== undefined && (after !== undefined || full)
58
+ ? { next: keyOf(last) }
59
+ : {}),
60
+ ...(first !== undefined && (after === undefined ? before : full)
61
+ ? { previous: keyOf(first) }
62
+ : {}),
63
+ };
64
+ }
65
+
66
+ export interface NativeBuildListInput extends KeysetPageInput {
67
+ /** Only the builds that embed this channel ID. */
68
+ readonly channelId?: string;
69
+ }
70
+
71
+ export type NativeBuildListResult = KeysetPage<NativeBuildRow>;
72
+
73
+ /**
74
+ * One page of native builds, newest first: one read of the registry's ID
75
+ * index, or of its channel index for one channel's builds.
76
+ */
77
+ export function listNativeBuilds(
78
+ core: Pick<HotUpdaterCoreApi, "listNativeBuilds">,
79
+ { channelId, ...page }: NativeBuildListInput,
80
+ ): Promise<NativeBuildListResult> {
81
+ return keysetPage(
82
+ (input) =>
83
+ core.listNativeBuilds({
84
+ ...input,
85
+ ...(channelId === undefined ? {} : { channelId }),
86
+ }),
87
+ ({ id }) => id,
88
+ page,
89
+ );
90
+ }
91
+
92
+ /** A patch from a build's built-in bundle: the bundle it updates to, and its size. */
93
+ export interface NativeBuildPatch {
94
+ readonly bundleId: string;
95
+ readonly byteSize: number;
96
+ }
97
+
98
+ export type NativeBuildPatchPage = KeysetPage<NativeBuildPatch>;
99
+
100
+ /** How many patches one page of a build's patches lists. */
101
+ export const NATIVE_BUILD_PATCH_PAGE_SIZE = 20;
102
+
103
+ /**
104
+ * One page of the patches from a built-in bundle, newest target first: one
105
+ * read of the patches index. The patch rows carry what the page shows, so
106
+ * no bundle is read.
107
+ */
108
+ export function listNativeBuildPatches(
109
+ core: Pick<HotUpdaterCoreApi, "listPatchesFromBase">,
110
+ minBundleId: string,
111
+ page: KeysetPageInput,
112
+ ): Promise<NativeBuildPatchPage> {
113
+ return keysetPage(
114
+ async (input) =>
115
+ (await core.listPatchesFromBase(minBundleId, input)).map(
116
+ ({ bundle_id, byte_size }) => ({
117
+ bundleId: bundle_id,
118
+ byteSize: byte_size,
119
+ }),
120
+ ),
121
+ ({ bundleId }) => bundleId,
122
+ page,
123
+ );
124
+ }
125
+
126
+ export interface NativeBuildDetail {
127
+ readonly build: NativeBuildRow;
128
+ /** The channel the build embeds. */
129
+ readonly channel: string;
130
+ /** The Release Catalog scope whose releases an install is offered. */
131
+ readonly catalogScopeKey: string;
132
+ /** How many bundles patch from the built-in bundle, or null when its row is gone. */
133
+ readonly builtInBundle: { readonly childCount: number } | null;
134
+ /**
135
+ * The newest build of the build's scope, which new deploys take their
136
+ * patch base from: this build, or the build that supersedes it.
137
+ */
138
+ readonly newestInScope: NativeBuildRow | null;
139
+ /**
140
+ * The releases the update check serves an install of this build now,
141
+ * newest first: the scope's catalog, at the build's app version, down to
142
+ * the built-in bundle the device floors at.
143
+ */
144
+ readonly servedNow: readonly ReleaseCatalogDescriptor[];
145
+ /** The newest page of patches from the built-in bundle. */
146
+ readonly patches: NativeBuildPatchPage;
147
+ }
148
+
149
+ /**
150
+ * Everything a native build's page shows, in five reads whatever the
151
+ * build: the build, then its built-in bundle, its scope's newest build, its
152
+ * scope's catalog, and one page of patches from it. Null for an unknown ID.
153
+ */
154
+ export async function getNativeBuildDetail(
155
+ core: Pick<
156
+ HotUpdaterCoreApi,
157
+ | "getNativeBuild"
158
+ | "getBundle"
159
+ | "findNativeBuild"
160
+ | "getReleaseCatalogRow"
161
+ | "listPatchesFromBase"
162
+ >,
163
+ id: string,
164
+ ): Promise<NativeBuildDetail | null> {
165
+ const build = await core.getNativeBuild(id);
166
+ if (build === null) return null;
167
+ const catalogScopeKey = nativeBuildCatalogScopeKey(build);
168
+ const [builtIn, newestInScope, catalog, patches] = await Promise.all([
169
+ core.getBundle(build.min_bundle_id),
170
+ core.findNativeBuild(nativeBuildScope(build)),
171
+ core.getReleaseCatalogRow(catalogScopeKey),
172
+ listNativeBuildPatches(core, build.min_bundle_id, {
173
+ limit: NATIVE_BUILD_PATCH_PAGE_SIZE,
174
+ }),
175
+ ]);
176
+ const servedNow =
177
+ catalog === null || catalog.is_tombstone
178
+ ? []
179
+ : projectCompiledCatalog(
180
+ JSON.parse(catalog.payload) as CompiledReleaseCatalog,
181
+ build.app_version ?? undefined,
182
+ ).filter(
183
+ // The floor the device applies: a release older than the build's
184
+ // built-in bundle is older than what the binary ships.
185
+ ({ bundleId }) =>
186
+ bundleId !== null && bundleId >= build.min_bundle_id,
187
+ );
188
+ return {
189
+ build,
190
+ channel: nativeBuildChannel(build),
191
+ catalogScopeKey,
192
+ builtInBundle: builtIn === null ? null : { childCount: builtIn.childCount },
193
+ newestInScope,
194
+ servedNow,
195
+ patches,
196
+ };
197
+ }
198
+
199
+ const protocolOf = (storageUri: string) =>
200
+ new URL(storageUri).protocol.replace(":", "");
201
+
202
+ /** The storage that holds `storageUri`, by its protocol. */
203
+ const storageFor = (storage: StorageAdapter, storageUri: string) =>
204
+ storage.protocol === protocolOf(storageUri) ? storage : undefined;
205
+
206
+ /** A public URL no storage owns, which is its own download URL. */
207
+ const isPublicUrl = (storageUri: string) =>
208
+ ["http", "https"].includes(protocolOf(storageUri));
209
+
210
+ /**
211
+ * The URL a phone downloads a native build's artifact from, without the
212
+ * console's sign-in: the storage's download URL, which may expire. Null
213
+ * when the storage gives none, or the build is gone.
214
+ */
215
+ export async function resolveNativeBuildDownloadUrl(
216
+ id: string,
217
+ {
218
+ core,
219
+ storage,
220
+ }: {
221
+ readonly core: Pick<HotUpdaterCoreApi, "getNativeBuild">;
222
+ readonly storage: StorageAdapter;
223
+ },
224
+ ): Promise<string | null> {
225
+ const build = await core.getNativeBuild(id);
226
+ if (build === null) return null;
227
+ const storageUri = build.artifact_storage_uri;
228
+ const adapter = storageFor(storage, storageUri);
229
+ if (adapter?.getDownloadUrl !== undefined) {
230
+ return (await adapter.getDownloadUrl({ storageUri })).url;
231
+ }
232
+ return adapter === undefined && isPublicUrl(storageUri) ? storageUri : null;
233
+ }
234
+
235
+ /**
236
+ * Downloads a native build's artifact: a redirect to the storage's download
237
+ * URL, or the object streamed through the console when the storage gives
238
+ * no URL. The redirect is resolved on each request, so the console's link
239
+ * never expires.
240
+ */
241
+ export async function downloadNativeBuild(
242
+ id: string,
243
+ {
244
+ core,
245
+ storage,
246
+ }: {
247
+ readonly core: Pick<HotUpdaterCoreApi, "getNativeBuild">;
248
+ readonly storage: StorageAdapter;
249
+ },
250
+ ): Promise<Response> {
251
+ const build = await core.getNativeBuild(id);
252
+ if (build === null) return new Response("Build not found", { status: 404 });
253
+
254
+ const storageUri = build.artifact_storage_uri;
255
+ const adapter = storageFor(storage, storageUri);
256
+ if (adapter?.getDownloadUrl !== undefined) {
257
+ const { url } = await adapter.getDownloadUrl({ storageUri });
258
+ return new Response(null, {
259
+ headers: { "cache-control": "private, no-store", location: url },
260
+ status: 302,
261
+ });
262
+ }
263
+ if (adapter?.get !== undefined) {
264
+ const { response } = await adapter.get({ storageUri });
265
+ if (!response) {
266
+ return new Response("Storage object not found", { status: 404 });
267
+ }
268
+ const fileName = decodeURIComponent(storageUri.split("/").at(-1) ?? "");
269
+ const headers = new Headers(response.headers);
270
+ headers.set("cache-control", "private, no-store");
271
+ headers.set(
272
+ "content-disposition",
273
+ `attachment; filename*=UTF-8''${encodeURIComponent(fileName)}`,
274
+ );
275
+ return new Response(response.body, {
276
+ headers,
277
+ status: response.status,
278
+ statusText: response.statusText,
279
+ });
280
+ }
281
+ if (adapter === undefined && isPublicUrl(storageUri)) {
282
+ return Response.redirect(storageUri, 302);
283
+ }
284
+ return new Response(
285
+ `No storage adapter for protocol: ${protocolOf(storageUri)}`,
286
+ { status: 503 },
287
+ );
288
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `value` as one POSIX shell word: as it is when it holds only characters
3
+ * no shell treats specially, else in single quotes, so a file name or link
4
+ * the Console shows in a command can't run anything when it is pasted.
5
+ */
6
+ export const shellQuote = (value: string): string =>
7
+ /^[\w@%+=:,./-]+$/.test(value)
8
+ ? value
9
+ : `'${value.replaceAll("'", `'\\''`)}'`;