@hot-updater/console 0.36.7 → 1.0.0-rc.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 (266) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/AnalyticsCapabilityContext-BCke57-7.js +1 -0
  3. package/.output/public/assets/BundleActivityChart-CnsBe_1J.js +42 -0
  4. package/.output/public/assets/BundleIdDisplay-Dky75zLU.js +1 -0
  5. package/.output/public/assets/alert-C-1KvDW8.js +1 -0
  6. package/.output/public/assets/analytics-Bqz0c-BT.js +1 -0
  7. package/.output/public/assets/analytics-api-CJdrTLLO.js +1 -0
  8. package/.output/public/assets/api-B_Rh-uzC.js +1 -0
  9. package/.output/public/assets/api-keys-BurCFhzH.js +1 -0
  10. package/.output/public/assets/api-keys-api-BEDJdQg3.js +1 -0
  11. package/.output/public/assets/badge-Bf0fZ-Gx.js +1 -0
  12. package/.output/public/assets/chart-no-axes-combined-FaYMGmQS.js +1 -0
  13. package/.output/public/assets/createServerFn-gM2-LFBy.js +3 -0
  14. package/.output/public/assets/dialog-BS60BRr2.js +1 -0
  15. package/.output/public/assets/dist-DspNKfMj.js +1 -0
  16. package/.output/public/assets/download-O-yNBc9H.js +1 -0
  17. package/.output/public/assets/field-Buq148-A.js +1 -0
  18. package/.output/public/assets/index-kFhd2S78.js +10 -0
  19. package/.output/public/assets/installations-BrQ-od41.js +2 -0
  20. package/.output/public/assets/installations-CGlZ3ROz.js +1 -0
  21. package/.output/public/assets/lazyRouteComponent-rPzgA7S-.js +1 -0
  22. package/.output/public/assets/loader-circle-DCYF-wJl.js +1 -0
  23. package/.output/public/assets/preload-helper-TKOBYGYD.js +1 -0
  24. package/.output/public/assets/refresh-cw-n8XYbPPj.js +1 -0
  25. package/.output/public/assets/routes-CrdokJ80.js +1 -0
  26. package/.output/public/assets/routes-DcQnVajO.js +2 -0
  27. package/.output/public/assets/search-DLhbWw3W.js +1 -0
  28. package/.output/public/assets/shield-check-DE_hVuHu.js +1 -0
  29. package/.output/public/assets/signing-HRexfjsH.js +1 -0
  30. package/.output/public/assets/styles-eB4EtvFS.css +2 -0
  31. package/.output/public/assets/table-D8CLieHX.js +1 -0
  32. package/.output/public/assets/textarea-YJLTVAoq.js +1 -0
  33. package/.output/public/assets/useMutation-JsGVjQMY.js +1 -0
  34. package/.output/public/assets/useOpenInteractionType-4cRD5xO0.js +1 -0
  35. package/.output/server/__23tanstack-start-server-fn-resolver-DBP9TQ7K.mjs +146 -0
  36. package/.output/server/_libs/@base-ui/react+[...].mjs +16124 -0
  37. package/.output/server/_libs/@reduxjs/toolkit+[...].mjs +1554 -0
  38. package/.output/server/_libs/@tanstack/react-router+[...].mjs +41 -10
  39. package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +132 -0
  40. package/.output/server/_libs/d3-array.mjs +231 -0
  41. package/.output/server/_libs/d3-color.mjs +330 -0
  42. package/.output/server/_libs/d3-format.mjs +252 -0
  43. package/.output/server/_libs/d3-interpolate.mjs +207 -0
  44. package/.output/server/_libs/d3-path.mjs +77 -0
  45. package/.output/server/_libs/d3-scale+[...].mjs +1907 -0
  46. package/.output/server/_libs/d3-shape.mjs +818 -0
  47. package/.output/server/_libs/decimal.js-light.mjs +884 -0
  48. package/.output/server/_libs/es-toolkit.mjs +1134 -0
  49. package/.output/server/_libs/eventemitter3.mjs +236 -0
  50. package/.output/server/_libs/immer.mjs +532 -0
  51. package/.output/server/_libs/lucide-react.mjs +141 -13
  52. package/.output/server/_libs/next-themes.mjs +2 -2
  53. package/.output/server/_libs/react-redux+[...].mjs +205 -0
  54. package/.output/server/_libs/recharts+[...].mjs +14117 -0
  55. package/.output/server/_libs/sonner.mjs +2 -2
  56. package/.output/server/_libs/tanstack__query-core.mjs +114 -1
  57. package/.output/server/_libs/tanstack__react-query.mjs +3 -4
  58. package/.output/server/_runtime.mjs +10 -1
  59. package/.output/server/_ssr/AnalyticsCapabilityContext-ui4DX8GO.mjs +15 -0
  60. package/.output/server/_ssr/BundleActivityChart-BiB9ZORH.mjs +288 -0
  61. package/.output/server/_ssr/BundleIdDisplay-Czk6iR6p.mjs +13 -0
  62. package/.output/server/_ssr/alert-Dim3MGY5.mjs +86 -0
  63. package/.output/server/_ssr/analytics-DtMTn8fn.mjs +858 -0
  64. package/.output/server/_ssr/analytics-api-Cg1M5v_H.mjs +71 -0
  65. package/.output/server/_ssr/analytics-input-fRRRn0uA.mjs +96 -0
  66. package/.output/server/_ssr/analytics-rpc-DY5uXvHT.mjs +155 -0
  67. package/.output/server/_ssr/api-BTSv1tZb.mjs +282 -0
  68. package/.output/server/_ssr/api-keys-BnM_o2nS.mjs +300 -0
  69. package/.output/server/_ssr/api-keys-api-CIywsSF5.mjs +60 -0
  70. package/.output/server/_ssr/api-keys-rpc-BSp1wkIV.mjs +136 -0
  71. package/.output/server/_ssr/api-rpc-cG5wd_tO.mjs +1468 -0
  72. package/.output/server/_ssr/auth-BF8mbAgF.mjs +21 -0
  73. package/.output/server/_ssr/auth-rpc-BrJ6obHJ.mjs +24 -0
  74. package/.output/server/_ssr/auth.server-DwKoZoQM.mjs +50 -0
  75. package/.output/server/_ssr/badge-DCxbEeof.mjs +26 -0
  76. package/.output/server/_ssr/bundle-signing-rpc-COoQhW8E.mjs +16 -0
  77. package/.output/server/_ssr/bundle-signing.server-D5u8KkMp.mjs +51 -0
  78. package/.output/server/_ssr/config-Dv8qLDZ0.mjs +13 -0
  79. package/.output/server/_ssr/config.server-DEiLhKmy.mjs +361 -0
  80. package/.output/server/_ssr/{createServerFn-DzU0k62V.mjs → createServerFn-B2hcXoPM.mjs} +1 -1
  81. package/.output/server/_ssr/createServerRpc-DB29C_5p.mjs +12 -0
  82. package/.output/server/_ssr/{sidebar-Cv_H0_Jv.mjs → createSsrRpc-9P0S0lJ3.mjs} +68 -50
  83. package/.output/server/_ssr/databaseRows-CwbNwYSl.mjs +166 -0
  84. package/.output/server/_ssr/deleteBundle-Cu_XOMt0.mjs +66 -0
  85. package/.output/server/_ssr/dialog-CQuRMNZ4.mjs +170 -0
  86. package/.output/server/_ssr/dist-BVSLTpoP.mjs +250 -0
  87. package/.output/server/_ssr/downloadBundle-CNnYxYO2.mjs +24 -0
  88. package/.output/server/_ssr/{empty-plugin-adapters-B4HCzgmM.mjs → empty-plugin-adapters-B5QvGpEl.mjs} +1 -1
  89. package/.output/server/_ssr/field-DRIXKjZG.mjs +74 -0
  90. package/.output/server/_ssr/{getBundleChildren-CVtSiH5t.mjs → getBundleChildren-BcRMFoDF.mjs} +10 -18
  91. package/.output/server/_ssr/installations-B-azgAvX.mjs +19 -0
  92. package/.output/server/_ssr/installations-Ctvzp2JH.mjs +535 -0
  93. package/.output/server/_ssr/router-C-KUBfOi.mjs +513 -0
  94. package/.output/server/_ssr/routes-BM4ht9jb.mjs +2286 -0
  95. package/.output/server/_ssr/routes-Z7TwC6nC.mjs +37 -0
  96. package/.output/server/_ssr/runtime.server-awTLiiXW.mjs +527 -0
  97. package/.output/server/_ssr/server-BjzmvXnO.mjs +7468 -0
  98. package/.output/server/_ssr/signing-k8GxyBDF.mjs +202 -0
  99. package/.output/server/_ssr/ssr.mjs +4 -5740
  100. package/.output/server/_ssr/start-DpViQGnt.mjs +4 -0
  101. package/.output/server/_ssr/table-v1yCvYo7.mjs +53 -0
  102. package/.output/server/_ssr/textarea-DYHnskhb.mjs +14 -0
  103. package/.output/server/_ssr/uuidv7-BbJ-9D_I.mjs +31 -0
  104. package/.output/server/_tanstack-start-manifest_v-CH4StzJu.mjs +108 -0
  105. package/.output/server/index.mjs +256 -53
  106. package/README.md +29 -8
  107. package/lib/index.d.mts +36 -0
  108. package/lib/index.mjs +4 -0
  109. package/lib/vite-internal.mjs +127 -0
  110. package/lib/vite.d.mts +10 -0
  111. package/lib/vite.mjs +5 -0
  112. package/package.json +51 -29
  113. package/public/apple-touch-icon.png +0 -0
  114. package/public/favicon-16x16.png +0 -0
  115. package/public/favicon-32x32.png +0 -0
  116. package/public/favicon.ico +0 -0
  117. package/public/logo.svg +1 -0
  118. package/public/manifest.json +30 -0
  119. package/public/robots.txt +3 -0
  120. package/src/components/AppSidebar.tsx +159 -0
  121. package/src/components/BundleIdDisplay.tsx +18 -0
  122. package/src/components/ChannelBadge.tsx +31 -0
  123. package/src/components/ConsoleAccessPage.tsx +122 -0
  124. package/src/components/EnabledStatusIcon.tsx +54 -0
  125. package/src/components/HashValueDisplay.tsx +85 -0
  126. package/src/components/HotUpdaterLogo.tsx +30 -0
  127. package/src/components/NotFoundPage.tsx +33 -0
  128. package/src/components/PlatformIcon.tsx +39 -0
  129. package/src/components/RolloutPercentageBadge.tsx +28 -0
  130. package/src/components/ThemeProvider.tsx +116 -0
  131. package/src/components/TimestampDisplay.tsx +41 -0
  132. package/src/components/features/analytics/ActivityChart.tsx +150 -0
  133. package/src/components/features/analytics/AnalyticsCapabilityContext.tsx +24 -0
  134. package/src/components/features/analytics/AnalyticsControls.tsx +115 -0
  135. package/src/components/features/analytics/AnalyticsErrorAlert.tsx +21 -0
  136. package/src/components/features/analytics/AnalyticsOverview.tsx +208 -0
  137. package/src/components/features/analytics/BundleDistribution.tsx +133 -0
  138. package/src/components/features/analytics/BundleSelector.tsx +114 -0
  139. package/src/components/features/analytics/InstallationHistoryCard.tsx +248 -0
  140. package/src/components/features/analytics/InstallationMatchesCard.tsx +132 -0
  141. package/src/components/features/analytics/InstallationPageHeader.tsx +138 -0
  142. package/src/components/features/analytics/InstallationPagination.tsx +48 -0
  143. package/src/components/features/analytics/InstallationSearch.tsx +230 -0
  144. package/src/components/features/analytics/UpdateOutcomes.tsx +147 -0
  145. package/src/components/features/api-keys/ApiKeysPage.tsx +377 -0
  146. package/src/components/features/bundles/BundleActivityChart.tsx +193 -0
  147. package/src/components/features/bundles/BundleAnalyticsSummary.tsx +123 -0
  148. package/src/components/features/bundles/BundleChildrenPanel.tsx +201 -0
  149. package/src/components/features/bundles/BundleMetadata.tsx +136 -0
  150. package/src/components/features/bundles/RolloutCohortsDialog.tsx +201 -0
  151. package/src/components/features/channels/ChannelManagementDialog.tsx +228 -0
  152. package/src/components/features/releases/ReleaseEditorSheet.tsx +879 -0
  153. package/src/components/features/releases/ReleaseStateBadge.tsx +20 -0
  154. package/src/components/features/signing/BundleSigningPage.tsx +247 -0
  155. package/src/components/ui/alert-dialog.tsx +185 -0
  156. package/src/components/ui/alert.tsx +76 -0
  157. package/src/components/ui/badge.tsx +49 -0
  158. package/src/components/ui/button.tsx +58 -0
  159. package/src/components/ui/card.tsx +89 -0
  160. package/src/components/ui/chart.tsx +371 -0
  161. package/src/components/ui/combobox.tsx +303 -0
  162. package/src/components/ui/dialog.tsx +156 -0
  163. package/src/components/ui/dropdown-menu.tsx +270 -0
  164. package/src/components/ui/field.tsx +235 -0
  165. package/src/components/ui/input-group.tsx +147 -0
  166. package/src/components/ui/input.tsx +19 -0
  167. package/src/components/ui/label.tsx +25 -0
  168. package/src/components/ui/progress.tsx +84 -0
  169. package/src/components/ui/select.tsx +205 -0
  170. package/src/components/ui/separator.tsx +23 -0
  171. package/src/components/ui/sheet.tsx +132 -0
  172. package/src/components/ui/sidebar.tsx +779 -0
  173. package/src/components/ui/skeleton.tsx +13 -0
  174. package/src/components/ui/slider.tsx +56 -0
  175. package/src/components/ui/sonner.tsx +45 -0
  176. package/src/components/ui/switch.tsx +25 -0
  177. package/src/components/ui/table.tsx +114 -0
  178. package/src/components/ui/textarea.tsx +18 -0
  179. package/src/components/ui/toggle-group.tsx +89 -0
  180. package/src/components/ui/toggle.tsx +43 -0
  181. package/src/components/ui/tooltip.tsx +66 -0
  182. package/src/hooks/use-mobile.ts +30 -0
  183. package/src/index.ts +48 -0
  184. package/src/lib/analytics-api.ts +145 -0
  185. package/src/lib/analytics-error.ts +19 -0
  186. package/src/lib/analytics-input.ts +187 -0
  187. package/src/lib/analytics-overview.ts +170 -0
  188. package/src/lib/analytics-rpc.ts +218 -0
  189. package/src/lib/api-keys-api.ts +69 -0
  190. package/src/lib/api-keys-rpc.ts +87 -0
  191. package/src/lib/api-rpc.ts +478 -0
  192. package/src/lib/api.ts +444 -0
  193. package/src/lib/auth-rpc.ts +16 -0
  194. package/src/lib/bundle-signing-api.ts +14 -0
  195. package/src/lib/bundle-signing-rpc.ts +16 -0
  196. package/src/lib/constants.ts +1 -0
  197. package/src/lib/git.ts +51 -0
  198. package/src/lib/server/auth.server.ts +43 -0
  199. package/src/lib/server/bundle-signing.server.ts +100 -0
  200. package/src/lib/server/config.server.ts +109 -0
  201. package/src/lib/server/console-runtime.server.ts +70 -0
  202. package/src/lib/server/deleteBundle.ts +145 -0
  203. package/src/lib/server/downloadBundle.ts +46 -0
  204. package/src/lib/server/getBundleChildren.ts +130 -0
  205. package/src/lib/server/listReleases.ts +61 -0
  206. package/src/lib/server/releaseActivity.ts +27 -0
  207. package/src/lib/server/releaseReachability.ts +95 -0
  208. package/src/lib/server/runtime.server.ts +172 -0
  209. package/src/lib/utils.ts +9 -0
  210. package/src/logo.svg +1 -0
  211. package/src/routeTree.gen.ts +199 -0
  212. package/src/router.tsx +21 -0
  213. package/src/routes/-installations-search.ts +19 -0
  214. package/src/routes/-releases-search.ts +74 -0
  215. package/src/routes/__root.tsx +174 -0
  216. package/src/routes/analytics.tsx +139 -0
  217. package/src/routes/api/auth/$.ts +19 -0
  218. package/src/routes/api/bundles/$bundleId/download.ts +17 -0
  219. package/src/routes/api-keys.tsx +37 -0
  220. package/src/routes/index.tsx +982 -0
  221. package/src/routes/installations.tsx +194 -0
  222. package/src/routes/signing.tsx +37 -0
  223. package/src/styles.css +152 -0
  224. package/src/virtual-modules.d.ts +21 -0
  225. package/src/vite-env.d.ts +1 -0
  226. package/src/vite-internal.ts +195 -0
  227. package/src/vite.ts +12 -0
  228. package/.output/public/assets/dist-DJX53faP.js +0 -3
  229. package/.output/public/assets/index-Iylis5kY.js +0 -10
  230. package/.output/public/assets/routes-HT21hs0C.js +0 -54
  231. package/.output/public/assets/styles-C0Re4quB.css +0 -2
  232. package/.output/server/__23tanstack-start-server-fn-resolver-CgwgZ0-i.mjs +0 -66
  233. package/.output/server/_libs/@floating-ui/core+[...].mjs +0 -698
  234. package/.output/server/_libs/@floating-ui/dom+[...].mjs +0 -644
  235. package/.output/server/_libs/@floating-ui/react-dom+[...].mjs +0 -839
  236. package/.output/server/_libs/@radix-ui/react-alert-dialog+[...].mjs +0 -2138
  237. package/.output/server/_libs/@radix-ui/react-popper+[...].mjs +0 -308
  238. package/.output/server/_libs/@radix-ui/react-select+[...].mjs +0 -1038
  239. package/.output/server/_libs/@tanstack/devtools-event-client+[...].mjs +0 -196
  240. package/.output/server/_libs/@tanstack/form-core+[...].mjs +0 -3228
  241. package/.output/server/_libs/@tanstack/react-form+[...].mjs +0 -430
  242. package/.output/server/_libs/@tanstack/react-table+[...].mjs +0 -2382
  243. package/.output/server/_libs/radix-ui__number.mjs +0 -6
  244. package/.output/server/_libs/radix-ui__primitive.mjs +0 -9
  245. package/.output/server/_libs/radix-ui__react-arrow.mjs +0 -23
  246. package/.output/server/_libs/radix-ui__react-collection.mjs +0 -78
  247. package/.output/server/_libs/radix-ui__react-direction.mjs +0 -13
  248. package/.output/server/_libs/radix-ui__react-label.mjs +0 -22
  249. package/.output/server/_libs/radix-ui__react-separator.mjs +0 -30
  250. package/.output/server/_libs/radix-ui__react-slider.mjs +0 -510
  251. package/.output/server/_libs/radix-ui__react-switch.mjs +0 -157
  252. package/.output/server/_libs/radix-ui__react-tooltip.mjs +0 -493
  253. package/.output/server/_ssr/api-rpc-BIbDUKSz.mjs +0 -292
  254. package/.output/server/_ssr/config.server-BSS366KT.mjs +0 -41
  255. package/.output/server/_ssr/deleteBundle-BgbZTtpW.mjs +0 -141
  256. package/.output/server/_ssr/dist-KboydRVk.mjs +0 -120
  257. package/.output/server/_ssr/router-C3hqEy3k.mjs +0 -307
  258. package/.output/server/_ssr/routes-B0lQvvlC.mjs +0 -3048
  259. package/.output/server/_ssr/start-02sLteYG.mjs +0 -4
  260. package/.output/server/_ssr/storageProfile-FVkjPoGb.mjs +0 -9
  261. package/.output/server/_tanstack-start-manifest_v-Bpx52WOU.mjs +0 -20
  262. package/.output/server/node_modules/tslib/modules/index.js +0 -70
  263. package/.output/server/node_modules/tslib/modules/package.json +0 -3
  264. package/.output/server/node_modules/tslib/package.json +0 -47
  265. package/.output/server/node_modules/tslib/tslib.js +0 -484
  266. package/.output/server/package.json +0 -9
@@ -0,0 +1,172 @@
1
+ import type {
2
+ AnalyticsModel,
3
+ ApiKeyModel,
4
+ BundleRepository,
5
+ } from "@hot-updater/plugin-core";
6
+ import {
7
+ type ActiveInstallationInput,
8
+ type AnalyticsProvider,
9
+ createAnalyticsProvider,
10
+ type InstallationHistoryRow,
11
+ type InstallationSearchRow,
12
+ type OffsetPaginationResult,
13
+ } from "@hot-updater/server";
14
+
15
+ import {
16
+ parseActiveInstallationInput,
17
+ parseBundleEventAnalyticsInput,
18
+ parseBundleEventSummaryInput,
19
+ parseInstallationHistoryInput,
20
+ parseSearchInstallationsInput,
21
+ } from "../analytics-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
+ }): AnalyticsProvider | null {
31
+ const models: unknown = Reflect.get(config.database, "models");
32
+ const analytics: unknown =
33
+ typeof models === "object" && models !== null
34
+ ? Reflect.get(models, "analytics")
35
+ : undefined;
36
+ if (
37
+ typeof analytics !== "object" ||
38
+ analytics === null ||
39
+ typeof Reflect.get(analytics, "append") !== "function" ||
40
+ typeof Reflect.get(analytics, "scan") !== "function"
41
+ ) {
42
+ return null;
43
+ }
44
+ return createAnalyticsProvider(analytics as AnalyticsModel);
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
+ "getBundleEventAnalytics",
73
+ "getBundleEventOverview",
74
+ "getActiveInstallationOverview",
75
+ "searchInstallations",
76
+ "getInstallationHistory",
77
+ ] as const;
78
+
79
+ const parseAnalyticsProvider = (value: unknown): AnalyticsProvider | 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 AnalyticsProvider;
92
+ };
93
+
94
+ export const getAnalyticsCapability = async (provider: unknown) => {
95
+ const parsed = parseAnalyticsProvider(provider);
96
+ return parsed === null
97
+ ? ({
98
+ analytics: false,
99
+ analyticsQueries: false,
100
+ eventIngestion: false,
101
+ } as const)
102
+ : ({
103
+ analytics: true,
104
+ analyticsQueries: true,
105
+ eventIngestion: true,
106
+ maxMatchingRows: parsed.maxMatchingRows,
107
+ mode: "bounded",
108
+ } as const);
109
+ };
110
+
111
+ const requireAnalyticsSupport = async (
112
+ provider: unknown,
113
+ ): Promise<AnalyticsProvider> => {
114
+ const parsedProvider = parseAnalyticsProvider(provider);
115
+ if (parsedProvider === null) {
116
+ throw new Error(
117
+ "Analytics 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 requireAnalyticsSupport(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 (
136
+ await requireAnalyticsSupport(provider)
137
+ ).getActiveInstallationOverview(parsed);
138
+ }
139
+
140
+ export async function getBundleEventAnalytics(
141
+ provider: unknown,
142
+ input: unknown,
143
+ ) {
144
+ const parsed = parseBundleEventAnalyticsInput(input);
145
+ return (await requireAnalyticsSupport(provider)).getBundleEventAnalytics(
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 requireAnalyticsSupport(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 requireAnalyticsSupport(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 ApiKeysRouteImport } from './routes/api-keys'
15
+ import { Route as AnalyticsRouteImport } from './routes/analytics'
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 ApiKeysRoute = ApiKeysRouteImport.update({
31
+ id: '/api-keys',
32
+ path: '/api-keys',
33
+ getParentRoute: () => rootRouteImport,
34
+ } as any)
35
+ const AnalyticsRoute = AnalyticsRouteImport.update({
36
+ id: '/analytics',
37
+ path: '/analytics',
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
+ '/analytics': typeof AnalyticsRoute
60
+ '/api-keys': typeof ApiKeysRoute
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
+ '/analytics': typeof AnalyticsRoute
69
+ '/api-keys': typeof ApiKeysRoute
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
+ '/analytics': typeof AnalyticsRoute
79
+ '/api-keys': typeof ApiKeysRoute
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
+ | '/analytics'
90
+ | '/api-keys'
91
+ | '/installations'
92
+ | '/signing'
93
+ | '/api/auth/$'
94
+ | '/api/bundles/$bundleId/download'
95
+ fileRoutesByTo: FileRoutesByTo
96
+ to:
97
+ | '/'
98
+ | '/analytics'
99
+ | '/api-keys'
100
+ | '/installations'
101
+ | '/signing'
102
+ | '/api/auth/$'
103
+ | '/api/bundles/$bundleId/download'
104
+ id:
105
+ | '__root__'
106
+ | '/'
107
+ | '/analytics'
108
+ | '/api-keys'
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
+ AnalyticsRoute: typeof AnalyticsRoute
118
+ ApiKeysRoute: typeof ApiKeysRoute
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
+ '/api-keys': {
142
+ id: '/api-keys'
143
+ path: '/api-keys'
144
+ fullPath: '/api-keys'
145
+ preLoaderRoute: typeof ApiKeysRouteImport
146
+ parentRoute: typeof rootRouteImport
147
+ }
148
+ '/analytics': {
149
+ id: '/analytics'
150
+ path: '/analytics'
151
+ fullPath: '/analytics'
152
+ preLoaderRoute: typeof AnalyticsRouteImport
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
+ AnalyticsRoute: AnalyticsRoute,
182
+ ApiKeysRoute: ApiKeysRoute,
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 { AnalyticsCapabilityProvider } from "@/components/features/analytics/AnalyticsCapabilityContext";
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
+ getAnalyticsCapabilityState,
20
+ useAnalyticsCapabilitiesQuery,
21
+ } from "@/lib/analytics-api";
22
+ import {
23
+ getConsoleAccessRpc,
24
+ getConsoleAuthProvidersRpc,
25
+ } from "@/lib/auth-rpc";
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 = useAnalyticsCapabilitiesQuery();
153
+ const capability = getAnalyticsCapabilityState(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
+ <AnalyticsCapabilityProvider value={capability}>
166
+ <SidebarProvider>
167
+ <AppSidebar />
168
+ <SidebarInset className="min-h-0 min-w-0 overflow-hidden">
169
+ <Outlet />
170
+ </SidebarInset>
171
+ </SidebarProvider>
172
+ </AnalyticsCapabilityProvider>
173
+ );
174
+ }