@hot-updater/console 0.36.7 → 1.0.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 (262) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/BundleIdDisplay-D4ErdU5_.js +1 -0
  3. package/.output/public/assets/InsightsCapabilityContext-B42VqvP6.js +1 -0
  4. package/.output/public/assets/api-keys-api-DGVTTvTJ.js +1 -0
  5. package/.output/public/assets/api-keys-z16OZIP4.js +1 -0
  6. package/.output/public/assets/badge-Br_KDO0J.js +1 -0
  7. package/.output/public/assets/card-DzuEXvyJ.js +1 -0
  8. package/.output/public/assets/chart-no-axes-combined-FaYMGmQS.js +1 -0
  9. package/.output/public/assets/createServerFn-gM2-LFBy.js +3 -0
  10. package/.output/public/assets/dialog-BeAENgez.js +1 -0
  11. package/.output/public/assets/dist-Bssh_cv0.js +1 -0
  12. package/.output/public/assets/field-Buq148-A.js +1 -0
  13. package/.output/public/assets/index-uOpQAIC4.js +10 -0
  14. package/.output/public/assets/input-group-B3p5UpyW.js +1 -0
  15. package/.output/public/assets/insights-DJTr7Ozn.js +1 -0
  16. package/.output/public/assets/insights-api-DVMOoRMS.js +1 -0
  17. package/.output/public/assets/installations-BY64iIAK.js +2 -0
  18. package/.output/public/assets/installations-D5v9i3AL.js +1 -0
  19. package/.output/public/assets/lazyRouteComponent-DKYu5cZJ.js +1 -0
  20. package/.output/public/assets/preload-helper-CQ36nxok.js +1 -0
  21. package/.output/public/assets/refresh-cw-BkbfZ62O.js +1 -0
  22. package/.output/public/assets/routes-BuoI5-Cr.js +2 -0
  23. package/.output/public/assets/routes-C8fvgETO.js +1 -0
  24. package/.output/public/assets/search-qsIDvS2I.js +1 -0
  25. package/.output/public/assets/shield-check-DE_hVuHu.js +1 -0
  26. package/.output/public/assets/signing-mJD88Y4C.js +1 -0
  27. package/.output/public/assets/styles-BZIG7t42.css +2 -0
  28. package/.output/public/assets/table-RamFEclP.js +1 -0
  29. package/.output/public/assets/useCompositeListItem-ChzDqdpc.js +42 -0
  30. package/.output/public/assets/useMutation-JsGVjQMY.js +1 -0
  31. package/.output/public/assets/useOpenInteractionType-Dwfy-SpA.js +1 -0
  32. package/.output/server/__23tanstack-start-server-fn-resolver-Dftxlm2E.mjs +146 -0
  33. package/.output/server/_libs/@base-ui/react+[...].mjs +16124 -0
  34. package/.output/server/_libs/@reduxjs/toolkit+[...].mjs +1554 -0
  35. package/.output/server/_libs/@tanstack/react-router+[...].mjs +41 -10
  36. package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +132 -0
  37. package/.output/server/_libs/d3-array.mjs +231 -0
  38. package/.output/server/_libs/d3-color.mjs +330 -0
  39. package/.output/server/_libs/d3-format.mjs +252 -0
  40. package/.output/server/_libs/d3-interpolate.mjs +207 -0
  41. package/.output/server/_libs/d3-path.mjs +77 -0
  42. package/.output/server/_libs/d3-scale+[...].mjs +1907 -0
  43. package/.output/server/_libs/d3-shape.mjs +818 -0
  44. package/.output/server/_libs/decimal.js-light.mjs +884 -0
  45. package/.output/server/_libs/es-toolkit.mjs +1134 -0
  46. package/.output/server/_libs/eventemitter3.mjs +236 -0
  47. package/.output/server/_libs/immer.mjs +532 -0
  48. package/.output/server/_libs/lucide-react.mjs +141 -13
  49. package/.output/server/_libs/next-themes.mjs +2 -2
  50. package/.output/server/_libs/react-redux+[...].mjs +205 -0
  51. package/.output/server/_libs/recharts+[...].mjs +14117 -0
  52. package/.output/server/_libs/sonner.mjs +2 -2
  53. package/.output/server/_libs/tanstack__query-core.mjs +114 -1
  54. package/.output/server/_libs/tanstack__react-query.mjs +3 -4
  55. package/.output/server/_runtime.mjs +10 -1
  56. package/.output/server/_ssr/BundleActivityChart-CWcdPuUn.mjs +288 -0
  57. package/.output/server/_ssr/BundleIdDisplay-BEvB3MvA.mjs +13 -0
  58. package/.output/server/_ssr/InsightsCapabilityContext-Cx8fjm2s.mjs +15 -0
  59. package/.output/server/_ssr/api-keys-CpBqHpuC.mjs +300 -0
  60. package/.output/server/_ssr/api-keys-api-Czkp57UV.mjs +60 -0
  61. package/.output/server/_ssr/api-keys-rpc-BTtMDSoR.mjs +136 -0
  62. package/.output/server/_ssr/api-rpc-BEX8ISai.mjs +1468 -0
  63. package/.output/server/_ssr/auth-BF8mbAgF.mjs +21 -0
  64. package/.output/server/_ssr/auth-rpc-BFMEC-TT.mjs +24 -0
  65. package/.output/server/_ssr/auth.server-DW71WzsD.mjs +47 -0
  66. package/.output/server/_ssr/badge-CpWXQwyF.mjs +26 -0
  67. package/.output/server/_ssr/bundle-signing-rpc-BbCFUQSu.mjs +16 -0
  68. package/.output/server/_ssr/bundle-signing.server-C4NW5mWx.mjs +12 -0
  69. package/.output/server/_ssr/card-Bsc8mRis.mjs +86 -0
  70. package/.output/server/_ssr/config-Dv8qLDZ0.mjs +13 -0
  71. package/.output/server/_ssr/config.server-DQYEr_5X.mjs +361 -0
  72. package/.output/server/_ssr/{createServerFn-DzU0k62V.mjs → createServerFn-B2hcXoPM.mjs} +1 -1
  73. package/.output/server/_ssr/createServerRpc-DB29C_5p.mjs +12 -0
  74. package/.output/server/_ssr/{sidebar-Cv_H0_Jv.mjs → createSsrRpc-DhfrxEjg.mjs} +68 -50
  75. package/.output/server/_ssr/databaseRows-QlCYLi0H.mjs +166 -0
  76. package/.output/server/_ssr/deleteBundle-Cu_XOMt0.mjs +66 -0
  77. package/.output/server/_ssr/dialog-C0vKUp8R.mjs +170 -0
  78. package/.output/server/_ssr/dist-BVSLTpoP.mjs +250 -0
  79. package/.output/server/_ssr/downloadBundle-CNnYxYO2.mjs +24 -0
  80. package/.output/server/_ssr/{empty-plugin-adapters-B4HCzgmM.mjs → empty-plugin-adapters-B5QvGpEl.mjs} +1 -1
  81. package/.output/server/_ssr/field-DASCh-5d.mjs +74 -0
  82. package/.output/server/_ssr/{getBundleChildren-CVtSiH5t.mjs → getBundleChildren-BcRMFoDF.mjs} +10 -18
  83. package/.output/server/_ssr/input-group-etIsBUqv.mjs +288 -0
  84. package/.output/server/_ssr/insights-api-C1F7UreR.mjs +71 -0
  85. package/.output/server/_ssr/insights-input-9fEJF-ZG.mjs +96 -0
  86. package/.output/server/_ssr/insights-rpc-C-8R6kED.mjs +155 -0
  87. package/.output/server/_ssr/insights-ygT_aIfo.mjs +858 -0
  88. package/.output/server/_ssr/installations-DqRT1QTd.mjs +535 -0
  89. package/.output/server/_ssr/installations-K8TCYTYh.mjs +19 -0
  90. package/.output/server/_ssr/router-BxBEvtF1.mjs +513 -0
  91. package/.output/server/_ssr/routes-BC7rn-tf.mjs +37 -0
  92. package/.output/server/_ssr/routes-CM5KAwpB.mjs +2285 -0
  93. package/.output/server/_ssr/runtime.server-la7TRAH_.mjs +527 -0
  94. package/.output/server/_ssr/server-CpzW--lT.mjs +7468 -0
  95. package/.output/server/_ssr/signing-jk4HANnl.mjs +134 -0
  96. package/.output/server/_ssr/ssr.mjs +4 -5740
  97. package/.output/server/_ssr/start-DpViQGnt.mjs +4 -0
  98. package/.output/server/_ssr/table-CU58HeI7.mjs +53 -0
  99. package/.output/server/_ssr/uuidv7-CaKuKXrV.mjs +31 -0
  100. package/.output/server/_tanstack-start-manifest_v-Mz9MmSlD.mjs +103 -0
  101. package/.output/server/index.mjs +229 -47
  102. package/README.md +29 -8
  103. package/lib/index.d.mts +35 -0
  104. package/lib/index.mjs +4 -0
  105. package/lib/vite-internal.mjs +127 -0
  106. package/lib/vite.d.mts +10 -0
  107. package/lib/vite.mjs +5 -0
  108. package/package.json +51 -29
  109. package/public/apple-touch-icon.png +0 -0
  110. package/public/favicon-16x16.png +0 -0
  111. package/public/favicon-32x32.png +0 -0
  112. package/public/favicon.ico +0 -0
  113. package/public/logo.svg +1 -0
  114. package/public/manifest.json +30 -0
  115. package/public/robots.txt +3 -0
  116. package/src/components/AppSidebar.tsx +159 -0
  117. package/src/components/BundleIdDisplay.tsx +18 -0
  118. package/src/components/ChannelBadge.tsx +31 -0
  119. package/src/components/ConsoleAccessPage.tsx +122 -0
  120. package/src/components/EnabledStatusIcon.tsx +54 -0
  121. package/src/components/HashValueDisplay.tsx +85 -0
  122. package/src/components/HotUpdaterLogo.tsx +30 -0
  123. package/src/components/NotFoundPage.tsx +33 -0
  124. package/src/components/PlatformIcon.tsx +39 -0
  125. package/src/components/RolloutPercentageBadge.tsx +28 -0
  126. package/src/components/ThemeProvider.tsx +116 -0
  127. package/src/components/TimestampDisplay.tsx +41 -0
  128. package/src/components/features/api-keys/ApiKeysPage.tsx +377 -0
  129. package/src/components/features/bundles/BundleActivityChart.tsx +193 -0
  130. package/src/components/features/bundles/BundleChildrenPanel.tsx +201 -0
  131. package/src/components/features/bundles/BundleInsightsSummary.tsx +123 -0
  132. package/src/components/features/bundles/BundleMetadata.tsx +136 -0
  133. package/src/components/features/bundles/RolloutCohortsDialog.tsx +201 -0
  134. package/src/components/features/channels/ChannelManagementDialog.tsx +228 -0
  135. package/src/components/features/insights/ActivityChart.tsx +150 -0
  136. package/src/components/features/insights/BundleDistribution.tsx +133 -0
  137. package/src/components/features/insights/BundleSelector.tsx +114 -0
  138. package/src/components/features/insights/InsightsCapabilityContext.tsx +24 -0
  139. package/src/components/features/insights/InsightsControls.tsx +115 -0
  140. package/src/components/features/insights/InsightsErrorAlert.tsx +21 -0
  141. package/src/components/features/insights/InsightsOverview.tsx +208 -0
  142. package/src/components/features/insights/InstallationHistoryCard.tsx +248 -0
  143. package/src/components/features/insights/InstallationMatchesCard.tsx +132 -0
  144. package/src/components/features/insights/InstallationPageHeader.tsx +138 -0
  145. package/src/components/features/insights/InstallationPagination.tsx +48 -0
  146. package/src/components/features/insights/InstallationSearch.tsx +230 -0
  147. package/src/components/features/insights/UpdateOutcomes.tsx +147 -0
  148. package/src/components/features/releases/ReleaseEditorSheet.tsx +879 -0
  149. package/src/components/features/releases/ReleaseStateBadge.tsx +20 -0
  150. package/src/components/features/signing/BundleSigningPage.tsx +139 -0
  151. package/src/components/ui/alert-dialog.tsx +185 -0
  152. package/src/components/ui/alert.tsx +76 -0
  153. package/src/components/ui/badge.tsx +49 -0
  154. package/src/components/ui/button.tsx +58 -0
  155. package/src/components/ui/card.tsx +89 -0
  156. package/src/components/ui/chart.tsx +371 -0
  157. package/src/components/ui/combobox.tsx +303 -0
  158. package/src/components/ui/dialog.tsx +156 -0
  159. package/src/components/ui/dropdown-menu.tsx +270 -0
  160. package/src/components/ui/field.tsx +235 -0
  161. package/src/components/ui/input-group.tsx +147 -0
  162. package/src/components/ui/input.tsx +19 -0
  163. package/src/components/ui/label.tsx +25 -0
  164. package/src/components/ui/progress.tsx +84 -0
  165. package/src/components/ui/select.tsx +205 -0
  166. package/src/components/ui/separator.tsx +23 -0
  167. package/src/components/ui/sheet.tsx +132 -0
  168. package/src/components/ui/sidebar.tsx +779 -0
  169. package/src/components/ui/skeleton.tsx +13 -0
  170. package/src/components/ui/slider.tsx +56 -0
  171. package/src/components/ui/sonner.tsx +45 -0
  172. package/src/components/ui/switch.tsx +25 -0
  173. package/src/components/ui/table.tsx +114 -0
  174. package/src/components/ui/textarea.tsx +18 -0
  175. package/src/components/ui/toggle-group.tsx +89 -0
  176. package/src/components/ui/toggle.tsx +43 -0
  177. package/src/components/ui/tooltip.tsx +66 -0
  178. package/src/hooks/use-mobile.ts +30 -0
  179. package/src/index.ts +47 -0
  180. package/src/lib/api-keys-api.ts +69 -0
  181. package/src/lib/api-keys-rpc.ts +87 -0
  182. package/src/lib/api-rpc.ts +478 -0
  183. package/src/lib/api.ts +444 -0
  184. package/src/lib/auth-rpc.ts +16 -0
  185. package/src/lib/bundle-signing-api.ts +14 -0
  186. package/src/lib/bundle-signing-rpc.ts +16 -0
  187. package/src/lib/constants.ts +1 -0
  188. package/src/lib/git.ts +51 -0
  189. package/src/lib/insights-api.ts +144 -0
  190. package/src/lib/insights-error.ts +19 -0
  191. package/src/lib/insights-input.ts +187 -0
  192. package/src/lib/insights-overview.ts +170 -0
  193. package/src/lib/insights-rpc.ts +218 -0
  194. package/src/lib/server/auth.server.ts +43 -0
  195. package/src/lib/server/bundle-signing.server.ts +23 -0
  196. package/src/lib/server/config.server.ts +109 -0
  197. package/src/lib/server/console-runtime.server.ts +61 -0
  198. package/src/lib/server/deleteBundle.ts +145 -0
  199. package/src/lib/server/downloadBundle.ts +46 -0
  200. package/src/lib/server/getBundleChildren.ts +130 -0
  201. package/src/lib/server/listReleases.ts +61 -0
  202. package/src/lib/server/releaseActivity.ts +27 -0
  203. package/src/lib/server/releaseReachability.ts +95 -0
  204. package/src/lib/server/runtime.server.ts +172 -0
  205. package/src/lib/utils.ts +9 -0
  206. package/src/logo.svg +1 -0
  207. package/src/routeTree.gen.ts +199 -0
  208. package/src/router.tsx +21 -0
  209. package/src/routes/-installations-search.ts +19 -0
  210. package/src/routes/-releases-search.ts +74 -0
  211. package/src/routes/__root.tsx +174 -0
  212. package/src/routes/api/auth/$.ts +19 -0
  213. package/src/routes/api/bundles/$bundleId/download.ts +17 -0
  214. package/src/routes/api-keys.tsx +37 -0
  215. package/src/routes/index.tsx +982 -0
  216. package/src/routes/insights.tsx +139 -0
  217. package/src/routes/installations.tsx +194 -0
  218. package/src/routes/signing.tsx +37 -0
  219. package/src/styles.css +152 -0
  220. package/src/virtual-modules.d.ts +21 -0
  221. package/src/vite-env.d.ts +1 -0
  222. package/src/vite-internal.ts +195 -0
  223. package/src/vite.ts +12 -0
  224. package/.output/public/assets/dist-DJX53faP.js +0 -3
  225. package/.output/public/assets/index-Iylis5kY.js +0 -10
  226. package/.output/public/assets/routes-HT21hs0C.js +0 -54
  227. package/.output/public/assets/styles-C0Re4quB.css +0 -2
  228. package/.output/server/__23tanstack-start-server-fn-resolver-CgwgZ0-i.mjs +0 -66
  229. package/.output/server/_libs/@floating-ui/core+[...].mjs +0 -698
  230. package/.output/server/_libs/@floating-ui/dom+[...].mjs +0 -644
  231. package/.output/server/_libs/@floating-ui/react-dom+[...].mjs +0 -839
  232. package/.output/server/_libs/@radix-ui/react-alert-dialog+[...].mjs +0 -2138
  233. package/.output/server/_libs/@radix-ui/react-popper+[...].mjs +0 -308
  234. package/.output/server/_libs/@radix-ui/react-select+[...].mjs +0 -1038
  235. package/.output/server/_libs/@tanstack/devtools-event-client+[...].mjs +0 -196
  236. package/.output/server/_libs/@tanstack/form-core+[...].mjs +0 -3228
  237. package/.output/server/_libs/@tanstack/react-form+[...].mjs +0 -430
  238. package/.output/server/_libs/@tanstack/react-table+[...].mjs +0 -2382
  239. package/.output/server/_libs/radix-ui__number.mjs +0 -6
  240. package/.output/server/_libs/radix-ui__primitive.mjs +0 -9
  241. package/.output/server/_libs/radix-ui__react-arrow.mjs +0 -23
  242. package/.output/server/_libs/radix-ui__react-collection.mjs +0 -78
  243. package/.output/server/_libs/radix-ui__react-direction.mjs +0 -13
  244. package/.output/server/_libs/radix-ui__react-label.mjs +0 -22
  245. package/.output/server/_libs/radix-ui__react-separator.mjs +0 -30
  246. package/.output/server/_libs/radix-ui__react-slider.mjs +0 -510
  247. package/.output/server/_libs/radix-ui__react-switch.mjs +0 -157
  248. package/.output/server/_libs/radix-ui__react-tooltip.mjs +0 -493
  249. package/.output/server/_ssr/api-rpc-BIbDUKSz.mjs +0 -292
  250. package/.output/server/_ssr/config.server-BSS366KT.mjs +0 -41
  251. package/.output/server/_ssr/deleteBundle-BgbZTtpW.mjs +0 -141
  252. package/.output/server/_ssr/dist-KboydRVk.mjs +0 -120
  253. package/.output/server/_ssr/router-C3hqEy3k.mjs +0 -307
  254. package/.output/server/_ssr/routes-B0lQvvlC.mjs +0 -3048
  255. package/.output/server/_ssr/start-02sLteYG.mjs +0 -4
  256. package/.output/server/_ssr/storageProfile-FVkjPoGb.mjs +0 -9
  257. package/.output/server/_tanstack-start-manifest_v-Bpx52WOU.mjs +0 -20
  258. package/.output/server/node_modules/tslib/modules/index.js +0 -70
  259. package/.output/server/node_modules/tslib/modules/package.json +0 -3
  260. package/.output/server/node_modules/tslib/package.json +0 -47
  261. package/.output/server/node_modules/tslib/tslib.js +0 -484
  262. package/.output/server/package.json +0 -9
@@ -0,0 +1,172 @@
1
+ import type {
2
+ InsightsModel,
3
+ ApiKeyModel,
4
+ BundleRepository,
5
+ } from "@hot-updater/plugin-core";
6
+ import {
7
+ type ActiveInstallationInput,
8
+ type InsightsProvider,
9
+ createInsightsProvider,
10
+ type InstallationHistoryRow,
11
+ type InstallationSearchRow,
12
+ type OffsetPaginationResult,
13
+ } from "@hot-updater/server";
14
+
15
+ import {
16
+ parseActiveInstallationInput,
17
+ parseBundleEventInsightsInput,
18
+ parseBundleEventSummaryInput,
19
+ parseInstallationHistoryInput,
20
+ parseSearchInstallationsInput,
21
+ } from "../insights-input";
22
+
23
+ export type InstallationSearchResult =
24
+ OffsetPaginationResult<InstallationSearchRow>;
25
+ export type InstallationHistoryResult =
26
+ OffsetPaginationResult<InstallationHistoryRow>;
27
+
28
+ export function createRuntimeHotUpdater(config: {
29
+ readonly database: BundleRepository;
30
+ }): InsightsProvider | null {
31
+ const models: unknown = Reflect.get(config.database, "models");
32
+ const insights: unknown =
33
+ typeof models === "object" && models !== null
34
+ ? Reflect.get(models, "insights")
35
+ : undefined;
36
+ if (
37
+ typeof insights !== "object" ||
38
+ insights === null ||
39
+ typeof Reflect.get(insights, "append") !== "function" ||
40
+ typeof Reflect.get(insights, "scan") !== "function"
41
+ ) {
42
+ return null;
43
+ }
44
+ return createInsightsProvider(insights as InsightsModel);
45
+ }
46
+
47
+ export function createApiKeyStore(config: {
48
+ readonly database: BundleRepository;
49
+ }): ApiKeyModel | null {
50
+ const models: unknown = Reflect.get(config.database, "models");
51
+ const apiKeys: unknown =
52
+ typeof models === "object" && models !== null
53
+ ? Reflect.get(models, "apiKeys")
54
+ : undefined;
55
+ if (
56
+ typeof apiKeys !== "object" ||
57
+ apiKeys === null ||
58
+ typeof Reflect.get(apiKeys, "create") !== "function" ||
59
+ typeof Reflect.get(apiKeys, "findByHash") !== "function" ||
60
+ typeof Reflect.get(apiKeys, "list") !== "function" ||
61
+ typeof Reflect.get(apiKeys, "revoke") !== "function"
62
+ ) {
63
+ return null;
64
+ }
65
+ return apiKeys as ApiKeyModel;
66
+ }
67
+
68
+ const providerMethods = [
69
+ "appendBundleEvent",
70
+ "getBundleEventSummary",
71
+ "getBundleEventSummaries",
72
+ "getBundleEventInsights",
73
+ "getBundleEventOverview",
74
+ "getActiveInstallationOverview",
75
+ "searchInstallations",
76
+ "getInstallationHistory",
77
+ ] as const;
78
+
79
+ const parseInsightsProvider = (value: unknown): InsightsProvider | null => {
80
+ if (
81
+ typeof value !== "object" ||
82
+ value === null ||
83
+ Reflect.get(value, "mode") !== "bounded" ||
84
+ typeof Reflect.get(value, "maxMatchingRows") !== "number" ||
85
+ !providerMethods.every(
86
+ (method) => typeof Reflect.get(value, method) === "function",
87
+ )
88
+ ) {
89
+ return null;
90
+ }
91
+ return value as InsightsProvider;
92
+ };
93
+
94
+ export const getInsightsCapability = async (provider: unknown) => {
95
+ const parsed = parseInsightsProvider(provider);
96
+ return parsed === null
97
+ ? ({
98
+ insights: false,
99
+ insightsQueries: false,
100
+ eventIngestion: false,
101
+ } as const)
102
+ : ({
103
+ insights: true,
104
+ insightsQueries: true,
105
+ eventIngestion: true,
106
+ maxMatchingRows: parsed.maxMatchingRows,
107
+ mode: "bounded",
108
+ } as const);
109
+ };
110
+
111
+ const requireInsightsSupport = async (
112
+ provider: unknown,
113
+ ): Promise<InsightsProvider> => {
114
+ const parsedProvider = parseInsightsProvider(provider);
115
+ if (parsedProvider === null) {
116
+ throw new Error(
117
+ "Insights are not supported by the configured database plugin.",
118
+ );
119
+ }
120
+ return parsedProvider;
121
+ };
122
+
123
+ export async function getBundleEventSummary(provider: unknown, input: unknown) {
124
+ const { bundleId } = parseBundleEventSummaryInput(input);
125
+ return (await requireInsightsSupport(provider)).getBundleEventSummary(
126
+ bundleId,
127
+ );
128
+ }
129
+
130
+ export async function getActiveInstallationOverview(
131
+ provider: unknown,
132
+ input: unknown,
133
+ ) {
134
+ const parsed: ActiveInstallationInput = parseActiveInstallationInput(input);
135
+ return (await requireInsightsSupport(provider)).getActiveInstallationOverview(
136
+ parsed,
137
+ );
138
+ }
139
+
140
+ export async function getBundleEventInsights(
141
+ provider: unknown,
142
+ input: unknown,
143
+ ) {
144
+ const parsed = parseBundleEventInsightsInput(input);
145
+ return (await requireInsightsSupport(provider)).getBundleEventInsights(
146
+ parsed.bundleId,
147
+ parsed.window,
148
+ parsed.limit ?? 50,
149
+ parsed.offset ?? 0,
150
+ );
151
+ }
152
+
153
+ export async function searchInstallations(provider: unknown, input: unknown) {
154
+ const parsed = parseSearchInstallationsInput(input);
155
+ return (await requireInsightsSupport(provider)).searchInstallations(
156
+ parsed.query,
157
+ parsed.limit ?? 50,
158
+ parsed.offset ?? 0,
159
+ );
160
+ }
161
+
162
+ export async function getInstallationHistory(
163
+ provider: unknown,
164
+ input: unknown,
165
+ ) {
166
+ const parsed = parseInstallationHistoryInput(input);
167
+ return (await requireInsightsSupport(provider)).getInstallationHistory(
168
+ parsed.installId,
169
+ parsed.limit ?? 50,
170
+ parsed.offset ?? 0,
171
+ );
172
+ }
@@ -0,0 +1,9 @@
1
+ import { type ClassValue, clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
7
+
8
+ export const sleep = (ms: number) =>
9
+ new Promise((resolve) => setTimeout(resolve, ms));
package/src/logo.svg ADDED
@@ -0,0 +1 @@
1
+ <svg id="hot-updater-layer" data-name="hot-updater-layer" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 447 652"><defs><linearGradient id="hot-updater-gradient" x1="223.54" y1="0.36" x2="223.54" y2="711.23" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ff7f33"/><stop offset="0.4" stop-color="#f9602b"/><stop offset="0.76" stop-color="#f54c25"/><stop offset="1" stop-color="#f44523"/></linearGradient></defs><title>Hotupdater</title><path d="M150.25,8.58c16.28,31.85,42.09,116-68.18,212.22C75.46,226.57,70,233,63.72,239.62c-28.48,30-84.71,114.89-55.82,207,.69,2.18,1.31,4.37,1.91,6.57,5.25,19.33,38.08,121,155.56,157.25a5.14,5.14,0,0,0,6-7.39c-14.72-26.53-46.07-96.89-8.89-162.55,0,0,11.5,44.5,50.5,83.5,6,6,14.72,15.64,19.53,21.23,14.47,16.77,32.24,53.55,1.82,98.18a5.63,5.63,0,0,0,6.85,8.34c75.64-32.1,333.29-164.6,128.68-410.85a7,7,0,0,0-12.46,4.15c-1.77,39.31-14.84,129-96.18,148.06a31.08,31.08,0,0,1-24.3-4.16c-13.34-8.9-25.41-32.06.53-92.3a250.9,250.9,0,0,1,20.12-37.29C282.78,220.56,356.18,79.83,157.33.77A5.64,5.64,0,0,0,150.25,8.58Z" fill="url(#hot-updater-gradient)"/></svg>
@@ -0,0 +1,199 @@
1
+ /* eslint-disable */
2
+
3
+ // @ts-nocheck
4
+
5
+ // noinspection JSUnusedGlobalSymbols
6
+
7
+ // This file was automatically generated by TanStack Router.
8
+ // You should NOT make any changes in this file as it will be overwritten.
9
+ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
10
+
11
+ import { Route as rootRouteImport } from './routes/__root'
12
+ import { Route as SigningRouteImport } from './routes/signing'
13
+ import { Route as InstallationsRouteImport } from './routes/installations'
14
+ import { Route as InsightsRouteImport } from './routes/insights'
15
+ import { Route as ApiKeysRouteImport } from './routes/api-keys'
16
+ import { Route as IndexRouteImport } from './routes/index'
17
+ import { Route as ApiAuthSplatRouteImport } from './routes/api/auth/$'
18
+ import { Route as ApiBundlesBundleIdDownloadRouteImport } from './routes/api/bundles/$bundleId/download'
19
+
20
+ const SigningRoute = SigningRouteImport.update({
21
+ id: '/signing',
22
+ path: '/signing',
23
+ getParentRoute: () => rootRouteImport,
24
+ } as any)
25
+ const InstallationsRoute = InstallationsRouteImport.update({
26
+ id: '/installations',
27
+ path: '/installations',
28
+ getParentRoute: () => rootRouteImport,
29
+ } as any)
30
+ const InsightsRoute = InsightsRouteImport.update({
31
+ id: '/insights',
32
+ path: '/insights',
33
+ getParentRoute: () => rootRouteImport,
34
+ } as any)
35
+ const ApiKeysRoute = ApiKeysRouteImport.update({
36
+ id: '/api-keys',
37
+ path: '/api-keys',
38
+ getParentRoute: () => rootRouteImport,
39
+ } as any)
40
+ const IndexRoute = IndexRouteImport.update({
41
+ id: '/',
42
+ path: '/',
43
+ getParentRoute: () => rootRouteImport,
44
+ } as any)
45
+ const ApiAuthSplatRoute = ApiAuthSplatRouteImport.update({
46
+ id: '/api/auth/$',
47
+ path: '/api/auth/$',
48
+ getParentRoute: () => rootRouteImport,
49
+ } as any)
50
+ const ApiBundlesBundleIdDownloadRoute =
51
+ ApiBundlesBundleIdDownloadRouteImport.update({
52
+ id: '/api/bundles/$bundleId/download',
53
+ path: '/api/bundles/$bundleId/download',
54
+ getParentRoute: () => rootRouteImport,
55
+ } as any)
56
+
57
+ export interface FileRoutesByFullPath {
58
+ '/': typeof IndexRoute
59
+ '/api-keys': typeof ApiKeysRoute
60
+ '/insights': typeof InsightsRoute
61
+ '/installations': typeof InstallationsRoute
62
+ '/signing': typeof SigningRoute
63
+ '/api/auth/$': typeof ApiAuthSplatRoute
64
+ '/api/bundles/$bundleId/download': typeof ApiBundlesBundleIdDownloadRoute
65
+ }
66
+ export interface FileRoutesByTo {
67
+ '/': typeof IndexRoute
68
+ '/api-keys': typeof ApiKeysRoute
69
+ '/insights': typeof InsightsRoute
70
+ '/installations': typeof InstallationsRoute
71
+ '/signing': typeof SigningRoute
72
+ '/api/auth/$': typeof ApiAuthSplatRoute
73
+ '/api/bundles/$bundleId/download': typeof ApiBundlesBundleIdDownloadRoute
74
+ }
75
+ export interface FileRoutesById {
76
+ __root__: typeof rootRouteImport
77
+ '/': typeof IndexRoute
78
+ '/api-keys': typeof ApiKeysRoute
79
+ '/insights': typeof InsightsRoute
80
+ '/installations': typeof InstallationsRoute
81
+ '/signing': typeof SigningRoute
82
+ '/api/auth/$': typeof ApiAuthSplatRoute
83
+ '/api/bundles/$bundleId/download': typeof ApiBundlesBundleIdDownloadRoute
84
+ }
85
+ export interface FileRouteTypes {
86
+ fileRoutesByFullPath: FileRoutesByFullPath
87
+ fullPaths:
88
+ | '/'
89
+ | '/api-keys'
90
+ | '/insights'
91
+ | '/installations'
92
+ | '/signing'
93
+ | '/api/auth/$'
94
+ | '/api/bundles/$bundleId/download'
95
+ fileRoutesByTo: FileRoutesByTo
96
+ to:
97
+ | '/'
98
+ | '/api-keys'
99
+ | '/insights'
100
+ | '/installations'
101
+ | '/signing'
102
+ | '/api/auth/$'
103
+ | '/api/bundles/$bundleId/download'
104
+ id:
105
+ | '__root__'
106
+ | '/'
107
+ | '/api-keys'
108
+ | '/insights'
109
+ | '/installations'
110
+ | '/signing'
111
+ | '/api/auth/$'
112
+ | '/api/bundles/$bundleId/download'
113
+ fileRoutesById: FileRoutesById
114
+ }
115
+ export interface RootRouteChildren {
116
+ IndexRoute: typeof IndexRoute
117
+ ApiKeysRoute: typeof ApiKeysRoute
118
+ InsightsRoute: typeof InsightsRoute
119
+ InstallationsRoute: typeof InstallationsRoute
120
+ SigningRoute: typeof SigningRoute
121
+ ApiAuthSplatRoute: typeof ApiAuthSplatRoute
122
+ ApiBundlesBundleIdDownloadRoute: typeof ApiBundlesBundleIdDownloadRoute
123
+ }
124
+
125
+ declare module '@tanstack/react-router' {
126
+ interface FileRoutesByPath {
127
+ '/signing': {
128
+ id: '/signing'
129
+ path: '/signing'
130
+ fullPath: '/signing'
131
+ preLoaderRoute: typeof SigningRouteImport
132
+ parentRoute: typeof rootRouteImport
133
+ }
134
+ '/installations': {
135
+ id: '/installations'
136
+ path: '/installations'
137
+ fullPath: '/installations'
138
+ preLoaderRoute: typeof InstallationsRouteImport
139
+ parentRoute: typeof rootRouteImport
140
+ }
141
+ '/insights': {
142
+ id: '/insights'
143
+ path: '/insights'
144
+ fullPath: '/insights'
145
+ preLoaderRoute: typeof InsightsRouteImport
146
+ parentRoute: typeof rootRouteImport
147
+ }
148
+ '/api-keys': {
149
+ id: '/api-keys'
150
+ path: '/api-keys'
151
+ fullPath: '/api-keys'
152
+ preLoaderRoute: typeof ApiKeysRouteImport
153
+ parentRoute: typeof rootRouteImport
154
+ }
155
+ '/': {
156
+ id: '/'
157
+ path: '/'
158
+ fullPath: '/'
159
+ preLoaderRoute: typeof IndexRouteImport
160
+ parentRoute: typeof rootRouteImport
161
+ }
162
+ '/api/auth/$': {
163
+ id: '/api/auth/$'
164
+ path: '/api/auth/$'
165
+ fullPath: '/api/auth/$'
166
+ preLoaderRoute: typeof ApiAuthSplatRouteImport
167
+ parentRoute: typeof rootRouteImport
168
+ }
169
+ '/api/bundles/$bundleId/download': {
170
+ id: '/api/bundles/$bundleId/download'
171
+ path: '/api/bundles/$bundleId/download'
172
+ fullPath: '/api/bundles/$bundleId/download'
173
+ preLoaderRoute: typeof ApiBundlesBundleIdDownloadRouteImport
174
+ parentRoute: typeof rootRouteImport
175
+ }
176
+ }
177
+ }
178
+
179
+ const rootRouteChildren: RootRouteChildren = {
180
+ IndexRoute: IndexRoute,
181
+ ApiKeysRoute: ApiKeysRoute,
182
+ InsightsRoute: InsightsRoute,
183
+ InstallationsRoute: InstallationsRoute,
184
+ SigningRoute: SigningRoute,
185
+ ApiAuthSplatRoute: ApiAuthSplatRoute,
186
+ ApiBundlesBundleIdDownloadRoute: ApiBundlesBundleIdDownloadRoute,
187
+ }
188
+ export const routeTree = rootRouteImport
189
+ ._addFileChildren(rootRouteChildren)
190
+ ._addFileTypes<FileRouteTypes>()
191
+
192
+ import type { getRouter } from './router.tsx'
193
+ import type { createStart } from '@tanstack/react-start'
194
+ declare module '@tanstack/react-start' {
195
+ interface Register {
196
+ ssr: true
197
+ router: Awaited<ReturnType<typeof getRouter>>
198
+ }
199
+ }
package/src/router.tsx ADDED
@@ -0,0 +1,21 @@
1
+ import { QueryClient } from "@tanstack/react-query";
2
+ import { createRouter } from "@tanstack/react-router";
3
+ import { setupRouterSsrQueryIntegration } from "@tanstack/react-router-ssr-query";
4
+ // Import the route tree selected by the local or hosted Vite integration.
5
+ import { routeTree } from "virtual:hot-updater-console/route-tree";
6
+
7
+ // Create a new router instance
8
+ export const getRouter = () => {
9
+ const queryClient = new QueryClient();
10
+ const router = createRouter({
11
+ routeTree,
12
+ context: { queryClient },
13
+
14
+ scrollRestoration: true,
15
+ defaultPreloadStaleTime: 0,
16
+ });
17
+
18
+ setupRouterSsrQueryIntegration({ router, queryClient });
19
+
20
+ return router;
21
+ };
@@ -0,0 +1,19 @@
1
+ export function validateInstallationsSearch(search: Record<string, unknown>) {
2
+ return {
3
+ query: typeof search.query === "string" ? search.query : undefined,
4
+ installId:
5
+ typeof search.installId === "string" ? search.installId : undefined,
6
+ searchOffset:
7
+ typeof search.searchOffset === "number" &&
8
+ Number.isSafeInteger(search.searchOffset) &&
9
+ search.searchOffset >= 0
10
+ ? search.searchOffset
11
+ : 0,
12
+ historyOffset:
13
+ typeof search.historyOffset === "number" &&
14
+ Number.isSafeInteger(search.historyOffset) &&
15
+ search.historyOffset >= 0
16
+ ? search.historyOffset
17
+ : 0,
18
+ };
19
+ }
@@ -0,0 +1,74 @@
1
+ export interface ReleaseSearch {
2
+ afterReleaseId?: string;
3
+ beforeReleaseId?: string;
4
+ bundleId?: string;
5
+ channelId?: string;
6
+ enabled?: boolean;
7
+ page?: number;
8
+ platform?: "ios" | "android";
9
+ releaseId?: string;
10
+ targetAppVersion?: string;
11
+ }
12
+
13
+ const optionalString = (value: unknown) =>
14
+ typeof value === "string" && value.trim().length > 0
15
+ ? value.trim()
16
+ : undefined;
17
+
18
+ export function validateReleaseSearch(
19
+ search: Record<string, unknown>,
20
+ ): ReleaseSearch {
21
+ const parsedPage =
22
+ typeof search.page === "number"
23
+ ? search.page
24
+ : typeof search.page === "string"
25
+ ? Number(search.page)
26
+ : undefined;
27
+ const beforeReleaseId = optionalString(search.beforeReleaseId);
28
+ const afterReleaseId = beforeReleaseId
29
+ ? undefined
30
+ : optionalString(search.afterReleaseId);
31
+ const enabled =
32
+ search.enabled === true || search.enabled === "true"
33
+ ? true
34
+ : search.enabled === false || search.enabled === "false"
35
+ ? false
36
+ : undefined;
37
+
38
+ return {
39
+ afterReleaseId,
40
+ beforeReleaseId,
41
+ bundleId: optionalString(search.bundleId),
42
+ channelId: optionalString(search.channelId),
43
+ enabled,
44
+ page:
45
+ parsedPage !== undefined && Number.isInteger(parsedPage) && parsedPage > 1
46
+ ? parsedPage
47
+ : undefined,
48
+ platform:
49
+ search.platform === "ios" || search.platform === "android"
50
+ ? search.platform
51
+ : undefined,
52
+ releaseId: optionalString(search.releaseId),
53
+ targetAppVersion: optionalString(search.targetAppVersion),
54
+ };
55
+ }
56
+
57
+ export function updateReleaseFilters(
58
+ current: ReleaseSearch,
59
+ filters: Partial<
60
+ Pick<
61
+ ReleaseSearch,
62
+ "bundleId" | "channelId" | "enabled" | "platform" | "targetAppVersion"
63
+ >
64
+ >,
65
+ ): ReleaseSearch {
66
+ return {
67
+ ...current,
68
+ ...filters,
69
+ afterReleaseId: undefined,
70
+ beforeReleaseId: undefined,
71
+ page: undefined,
72
+ releaseId: undefined,
73
+ };
74
+ }
@@ -0,0 +1,174 @@
1
+ import type { QueryClient } from "@tanstack/react-query";
2
+ import {
3
+ createRootRouteWithContext,
4
+ HeadContent,
5
+ Outlet,
6
+ Scripts,
7
+ } from "@tanstack/react-router";
8
+ import { useEffect, useState } from "react";
9
+
10
+ import { AppSidebar } from "@/components/AppSidebar";
11
+ import { ConsoleAccessPage } from "@/components/ConsoleAccessPage";
12
+ import { InsightsCapabilityProvider } from "@/components/features/insights/InsightsCapabilityContext";
13
+ import { NotFoundPage } from "@/components/NotFoundPage";
14
+ import { ThemeProvider } from "@/components/ThemeProvider";
15
+ import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
16
+ import { Toaster } from "@/components/ui/sonner";
17
+ import { TooltipProvider } from "@/components/ui/tooltip";
18
+ import {
19
+ getConsoleAccessRpc,
20
+ getConsoleAuthProvidersRpc,
21
+ } from "@/lib/auth-rpc";
22
+ import {
23
+ getInsightsCapabilityState,
24
+ useInsightsCapabilitiesQuery,
25
+ } from "@/lib/insights-api";
26
+
27
+ import appCss from "../styles.css?url";
28
+
29
+ const LOCAL_DEBUG_HOSTS = new Set(["127.0.0.1", "localhost"]);
30
+
31
+ type LocalDevtools = {
32
+ readonly Devtools: typeof import("@tanstack/react-devtools").TanStackDevtools;
33
+ readonly RouterPanel: typeof import("@tanstack/react-router-devtools").TanStackRouterDevtoolsPanel;
34
+ };
35
+
36
+ export const Route = createRootRouteWithContext<{
37
+ readonly queryClient: QueryClient;
38
+ }>()({
39
+ head: () => ({
40
+ meta: [
41
+ {
42
+ charSet: "utf-8",
43
+ },
44
+ {
45
+ name: "viewport",
46
+ content: "width=device-width, initial-scale=1, viewport-fit=cover",
47
+ },
48
+ {
49
+ name: "theme-color",
50
+ content: "#1f1d1c",
51
+ },
52
+ {
53
+ name: "color-scheme",
54
+ content: "dark light",
55
+ },
56
+ {
57
+ title: "Hot Updater Console",
58
+ },
59
+ ],
60
+ links: [
61
+ {
62
+ rel: "stylesheet",
63
+ href: appCss,
64
+ },
65
+ ],
66
+ }),
67
+
68
+ loader: async () => {
69
+ const [access, providers] = await Promise.all([
70
+ getConsoleAccessRpc(),
71
+ getConsoleAuthProvidersRpc(),
72
+ ]);
73
+ return { access, providers };
74
+ },
75
+
76
+ component: RootLayout,
77
+ notFoundComponent: NotFoundPage,
78
+ shellComponent: RootDocument,
79
+ });
80
+
81
+ export function RootDocument({ children }: { children: React.ReactNode }) {
82
+ const [localDevtools, setLocalDevtools] = useState<LocalDevtools | null>(
83
+ null,
84
+ );
85
+
86
+ useEffect(() => {
87
+ if (!import.meta.env.DEV || typeof window === "undefined") {
88
+ return;
89
+ }
90
+
91
+ if (LOCAL_DEBUG_HOSTS.has(window.location.hostname)) {
92
+ void Promise.all([
93
+ import("@tanstack/react-devtools"),
94
+ import("@tanstack/react-router-devtools"),
95
+ import("react-grab/core"),
96
+ ]).then(
97
+ ([{ TanStackDevtools }, { TanStackRouterDevtoolsPanel }, { init }]) => {
98
+ setLocalDevtools({
99
+ Devtools: TanStackDevtools,
100
+ RouterPanel: TanStackRouterDevtoolsPanel,
101
+ });
102
+ init({
103
+ activationKey: (event) =>
104
+ event.key.toLowerCase() === "c" && event.metaKey,
105
+ });
106
+ },
107
+ );
108
+ }
109
+ }, []);
110
+ return (
111
+ <html lang="en" suppressHydrationWarning>
112
+ <head>
113
+ <HeadContent />
114
+ </head>
115
+ <body>
116
+ <ThemeProvider defaultTheme="dark">
117
+ <TooltipProvider>
118
+ {children}
119
+ <Toaster />
120
+ {localDevtools ? (
121
+ <localDevtools.Devtools
122
+ config={{
123
+ position: "bottom-right",
124
+ }}
125
+ plugins={[
126
+ {
127
+ name: "Tanstack Router",
128
+ render: <localDevtools.RouterPanel />,
129
+ },
130
+ ]}
131
+ />
132
+ ) : null}
133
+ </TooltipProvider>
134
+ </ThemeProvider>
135
+ <Scripts />
136
+ </body>
137
+ </html>
138
+ );
139
+ }
140
+
141
+ function RootLayout() {
142
+ const { access, providers } = Route.useLoaderData();
143
+
144
+ if (access.status !== "authorized") {
145
+ return <ConsoleAccessPage access={access} providers={providers} />;
146
+ }
147
+
148
+ return <AuthorizedConsole />;
149
+ }
150
+
151
+ function AuthorizedConsole() {
152
+ const capabilityQuery = useInsightsCapabilitiesQuery();
153
+ const capability = getInsightsCapabilityState(capabilityQuery);
154
+
155
+ useEffect(() => {
156
+ if (
157
+ import.meta.env.DEV &&
158
+ typeof window !== "undefined" &&
159
+ LOCAL_DEBUG_HOSTS.has(window.location.hostname)
160
+ ) {
161
+ void import("react-grab");
162
+ }
163
+ }, []);
164
+ return (
165
+ <InsightsCapabilityProvider value={capability}>
166
+ <SidebarProvider>
167
+ <AppSidebar />
168
+ <SidebarInset className="min-h-0 min-w-0 overflow-hidden">
169
+ <Outlet />
170
+ </SidebarInset>
171
+ </SidebarProvider>
172
+ </InsightsCapabilityProvider>
173
+ );
174
+ }
@@ -0,0 +1,19 @@
1
+ import { createFileRoute } from "@tanstack/react-router";
2
+
3
+ const handleAuthRequest = async ({
4
+ request,
5
+ }: {
6
+ readonly request: Request;
7
+ }) => {
8
+ const { handleConsoleAuth } = await import("@/lib/server/auth.server");
9
+ return handleConsoleAuth(request);
10
+ };
11
+
12
+ export const Route = createFileRoute("/api/auth/$")({
13
+ server: {
14
+ handlers: {
15
+ GET: handleAuthRequest,
16
+ POST: handleAuthRequest,
17
+ },
18
+ },
19
+ });