@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.
- package/.output/nitro.json +1 -1
- package/.output/public/assets/-native-builds-search-C-TGpFAm.js +1 -0
- package/.output/public/assets/{ConsoleFeatureUnavailable-C0KfsKWL.js → ConsoleFeatureUnavailable-CUtcXuY2.js} +1 -1
- package/.output/public/assets/EventDetails-Dl2RvPAk.js +1 -0
- package/.output/public/assets/HashValueDisplay-6lNRxDJX.js +1 -0
- package/.output/public/assets/InsightsPageHeader-DHhqY3DT.js +1 -0
- package/.output/public/assets/NotFoundPage-DZ3DxyRA.js +1 -0
- package/.output/public/assets/PlatformIcon-BBWQlWm9.js +1 -0
- package/.output/public/assets/RolloutPercentageBadge-Cxogpi8F.js +1 -0
- package/.output/public/assets/StoreSubmission-JpngSvHS.js +1 -0
- package/.output/public/assets/alert-dialog-BqYScr9y.js +1 -0
- package/.output/public/assets/api-P2g8dCNg.js +1 -0
- package/.output/public/assets/api-keys-DWQC2q6y.js +1 -0
- package/.output/public/assets/api-keys-FueRkF9q.js +1 -0
- package/.output/public/assets/arrow-left-g9cKqXuC.js +1 -0
- package/.output/public/assets/arrow-right-CNII8TYn.js +1 -0
- package/.output/public/assets/arrow-up-right-DK8Zdg40.js +1 -0
- package/.output/public/assets/badge-DWFcDWho.js +1 -0
- package/.output/public/assets/check-CXVR3s3X.js +1 -0
- package/.output/public/assets/chevron-right-BoUXo82O.js +1 -0
- package/.output/public/assets/circle-off-C7Muo0ss.js +1 -0
- package/.output/public/assets/{clipboard-DTkrlIMn.js → clipboard-DQbOcrL7.js} +1 -1
- package/.output/public/assets/console-features-api-DEXSwFtc.js +1 -0
- package/.output/public/assets/copy-DsuLj3oj.js +1 -0
- package/.output/public/assets/dialog-CNUPxNxH.js +1 -0
- package/.output/public/assets/{dist-dDj-R6uh.js → dist-CPlO6ko9.js} +1 -1
- package/.output/public/assets/download-Cf24LBde.js +1 -0
- package/.output/public/assets/{empty-Bf4DanOL.js → empty-B3Uwdd6k.js} +1 -1
- package/.output/public/assets/{field-GxF9SyUo.js → field-D-2-mnNh.js} +1 -1
- package/.output/public/assets/history-DqHlgSuV.js +1 -0
- package/.output/public/assets/index-BWU6owTG.js +10 -0
- package/.output/public/assets/{input-group-B4nrqIh0.js → input-group-D3OUBk4n.js} +1 -1
- package/.output/public/assets/insights-B-0odqpt.js +42 -0
- package/.output/public/assets/insights-BSVGfxSL.js +1 -0
- package/.output/public/assets/insights-NK5B6ReS.js +2 -0
- package/.output/public/assets/insights-api-BVCXJsoV.js +1 -0
- package/.output/public/assets/insights-search-DghCbEnk.js +1 -0
- package/.output/public/assets/insights-usage-rpc-CKQH0pUS.js +1 -0
- package/.output/public/assets/insights_.distribution-CJvgwcVZ.js +2 -0
- package/.output/public/assets/insights_.distribution-DblZM0mO.js +1 -0
- package/.output/public/assets/insights_.distribution-dvk6vYDM.js +1 -0
- package/.output/public/assets/installations-B674P5Z5.js +2 -0
- package/.output/public/assets/installations-BWxczZ2t.js +1 -0
- package/.output/public/assets/installations-Dv4kWQYz.js +1 -0
- package/.output/public/assets/{key-round-B4fI1psK.js → key-round-DwNKkHhy.js} +1 -1
- package/.output/public/assets/link-DTEyW70e.js +2 -0
- package/.output/public/assets/{list-Dtj7TtzV.js → list--NIee1FZ.js} +1 -1
- package/.output/public/assets/native-builds-BOC71grI.js +1 -0
- package/.output/public/assets/native-builds-BswZazmc.js +1 -0
- package/.output/public/assets/native-builds-c4BpOLAe.js +2 -0
- package/.output/public/assets/native-builds_._buildId-D_Spjl10.js +1 -0
- package/.output/public/assets/native-builds_._buildId-DtoXD8rz.js +2 -0
- package/.output/public/assets/plus-BReVKct-.js +1 -0
- package/.output/public/assets/preload-helper-B44EvKhG.js +1 -0
- package/.output/public/assets/{refresh-cw-DjIKjmrS.js → refresh-cw-B9HHjLej.js} +1 -1
- package/.output/public/assets/relativeTime-YQt84dYr.js +1 -0
- package/.output/public/assets/remote-config-B7ujqhLa.js +6 -0
- package/.output/public/assets/remote-config-WjpozBG5.js +1 -0
- package/.output/public/assets/remote-config-gXeH6PBW.js +1 -0
- package/.output/public/assets/rotate-cw-Cv2aVbYr.js +1 -0
- package/.output/public/assets/routes-BDdBIxYS.js +1 -0
- package/.output/public/assets/routes-CCD33Azv.js +2 -0
- package/.output/public/assets/search-BCel4Oy6.js +1 -0
- package/.output/public/assets/select-CBIU0_M1.js +1 -0
- package/.output/public/assets/{sidebar-BqX460IW.js → sidebar-Di9SN47G.js} +1 -1
- package/.output/public/assets/smartphone-CMaSy_ir.js +1 -0
- package/.output/public/assets/styles-Ccxthuz1.css +2 -0
- package/.output/public/assets/switch-BpdiHL_t.js +1 -0
- package/.output/public/assets/{table-ek1DmRHq.js → table-Bpy3SvSo.js} +1 -1
- package/.output/public/assets/{tabs-CgEH_fMT.js → tabs-Xmt7tZeG.js} +1 -1
- package/.output/public/assets/{undo-2-DxXx9Zvr.js → undo-2-CUX96_BG.js} +1 -1
- package/.output/public/assets/useCompositeListItem-JmyDfo3-.js +1 -0
- package/.output/public/assets/useInfiniteQuery-DX4WpBRG.js +1 -0
- package/.output/public/assets/useMutation-DbYw7boA.js +1 -0
- package/.output/public/assets/uuidv7-zXMtwR9Q.js +1 -0
- package/.output/public/assets/{valueToPercent-B44vDssN.js → valueToPercent-BLwGy_xK.js} +1 -1
- package/.output/server/{__23tanstack-start-server-fn-resolver-KpQ5Z_24.mjs → __23tanstack-start-server-fn-resolver-CygMPYHZ.mjs} +64 -40
- package/.output/server/_chunks/ssr-renderer.mjs +2 -2
- package/.output/server/_libs/@tanstack/react-router+[...].mjs +1 -1
- package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +1 -1
- package/.output/server/_libs/lucide-react.mjs +88 -1
- package/.output/server/_libs/uqr.mjs +858 -0
- package/.output/server/_ssr/-native-builds-search-CwNcoXf4.mjs +37 -0
- package/.output/server/_ssr/{BundleIdDisplay-CjSnY65J.mjs → BundleIdDisplay-DOPz4pUS.mjs} +2 -2
- package/.output/server/_ssr/{ConsoleFeatureUnavailable-CHxn9f-w.mjs → ConsoleFeatureUnavailable-Xb1JKqcc.mjs} +7 -7
- package/.output/server/_ssr/{EventDetails-l6dF54zp.mjs → EventDetails-D7MejFCN.mjs} +8 -8
- package/.output/server/_ssr/{HashValueDisplay-Zs7ABoOi.mjs → HashValueDisplay-Dd-wu5rY.mjs} +2 -2
- package/.output/server/_ssr/{InsightsPageHeader-ZlAlDxQy.mjs → InsightsPageHeader-CWf1n4Vt.mjs} +13 -11
- package/.output/server/_ssr/{NotFoundPage-u28ndvp1.mjs → NotFoundPage-C5rFdUpg.mjs} +2 -2
- package/.output/server/_ssr/PlatformIcon-CFLiTjh1.mjs +29 -0
- package/.output/server/_ssr/RolloutPercentageBadge-g0Bhfl3M.mjs +21 -0
- package/.output/server/_ssr/StoreSubmission-JZ2Zltak.mjs +139 -0
- package/.output/server/_ssr/{alert-dialog-B8AqlJwo.mjs → alert-dialog-CvNvTvIJ.mjs} +1 -1
- package/.output/server/_ssr/{api-C_0B7NXM.mjs → api-0vtwA5vY.mjs} +126 -7
- package/.output/server/_ssr/{api-keys-pRNvn2-9.mjs → api-keys-D5dyA4D1.mjs} +10 -10
- package/.output/server/_ssr/{api-keys-rpc-wecWs0nA.mjs → api-keys-rpc-BOIUHhDi.mjs} +5 -5
- package/.output/server/_ssr/{api-keys-CLH38u9B.mjs → api-keys-yFlQDwQF.mjs} +2 -2
- package/.output/server/_ssr/{api-rpc-DWN8eOHS.mjs → api-rpc-COeFOpYi.mjs} +79 -12
- package/.output/server/_ssr/{auth-UP0wXs4i.mjs → auth-Z-wLddzh.mjs} +1 -1
- package/.output/server/_ssr/{auth-rpc-CLRArbIl.mjs → auth-rpc-D1ss2GUI.mjs} +6 -6
- package/.output/server/_ssr/{auth.server-DR9zc0EU.mjs → auth.server-DYS1OJ6P.mjs} +771 -641
- package/.output/server/_ssr/{badge-BpT7r-ng.mjs → badge-CEdVRgyl.mjs} +1 -1
- package/.output/server/_ssr/{bundleActivity-0qFIcvqt.mjs → bundleActivity-MLrjNJCT.mjs} +1 -1
- package/.output/server/_ssr/{config-WVX4IxSR.mjs → config-CjIBFhJX.mjs} +1 -1
- package/.output/server/_ssr/config.server-NnMFjMFX.mjs +43 -0
- package/.output/server/_ssr/{console-access-aUpQs49I.mjs → console-access-33BNlH97.mjs} +3 -3
- package/.output/server/_ssr/{console-features-4xAGfZHw.mjs → console-features-BTV-XuhQ.mjs} +1 -1
- package/.output/server/_ssr/{console-features-api-DSSgCV3v.mjs → console-features-api-v1fSycue.mjs} +6 -6
- package/.output/server/_ssr/{console-features-rpc-D6kMlR2A.mjs → console-features-rpc-C7XNVqm0.mjs} +5 -5
- package/.output/server/_ssr/createCsrfMiddleware-BwHbC8n-.mjs +159 -0
- package/.output/server/_ssr/createMiddleware-C3ACsfuG.mjs +24 -0
- package/.output/server/_ssr/{createMiddleware-B1iPez7u.mjs → createServerFn-AQvBMBjJ.mjs} +2 -23
- package/.output/server/_ssr/{createServerRpc-BaDJLGBx.mjs → createServerRpc-BVvRVOz5.mjs} +2 -2
- package/.output/server/_ssr/{databaseConfig-C5SkAyUy.mjs → databaseConfig-B_Bg7qS5.mjs} +1 -1
- package/.output/server/_ssr/{databaseJsonValue-Csbuzc9Q.mjs → databaseJsonValue-kS7cvEtz.mjs} +1 -1
- package/.output/server/_ssr/{databaseRows-CPphvbag.mjs → databaseRows-B6H8Cmgq.mjs} +3 -3
- package/.output/server/_ssr/{dialog-B4ALf7NJ.mjs → dialog-C8CS8RMt.mjs} +1 -1
- package/.output/server/_ssr/{dist-CbQbBIw-.mjs → dist-TEpM_UAD.mjs} +125 -125
- package/.output/server/_ssr/{downloadBundle-94jPBGSO.mjs → downloadBundle-DCmr7vsj.mjs} +1 -1
- package/.output/server/_ssr/{empty-Bh1FEeYF.mjs → empty-CkdEAvCY.mjs} +2 -2
- package/.output/server/_ssr/{empty-plugin-adapters-CpCHi1TV.mjs → empty-plugin-adapters-zrZ0xs6n.mjs} +1 -1
- package/.output/server/_ssr/{failureReports-Bh22wois.mjs → failureReports-Cn_zU9S8.mjs} +3 -3
- package/.output/server/_ssr/{field-HKCaPH3e.mjs → field-CW08sZXd.mjs} +2 -2
- package/.output/server/_ssr/{getBundleChildren-DfxB2K8D.mjs → getBundleChildren-rtkEEJw5.mjs} +2 -2
- package/.output/server/_ssr/git-CTA8Aqr0.mjs +56 -0
- package/.output/server/_ssr/{input-group-xm3LzDuq.mjs → input-group-D-TpFPuG.mjs} +1 -1
- package/.output/server/_ssr/{insights-BKZpJ_K4.mjs → insights-CMyzPs8l.mjs} +28 -27
- package/.output/server/_ssr/{insights-CCyTACIw.mjs → insights-CYeBl8N9.mjs} +5 -5
- package/.output/server/_ssr/{insights-CNqWQLJR.mjs → insights-DAi9KknT.mjs} +2 -2
- package/.output/server/_ssr/{insights-api-DtRFz5X3.mjs → insights-api-BFARQ5ct.mjs} +6 -6
- package/.output/server/_ssr/{insights-errors-Iy-lBaM7.mjs → insights-errors-7BCwFybo.mjs} +2 -2
- package/.output/server/_ssr/{insights-failures-BJN3RfLu.mjs → insights-failures-B9VPkzZE.mjs} +2 -2
- package/.output/server/_ssr/{insights-failures-rpc-DLZ91uOG.mjs → insights-failures-rpc-Q4WNNJkU.mjs} +12 -12
- package/.output/server/_ssr/{insights-recovery-BUuQdlA3.mjs → insights-recovery-BtXVU3ro.mjs} +1 -1
- package/.output/server/_ssr/{insights-recovery-rpc-BQHUhPi7.mjs → insights-recovery-rpc-CY5RYJKa.mjs} +8 -8
- package/.output/server/_ssr/{insights-rpc-C6Vukyn7.mjs → insights-rpc-BO7_evvs.mjs} +5 -5
- package/.output/server/_ssr/{insights-search-CBYoxT7v.mjs → insights-search-DKfxgHD3.mjs} +5 -5
- package/.output/server/_ssr/{insights-usage-CHqvtehH.mjs → insights-usage-DiGnuxW8.mjs} +1 -1
- package/.output/server/_ssr/{insights-usage-rpc-BJ7a-xco.mjs → insights-usage-rpc-B4czku50.mjs} +8 -8
- package/.output/server/_ssr/{insights-usage-rpc-C-lNUIsD.mjs → insights-usage-rpc-CUCTFska.mjs} +5 -5
- package/.output/server/_ssr/{insightsUsage-COUG-_-U.mjs → insightsUsage-ClAmoUz8.mjs} +3 -3
- package/.output/server/_ssr/{insights_.distribution-DmLW8I8a.mjs → insights_.distribution-BDeUhZGj.mjs} +2 -2
- package/.output/server/_ssr/{insights_.distribution-CTwilMPm.mjs → insights_.distribution-BRrbZN1X.mjs} +5 -5
- package/.output/server/_ssr/{insights_.distribution-DztDyrBj.mjs → insights_.distribution-C2MXpzKd.mjs} +15 -14
- package/.output/server/_ssr/{installations-Bct7ag5y.mjs → installations-BVk7okiY.mjs} +4 -4
- package/.output/server/_ssr/{installations-B6MK5iA8.mjs → installations-BjLMgBoH.mjs} +2 -2
- package/.output/server/_ssr/{installations-_N4PtYFl.mjs → installations-DxGZEC5G.mjs} +12 -12
- package/.output/server/_ssr/{listReleases-BfUBlCHo.mjs → listReleases-D_GY00SQ.mjs} +1 -1
- package/.output/server/_ssr/native-builds-CSCgFB21.mjs +10 -0
- package/.output/server/_ssr/native-builds-CdlyAldW.mjs +569 -0
- package/.output/server/_ssr/native-builds-DhXL5zf_.mjs +137 -0
- package/.output/server/_ssr/native-builds_._buildId-CMp1h8dT.mjs +10 -0
- package/.output/server/_ssr/native-builds_._buildId-GHpGaYyE.mjs +813 -0
- package/.output/server/_ssr/nativeBuildQr-84cnFQPX.mjs +59 -0
- package/.output/server/_ssr/nativeBuilds-B_7BPjt_.mjs +133 -0
- package/.output/server/_ssr/{plugins-BaD1slHx.mjs → plugins-RIryUNWj.mjs} +6 -5
- package/.output/server/_ssr/qr-code-C7kNRIDX.mjs +22 -0
- package/.output/server/_ssr/{release-adoption-DuYJTGuX.mjs → release-adoption-DoAtNPED.mjs} +2 -2
- package/.output/server/_ssr/{release-adoption-rpc-CHl7G_Zy.mjs → release-adoption-rpc-BJXZvtoQ.mjs} +10 -10
- package/.output/server/_ssr/{releaseAdoption-B39cpvfo.mjs → releaseAdoption-a2byXMfz.mjs} +4 -4
- package/.output/server/_ssr/releaseCatalogCompiler-DLcean2k.mjs +364 -0
- package/.output/server/_ssr/{releaseManagement-DFMUnDF5.mjs → releaseManagement-DHUux2az.mjs} +1 -1
- package/.output/server/_ssr/{releaseReachability-bFsuElSS.mjs → releaseReachability-DZOK_dH-.mjs} +2 -2
- package/.output/server/_ssr/{remote-config-Mj05qVYZ.mjs → remote-config-DjsEl270.mjs} +20 -19
- package/.output/server/_ssr/{remote-config-10-obSZ0.mjs → remote-config-DlyyohL2.mjs} +2 -2
- package/.output/server/_ssr/{remote-config-Dj1eTYi4.mjs → remote-config-XdYuByHz.mjs} +2 -2
- package/.output/server/_ssr/{remote-config-rpc-lteqDSLn.mjs → remote-config-rpc-GMLPb-90.mjs} +6 -6
- package/.output/server/_ssr/request-guard-CPdRBuGW.mjs +108 -0
- package/.output/server/_ssr/{router-Dr4jjjhx.mjs → router-vpxd56RW.mjs} +317 -71
- package/.output/server/_ssr/{routes-BZbrY_qw.mjs → routes-DyEM4b3x.mjs} +14 -14
- package/.output/server/_ssr/{routes-B5KrCDwk.mjs → routes-dQe3XhiS.mjs} +99 -81
- package/.output/server/_ssr/{runtime.server-DsHqVqTr.mjs → runtime.server-BdMY1blX.mjs} +4 -4
- package/.output/server/_ssr/schema-ChlRtZIo.mjs +23 -0
- package/.output/server/_ssr/schema-migration-rpc-BfGzEK_o.mjs +65 -0
- package/.output/server/_ssr/{select-BtdGNSmp.mjs → select-DM1uwtk9.mjs} +3 -27
- package/.output/server/_ssr/{server-BxaHSwHh.mjs → server-BUUKNSPI.mjs} +7 -161
- package/.output/server/_ssr/{sidebar-CVsgosaD.mjs → sidebar-DB2YQlok.mjs} +4 -4
- package/.output/server/_ssr/ssr.mjs +1 -1
- package/.output/server/_ssr/start-BvDsQehg.mjs +49 -0
- package/.output/server/_ssr/{switch-BmARgkUP.mjs → switch-DLD58zLy.mjs} +2 -2
- package/.output/server/_ssr/{table-M5P3YUfY.mjs → table-P4TnAZhl.mjs} +1 -1
- package/.output/server/_ssr/{tabs-CrmfOlJj.mjs → tabs-B7ZeLicF.mjs} +2 -2
- package/.output/server/_ssr/{updateFailures-z1IQy_4q.mjs → updateFailures-n-tGv90S.mjs} +3 -3
- package/.output/server/_ssr/{uuidv7-C88s9jWF.mjs → uuidv7-BIF4nb_q.mjs} +2 -2
- package/.output/server/_ssr/{schema-BiAVyEj2.mjs → version-CQ98ZBpL-CWSkTT9J.mjs} +2 -22
- package/.output/server/_tanstack-start-manifest_v-C-KC63Zq.mjs +184 -0
- package/.output/server/index.mjs +432 -271
- package/lib/vite-internal.mjs +32 -1
- package/package.json +8 -7
- package/src/components/AppSidebar.tsx +12 -1
- package/src/components/ConsoleAccessPage.tsx +49 -3
- package/src/components/QrCode.tsx +35 -0
- package/src/components/SchemaMigrationRequired.tsx +139 -0
- package/src/components/features/insights/BuiltinBundle.tsx +5 -3
- package/src/components/features/native-builds/BuiltInBundleCard.tsx +163 -0
- package/src/components/features/native-builds/CommitLink.tsx +30 -0
- package/src/components/features/native-builds/CopyableText.tsx +48 -0
- package/src/components/features/native-builds/ImportBuild.tsx +91 -0
- package/src/components/features/native-builds/InstallCard.tsx +205 -0
- package/src/components/features/native-builds/NativeBuildDetailPage.tsx +175 -0
- package/src/components/features/native-builds/NativeBuildsPage.tsx +151 -0
- package/src/components/features/native-builds/NativeBuildsTable.tsx +426 -0
- package/src/components/features/native-builds/PatchesCard.tsx +222 -0
- package/src/components/features/native-builds/ReleaseNativeBuildLink.tsx +101 -0
- package/src/components/features/native-builds/ServedNowCard.tsx +214 -0
- package/src/components/features/native-builds/ShortHash.tsx +41 -0
- package/src/components/features/native-builds/StoreSubmission.tsx +73 -0
- package/src/components/features/releases/ReleaseEditorSheet.tsx +8 -0
- package/src/lib/api-rpc.ts +98 -0
- package/src/lib/api.ts +120 -0
- package/src/lib/native-builds.ts +162 -0
- package/src/lib/qr-code.ts +16 -0
- package/src/lib/schema-migration-api.ts +54 -0
- package/src/lib/schema-migration-rpc.ts +26 -0
- package/src/lib/schema-migration.ts +101 -0
- package/src/lib/server/config.server.ts +23 -14
- package/src/lib/server/console-runtime.server.ts +4 -6
- package/src/lib/server/local-console.ts +164 -0
- package/src/lib/server/nativeBuildQr.ts +73 -0
- package/src/lib/server/nativeBuilds.ts +288 -0
- package/src/lib/shell.ts +9 -0
- package/src/routeTree.gen.ts +88 -1
- package/src/router.tsx +11 -1
- package/src/routes/-native-builds-search.ts +51 -0
- package/src/routes/[_]hot-updater/native-builds/qr.ts +14 -0
- package/src/routes/__root.tsx +11 -1
- package/src/routes/api/native-builds/$buildId/download.ts +50 -0
- package/src/routes/native-builds.tsx +21 -0
- package/src/routes/native-builds_.$buildId.tsx +23 -0
- package/src/start.ts +33 -0
- package/src/virtual-modules.d.ts +18 -0
- package/src/vite-internal.ts +57 -1
- package/.output/public/assets/EventDetails-gbD_fCqc.js +0 -1
- package/.output/public/assets/HashValueDisplay-BGkDPaYi.js +0 -1
- package/.output/public/assets/InsightsPageHeader-BRw8XkJW.js +0 -1
- package/.output/public/assets/NotFoundPage-Cei6W2lm.js +0 -1
- package/.output/public/assets/alert-dialog-BtVg50WQ.js +0 -1
- package/.output/public/assets/api-DoicFJfd.js +0 -1
- package/.output/public/assets/api-keys-D81VHHNM.js +0 -1
- package/.output/public/assets/api-keys-Dia6i8K1.js +0 -1
- package/.output/public/assets/arrow-left-DsivSewE.js +0 -1
- package/.output/public/assets/arrow-up-right-B5aM0F7f.js +0 -1
- package/.output/public/assets/badge-Bqf3UDee.js +0 -1
- package/.output/public/assets/circle-off-BsbZQtsF.js +0 -1
- package/.output/public/assets/console-features-api-DPE4C06Y.js +0 -2
- package/.output/public/assets/dialog-CGKPxz3n.js +0 -1
- package/.output/public/assets/index-DrkdiRIn.js +0 -10
- package/.output/public/assets/insights-C-5t8wh2.js +0 -2
- package/.output/public/assets/insights-D_ngQdiz.js +0 -1
- package/.output/public/assets/insights-KigRBDgC.js +0 -42
- package/.output/public/assets/insights-api-1ZjVu3Sb.js +0 -1
- package/.output/public/assets/insights-search-aT8BVCci.js +0 -1
- package/.output/public/assets/insights-usage-rpc-DqTiXI1_.js +0 -1
- package/.output/public/assets/insights_.distribution-Bzl6RFBR.js +0 -2
- package/.output/public/assets/insights_.distribution-Cthgttqb.js +0 -1
- package/.output/public/assets/insights_.distribution-D2c2v1bu.js +0 -1
- package/.output/public/assets/installations-2Nx8Htf-.js +0 -1
- package/.output/public/assets/installations-BskJ1xQr.js +0 -2
- package/.output/public/assets/installations-K2iJ_r0h.js +0 -1
- package/.output/public/assets/remote-config-D9X3NiwV.js +0 -6
- package/.output/public/assets/remote-config-DzOOiBIO.js +0 -1
- package/.output/public/assets/remote-config-ikg7kak0.js +0 -1
- package/.output/public/assets/routes-9rb9P5Y7.js +0 -2
- package/.output/public/assets/routes-P7683vKQ.js +0 -1
- package/.output/public/assets/search-DuI1FBpg.js +0 -1
- package/.output/public/assets/select-M3H2gxCc.js +0 -1
- package/.output/public/assets/smartphone-CtsHuiIC.js +0 -1
- package/.output/public/assets/styles-CpiW_YdW.css +0 -2
- package/.output/public/assets/switch-By_vfm8I.js +0 -1
- package/.output/public/assets/useCompositeListItem-B-UkrDlu.js +0 -1
- package/.output/server/_ssr/config.server-DTdsG47T.mjs +0 -35
- package/.output/server/_ssr/start-B6ECIE_7.mjs +0 -4
- package/.output/server/_tanstack-start-manifest_v-_BY9hYTm.mjs +0 -136
package/src/lib/api.ts
CHANGED
|
@@ -13,8 +13,13 @@ import {
|
|
|
13
13
|
getBundleChildren,
|
|
14
14
|
getChannels,
|
|
15
15
|
getConfig,
|
|
16
|
+
getNativeBuildDetail,
|
|
17
|
+
getNativeBuildDownloadUrl,
|
|
18
|
+
getNativeBuildPatches,
|
|
19
|
+
getNativeBuilds,
|
|
16
20
|
getRelease,
|
|
17
21
|
getReleaseCatalogDiagnostics,
|
|
22
|
+
getReleaseNativeBuild,
|
|
18
23
|
getReleases,
|
|
19
24
|
preflightRelease as preflightReleaseApi,
|
|
20
25
|
promoteRelease as promoteReleaseApi,
|
|
@@ -40,8 +45,45 @@ export const queryKeys = {
|
|
|
40
45
|
["bundle-children", "counts", ...bundleIds] as const,
|
|
41
46
|
},
|
|
42
47
|
bundle: (bundleId: string) => ["bundle", bundleId] as const,
|
|
48
|
+
nativeBuilds: {
|
|
49
|
+
all: ["native-builds"] as const,
|
|
50
|
+
list: (page: NativeBuildPage) => ["native-builds", page] as const,
|
|
51
|
+
},
|
|
52
|
+
nativeBuild: (id: string) => ["native-build", id] as const,
|
|
53
|
+
nativeBuildPatches: (minBundleId: string, page: KeysetPage) =>
|
|
54
|
+
["native-build-patches", minBundleId, page] as const,
|
|
55
|
+
nativeBuildDownloadUrl: (id: string) =>
|
|
56
|
+
["native-build-download-url", id] as const,
|
|
57
|
+
releaseNativeBuild: (release: ReleaseNativeBuildInput) =>
|
|
58
|
+
["release-native-build", release] as const,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export type KeysetPage = {
|
|
62
|
+
/** Rows older than this key: the next page. */
|
|
63
|
+
before?: string;
|
|
64
|
+
/** Rows newer than this key: the previous page. */
|
|
65
|
+
after?: string;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export type NativeBuildPage = KeysetPage & {
|
|
69
|
+
/** Only the builds that embed this channel. */
|
|
70
|
+
channel?: string;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export type ReleaseNativeBuildInput = {
|
|
74
|
+
platform: "ios" | "android";
|
|
75
|
+
channel: string;
|
|
76
|
+
fingerprintHash: string | null;
|
|
77
|
+
targetAppVersion: string | null;
|
|
43
78
|
};
|
|
44
79
|
|
|
80
|
+
/**
|
|
81
|
+
* How often an open install QR reads its download URL again. Storage gives
|
|
82
|
+
* no lifetime with the URL, and adapters let it be configured down to
|
|
83
|
+
* minutes, so the QR never shows one older than this.
|
|
84
|
+
*/
|
|
85
|
+
const DOWNLOAD_URL_REFRESH_MS = 30_000;
|
|
86
|
+
|
|
45
87
|
export type ReleaseFilters = {
|
|
46
88
|
/** One of the filter sets the release indexes serve; none lists every release. */
|
|
47
89
|
filter?: ReleaseFilter;
|
|
@@ -125,6 +167,84 @@ export function useBundleChildCountsQuery(bundleIds: string[]) {
|
|
|
125
167
|
});
|
|
126
168
|
}
|
|
127
169
|
|
|
170
|
+
/** One page of native builds, newest first, kept on screen while the next loads. */
|
|
171
|
+
export function useNativeBuildsQuery(page: NativeBuildPage) {
|
|
172
|
+
return useQuery({
|
|
173
|
+
queryKey: queryKeys.nativeBuilds.list(page),
|
|
174
|
+
queryFn: () => getNativeBuilds({ data: page }),
|
|
175
|
+
placeholderData: (previousData) => previousData,
|
|
176
|
+
staleTime: 60_000,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* A native build's page in one round trip, or null for an unknown ID. The
|
|
182
|
+
* page reads it again when it opens, since its catalog and patches change
|
|
183
|
+
* with each deploy.
|
|
184
|
+
*/
|
|
185
|
+
export function useNativeBuildDetailQuery(id: string) {
|
|
186
|
+
return useQuery({
|
|
187
|
+
queryKey: queryKeys.nativeBuild(id),
|
|
188
|
+
queryFn: () => getNativeBuildDetail({ data: { id } }),
|
|
189
|
+
enabled: id.length > 0,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* A page of patches from a built-in bundle other than the newest, which the
|
|
195
|
+
* build's page already holds; kept on screen while the next one loads.
|
|
196
|
+
*/
|
|
197
|
+
export function useNativeBuildPatchesQuery(
|
|
198
|
+
minBundleId: string,
|
|
199
|
+
page: KeysetPage,
|
|
200
|
+
enabled: boolean,
|
|
201
|
+
) {
|
|
202
|
+
return useQuery({
|
|
203
|
+
queryKey: queryKeys.nativeBuildPatches(minBundleId, page),
|
|
204
|
+
queryFn: () => getNativeBuildPatches({ data: { minBundleId, ...page } }),
|
|
205
|
+
enabled: enabled && minBundleId.length > 0,
|
|
206
|
+
placeholderData: (previousData) => previousData,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* The newest native build of a release's scope, or none: read only for a
|
|
212
|
+
* release one build can serve, by its fingerprint or one exact app version.
|
|
213
|
+
*/
|
|
214
|
+
export function useReleaseNativeBuildQuery(
|
|
215
|
+
release: ReleaseNativeBuildInput | null,
|
|
216
|
+
) {
|
|
217
|
+
return useQuery({
|
|
218
|
+
queryKey: queryKeys.releaseNativeBuild(
|
|
219
|
+
release ?? {
|
|
220
|
+
platform: "ios",
|
|
221
|
+
channel: "",
|
|
222
|
+
fingerprintHash: null,
|
|
223
|
+
targetAppVersion: null,
|
|
224
|
+
},
|
|
225
|
+
),
|
|
226
|
+
queryFn: () => getReleaseNativeBuild({ data: release! }),
|
|
227
|
+
enabled: release !== null,
|
|
228
|
+
staleTime: 60_000,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* The storage URL a phone downloads a build from, read while the install
|
|
234
|
+
* QR is open and again before it expires.
|
|
235
|
+
*/
|
|
236
|
+
export function useNativeBuildDownloadUrlQuery(id: string, enabled: boolean) {
|
|
237
|
+
return useQuery({
|
|
238
|
+
queryKey: queryKeys.nativeBuildDownloadUrl(id),
|
|
239
|
+
queryFn: () => getNativeBuildDownloadUrl({ data: { id } }),
|
|
240
|
+
enabled,
|
|
241
|
+
staleTime: DOWNLOAD_URL_REFRESH_MS,
|
|
242
|
+
// A storage without download URLs has none to refresh.
|
|
243
|
+
refetchInterval: (query) =>
|
|
244
|
+
query.state.data?.url === null ? false : DOWNLOAD_URL_REFRESH_MS,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
128
248
|
// Mutation Hooks
|
|
129
249
|
export function useCreateChannelMutation() {
|
|
130
250
|
const queryClient = useQueryClient();
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
NativeBuildArtifactKind,
|
|
3
|
+
NativeBuildRow,
|
|
4
|
+
NativeBuildScope,
|
|
5
|
+
} from "@hot-updater/plugin-core";
|
|
6
|
+
import {
|
|
7
|
+
canonicalizeAppVersion,
|
|
8
|
+
createReleaseCatalogScopeKey,
|
|
9
|
+
decodeChannelKey,
|
|
10
|
+
encodeChannelKey,
|
|
11
|
+
} from "@hot-updater/protocol";
|
|
12
|
+
|
|
13
|
+
import { shellQuote } from "./shell";
|
|
14
|
+
|
|
15
|
+
/** How each artifact installs, as the Console's install dialog shows it. */
|
|
16
|
+
export const nativeBuildArtifacts: Record<
|
|
17
|
+
NativeBuildArtifactKind,
|
|
18
|
+
{ readonly label: string; readonly installs: string }
|
|
19
|
+
> = {
|
|
20
|
+
apk: { label: "APK", installs: "Android device or emulator" },
|
|
21
|
+
aab: { label: "AAB", installs: "Google Play or bundletool" },
|
|
22
|
+
app: { label: "Simulator app", installs: "iOS Simulator" },
|
|
23
|
+
ipa: { label: "IPA", installs: "registered iOS devices" },
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** A platform's name as the console shows it. */
|
|
27
|
+
export const platformLabel = (platform: "ios" | "android") =>
|
|
28
|
+
platform === "ios" ? "iOS" : "Android";
|
|
29
|
+
|
|
30
|
+
/** The console route that redirects to a build's artifact in storage. */
|
|
31
|
+
export const nativeBuildDownloadPath = (id: string) =>
|
|
32
|
+
`/api/native-builds/${encodeURIComponent(id)}/download`;
|
|
33
|
+
|
|
34
|
+
/** The console page of one native build. */
|
|
35
|
+
export const nativeBuildDetailPath = (id: string) =>
|
|
36
|
+
`/native-builds/${encodeURIComponent(id)}`;
|
|
37
|
+
|
|
38
|
+
const CHANNEL_ID_PREFIX = "channel:";
|
|
39
|
+
|
|
40
|
+
/** A channel row's ID, `channel:` and its channel key. */
|
|
41
|
+
export const channelIdOf = (channel: string) =>
|
|
42
|
+
`${CHANNEL_ID_PREFIX}${encodeChannelKey(channel)}`;
|
|
43
|
+
|
|
44
|
+
/** The name of the channel a build embeds, decoded from its channel ID: no read. */
|
|
45
|
+
export const nativeBuildChannel = (build: Pick<NativeBuildRow, "channel_id">) =>
|
|
46
|
+
decodeChannelKey(build.channel_id.slice(CHANNEL_ID_PREFIX.length));
|
|
47
|
+
|
|
48
|
+
/** The scope a build was registered in, as `findNativeBuild` takes it. */
|
|
49
|
+
export const nativeBuildScope = (build: NativeBuildRow): NativeBuildScope =>
|
|
50
|
+
build.fingerprint_hash === null
|
|
51
|
+
? {
|
|
52
|
+
platform: build.platform,
|
|
53
|
+
channel: nativeBuildChannel(build),
|
|
54
|
+
fingerprintHash: null,
|
|
55
|
+
appVersion: build.app_version ?? "",
|
|
56
|
+
}
|
|
57
|
+
: {
|
|
58
|
+
platform: build.platform,
|
|
59
|
+
channel: nativeBuildChannel(build),
|
|
60
|
+
fingerprintHash: build.fingerprint_hash,
|
|
61
|
+
appVersion: null,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The Release Catalog scope whose releases an install of the build is
|
|
66
|
+
* offered. A fingerprint build's scope key is that catalog's own key; an
|
|
67
|
+
* app-version build's adds its exact app version, which the catalog of
|
|
68
|
+
* every app version leaves out.
|
|
69
|
+
*/
|
|
70
|
+
export const nativeBuildCatalogScopeKey = (build: NativeBuildRow): string => {
|
|
71
|
+
const channelKey = build.channel_id.slice(CHANNEL_ID_PREFIX.length);
|
|
72
|
+
return build.fingerprint_hash === null
|
|
73
|
+
? createReleaseCatalogScopeKey({
|
|
74
|
+
channelKey,
|
|
75
|
+
platform: build.platform,
|
|
76
|
+
strategy: "APP_VERSION",
|
|
77
|
+
})
|
|
78
|
+
: createReleaseCatalogScopeKey({
|
|
79
|
+
channelKey,
|
|
80
|
+
fingerprintHash: build.fingerprint_hash,
|
|
81
|
+
platform: build.platform,
|
|
82
|
+
strategy: "FINGERPRINT",
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The native build scope a release serves, or null when no one build has
|
|
88
|
+
* it: a release of an app version range, rather than one exact version,
|
|
89
|
+
* targets many builds.
|
|
90
|
+
*/
|
|
91
|
+
export const releaseNativeBuildScope = (release: {
|
|
92
|
+
readonly platform: "ios" | "android";
|
|
93
|
+
readonly channel: string;
|
|
94
|
+
readonly fingerprintHash: string | null;
|
|
95
|
+
readonly targetAppVersion: string | null;
|
|
96
|
+
}): NativeBuildScope | null => {
|
|
97
|
+
if (release.fingerprintHash !== null) {
|
|
98
|
+
return {
|
|
99
|
+
platform: release.platform,
|
|
100
|
+
channel: release.channel,
|
|
101
|
+
fingerprintHash: release.fingerprintHash,
|
|
102
|
+
appVersion: null,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const target = release.targetAppVersion;
|
|
106
|
+
if (target === null || canonicalizeAppVersion(target) !== target) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
platform: release.platform,
|
|
111
|
+
channel: release.channel,
|
|
112
|
+
fingerprintHash: null,
|
|
113
|
+
appVersion: target,
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The artifact's file name, the last path segment of its storage URI, so a
|
|
119
|
+
* public URL's query or fragment stays out of it.
|
|
120
|
+
*/
|
|
121
|
+
export const nativeBuildFileName = (build: NativeBuildRow): string => {
|
|
122
|
+
const path = build.artifact_storage_uri.split(/[?#]/, 1)[0] ?? "";
|
|
123
|
+
const segment = path.split("/").at(-1) ?? "";
|
|
124
|
+
try {
|
|
125
|
+
return decodeURIComponent(segment);
|
|
126
|
+
} catch {
|
|
127
|
+
return segment;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/** What the build compiled in: its app version, or its fingerprint. */
|
|
132
|
+
export const nativeBuildTarget = (build: NativeBuildRow) =>
|
|
133
|
+
build.app_version === null
|
|
134
|
+
? { label: "Fingerprint", value: build.fingerprint_hash ?? "" }
|
|
135
|
+
: { label: "App version", value: build.app_version };
|
|
136
|
+
|
|
137
|
+
const UNITS = ["B", "KB", "MB", "GB"] as const;
|
|
138
|
+
|
|
139
|
+
/** A byte count in the largest unit that keeps it at or above 1. */
|
|
140
|
+
export const formatByteSize = (bytes: number): string => {
|
|
141
|
+
let value = bytes;
|
|
142
|
+
let unit = 0;
|
|
143
|
+
while (value >= 1024 && unit < UNITS.length - 1) {
|
|
144
|
+
value /= 1024;
|
|
145
|
+
unit += 1;
|
|
146
|
+
}
|
|
147
|
+
return `${unit === 0 ? value : value.toFixed(1)} ${UNITS[unit]}`;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The commands that install a simulator build: unzip the download, then
|
|
152
|
+
* install the `.app` on the booted simulator.
|
|
153
|
+
*/
|
|
154
|
+
export const simulatorInstallCommand = (build: NativeBuildRow) => {
|
|
155
|
+
const archive = nativeBuildFileName(build);
|
|
156
|
+
const app = archive.replace(/\.zip$/, "");
|
|
157
|
+
return `unzip -o ${shellQuote(archive)} && xcrun simctl install booted ${shellQuote(app)}`;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/** The command that installs a downloaded APK on an Android device or emulator. */
|
|
161
|
+
export const apkInstallCommand = (build: NativeBuildRow) =>
|
|
162
|
+
`adb install -r ${shellQuote(nativeBuildFileName(build))}`;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { encode } from "uqr";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A QR code of `value` as one SVG path of unit squares, with the standard
|
|
5
|
+
* four-module quiet zone: `size` is the side of its viewBox.
|
|
6
|
+
*/
|
|
7
|
+
export const qrCodePath = (value: string): { size: number; path: string } => {
|
|
8
|
+
const { data, size } = encode(value, { ecc: "M", border: 4 });
|
|
9
|
+
let path = "";
|
|
10
|
+
data.forEach((row, y) =>
|
|
11
|
+
row.forEach((dark, x) => {
|
|
12
|
+
if (dark) path += `M${x} ${y}h1v1h-1z`;
|
|
13
|
+
}),
|
|
14
|
+
);
|
|
15
|
+
return { size, path };
|
|
16
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { queryOptions, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
2
|
+
import { useCallback, useSyncExternalStore } from "react";
|
|
3
|
+
|
|
4
|
+
import { getSchemaMigrationRpc } from "./schema-migration-rpc";
|
|
5
|
+
|
|
6
|
+
export const schemaMigrationQueryKey = ["schema-migration"] as const;
|
|
7
|
+
|
|
8
|
+
export const schemaMigrationQueryOptions = () =>
|
|
9
|
+
queryOptions({
|
|
10
|
+
queryKey: schemaMigrationQueryKey,
|
|
11
|
+
queryFn: () => getSchemaMigrationRpc(),
|
|
12
|
+
retry: false,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const isSchemaQuery = (queryKey: readonly unknown[]) =>
|
|
16
|
+
queryKey[0] === schemaMigrationQueryKey[0];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The schema fence's refusal of the console's database, asked for once any
|
|
20
|
+
* page's read fails, so a healthy console reads nothing more. `retry` asks
|
|
21
|
+
* again, and once the fence passes, reads the failed pages again.
|
|
22
|
+
*/
|
|
23
|
+
export const useSchemaMigration = () => {
|
|
24
|
+
const queryClient = useQueryClient();
|
|
25
|
+
const cache = queryClient.getQueryCache();
|
|
26
|
+
const failing = useSyncExternalStore(
|
|
27
|
+
(onChange) => cache.subscribe(onChange),
|
|
28
|
+
() =>
|
|
29
|
+
cache
|
|
30
|
+
.getAll()
|
|
31
|
+
.some(
|
|
32
|
+
({ queryKey, state }) =>
|
|
33
|
+
!isSchemaQuery(queryKey) && state.fetchFailureCount > 0,
|
|
34
|
+
),
|
|
35
|
+
() => false,
|
|
36
|
+
);
|
|
37
|
+
const { data } = useQuery({
|
|
38
|
+
...schemaMigrationQueryOptions(),
|
|
39
|
+
enabled: failing,
|
|
40
|
+
});
|
|
41
|
+
const retry = useCallback(async () => {
|
|
42
|
+
const status = await queryClient.fetchQuery({
|
|
43
|
+
...schemaMigrationQueryOptions(),
|
|
44
|
+
staleTime: 0,
|
|
45
|
+
});
|
|
46
|
+
if (status !== null) return;
|
|
47
|
+
await queryClient.resetQueries({
|
|
48
|
+
predicate: ({ queryKey, state }) =>
|
|
49
|
+
!isSchemaQuery(queryKey) &&
|
|
50
|
+
(state.fetchFailureCount > 0 || state.status === "error"),
|
|
51
|
+
});
|
|
52
|
+
}, [queryClient]);
|
|
53
|
+
return { status: data ?? null, retry };
|
|
54
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createServerFn } from "@tanstack/react-start";
|
|
2
|
+
|
|
3
|
+
import { consoleAccess } from "./console-access";
|
|
4
|
+
import {
|
|
5
|
+
type SchemaMigrationStatus,
|
|
6
|
+
schemaMigrationOf,
|
|
7
|
+
} from "./schema-migration";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Whether the schema fence refuses the console's database, and what it
|
|
11
|
+
* found: one read through core, which the console makes only after a page's
|
|
12
|
+
* read failed. Null when the read passes or fails for another reason, which
|
|
13
|
+
* the page reports itself.
|
|
14
|
+
*/
|
|
15
|
+
export const getSchemaMigrationRpc = createServerFn({ method: "GET" })
|
|
16
|
+
.middleware([consoleAccess])
|
|
17
|
+
.handler(async (): Promise<SchemaMigrationStatus | null> => {
|
|
18
|
+
const { prepareConfig } = await import("./server/config.server");
|
|
19
|
+
const { config, core } = await prepareConfig();
|
|
20
|
+
try {
|
|
21
|
+
await core.listChannels();
|
|
22
|
+
return null;
|
|
23
|
+
} catch (error) {
|
|
24
|
+
return schemaMigrationOf(error, config.plugins);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/** A schema setting the fence found missing or different. */
|
|
2
|
+
export interface SchemaSetting {
|
|
3
|
+
readonly key: string;
|
|
4
|
+
readonly expected: string;
|
|
5
|
+
readonly found: string | null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The database needs migrations before the console can read it: what the
|
|
10
|
+
* schema fence reported, as plain data. TanStack Start sends a thrown error
|
|
11
|
+
* as its message alone, so the console asks for this separately.
|
|
12
|
+
*/
|
|
13
|
+
export interface SchemaMigrationStatus {
|
|
14
|
+
readonly message: string;
|
|
15
|
+
readonly settings: readonly SchemaSetting[];
|
|
16
|
+
/** The plugins whose tables a migration has yet to create or change. */
|
|
17
|
+
readonly plugins: readonly string[];
|
|
18
|
+
/**
|
|
19
|
+
* Whether a migration moves it forward: every setting is missing or older.
|
|
20
|
+
* False for a database a newer release migrated, a different storage
|
|
21
|
+
* layout, or a v0 database, which doctor reports and nothing moves.
|
|
22
|
+
*/
|
|
23
|
+
readonly migratable: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Compares dotted numeric versions, such as "1" and "1.2.0"; NaN when either is not one. */
|
|
27
|
+
const compareVersions = (left: string, right: string): number => {
|
|
28
|
+
const parse = (version: string) => version.split(".").map(Number);
|
|
29
|
+
const [a, b] = [parse(left), parse(right)];
|
|
30
|
+
if ([...a, ...b].some(Number.isNaN)) return Number.NaN;
|
|
31
|
+
for (let index = 0; index < Math.max(a.length, b.length); index += 1) {
|
|
32
|
+
const difference = (a[index] ?? 0) - (b[index] ?? 0);
|
|
33
|
+
if (difference !== 0) return difference;
|
|
34
|
+
}
|
|
35
|
+
return 0;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/** The brand Hot Updater's own plugin factories set, read as the server reads it. */
|
|
39
|
+
const OFFICIAL = Symbol.for("@hot-updater/server/official-plugin");
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Whether a migration moves a setting forward: a missing one, or an older
|
|
43
|
+
* version of core's or of one of Hot Updater's own plugins. Another plugin's
|
|
44
|
+
* changed tables need its own migration.
|
|
45
|
+
*/
|
|
46
|
+
const migrates = (
|
|
47
|
+
{ key, expected, found }: SchemaSetting,
|
|
48
|
+
official: ReadonlySet<string>,
|
|
49
|
+
) =>
|
|
50
|
+
found === null ||
|
|
51
|
+
((key === "schema.core" || official.has(key)) &&
|
|
52
|
+
compareVersions(found, expected) < 0);
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The fence's refusal in `error`, recognized by name: plugin-core may be
|
|
56
|
+
* another copy than the console's. Null for any other error.
|
|
57
|
+
*/
|
|
58
|
+
export const schemaMigrationOf = (
|
|
59
|
+
error: unknown,
|
|
60
|
+
/** The plugins the console's config lists. */
|
|
61
|
+
plugins: readonly unknown[] = [],
|
|
62
|
+
): SchemaMigrationStatus | null => {
|
|
63
|
+
if (
|
|
64
|
+
!(error instanceof Error) ||
|
|
65
|
+
error.name !== "HotUpdaterSchemaMigrationRequiredError"
|
|
66
|
+
) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const official = new Set(
|
|
70
|
+
plugins.flatMap((plugin) =>
|
|
71
|
+
typeof plugin === "object" &&
|
|
72
|
+
plugin !== null &&
|
|
73
|
+
Reflect.get(plugin, OFFICIAL) === true
|
|
74
|
+
? [`schema.${String(Reflect.get(plugin, "id"))}`]
|
|
75
|
+
: [],
|
|
76
|
+
),
|
|
77
|
+
);
|
|
78
|
+
const {
|
|
79
|
+
settings = [],
|
|
80
|
+
plugins: refused = [],
|
|
81
|
+
currentVersion,
|
|
82
|
+
} = error as Error & {
|
|
83
|
+
readonly settings?: readonly SchemaSetting[];
|
|
84
|
+
readonly plugins?: readonly string[];
|
|
85
|
+
/** A v0 database's schema version. */
|
|
86
|
+
readonly currentVersion?: string;
|
|
87
|
+
};
|
|
88
|
+
return {
|
|
89
|
+
message: error.message,
|
|
90
|
+
settings: settings.map(({ key, expected, found }) => ({
|
|
91
|
+
key,
|
|
92
|
+
expected,
|
|
93
|
+
found,
|
|
94
|
+
})),
|
|
95
|
+
plugins: [...refused],
|
|
96
|
+
migratable:
|
|
97
|
+
currentVersion === undefined &&
|
|
98
|
+
settings.length > 0 &&
|
|
99
|
+
settings.every((setting) => migrates(setting, official)),
|
|
100
|
+
};
|
|
101
|
+
};
|
|
@@ -23,23 +23,32 @@ const loadCachedConfig = async (request: Request) => {
|
|
|
23
23
|
return configPromise;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* The console's config, core, and features, without the access check: for
|
|
28
|
+
* routes that take no session, such as a channel link's QR code. Everything
|
|
29
|
+
* else goes through {@link prepareConfig}.
|
|
30
|
+
*/
|
|
31
|
+
export const loadConsole = async (request: Request = getRequest()) => {
|
|
32
|
+
const config = await loadCachedConfig(request);
|
|
33
|
+
|
|
34
|
+
// Bundles, releases, catalogs, and channels: core assembled as the
|
|
35
|
+
// server's, on the server's own path, or a self-hosted server's admin API.
|
|
36
|
+
core ??= config.core;
|
|
37
|
+
|
|
38
|
+
// The console's features: those of the plugins its config lists.
|
|
39
|
+
if (!runtime) {
|
|
40
|
+
const { createConsoleRuntime } = await import("./runtime.server");
|
|
41
|
+
runtime = createConsoleRuntime(config);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Each bundle file is read and deleted with its protocol's storage.
|
|
45
|
+
return { config, core, runtime, storage: config.storage };
|
|
46
|
+
};
|
|
47
|
+
|
|
26
48
|
export const prepareConfig = async (request: Request = getRequest()) => {
|
|
27
49
|
try {
|
|
28
50
|
await requireConsoleAccess(request);
|
|
29
|
-
|
|
30
|
-
|
|
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;
|
|
34
|
-
|
|
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);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Each bundle file is read and deleted with its protocol's storage.
|
|
42
|
-
return { config, core, runtime, storage: config.storage };
|
|
51
|
+
return await loadConsole(request);
|
|
43
52
|
} catch (error) {
|
|
44
53
|
if (
|
|
45
54
|
!(
|
|
@@ -72,10 +72,8 @@ export const resolveConsoleConfig = async (
|
|
|
72
72
|
|
|
73
73
|
const config = typeof source === "function" ? await source(request) : source;
|
|
74
74
|
const { database, storage, plugins = [] } = config;
|
|
75
|
-
const gitUrl =
|
|
76
|
-
|
|
77
|
-
? {}
|
|
78
|
-
: { gitUrl: config.console.gitUrl };
|
|
75
|
+
const { gitUrl } = config.console ?? {};
|
|
76
|
+
const consoleOptions = gitUrl === undefined ? {} : { gitUrl };
|
|
79
77
|
// A self-hosted server runs the plugins itself: core is its admin API.
|
|
80
78
|
// The plugins are still checked as the server checks them, on a database
|
|
81
79
|
// nothing reads, so one the server would refuse is refused here too.
|
|
@@ -86,7 +84,7 @@ export const resolveConsoleConfig = async (
|
|
|
86
84
|
plugins,
|
|
87
85
|
);
|
|
88
86
|
return {
|
|
89
|
-
...
|
|
87
|
+
...consoleOptions,
|
|
90
88
|
database,
|
|
91
89
|
core: database.core,
|
|
92
90
|
storage,
|
|
@@ -95,7 +93,7 @@ export const resolveConsoleConfig = async (
|
|
|
95
93
|
}
|
|
96
94
|
const server = assemble(database, storage, plugins);
|
|
97
95
|
return {
|
|
98
|
-
...
|
|
96
|
+
...consoleOptions,
|
|
99
97
|
database,
|
|
100
98
|
core: server.core,
|
|
101
99
|
storage,
|