@hot-updater/console 1.0.0-rc.9 → 1.0.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 (341) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/ConsoleFeatureUnavailable-jB4Loglh.js +1 -0
  3. package/.output/public/assets/EventDetails-B6yUsyNA.js +1 -0
  4. package/.output/public/assets/HashValueDisplay-BFX_kY3-.js +1 -0
  5. package/.output/public/assets/InsightsInfo-BNO0WznZ.js +1 -0
  6. package/.output/public/assets/InsightsPageHeader-B892oVml.js +1 -0
  7. package/.output/public/assets/NotFoundPage-DIqCpWa3.js +1 -0
  8. package/.output/public/assets/alert-dialog-Bbk_4-WJ.js +1 -0
  9. package/.output/public/assets/api-DCM0wr6O.js +1 -0
  10. package/.output/public/assets/api-keys-CIO9L_wu.js +1 -0
  11. package/.output/public/assets/api-keys-DVTL_--f.js +1 -0
  12. package/.output/public/assets/arrow-left-CnMYpqB7.js +1 -0
  13. package/.output/public/assets/arrow-up-right-BtffZA6e.js +1 -0
  14. package/.output/public/assets/chevron-right-BqD5wtFb.js +1 -0
  15. package/.output/public/assets/clipboard-DS9ucUQL.js +1 -0
  16. package/.output/public/assets/console-features-api-BH5m_PtQ.js +1 -0
  17. package/.output/public/assets/dialog-Bqbb7B0N.js +1 -0
  18. package/.output/public/assets/dist-LxKJB-Vu.js +1 -0
  19. package/.output/public/assets/empty-CuT2UhFp.js +1 -0
  20. package/.output/public/assets/field-BBDpjIqu.js +1 -0
  21. package/.output/public/assets/index-6o51GkFe.js +11 -0
  22. package/.output/public/assets/input-group-ja_30qaF.js +1 -0
  23. package/.output/public/assets/insights-CmraDzQh.js +1 -0
  24. package/.output/public/assets/insights-CxZ2GCYV.js +2 -0
  25. package/.output/public/assets/insights-api-BigAU9sZ.js +1 -0
  26. package/.output/public/assets/insights-usage-rpc-DYEhsCKT.js +1 -0
  27. package/.output/public/assets/insights-vNRCrfnS.js +42 -0
  28. package/.output/public/assets/insights_.distribution-ChOerfJt.js +1 -0
  29. package/.output/public/assets/insights_.distribution-D-e4iga8.js +1 -0
  30. package/.output/public/assets/insights_.distribution-DZtkibX1.js +2 -0
  31. package/.output/public/assets/installations-BazSJXE0.js +2 -0
  32. package/.output/public/assets/installations-ClHTqdAO.js +1 -0
  33. package/.output/public/assets/installations-DpKsBuHi.js +1 -0
  34. package/.output/public/assets/key-round-wS_SIZ6b.js +1 -0
  35. package/.output/public/assets/list-CgrYiadM.js +1 -0
  36. package/.output/public/assets/refresh-cw-BYgoNCco.js +1 -0
  37. package/.output/public/assets/remote-config-BWwHOkCK.js +1 -0
  38. package/.output/public/assets/remote-config-Br56BODb.js +6 -0
  39. package/.output/public/assets/remote-config-CXOi0LRu.js +1 -0
  40. package/.output/public/assets/routes-B88Erkpk.js +2 -0
  41. package/.output/public/assets/routes-CfzsAleC.js +1 -0
  42. package/.output/public/assets/search-DaTDPW25.js +1 -0
  43. package/.output/public/assets/select-BZZBXMkr.js +1 -0
  44. package/.output/public/assets/sidebar-DNVyJPfX.js +3 -0
  45. package/.output/public/assets/smartphone-DpTFgtKf.js +1 -0
  46. package/.output/public/assets/styles-DTGEAJFv.css +2 -0
  47. package/.output/public/assets/switch-DZ1TPwx-.js +1 -0
  48. package/.output/public/assets/table-Gm4qRWwE.js +1 -0
  49. package/.output/public/assets/tabs-C7FOEjFC.js +1 -0
  50. package/.output/public/assets/undo-2-CmrHjwFc.js +1 -0
  51. package/.output/public/assets/useCompositeListItem-9E4pj_CH.js +1 -0
  52. package/.output/public/assets/valueToPercent-CH8GCHaL.js +1 -0
  53. package/.output/server/__23tanstack-start-server-fn-resolver-BpRlos5Q.mjs +170 -0
  54. package/.output/server/_libs/@base-ui/react+[...].mjs +4389 -4207
  55. package/.output/server/_libs/@tanstack/react-router+[...].mjs +3802 -4022
  56. package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +140 -97
  57. package/.output/server/_libs/lucide-react.mjs +221 -9
  58. package/.output/server/_libs/next-themes.mjs +1 -1
  59. package/.output/server/_libs/react-redux+[...].mjs +1 -1
  60. package/.output/server/_libs/recharts+[...].mjs +483 -901
  61. package/.output/server/_libs/sonner.mjs +1 -1
  62. package/.output/server/_libs/tanstack__history.mjs +68 -41
  63. package/.output/server/_libs/tanstack__query-core.mjs +2010 -309
  64. package/.output/server/_libs/tanstack__react-query.mjs +448 -21
  65. package/.output/server/_libs/verkit.mjs +102 -1
  66. package/.output/server/_ssr/BundleIdDisplay-x94o9rlU.mjs +13 -0
  67. package/.output/server/_ssr/ConsoleFeatureUnavailable-ldW8v8-q.mjs +58 -0
  68. package/.output/server/_ssr/EventDetails-Q8kfj9Nr.mjs +344 -0
  69. package/.output/server/_ssr/{HashValueDisplay-CyMmr0O9.mjs → HashValueDisplay-n1GholGS.mjs} +3 -3
  70. package/.output/server/_ssr/InsightsInfo-DuJFatQO.mjs +75 -0
  71. package/.output/server/_ssr/InsightsPageHeader-BSUX3Ye9.mjs +145 -0
  72. package/.output/server/_ssr/NotFoundPage-DZzOOzS3.mjs +37 -0
  73. package/.output/server/_ssr/{dialog-CKvQ4VIE.mjs → alert-dialog-C43Doa0I.mjs} +6 -83
  74. package/.output/server/_ssr/{api-keys-CzZ7Ont4.mjs → api-keys-B4sYm8dM.mjs} +48 -9
  75. package/.output/server/_ssr/api-keys-CEnM9fqG.mjs +5 -0
  76. package/.output/server/_ssr/api-keys-rpc-BIFuUgMt.mjs +52 -0
  77. package/.output/server/_ssr/api-oZjRn9tP.mjs +186 -0
  78. package/.output/server/_ssr/api-rpc-BNiFRRK8.mjs +240 -0
  79. package/.output/server/_ssr/{auth-BF8mbAgF.mjs → auth-UP0wXs4i.mjs} +1 -1
  80. package/.output/server/_ssr/{auth-rpc-DH6Bhwcz.mjs → auth-rpc-DnZqYxyq.mjs} +6 -6
  81. package/.output/server/_ssr/auth.server-DFpyxRDd.mjs +5374 -0
  82. package/.output/server/_ssr/bundleActivity-M5gzBFNg.mjs +18 -0
  83. package/.output/server/_ssr/config-WVX4IxSR.mjs +15 -0
  84. package/.output/server/_ssr/config.server-DskXCAyI.mjs +35 -0
  85. package/.output/server/_ssr/console-access-BJfAXknd.mjs +17 -0
  86. package/.output/server/_ssr/console-features-Oz0q20jJ.mjs +61 -0
  87. package/.output/server/_ssr/console-features-api-C9kiKvaV.mjs +43 -0
  88. package/.output/server/_ssr/console-features-rpc-P0GL7Z-R.mjs +19 -0
  89. package/.output/server/_ssr/{createServerFn-B2hcXoPM.mjs → createMiddleware-Cm6Tn0_Y.mjs} +67 -56
  90. package/.output/server/_ssr/{createServerRpc-DB29C_5p.mjs → createServerRpc-RLR4H-Fn.mjs} +2 -2
  91. package/.output/server/_ssr/databaseConfig-C5SkAyUy.mjs +5 -0
  92. package/.output/server/_ssr/{databaseJsonValue-d6I3nAS4.mjs → databaseJsonValue-Csbuzc9Q.mjs} +28 -5
  93. package/.output/server/_ssr/databaseRows-CPphvbag.mjs +73 -0
  94. package/.output/server/_ssr/dialog-BbYDMjdC.mjs +84 -0
  95. package/.output/server/_ssr/{dist-CyzPaI5v.mjs → dist-CbQbBIw-.mjs} +140 -7
  96. package/.output/server/_ssr/{downloadBundle-CI7JUKvC.mjs → downloadBundle-CZysjmNW.mjs} +10 -10
  97. package/.output/server/_ssr/empty-CVLGfxuu.mjs +60 -0
  98. package/.output/server/_ssr/{empty-plugin-adapters-B5QvGpEl.mjs → empty-plugin-adapters-CPtwX8a7.mjs} +1 -1
  99. package/.output/server/_ssr/failureReports-DKDhVsXA.mjs +33 -0
  100. package/.output/server/_ssr/field-asw83X0v.mjs +189 -0
  101. package/.output/server/_ssr/getBundleChildren-Cv9iz1As.mjs +22 -0
  102. package/.output/server/_ssr/{input-group-BrGSjz4S.mjs → input-group-CuYJHosb.mjs} +2 -2
  103. package/.output/server/_ssr/insights-B55FG5fm.mjs +14 -0
  104. package/.output/server/_ssr/insights-CWnHIFUV.mjs +5 -0
  105. package/.output/server/_ssr/insights-CpLpedFZ.mjs +2315 -0
  106. package/.output/server/_ssr/insights-api-sjws23WF.mjs +122 -0
  107. package/.output/server/_ssr/insights-errors-BjnFUyZj.mjs +34 -0
  108. package/.output/server/_ssr/insights-failures-rpc-BvbWvqNf.mjs +36 -0
  109. package/.output/server/_ssr/insights-failures-xaa41Buf.mjs +46 -0
  110. package/.output/server/_ssr/insights-recovery-BWQhiZaF.mjs +28 -0
  111. package/.output/server/_ssr/insights-recovery-rpc-GiYXdkaY.mjs +32 -0
  112. package/.output/server/_ssr/{insights-rpc-DDKmIrpV.mjs → insights-rpc-DP_8Rmz4.mjs} +22 -14
  113. package/.output/server/_ssr/insights-usage-BpBM0U0c.mjs +29 -0
  114. package/.output/server/_ssr/insights-usage-rpc-BiolgB8e.mjs +29 -0
  115. package/.output/server/_ssr/insights-usage-rpc-KStL0zOx.mjs +41 -0
  116. package/.output/server/_ssr/insightsUsage-CzhVjsc8.mjs +56 -0
  117. package/.output/server/_ssr/insights_.distribution-CVrnbP3R.mjs +7 -0
  118. package/.output/server/_ssr/insights_.distribution-D2HaNTAA.mjs +16 -0
  119. package/.output/server/_ssr/{insights_.distribution-cIsU-kHG.mjs → insights_.distribution-DN9pR422.mjs} +50 -22
  120. package/.output/server/_ssr/installations-BHmbbnH8.mjs +5 -0
  121. package/.output/server/_ssr/{installations-C5anhyP0.mjs → installations-BSCmFq8Q.mjs} +279 -367
  122. package/.output/server/_ssr/installations-DD1g__Nc.mjs +69 -0
  123. package/.output/server/_ssr/listReleases-BfUBlCHo.mjs +79 -0
  124. package/.output/server/_ssr/plugins-BaD1slHx.mjs +2086 -0
  125. package/.output/server/_ssr/release-adoption-005tQRpo.mjs +55 -0
  126. package/.output/server/_ssr/release-adoption-rpc-BsstqxUp.mjs +41 -0
  127. package/.output/server/_ssr/releaseAdoption-CtOFkwli.mjs +114 -0
  128. package/.output/server/_ssr/releaseManagement-DFMUnDF5.mjs +11 -0
  129. package/.output/server/_ssr/remote-config-C2Ee8lXO.mjs +5 -0
  130. package/.output/server/_ssr/remote-config-Dv1ccMo-.mjs +17 -0
  131. package/.output/server/_ssr/remote-config-of0lGQH8.mjs +2648 -0
  132. package/.output/server/_ssr/remote-config-rpc-BzX31mUS.mjs +115 -0
  133. package/.output/server/_ssr/rolldown-runtime-rSIU-vHC.mjs +13 -0
  134. package/.output/server/_ssr/{router-BuanSsRI.mjs → router-D8NuoJ_I.mjs} +107 -134
  135. package/.output/server/_ssr/{routes-DY-vqU6R.mjs → routes-DgvnFqDy.mjs} +195 -429
  136. package/.output/server/_ssr/routes-V8sDybGf.mjs +409 -0
  137. package/.output/server/_ssr/runtime.server-De0QVWc9.mjs +104 -0
  138. package/.output/server/_ssr/schema-BiAVyEj2.mjs +361 -0
  139. package/.output/server/_ssr/{select-DD2szs3Y.mjs → select-t3iXRwc9.mjs} +3 -3
  140. package/.output/server/_ssr/server-h4VxnExB.mjs +8325 -0
  141. package/.output/server/_ssr/{createSsrRpc-CdlfejmI.mjs → sidebar-DJcErBMo.mjs} +107 -47
  142. package/.output/server/_ssr/ssr.mjs +1 -1
  143. package/.output/server/_ssr/start-BfFbHKMK.mjs +4 -0
  144. package/.output/server/_ssr/switch-E-btwMGt.mjs +43 -0
  145. package/.output/server/_ssr/{table-CTj6n-4F.mjs → table-37Dd23TN.mjs} +2 -2
  146. package/.output/server/_ssr/tabs-DEFnTm95.mjs +45 -0
  147. package/.output/server/_ssr/updateFailures-H6pnnYK4.mjs +40 -0
  148. package/.output/server/_ssr/{uuidv7-D0QFkPL_.mjs → uuidv7-C88s9jWF.mjs} +2 -2
  149. package/.output/server/_tanstack-start-manifest_v-55AGsehh.mjs +139 -0
  150. package/.output/server/index.mjs +309 -211
  151. package/LICENSE +13 -14
  152. package/README.md +80 -71
  153. package/lib/index.d.mts +23 -11
  154. package/lib/vite-internal.mjs +13 -3
  155. package/lib/vite.d.mts +2 -0
  156. package/package.json +14 -14
  157. package/src/components/AppSidebar.tsx +69 -74
  158. package/src/components/ConsoleFeatureUnavailable.tsx +80 -0
  159. package/src/components/NotFoundPage.tsx +7 -7
  160. package/src/components/features/FeatureSlots.tsx +103 -0
  161. package/src/components/features/api-keys/apiKeysFeatureUi.tsx +15 -0
  162. package/src/components/features/bundles/BundleMetadata.tsx +8 -5
  163. package/src/components/features/bundles/RolloutCohortsDialog.tsx +1 -1
  164. package/src/components/features/channels/ChannelManagementDialog.tsx +8 -6
  165. package/src/components/features/insights/AppUsage.tsx +41 -14
  166. package/src/components/features/insights/BuiltinBundle.tsx +36 -0
  167. package/src/components/features/insights/ComparedBundlesChart.tsx +310 -0
  168. package/src/components/features/insights/EstimatedCount.tsx +18 -0
  169. package/src/components/features/insights/EventDetails.tsx +390 -26
  170. package/src/components/features/insights/EventHistoryCard.tsx +111 -25
  171. package/src/components/features/insights/EventHistoryList.tsx +1 -1
  172. package/src/components/features/insights/FailureReports.tsx +407 -0
  173. package/src/components/features/insights/InsightsControls.tsx +240 -142
  174. package/src/components/features/insights/InsightsErrorAlert.tsx +14 -1
  175. package/src/components/features/insights/InsightsOverview.tsx +601 -403
  176. package/src/components/features/insights/InsightsPageHeader.tsx +19 -14
  177. package/src/components/features/insights/InsightsPeriodSelector.tsx +67 -12
  178. package/src/components/features/insights/InstallationHistoryCard.tsx +65 -10
  179. package/src/components/features/insights/InstallationMatchesCard.tsx +1 -1
  180. package/src/components/features/insights/ReleaseActivity.tsx +308 -0
  181. package/src/components/features/insights/ReportingDevicesSummary.tsx +13 -13
  182. package/src/components/features/insights/RollbackButton.tsx +118 -0
  183. package/src/components/features/insights/UpdateFailures.tsx +382 -0
  184. package/src/components/features/insights/insightsFeatureUi.tsx +35 -0
  185. package/src/components/features/releases/ReleaseEditorSheet.tsx +27 -13
  186. package/src/components/features/remote-config/ConditionDialog.tsx +592 -0
  187. package/src/components/features/remote-config/ConditionsCard.tsx +247 -0
  188. package/src/components/features/remote-config/ParameterDialog.tsx +426 -0
  189. package/src/components/features/remote-config/ParametersCard.tsx +254 -0
  190. package/src/components/features/remote-config/PreviewCard.tsx +284 -0
  191. package/src/components/features/remote-config/PublishDialog.tsx +325 -0
  192. package/src/components/features/remote-config/RemoteConfigPage.tsx +353 -0
  193. package/src/components/features/remote-config/RuleSummary.tsx +21 -0
  194. package/src/components/features/remote-config/ValueInput.tsx +151 -0
  195. package/src/components/features/remote-config/VersionsCard.tsx +384 -0
  196. package/src/components/features/remote-config/remoteConfigFeatureUi.tsx +15 -0
  197. package/src/components/features/remote-config/useDateTimeText.ts +16 -0
  198. package/src/components/ui/sidebar.tsx +22 -29
  199. package/src/index.ts +26 -19
  200. package/src/lib/api-keys-api.ts +0 -28
  201. package/src/lib/api-keys-rpc.ts +27 -38
  202. package/src/lib/api-rpc.ts +125 -288
  203. package/src/lib/api.ts +15 -134
  204. package/src/lib/bundle-activity.ts +24 -4
  205. package/src/lib/console-access.ts +20 -0
  206. package/src/lib/console-features-api.ts +68 -0
  207. package/src/lib/console-features-rpc.ts +13 -0
  208. package/src/lib/console-features.ts +98 -0
  209. package/src/lib/insights-api.ts +56 -2
  210. package/src/lib/insights-errors.ts +64 -0
  211. package/src/lib/insights-failures-rpc.ts +36 -0
  212. package/src/lib/insights-failures.ts +130 -0
  213. package/src/lib/insights-recovery-rpc.ts +13 -17
  214. package/src/lib/insights-recovery.ts +12 -36
  215. package/src/lib/insights-retention.ts +21 -0
  216. package/src/lib/insights-rpc.ts +30 -12
  217. package/src/lib/insights-search.ts +50 -4
  218. package/src/lib/insights-usage-rpc.ts +15 -9
  219. package/src/lib/insights-usage.ts +41 -3
  220. package/src/lib/insights-view.ts +34 -5
  221. package/src/lib/release-adoption-api.ts +214 -0
  222. package/src/lib/release-adoption-rpc.ts +49 -0
  223. package/src/lib/release-adoption.ts +164 -0
  224. package/src/lib/remote-config-api.ts +119 -0
  225. package/src/lib/remote-config-draft.ts +666 -0
  226. package/src/lib/remote-config-rpc.ts +196 -0
  227. package/src/lib/scroll-restoration.ts +21 -0
  228. package/src/lib/server/auth.server.ts +13 -5
  229. package/src/lib/server/bundleActivity.ts +27 -28
  230. package/src/lib/server/config.server.ts +21 -76
  231. package/src/lib/server/console-feature-apis.ts +70 -0
  232. package/src/lib/server/console-runtime.server.ts +82 -38
  233. package/src/lib/server/downloadBundle.ts +14 -15
  234. package/src/lib/server/failureReports.ts +50 -0
  235. package/src/lib/server/getBundleChildren.ts +31 -122
  236. package/src/lib/server/insightsUsage.ts +52 -113
  237. package/src/lib/server/listReleases.ts +109 -44
  238. package/src/lib/server/releaseAdoption.ts +151 -0
  239. package/src/lib/server/releaseReachability.ts +8 -8
  240. package/src/lib/server/remoteConfig.ts +75 -0
  241. package/src/lib/server/runtime.server.ts +120 -31
  242. package/src/lib/server/updateFailures.ts +66 -0
  243. package/src/lib/use-dialog-target.ts +27 -0
  244. package/src/routeTree.gen.ts +44 -44
  245. package/src/router.tsx +3 -2
  246. package/src/routes/-installations-search.ts +32 -8
  247. package/src/routes/-releases-search.ts +76 -29
  248. package/src/routes/__root.tsx +7 -1
  249. package/src/routes/api/bundles/$bundleId/download.ts +2 -5
  250. package/src/routes/api-keys.tsx +5 -2
  251. package/src/routes/index.tsx +210 -152
  252. package/src/routes/insights.tsx +91 -23
  253. package/src/routes/insights_.distribution.tsx +50 -9
  254. package/src/routes/installations.tsx +97 -40
  255. package/src/routes/remote-config.tsx +42 -0
  256. package/src/styles.css +9 -0
  257. package/src/vite-internal.ts +15 -3
  258. package/src/vite.ts +2 -0
  259. package/.output/public/assets/HashValueDisplay-BpTc3Oyg.js +0 -1
  260. package/.output/public/assets/InsightsPageHeader-DC2eEst8.js +0 -1
  261. package/.output/public/assets/api-CdUyUqzR.js +0 -42
  262. package/.output/public/assets/api-keys-64PM9PwW.js +0 -1
  263. package/.output/public/assets/api-keys-api-BeaJnvY5.js +0 -1
  264. package/.output/public/assets/arrow-left-04Rh8Zgp.js +0 -1
  265. package/.output/public/assets/card-CYHs5FZv.js +0 -1
  266. package/.output/public/assets/chart-no-axes-combined-DwyMsVFW.js +0 -1
  267. package/.output/public/assets/createServerFn-DU71xucx.js +0 -3
  268. package/.output/public/assets/dialog-IVMkBRCq.js +0 -1
  269. package/.output/public/assets/dist-BR4GzeEu.js +0 -1
  270. package/.output/public/assets/field-B3CYDpjg.js +0 -1
  271. package/.output/public/assets/index-Cb_D1D6N.js +0 -10
  272. package/.output/public/assets/info-C3y9HNoU.js +0 -1
  273. package/.output/public/assets/input-group-nLZylhWF.js +0 -1
  274. package/.output/public/assets/insights-Bm9v8uO3.js +0 -2
  275. package/.output/public/assets/insights-DS3iO7Up.js +0 -1
  276. package/.output/public/assets/insights-search-Byj3g3Mp.js +0 -1
  277. package/.output/public/assets/insights-usage-rpc-BaGR3lgq.js +0 -1
  278. package/.output/public/assets/insights_.distribution-B1i6cjlQ.js +0 -1
  279. package/.output/public/assets/insights_.distribution-C1Wf27eB.js +0 -2
  280. package/.output/public/assets/installations-CkFW4fV9.js +0 -2
  281. package/.output/public/assets/installations-DxTaIcHD.js +0 -1
  282. package/.output/public/assets/lazyRouteComponent-BapzjYnb.js +0 -1
  283. package/.output/public/assets/list-Dqt53fWZ.js +0 -1
  284. package/.output/public/assets/loader-circle-CUKfUqow.js +0 -1
  285. package/.output/public/assets/preload-helper-ClBMtM1g.js +0 -1
  286. package/.output/public/assets/refresh-cw-CNE_2PnD.js +0 -1
  287. package/.output/public/assets/routes-Cgtvspjg.js +0 -2
  288. package/.output/public/assets/routes-O2r2UUN8.js +0 -1
  289. package/.output/public/assets/shield-check-BFcJ46kz.js +0 -1
  290. package/.output/public/assets/signing-BgspC-xP.js +0 -1
  291. package/.output/public/assets/styles-DnrWuBwd.css +0 -2
  292. package/.output/public/assets/table-BuWQgxJJ.js +0 -1
  293. package/.output/public/assets/useMutation-CSV9NCUl.js +0 -1
  294. package/.output/public/assets/useOpenInteractionType-D5kduCTD.js +0 -1
  295. package/.output/public/assets/valueToPercent-BAtcbSLk.js +0 -1
  296. package/.output/server/__23tanstack-start-server-fn-resolver-DBRojHaG.mjs +0 -150
  297. package/.output/server/_ssr/InsightsPageHeader-mtxVuZKK.mjs +0 -64
  298. package/.output/server/_ssr/api-BfCgYlj5.mjs +0 -332
  299. package/.output/server/_ssr/api-keys-api-C3oeq01R.mjs +0 -60
  300. package/.output/server/_ssr/api-keys-rpc-CdPFdGOx.mjs +0 -136
  301. package/.output/server/_ssr/api-rpc-B2E1ZIG5.mjs +0 -1399
  302. package/.output/server/_ssr/auth.server-CmicHh0J.mjs +0 -47
  303. package/.output/server/_ssr/bundle-signing-rpc-Di7-I6fE.mjs +0 -16
  304. package/.output/server/_ssr/bundle-signing.server-Uqt1OF49.mjs +0 -12
  305. package/.output/server/_ssr/bundleActivity-pBrPSG01.mjs +0 -28
  306. package/.output/server/_ssr/card-Bemj0dpY.mjs +0 -108
  307. package/.output/server/_ssr/config-Dv8qLDZ0.mjs +0 -13
  308. package/.output/server/_ssr/config.server-B8WsT3s0.mjs +0 -368
  309. package/.output/server/_ssr/databaseRows-5bCRTxm-.mjs +0 -126
  310. package/.output/server/_ssr/deleteBundle-8X-jq7Z7.mjs +0 -66
  311. package/.output/server/_ssr/field-BrQ2u0fT.mjs +0 -74
  312. package/.output/server/_ssr/getBundleChildren-DoL8l_AS.mjs +0 -50
  313. package/.output/server/_ssr/insights-BwQ-wF2a.mjs +0 -10
  314. package/.output/server/_ssr/insights-gr1I_dJU.mjs +0 -938
  315. package/.output/server/_ssr/insights-recovery-Cv6RSyeR.mjs +0 -21
  316. package/.output/server/_ssr/insights-recovery-rpc-DOzxfYfv.mjs +0 -37
  317. package/.output/server/_ssr/insights-search-1czJMZgk.mjs +0 -21
  318. package/.output/server/_ssr/insights-usage-rpc-4ImOq_0p.mjs +0 -184
  319. package/.output/server/_ssr/insights-usage-rpc-BUruODjE.mjs +0 -24
  320. package/.output/server/_ssr/insightsContract-B09K_Px1.mjs +0 -303
  321. package/.output/server/_ssr/insightsHistory-Ci4rYAgl.mjs +0 -41
  322. package/.output/server/_ssr/insightsRecovery-CaghWL2t.mjs +0 -113
  323. package/.output/server/_ssr/insightsUsage-DNIj6jxF.mjs +0 -71
  324. package/.output/server/_ssr/insights_.distribution-VmHuikRE.mjs +0 -10
  325. package/.output/server/_ssr/installations-HXMF_kKf.mjs +0 -29
  326. package/.output/server/_ssr/routes-Dru3IRj0.mjs +0 -37
  327. package/.output/server/_ssr/runtime.server-CsiKFp_Z.mjs +0 -436
  328. package/.output/server/_ssr/server-DJm72Kj-.mjs +0 -7468
  329. package/.output/server/_ssr/signing-H8Lg4lPe.mjs +0 -133
  330. package/.output/server/_ssr/start-DpViQGnt.mjs +0 -4
  331. package/.output/server/_tanstack-start-manifest_v-DsFcTLw_.mjs +0 -125
  332. package/src/components/features/bundles/BundleActivityChart.tsx +0 -97
  333. package/src/components/features/bundles/BundleInsightsSummary.tsx +0 -126
  334. package/src/components/features/signing/BundleSigningPage.tsx +0 -139
  335. package/src/lib/bundle-signing-api.ts +0 -14
  336. package/src/lib/bundle-signing-rpc.ts +0 -16
  337. package/src/lib/server/bundle-signing.server.ts +0 -23
  338. package/src/lib/server/deleteBundle.ts +0 -145
  339. package/src/lib/server/insightsHistory.ts +0 -52
  340. package/src/lib/server/insightsRecovery.ts +0 -187
  341. package/src/routes/signing.tsx +0 -37
@@ -0,0 +1,196 @@
1
+ import type {
2
+ RemoteConfigActive,
3
+ RemoteConfigEvaluatedParameter,
4
+ RemoteConfigEvaluationContext,
5
+ RemoteConfigTemplateIssue,
6
+ RemoteConfigVersionDetail,
7
+ RemoteConfigVersionsPage,
8
+ } from "@hot-updater/server/plugins";
9
+ import { createServerFn } from "@tanstack/react-start";
10
+
11
+ import { consoleAccess } from "./console-access";
12
+ import type { ConsolePublishResult } from "./server/remoteConfig";
13
+
14
+ export type { ConsolePublishResult } from "./server/remoteConfig";
15
+
16
+ /** What the preview shows: each parameter's value for the device, or what to fix first. */
17
+ export type RemoteConfigPreview =
18
+ | {
19
+ readonly status: "ok";
20
+ readonly parameters: Readonly<
21
+ Record<string, RemoteConfigEvaluatedParameter>
22
+ >;
23
+ }
24
+ | {
25
+ readonly status: "invalid";
26
+ readonly issues: readonly RemoteConfigTemplateIssue[];
27
+ };
28
+
29
+ /** Remote Config, once the console's access check and the feature guard pass. */
30
+ const remoteConfig = async () => {
31
+ const [{ prepareConfig }, { requireFeature }] = await Promise.all([
32
+ import("./server/config.server"),
33
+ import("./server/runtime.server"),
34
+ ]);
35
+ const { runtime } = await prepareConfig();
36
+ return requireFeature(runtime, "remoteConfig");
37
+ };
38
+
39
+ const field = (input: unknown, key: string): unknown =>
40
+ typeof input === "object" && input !== null
41
+ ? Reflect.get(input, key)
42
+ : undefined;
43
+
44
+ const versionNumber = (value: unknown, name: string): number => {
45
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
46
+ throw new TypeError(`${name} must be a non-negative integer.`);
47
+ }
48
+ return value;
49
+ };
50
+
51
+ const parseCursor = (input: unknown): { readonly cursor?: string } => {
52
+ const cursor = field(input, "cursor");
53
+ if (cursor !== undefined && typeof cursor !== "string") {
54
+ throw new TypeError("cursor must be text.");
55
+ }
56
+ return cursor === undefined ? {} : { cursor };
57
+ };
58
+
59
+ const parseVersion = (input: unknown): { readonly version: number } => ({
60
+ version: versionNumber(field(input, "version"), "version"),
61
+ });
62
+
63
+ const parsePublish = (
64
+ input: unknown,
65
+ ): {
66
+ readonly template: unknown;
67
+ readonly baseVersion: number;
68
+ readonly description?: string;
69
+ } => {
70
+ const description = field(input, "description");
71
+ if (description !== undefined && typeof description !== "string") {
72
+ throw new TypeError("description must be text.");
73
+ }
74
+ return {
75
+ template: field(input, "template"),
76
+ baseVersion: versionNumber(field(input, "baseVersion"), "baseVersion"),
77
+ ...(description === undefined ? {} : { description }),
78
+ };
79
+ };
80
+
81
+ const parseRollback = (
82
+ input: unknown,
83
+ ): { readonly version: number; readonly baseVersion: number } => ({
84
+ version: versionNumber(field(input, "version"), "version"),
85
+ baseVersion: versionNumber(field(input, "baseVersion"), "baseVersion"),
86
+ });
87
+
88
+ const CONTEXT_FIELDS = [
89
+ "platform",
90
+ "appVersion",
91
+ "channel",
92
+ "cohort",
93
+ "fingerprintHash",
94
+ ] as const;
95
+
96
+ const parsePreview = (
97
+ input: unknown,
98
+ ): {
99
+ readonly template: unknown;
100
+ readonly context: RemoteConfigEvaluationContext;
101
+ } => {
102
+ const raw = field(input, "context");
103
+ const context: Record<string, string> = {};
104
+ for (const key of CONTEXT_FIELDS) {
105
+ const value = field(raw, key);
106
+ if (typeof value === "string" && value.trim().length > 0) {
107
+ context[key] = value.trim();
108
+ }
109
+ }
110
+ if (
111
+ context.platform !== undefined &&
112
+ context.platform !== "ios" &&
113
+ context.platform !== "android"
114
+ ) {
115
+ throw new TypeError('platform must be "ios" or "android".');
116
+ }
117
+ const now = field(raw, "now");
118
+ if (now !== undefined && (typeof now !== "number" || !Number.isFinite(now))) {
119
+ throw new TypeError("now must be a time in milliseconds since the epoch.");
120
+ }
121
+ return {
122
+ template: field(input, "template"),
123
+ // Without a time, the preview evaluates at the server's clock, as devices do.
124
+ context: {
125
+ ...(context as RemoteConfigEvaluationContext),
126
+ now: now ?? Date.now(),
127
+ },
128
+ };
129
+ };
130
+
131
+ export const getRemoteConfigRpc = createServerFn({ method: "GET" })
132
+ .middleware([consoleAccess])
133
+ .handler(
134
+ async (): Promise<RemoteConfigActive> => (await remoteConfig()).getActive(),
135
+ );
136
+
137
+ export const listRemoteConfigVersionsRpc = createServerFn({ method: "GET" })
138
+ .middleware([consoleAccess])
139
+ .validator(parseCursor)
140
+ .handler(
141
+ async ({ data }): Promise<RemoteConfigVersionsPage> =>
142
+ (await remoteConfig()).listVersions({ limit: 20, ...data }),
143
+ );
144
+
145
+ export const getRemoteConfigVersionRpc = createServerFn({ method: "GET" })
146
+ .middleware([consoleAccess])
147
+ .validator(parseVersion)
148
+ .handler(
149
+ async ({ data }): Promise<RemoteConfigVersionDetail | null> =>
150
+ (await remoteConfig()).getVersion(data.version),
151
+ );
152
+
153
+ export const publishRemoteConfigRpc = createServerFn({ method: "POST" })
154
+ .middleware([consoleAccess])
155
+ .validator(parsePublish)
156
+ .handler(
157
+ async ({ data }): Promise<ConsolePublishResult> =>
158
+ (await remoteConfig()).publish(data),
159
+ );
160
+
161
+ export const rollbackRemoteConfigRpc = createServerFn({ method: "POST" })
162
+ .middleware([consoleAccess])
163
+ .validator(parseRollback)
164
+ .handler(
165
+ async ({
166
+ data,
167
+ }): Promise<ConsolePublishResult | { readonly status: "not_found" }> =>
168
+ (await remoteConfig()).rollback(data),
169
+ );
170
+
171
+ /** What a device with `context` would receive from a template, published or not. */
172
+ export const previewRemoteConfigRpc = createServerFn({ method: "POST" })
173
+ .middleware([consoleAccess])
174
+ .validator(parsePreview)
175
+ .handler(async ({ data }): Promise<RemoteConfigPreview> => {
176
+ await remoteConfig();
177
+ const {
178
+ evaluateRemoteConfig,
179
+ RemoteConfigValidationError,
180
+ validateRemoteConfigTemplate,
181
+ } = await import("@hot-updater/server/plugins");
182
+ try {
183
+ return {
184
+ status: "ok",
185
+ parameters: evaluateRemoteConfig(
186
+ validateRemoteConfigTemplate(data.template),
187
+ data.context,
188
+ ),
189
+ };
190
+ } catch (error) {
191
+ if (error instanceof RemoteConfigValidationError) {
192
+ return { status: "invalid", issues: error.issues };
193
+ }
194
+ throw error;
195
+ }
196
+ });
@@ -0,0 +1,21 @@
1
+ import type { AnyRouter, ParsedLocation } from "@tanstack/react-router";
2
+
3
+ declare module "@tanstack/react-router" {
4
+ interface StaticDataRouteOption {
5
+ /**
6
+ * The route's scroll-restoration key for a location; without one, each
7
+ * history entry keeps its own scroll position.
8
+ */
9
+ readonly scrollRestorationKey?: (location: ParsedLocation) => string;
10
+ }
11
+ }
12
+
13
+ /** A location's scroll-restoration key: its route's own, else its history entry's. */
14
+ export const routeScrollRestorationKey = (
15
+ router: Pick<AnyRouter, "getMatchedRoutes">,
16
+ location: ParsedLocation,
17
+ ): string => {
18
+ const [, , foundRoute] = router.getMatchedRoutes(location.pathname);
19
+ const routeKey = foundRoute?.options.staticData?.scrollRestorationKey;
20
+ return routeKey?.(location) ?? location.state.__TSR_key!;
21
+ };
@@ -13,11 +13,19 @@ const accessError = (
13
13
  { status: access.status === "unauthenticated" ? 401 : 403 },
14
14
  );
15
15
 
16
- export const getConsoleAccess = async (
17
- request: Request,
18
- ): Promise<ConsoleAccess> => {
19
- const adapter = await getConsoleAuthAdapter();
20
- return adapter.getAccess(request);
16
+ // Server function middleware checks access before input, and prepareConfig
17
+ // checks again for its callers: one request asks the adapter once.
18
+ const accessByRequest = new WeakMap<Request, Promise<ConsoleAccess>>();
19
+
20
+ export const getConsoleAccess = (request: Request): Promise<ConsoleAccess> => {
21
+ let access = accessByRequest.get(request);
22
+ if (!access) {
23
+ access = getConsoleAuthAdapter().then((adapter) =>
24
+ adapter.getAccess(request),
25
+ );
26
+ accessByRequest.set(request, access);
27
+ }
28
+ return access;
21
29
  };
22
30
 
23
31
  export const getConsoleAuthProviders = async (
@@ -1,37 +1,36 @@
1
- import type { InsightsModel } from "@hot-updater/plugin-core";
1
+ import type {
2
+ InsightsModel,
3
+ ReleaseReference,
4
+ } from "@hot-updater/server/plugins";
2
5
 
3
6
  import type { BundleActivityInput } from "../bundle-activity";
4
- import type { RecoveryReport } from "../insights-recovery";
5
- import { readInsightsHistory } from "./insightsHistory";
6
- import { buildRecoveryReport } from "./insightsRecovery";
7
7
 
8
8
  export async function getBundleActivity(
9
9
  model: InsightsModel,
10
10
  inputs: readonly BundleActivityInput[],
11
- now = Date.now(),
12
11
  ) {
13
12
  if (inputs.length === 0) return {};
14
- // One bounded read for the visible page, independent of the management query.
15
- const history = await readInsightsHistory(model, "30d", now);
16
- const scopes = new Map<string, RecoveryReport>();
17
- const result = new Map<string, RecoveryReport>();
18
- for (const input of inputs) {
19
- const key = JSON.stringify([input.platform, input.channel]);
20
- let report = scopes.get(key);
21
- if (!report) {
22
- report = buildRecoveryReport(history, {
23
- platform: input.platform,
24
- channel: input.channel,
25
- window: "30d",
26
- });
27
- scopes.set(key, report);
28
- }
29
- result.set(input.releaseId, {
30
- ...report,
31
- series: report.series.filter(
32
- (series) => series.releaseId === input.releaseId,
33
- ),
34
- });
35
- }
36
- return Object.fromEntries(result);
13
+ const releases: ReleaseReference[] = inputs.map((input) => ({
14
+ releaseId: input.releaseId,
15
+ platform: input.platform,
16
+ channel: input.channel,
17
+ }));
18
+ const activity = await model.getReleaseActivity({ releases });
19
+ return Object.fromEntries(
20
+ activity.data.flatMap((item) =>
21
+ item.release === undefined
22
+ ? []
23
+ : [
24
+ [
25
+ item.release.releaseId,
26
+ {
27
+ downloads: item.metrics.downloads,
28
+ launched: item.metrics.applies,
29
+ failedLaunches: item.metrics.failedLaunches,
30
+ measuredAtMs: activity.measuredAtMs,
31
+ },
32
+ ],
33
+ ],
34
+ ),
35
+ );
37
36
  }
@@ -1,98 +1,45 @@
1
- import {
2
- assertStorageOperations,
3
- createDatabaseClient,
4
- type DatabaseClient,
5
- type StoragePluginWith,
6
- } from "@hot-updater/plugin-core";
1
+ import type { HotUpdaterCoreApi } from "@hot-updater/plugin-core";
7
2
  import { getRequest } from "@tanstack/react-start/server";
8
3
 
9
- import type { ResolvedHotUpdaterConsoleConfig } from "../../index";
10
4
  import { requireConsoleAccess } from "./auth.server";
11
- import { resolveConsoleConfig } from "./console-runtime.server";
12
-
13
- type ResolvedConsoleConfig = ResolvedHotUpdaterConsoleConfig & {
14
- readonly console: NonNullable<ResolvedHotUpdaterConsoleConfig["console"]>;
15
- };
5
+ import {
6
+ resolveConsoleConfig,
7
+ type ResolvedConsoleConfig,
8
+ } from "./console-runtime.server";
9
+ import type { ConsoleRuntime } from "./runtime.server";
16
10
 
17
11
  let configPromise: Promise<ResolvedConsoleConfig> | null = null;
18
- let databaseClient: DatabaseClient | null = null;
19
- let hotUpdater: ReturnType<
20
- typeof import("./runtime.server").createRuntimeHotUpdater
21
- > | null = null;
22
- let apiKeyStore: ReturnType<
23
- typeof import("./runtime.server").createApiKeyStore
24
- > | null = null;
25
- let apiKeyStoreResolved = false;
26
- let storagePluginPromise: Promise<
27
- StoragePluginWith<"get" | "put" | "exists" | "delete">
28
- > | null = null;
12
+ let core: HotUpdaterCoreApi | null = null;
13
+ let runtime: ConsoleRuntime | null = null;
29
14
 
30
15
  const loadCachedConfig = async (request: Request) => {
31
16
  if (!configPromise) {
32
- configPromise = resolveConsoleConfig(request)
33
- .then((config) => ({
34
- ...config,
35
- console: config.console ?? {},
36
- }))
37
- .catch((error) => {
38
- configPromise = null;
39
- throw error;
40
- });
17
+ configPromise = resolveConsoleConfig(request).catch((error) => {
18
+ configPromise = null;
19
+ throw error;
20
+ });
41
21
  }
42
22
 
43
23
  return configPromise;
44
24
  };
45
25
 
46
- const loadCachedStoragePlugin = async (config: ResolvedConsoleConfig) => {
47
- if (!storagePluginPromise) {
48
- storagePluginPromise = Promise.resolve(config.storage)
49
- .then((storagePlugin) => {
50
- assertStorageOperations(storagePlugin, [
51
- "get",
52
- "put",
53
- "exists",
54
- "delete",
55
- ]);
56
- return storagePlugin;
57
- })
58
- .catch((error) => {
59
- storagePluginPromise = null;
60
- throw error;
61
- });
62
- }
63
-
64
- return storagePluginPromise;
65
- };
66
-
67
26
  export const prepareConfig = async (request: Request = getRequest()) => {
68
27
  try {
69
28
  await requireConsoleAccess(request);
70
29
  const config = await loadCachedConfig(request);
71
30
 
72
- if (!databaseClient) {
73
- databaseClient = createDatabaseClient(config.database);
74
- }
75
-
76
- if (!hotUpdater) {
77
- const { createRuntimeHotUpdater } = await import("./runtime.server");
78
- hotUpdater = createRuntimeHotUpdater(config);
79
- }
31
+ // Bundles, releases, catalogs, and channels: core assembled as the
32
+ // server's, on the server's own path, or a self-hosted server's admin API.
33
+ core ??= config.core;
80
34
 
81
- if (!apiKeyStoreResolved) {
82
- const { createApiKeyStore } = await import("./runtime.server");
83
- apiKeyStore = createApiKeyStore(config);
84
- apiKeyStoreResolved = true;
35
+ // The console's features: those of the plugins its config lists.
36
+ if (!runtime) {
37
+ const { createConsoleRuntime } = await import("./runtime.server");
38
+ runtime = createConsoleRuntime(config);
85
39
  }
86
40
 
87
- const storagePlugin = await loadCachedStoragePlugin(config);
88
-
89
- return {
90
- config,
91
- databaseClient,
92
- hotUpdater,
93
- apiKeyStore,
94
- storagePlugin,
95
- };
41
+ // Each bundle file is read and deleted with its protocol's storage.
42
+ return { config, core, runtime, storage: config.storage };
96
43
  } catch (error) {
97
44
  if (
98
45
  !(
@@ -105,5 +52,3 @@ export const prepareConfig = async (request: Request = getRequest()) => {
105
52
  throw error;
106
53
  }
107
54
  };
108
-
109
- export const isConfigLoaded = () => Boolean(configPromise);
@@ -0,0 +1,70 @@
1
+ import type { RemoteDatabase } from "@hot-updater/plugin-core";
2
+ import {
3
+ type ApiKeyManagementAPI,
4
+ createInsightsAdminReads,
5
+ createInsightsModel,
6
+ createInsightsReads,
7
+ type InsightsApi,
8
+ type InsightsModel,
9
+ type RemoteConfigApi,
10
+ } from "@hot-updater/server/plugins";
11
+
12
+ import {
13
+ type ConsoleFeature,
14
+ ConsoleFeatureUnavailableError,
15
+ } from "../console-features";
16
+ import {
17
+ type ConsoleRemoteConfig,
18
+ createAdminRemoteConfig,
19
+ createLocalRemoteConfig,
20
+ } from "./remoteConfig";
21
+
22
+ /** A GET on a self-hosted server's admin handler. */
23
+ export type FetchAdmin = RemoteDatabase["fetchAdmin"];
24
+
25
+ /**
26
+ * What serves one console feature: `local` builds it from the API of the
27
+ * feature's plugin, which the console assembles over the database as the
28
+ * server does; `remote` builds it over a self-hosted server's admin API, for
29
+ * a feature that API serves.
30
+ */
31
+ export interface ConsoleFeatureApi<TPluginApi, TApi> {
32
+ readonly local: (pluginApi: TPluginApi) => TApi;
33
+ readonly remote?: (fetchAdmin: FetchAdmin) => TApi;
34
+ }
35
+
36
+ const featureApi = <TPluginApi, TApi>(
37
+ api: ConsoleFeatureApi<TPluginApi, TApi>,
38
+ ): ConsoleFeatureApi<TPluginApi, TApi> => api;
39
+
40
+ /** What serves each console feature, keyed like `consoleFeatures`. */
41
+ export const consoleFeatureApis = {
42
+ insights: featureApi({
43
+ local: createInsightsReads,
44
+ remote: (fetchAdmin: FetchAdmin) =>
45
+ createInsightsAdminReads(fetchAdmin, {
46
+ unavailable: () =>
47
+ new ConsoleFeatureUnavailableError("insights", { remote: true }),
48
+ }),
49
+ }),
50
+ insightsAnalytics: featureApi({
51
+ local: (api: InsightsApi): InsightsModel => createInsightsModel(api),
52
+ }),
53
+ remoteConfig: featureApi({
54
+ local: (api: RemoteConfigApi): ConsoleRemoteConfig =>
55
+ createLocalRemoteConfig(api),
56
+ remote: createAdminRemoteConfig,
57
+ }),
58
+ apiKeys: featureApi({
59
+ local: (api: ApiKeyManagementAPI) => api,
60
+ }),
61
+ } satisfies {
62
+ readonly [F in ConsoleFeature]: ConsoleFeatureApi<never, unknown>;
63
+ };
64
+
65
+ /** What serves each console feature, as its server functions use it. */
66
+ export type ConsoleFeatureApis = {
67
+ readonly [F in ConsoleFeature]: ReturnType<
68
+ (typeof consoleFeatureApis)[F]["local"]
69
+ >;
70
+ };
@@ -1,42 +1,61 @@
1
+ import {
2
+ type AnyHotUpdaterPlugin,
3
+ type ConfiguredDatabase,
4
+ createMemoryAdapter,
5
+ type EngineDatabase,
6
+ type HotUpdaterCoreApi,
7
+ isRemoteDatabase,
8
+ type StorageAdapter,
9
+ } from "@hot-updater/plugin-core";
10
+ import { createHotUpdater } from "@hot-updater/server";
11
+
1
12
  import type {
2
13
  ConsoleAuthAdapter,
3
- ConsoleSigningConfig,
4
14
  HotUpdaterConsoleConfigSource,
5
- ResolvedHotUpdaterConsoleConfig,
6
15
  } from "../../index";
7
16
 
8
- const readString = (value: unknown, key: string): string | undefined => {
9
- if (typeof value !== "object" || value === null) return undefined;
10
- const member = Reflect.get(value, key);
11
- return typeof member === "string" && member.trim().length > 0
12
- ? member.trim()
13
- : undefined;
14
- };
17
+ /** What the console runs on, assembled from its config as the server is. */
18
+ export interface ResolvedConsoleConfig {
19
+ readonly gitUrl?: string;
20
+ /** The server's database, or its admin API. */
21
+ readonly database: ConfiguredDatabase;
22
+ /**
23
+ * Core's API, assembled over the database as the server assembles it, so
24
+ * the console's writes take the server's path, or a self-hosted server's
25
+ * admin API.
26
+ */
27
+ readonly core: HotUpdaterCoreApi;
28
+ /** The server's storage, which reads the bundle files. */
29
+ readonly storage: StorageAdapter;
30
+ /** The plugins the server runs, as the config lists them. */
31
+ readonly plugins: readonly AnyHotUpdaterPlugin[];
32
+ /**
33
+ * The plugins' APIs by id, which serve their features; absent over a
34
+ * self-hosted server's admin API, whose plugins run on the server.
35
+ */
36
+ readonly api?: Readonly<Record<string, unknown>>;
37
+ }
15
38
 
16
- const getProviderName = (signing: unknown): string => {
17
- return (
18
- readString(signing, "name")?.slice(0, 80) ??
19
- (readString(signing, "privateKeyPath") ? "localSigning" : undefined) ??
20
- "Configured provider"
21
- );
22
- };
23
-
24
- export const sanitizeConsoleSigningConfig = (
25
- signing: unknown,
26
- ): ConsoleSigningConfig | undefined => {
27
- if (typeof signing !== "object" || signing === null) return undefined;
28
- const privateKeyPath = readString(signing, "privateKeyPath");
29
- if (
30
- Reflect.get(signing, "enabled") === false ||
31
- (privateKeyPath && Reflect.get(signing, "enabled") !== true)
32
- )
33
- return undefined;
34
-
35
- return {
36
- enabled: true,
37
- provider: getProviderName(signing),
38
- };
39
- };
39
+ /**
40
+ * Core and the plugins over `database`, assembled by `createHotUpdater` as
41
+ * the server assembles them, with public client routes unless a plugin
42
+ * provides clientAuth: the console mounts no handlers. cli-tools'
43
+ * `assembleServer` does the same for the CLI; the console keeps its own
44
+ * copy, since a hosted console never loads cli-tools.
45
+ */
46
+ const assemble = (
47
+ database: EngineDatabase,
48
+ storage: StorageAdapter,
49
+ plugins: readonly AnyHotUpdaterPlugin[],
50
+ ) =>
51
+ createHotUpdater({
52
+ database,
53
+ storage,
54
+ plugins,
55
+ ...(plugins.some((plugin) => plugin.provides?.clientAuth)
56
+ ? {}
57
+ : { clientAccess: "public" }),
58
+ } as Parameters<typeof createHotUpdater>[0]);
40
59
 
41
60
  export const getConsoleAuthAdapter = async (): Promise<ConsoleAuthAdapter> => {
42
61
  const module = await import("virtual:hot-updater-console/auth");
@@ -45,17 +64,42 @@ export const getConsoleAuthAdapter = async (): Promise<ConsoleAuthAdapter> => {
45
64
 
46
65
  export const resolveConsoleConfig = async (
47
66
  request: Request,
48
- ): Promise<ResolvedHotUpdaterConsoleConfig> => {
67
+ ): Promise<ResolvedConsoleConfig> => {
49
68
  const { default: source } =
50
69
  (await import("virtual:hot-updater-console/config")) as {
51
70
  readonly default: HotUpdaterConsoleConfigSource;
52
71
  };
53
72
 
54
73
  const config = typeof source === "function" ? await source(request) : source;
55
- const { signing: signingSource, ...configWithoutSigning } = config;
56
- const signing = sanitizeConsoleSigningConfig(signingSource);
74
+ const { database, storage, plugins = [] } = config;
75
+ const gitUrl =
76
+ config.console?.gitUrl === undefined
77
+ ? {}
78
+ : { gitUrl: config.console.gitUrl };
79
+ // A self-hosted server runs the plugins itself: core is its admin API.
80
+ // The plugins are still checked as the server checks them, on a database
81
+ // nothing reads, so one the server would refuse is refused here too.
82
+ if (isRemoteDatabase(database)) {
83
+ const checked = assemble(
84
+ { name: "memory", adapter: createMemoryAdapter() },
85
+ storage,
86
+ plugins,
87
+ );
88
+ return {
89
+ ...gitUrl,
90
+ database,
91
+ core: database.core,
92
+ storage,
93
+ plugins: checked.plugins,
94
+ };
95
+ }
96
+ const server = assemble(database, storage, plugins);
57
97
  return {
58
- ...configWithoutSigning,
59
- ...(signing === undefined ? {} : { signing }),
98
+ ...gitUrl,
99
+ database,
100
+ core: server.core,
101
+ storage,
102
+ plugins: server.plugins,
103
+ api: server.api,
60
104
  };
61
105
  };