@hot-updater/console 0.36.6 → 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 +249 -46
  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
package/README.md CHANGED
@@ -17,6 +17,7 @@ Modern web-based management console for Hot Updater built with **TanStack Start*
17
17
  ## 🛠️ Tech Stack
18
18
 
19
19
  ### Frontend
20
+
20
21
  - **TanStack Start** - Full-stack React framework with SSR
21
22
  - **TanStack Router** - File-based routing with type safety
22
23
  - **TanStack Query** - Data fetching and caching
@@ -25,14 +26,16 @@ Modern web-based management console for Hot Updater built with **TanStack Start*
25
26
  - **React 19** - Latest React features
26
27
 
27
28
  ### UI Components
29
+
28
30
  - **shadcn/ui** - High-quality accessible components (Radix Mira style)
29
31
  - **Tailwind CSS v4** - Utility-first CSS with oklch color system
30
32
  - **Lucide React** - Beautiful icon library
31
33
  - **Sonner** - Toast notifications
32
34
 
33
35
  ### Backend
36
+
34
37
  - **TanStack Start Server Functions** - Type-safe server endpoints
35
- - **Hot Updater Plugins** - Storage and database plugin integration
38
+ - **Hot Updater Integration** - Storage and database plugin integration
36
39
 
37
40
  ## 📦 Installation
38
41
 
@@ -92,23 +95,26 @@ src/
92
95
  ## 🎨 Key Components
93
96
 
94
97
  ### Bundle List Page
98
+
95
99
  - **FilterToolbar** - Platform and channel filters with reset button
96
100
  - **BundlesTable** - Server-side paginated table (20 per page)
97
101
  - **BundleTableColumns** - Column definitions with custom cell renderers
98
102
 
99
103
  ### Bundle Editor Sheet
104
+
100
105
  - **BundleEditorSheet** - Right-side slide-out panel
101
106
  - **BundleEditorForm** - TanStack Form with validation
102
107
  - **BundleMetadata** - Read-only bundle information display
103
108
 
104
109
  ### Dialogs
110
+
105
111
  - **PromoteChannelDialog** - Channel promotion with copy/move toggle
106
112
  - **DeleteBundleDialog** - Confirmation dialog with bundle details
107
113
  - **EmergencyRollbackButton** - One-click disable + 0% rollout
108
114
 
109
115
  ## 🔌 API Integration
110
116
 
111
- The console integrates with Hot Updater's plugin system through TanStack Start server functions:
117
+ The console integrates with the configured Hot Updater storage and database plugins through TanStack Start server functions:
112
118
 
113
119
  - `getConfig()` - Load console configuration
114
120
  - `getChannels()` - List available channels
@@ -123,15 +129,27 @@ The console integrates with Hot Updater's plugin system through TanStack Start s
123
129
  Configure Hot Updater in `hot-updater.config.ts`:
124
130
 
125
131
  ```typescript
126
- import { mockDatabase, mockStorage } from "@hot-updater/mock";
132
+ import {
133
+ createMockDatabaseData,
134
+ mockDatabase,
135
+ mockStorage,
136
+ } from "@hot-updater/mock";
137
+ import { bundleToRow, type Bundle } from "@hot-updater/plugin-core";
138
+
139
+ const bundles: Bundle[] = [
140
+ // ... your bundles
141
+ ];
142
+
143
+ const databaseData = createMockDatabaseData();
144
+ for (const bundle of bundles) {
145
+ databaseData.bundles.set(bundle.id, bundleToRow(bundle));
146
+ }
127
147
 
128
148
  export default {
129
149
  storage: mockStorage({}),
130
150
  database: mockDatabase({
131
151
  latency: { min: 500, max: 700 },
132
- initialBundles: [
133
- // ... your bundles
134
- ],
152
+ data: databaseData,
135
153
  }),
136
154
  };
137
155
  ```
@@ -154,7 +172,7 @@ The console uses Tailwind CSS v4 with oklch color space for accessible colors. T
154
172
  ## 📊 Data Flow
155
173
 
156
174
  1. **URL State** → `useFilterParams()` hook manages filter state in URL
157
- 2. **Server Functions** → TanStack Start server functions call Hot Updater plugins
175
+ 2. **Server Functions** → TanStack Start server functions call the configured storage and database plugins
158
176
  3. **React Query** → `useBundlesQuery()` fetches and caches data
159
177
  4. **UI Components** → Display data with shadcn components
160
178
  5. **Mutations** → `useUpdateBundleMutation()` updates data with optimistic updates
@@ -171,16 +189,19 @@ The console uses Tailwind CSS v4 with oklch color space for accessible colors. T
171
189
  ## 🐛 Troubleshooting
172
190
 
173
191
  ### Build Errors
192
+
174
193
  - Ensure `.server.ts` files are not imported on the client
175
194
  - Check that native modules (`.node`) are excluded from bundling
176
195
 
177
196
  ### Development Server
197
+
178
198
  - Default port is 3000
179
199
  - Change port: `pnpm dev --port 3001`
180
200
 
181
201
  ### Hot Updater Config
202
+
182
203
  - Ensure `hot-updater.config.ts` is at package root
183
- - Verify storage and database plugins are correctly initialized
204
+ - Verify the storage and database plugins are correctly initialized
184
205
 
185
206
  ## 📝 License
186
207
 
@@ -0,0 +1,36 @@
1
+ import { ConfigInput } from "@hot-updater/plugin-core";
2
+
3
+ //#region src/index.d.ts
4
+ type ConsoleAuthProvider = "google" | "github";
5
+ type ConsolePrincipal = Readonly<{
6
+ email: string;
7
+ name?: string | null;
8
+ image?: string | null;
9
+ }>;
10
+ type ConsoleAccess = {
11
+ status: "unauthenticated";
12
+ } | {
13
+ status: "forbidden";
14
+ principal: ConsolePrincipal;
15
+ } | {
16
+ status: "authorized";
17
+ principal: ConsolePrincipal;
18
+ };
19
+ type ConsoleAuthAdapter = Readonly<{
20
+ handle(request: Request): Promise<Response>;
21
+ getAccess(request: Request): Promise<ConsoleAccess>;
22
+ getProviders(request: Request): Promise<readonly ConsoleAuthProvider[]>;
23
+ }>;
24
+ type ConsoleSigningConfig = Readonly<{
25
+ enabled: boolean;
26
+ provider?: string;
27
+ publicKeyPath?: string;
28
+ }>;
29
+ type HotUpdaterConsoleConfig = Readonly<Pick<ConfigInput, "console" | "database" | "signing" | "storage">>;
30
+ type ResolvedHotUpdaterConsoleConfig = Readonly<Omit<HotUpdaterConsoleConfig, "signing"> & {
31
+ signing?: ConsoleSigningConfig;
32
+ }>;
33
+ type HotUpdaterConsoleConfigSource = HotUpdaterConsoleConfig | ((request: Request) => HotUpdaterConsoleConfig | Promise<HotUpdaterConsoleConfig>);
34
+ declare const defineConsoleConfig: <const TConfig extends HotUpdaterConsoleConfigSource>(config: TConfig) => TConfig;
35
+ //#endregion
36
+ export { ConsoleAccess, ConsoleAuthAdapter, ConsoleAuthProvider, ConsolePrincipal, ConsoleSigningConfig, HotUpdaterConsoleConfig, HotUpdaterConsoleConfigSource, ResolvedHotUpdaterConsoleConfig, defineConsoleConfig };
package/lib/index.mjs ADDED
@@ -0,0 +1,4 @@
1
+ //#region src/index.ts
2
+ const defineConsoleConfig = (config) => config;
3
+ //#endregion
4
+ export { defineConsoleConfig };
@@ -0,0 +1,127 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import tailwindcss from "@tailwindcss/vite";
4
+ import { tanstackStart } from "@tanstack/react-start/plugin/vite";
5
+ import viteReact from "@vitejs/plugin-react";
6
+ import { nitro } from "nitro/vite";
7
+ //#region src/vite-internal.ts
8
+ const virtualAuthModuleId = "virtual:hot-updater-console/auth";
9
+ const virtualConfigModuleId = "virtual:hot-updater-console/config";
10
+ const virtualPackageRouterModuleId = "virtual:hot-updater-console/package-router";
11
+ const virtualRouteTreeModuleId = "virtual:hot-updater-console/route-tree";
12
+ const resolvedVirtualAuthModuleId = `\0${virtualAuthModuleId}`;
13
+ const resolvedVirtualConfigModuleId = `\0${virtualConfigModuleId}`;
14
+ const resolvedVirtualPackageRouterModuleId = `\0${virtualPackageRouterModuleId}`;
15
+ const packageRoot = path.resolve(import.meta.dirname, "..");
16
+ const packagePublicDirectory = path.join(packageRoot, "public");
17
+ const packageRouterFile = path.join(packageRoot, "src/router.tsx");
18
+ const packageRoutesDirectory = path.join(packageRoot, "src/routes");
19
+ const packageSourceDirectory = path.join(packageRoot, "src");
20
+ const localRouteTreeFile = path.join(packageSourceDirectory, "routeTree.gen.ts");
21
+ const resolveFromRoot = (root, file) => path.isAbsolute(file) ? file : path.resolve(root, file);
22
+ const toImportPath = (file) => file.split(path.sep).join("/");
23
+ const writeFileIfChanged = (file, contents) => {
24
+ if (existsSync(file) && readFileSync(file, "utf8") === contents) return;
25
+ mkdirSync(path.dirname(file), { recursive: true });
26
+ writeFileSync(file, contents);
27
+ };
28
+ const createConsoleModulesPlugin = (mode) => {
29
+ let authFile = "";
30
+ let configFile = "";
31
+ let generatedRouteTreeFile = localRouteTreeFile;
32
+ return {
33
+ name: `hot-updater-console:${mode.type}-modules`,
34
+ enforce: "pre",
35
+ config(userConfig, configEnvironment) {
36
+ const root = path.resolve(process.cwd(), userConfig.root ?? ".");
37
+ if (mode.type === "hosted") {
38
+ const cacheDirectory = path.join(root, ".hot-updater/console");
39
+ generatedRouteTreeFile = path.join(cacheDirectory, "routeTree.gen.ts");
40
+ authFile = resolveFromRoot(root, mode.options.auth ?? "console.auth.ts");
41
+ configFile = resolveFromRoot(root, mode.options.config ?? "hot-updater.config.ts");
42
+ writeFileIfChanged(path.join(cacheDirectory, "router.ts"), [`export { getRouter } from ${JSON.stringify(virtualPackageRouterModuleId)};`, ""].join("\n"));
43
+ }
44
+ return {
45
+ assetsInclude: ["**/*.node"],
46
+ oxc: { jsx: { development: configEnvironment.command === "serve" } },
47
+ resolve: {
48
+ alias: [{
49
+ find: /^@\//,
50
+ replacement: `${toImportPath(packageSourceDirectory)}/`
51
+ }],
52
+ dedupe: [
53
+ "react",
54
+ "react-dom",
55
+ "@tanstack/react-query",
56
+ "@tanstack/react-router",
57
+ "@tanstack/react-start"
58
+ ]
59
+ }
60
+ };
61
+ },
62
+ resolveId(id) {
63
+ if (id === virtualAuthModuleId) return resolvedVirtualAuthModuleId;
64
+ if (id === virtualConfigModuleId) return resolvedVirtualConfigModuleId;
65
+ if (id === virtualPackageRouterModuleId) return resolvedVirtualPackageRouterModuleId;
66
+ if (id === virtualRouteTreeModuleId) return generatedRouteTreeFile;
67
+ },
68
+ load(id) {
69
+ if (id === resolvedVirtualPackageRouterModuleId) return `export { getRouter } from ${JSON.stringify(toImportPath(packageRouterFile))};`;
70
+ if (id === resolvedVirtualConfigModuleId) {
71
+ if (mode.type === "local") return [
72
+ "import { loadConfig } from \"@hot-updater/cli-tools\";",
73
+ "export default async () => {",
74
+ " const config = await loadConfig(null);",
75
+ " return {",
76
+ " console: { gitUrl: config.console.gitUrl },",
77
+ " database: config.database,",
78
+ " signing: config.signing,",
79
+ " storage: config.storage,",
80
+ " };",
81
+ "};"
82
+ ].join("\n");
83
+ this.addWatchFile(configFile);
84
+ return `export { default } from ${JSON.stringify(toImportPath(configFile))};`;
85
+ }
86
+ if (id === resolvedVirtualAuthModuleId) {
87
+ if (mode.type === "local") return [
88
+ "const principal = { email: \"local@hot-updater.dev\", name: \"Local Console\" };",
89
+ "export default {",
90
+ " async handle() {",
91
+ " return new Response(\"Not Found\", { status: 404 });",
92
+ " },",
93
+ " async getAccess() {",
94
+ " return { status: \"authorized\", principal };",
95
+ " },",
96
+ " async getProviders() {",
97
+ " return [];",
98
+ " },",
99
+ "};"
100
+ ].join("\n");
101
+ this.addWatchFile(authFile);
102
+ return `export { default } from ${JSON.stringify(toImportPath(authFile))};`;
103
+ }
104
+ }
105
+ };
106
+ };
107
+ const createHostedConsolePlugins = (options) => [
108
+ createConsoleModulesPlugin({
109
+ type: "hosted",
110
+ options
111
+ }),
112
+ nitro({ publicAssets: [{
113
+ dir: packagePublicDirectory,
114
+ maxAge: 3600 * 24
115
+ }] }),
116
+ tailwindcss(),
117
+ tanstackStart({
118
+ srcDirectory: ".hot-updater/console",
119
+ router: {
120
+ generatedRouteTree: "routeTree.gen.ts",
121
+ routesDirectory: packageRoutesDirectory
122
+ }
123
+ }),
124
+ viteReact()
125
+ ];
126
+ //#endregion
127
+ export { createHostedConsolePlugins };
package/lib/vite.d.mts ADDED
@@ -0,0 +1,10 @@
1
+ import { PluginOption } from "vite";
2
+
3
+ //#region src/vite.d.ts
4
+ interface HotUpdaterConsolePluginOptions {
5
+ readonly auth?: string;
6
+ readonly config?: string;
7
+ }
8
+ declare const hotUpdaterConsole: (options?: HotUpdaterConsolePluginOptions) => PluginOption[];
9
+ //#endregion
10
+ export { HotUpdaterConsolePluginOptions, hotUpdaterConsole };
package/lib/vite.mjs ADDED
@@ -0,0 +1,5 @@
1
+ import { createHostedConsolePlugins } from "./vite-internal.mjs";
2
+ //#region src/vite.ts
3
+ const hotUpdaterConsole = (options = {}) => createHostedConsolePlugins(options);
4
+ //#endregion
5
+ export { hotUpdaterConsole };
package/package.json CHANGED
@@ -1,71 +1,93 @@
1
1
  {
2
2
  "name": "@hot-updater/console",
3
3
  "type": "module",
4
- "version": "0.36.6",
4
+ "version": "1.0.0-rc.0",
5
5
  "files": [
6
6
  ".output",
7
+ "lib",
8
+ "public",
9
+ "src",
10
+ "!src/**/*.spec.ts",
11
+ "!src/**/*.spec.tsx",
7
12
  "package.json"
8
13
  ],
9
14
  "exports": {
15
+ ".": {
16
+ "types": "./lib/index.d.mts",
17
+ "import": "./lib/index.mjs"
18
+ },
19
+ "./vite": {
20
+ "types": "./lib/vite.d.mts",
21
+ "import": "./lib/vite.mjs"
22
+ },
10
23
  "./package.json": "./package.json"
11
24
  },
12
25
  "peerDependencies": {
13
- "@hot-updater/cli-tools": "*"
26
+ "@hot-updater/cli-tools": "*",
27
+ "react": "^19.0.0",
28
+ "react-dom": "^19.0.0",
29
+ "vite": "^8.0.0"
14
30
  },
15
- "dependencies": {
16
- "@hot-updater/bsdiff": "0.36.6",
17
- "@hot-updater/server": "0.36.6"
31
+ "peerDependenciesMeta": {
32
+ "@hot-updater/cli-tools": {
33
+ "optional": true
34
+ }
18
35
  },
19
- "devDependencies": {
36
+ "dependencies": {
20
37
  "@base-ui/react": "^1.3.0",
21
38
  "@fontsource-variable/inter": "^5.2.8",
22
39
  "@tailwindcss/vite": "^4.2.1",
23
- "@tanstack/devtools-vite": "^0.6.0",
24
40
  "@tanstack/react-devtools": "^0.10.0",
25
- "@tanstack/react-form": "^1.28.5",
26
41
  "@tanstack/react-query": "^5.90.21",
27
42
  "@tanstack/react-router": "^1.167.0",
28
43
  "@tanstack/react-router-devtools": "^1.166.7",
29
44
  "@tanstack/react-router-ssr-query": "^1.166.7",
30
45
  "@tanstack/react-start": "^1.166.11",
31
- "@tanstack/react-table": "^8.21.3",
32
- "@tanstack/router-plugin": "^1.166.9",
33
- "@testing-library/dom": "^10.4.0",
34
- "@testing-library/react": "^16.3.2",
35
- "@types/node": "^25.5.0",
36
- "@types/react": "^19.2.14",
37
- "@types/react-dom": "^19.2.0",
38
46
  "@vitejs/plugin-react": "^6.0.1",
39
47
  "class-variance-authority": "^0.7.1",
40
48
  "clsx": "^2.1.1",
41
49
  "dayjs": "^1.11.20",
42
- "jsdom": "^29.0.0",
43
- "jszip": "^3.10.1",
44
50
  "lucide-react": "^0.577.0",
45
51
  "next-themes": "^0.4.6",
46
- "nitro": "latest",
47
- "radix-ui": "^1.4.3",
48
- "react": "^19.2.4",
49
- "react-dom": "^19.2.4",
52
+ "nitro": "3.0.260610-beta",
50
53
  "react-grab": "^0.1.28",
54
+ "recharts": "3.8.0",
51
55
  "shadcn": "^4.0.8",
52
56
  "sonner": "^2.0.7",
53
57
  "tailwind-merge": "^3.5.0",
54
58
  "tailwindcss": "^4.2.1",
55
- "tar": "^7.5.16",
56
- "tslib": "^2.8.1",
57
59
  "tw-animate-css": "^1.4.0",
60
+ "verkit": "0.3.2",
61
+ "@hot-updater/bsdiff": "1.0.0-rc.0",
62
+ "@hot-updater/core": "1.0.0-rc.0",
63
+ "@hot-updater/server": "1.0.0-rc.0",
64
+ "@hot-updater/plugin-core": "1.0.0-rc.0"
65
+ },
66
+ "devDependencies": {
67
+ "@tanstack/devtools-vite": "^0.6.0",
68
+ "@tanstack/react-form": "^1.28.5",
69
+ "@tanstack/react-table": "^8.21.3",
70
+ "@tanstack/router-plugin": "^1.166.9",
71
+ "@testing-library/dom": "^10.4.0",
72
+ "@testing-library/react": "^16.3.2",
73
+ "@types/node": "^25.5.0",
74
+ "@types/react": "^19.2.14",
75
+ "@types/react-dom": "^19.2.0",
58
76
  "@typescript/native": "npm:typescript@^7.0.2",
77
+ "jsdom": "^29.0.0",
78
+ "jszip": "^3.10.1",
79
+ "react": "^19.2.4",
80
+ "react-dom": "^19.2.4",
81
+ "tar": "^7.5.16",
82
+ "tsdown": "0.21.6",
83
+ "tslib": "^2.8.1",
59
84
  "typescript": "npm:@typescript/typescript6@^6.0.2",
60
- "verkit": "0.3.2",
61
85
  "vite": "8.0.8",
62
86
  "vite-tsconfig-paths": "^6.1.1",
63
87
  "vitest": "4.1.4",
64
88
  "web-vitals": "^5.1.0",
65
- "@hot-updater/core": "0.36.6",
66
- "@hot-updater/mock": "0.36.6",
67
- "@hot-updater/cli-tools": "0.36.6",
68
- "@hot-updater/plugin-core": "0.36.6"
89
+ "@hot-updater/cli-tools": "1.0.0-rc.0",
90
+ "@hot-updater/mock": "1.0.0-rc.0"
69
91
  },
70
92
  "description": "React Native OTA solution for self-hosted",
71
93
  "license": "MIT",
@@ -102,7 +124,7 @@
102
124
  },
103
125
  "scripts": {
104
126
  "dev": "vite dev --port 3000",
105
- "build": "vite build && node copy-assets.mjs",
127
+ "build": "NODE_ENV=production vite build && tsdown",
106
128
  "check": "oxfmt --write \"**/*.{ts,tsx}\" && oxlint --fix --fix-suggestions --fix-dangerously .",
107
129
  "format": "oxfmt --write \"**/*.{ts,tsx}\"",
108
130
  "preview": "vite preview",
Binary file
Binary file
Binary file
Binary file
@@ -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,30 @@
1
+ {
2
+ "short_name": "Hot Updater",
3
+ "name": "Hot Updater Console",
4
+ "icons": [
5
+ {
6
+ "src": "favicon.ico",
7
+ "sizes": "64x64 32x32 24x24 16x16",
8
+ "type": "image/x-icon"
9
+ },
10
+ {
11
+ "src": "favicon-16x16.png",
12
+ "type": "image/png",
13
+ "sizes": "16x16"
14
+ },
15
+ {
16
+ "src": "favicon-32x32.png",
17
+ "type": "image/png",
18
+ "sizes": "32x32"
19
+ },
20
+ {
21
+ "src": "apple-touch-icon.png",
22
+ "type": "image/png",
23
+ "sizes": "180x180"
24
+ }
25
+ ],
26
+ "start_url": ".",
27
+ "display": "standalone",
28
+ "theme_color": "#f97316",
29
+ "background_color": "#ffffff"
30
+ }
@@ -0,0 +1,3 @@
1
+ # https://www.robotstxt.org/robotstxt.html
2
+ User-agent: *
3
+ Disallow:
@@ -0,0 +1,159 @@
1
+ import { Link, useRouterState } from "@tanstack/react-router";
2
+ import {
3
+ ChartNoAxesCombined,
4
+ KeyRound,
5
+ Moon,
6
+ Package,
7
+ ShieldCheck,
8
+ Sun,
9
+ } from "lucide-react";
10
+
11
+ import { useAnalyticsCapability } from "@/components/features/analytics/AnalyticsCapabilityContext";
12
+ import { HotUpdaterLogo } from "@/components/HotUpdaterLogo";
13
+ import { useTheme } from "@/components/ThemeProvider";
14
+ import {
15
+ Sidebar,
16
+ SidebarContent,
17
+ SidebarFooter,
18
+ SidebarGroup,
19
+ SidebarGroupContent,
20
+ SidebarGroupLabel,
21
+ SidebarHeader,
22
+ SidebarMenu,
23
+ SidebarMenuButton,
24
+ SidebarMenuItem,
25
+ } from "@/components/ui/sidebar";
26
+ import { useApiKeyCapabilityQuery } from "@/lib/api-keys-api";
27
+
28
+ export function AppSidebar() {
29
+ const analyticsCapability = useAnalyticsCapability();
30
+ const apiKeyCapability = useApiKeyCapabilityQuery();
31
+ const { theme, setTheme } = useTheme();
32
+ const routerState = useRouterState();
33
+ const currentPath = routerState.location.pathname;
34
+
35
+ const isBundlesActive = currentPath === "/";
36
+ const isAnalyticsActive =
37
+ currentPath === "/analytics" || currentPath === "/installations";
38
+ const isApiKeysActive = currentPath === "/api-keys";
39
+ const isSigningActive = currentPath === "/signing";
40
+
41
+ return (
42
+ <Sidebar collapsible="icon">
43
+ <SidebarHeader className="h-12 justify-center">
44
+ <Link
45
+ to="/"
46
+ search={{
47
+ afterReleaseId: undefined,
48
+ beforeReleaseId: undefined,
49
+ channelId: undefined,
50
+ enabled: undefined,
51
+ releaseId: undefined,
52
+ platform: undefined,
53
+ targetAppVersion: undefined,
54
+ bundleId: undefined,
55
+ page: undefined,
56
+ }}
57
+ className="flex items-center gap-3 p-1 group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:p-2"
58
+ >
59
+ <HotUpdaterLogo className="h-7 w-7 shrink-0" />
60
+ <div className="flex flex-col gap-0 group-data-[collapsible=icon]:hidden">
61
+ <span className="text-sm font-semibold tracking-tight text-sidebar-foreground leading-tight">
62
+ Hot Updater
63
+ </span>
64
+ <span className="text-[10px] text-sidebar-foreground/60 leading-tight">
65
+ Console
66
+ </span>
67
+ </div>
68
+ </Link>
69
+ </SidebarHeader>
70
+
71
+ <SidebarContent>
72
+ <SidebarGroup>
73
+ <SidebarGroupLabel>Navigation</SidebarGroupLabel>
74
+ <SidebarGroupContent>
75
+ <SidebarMenu>
76
+ <SidebarMenuItem>
77
+ <SidebarMenuButton
78
+ isActive={isBundlesActive}
79
+ render={
80
+ <Link
81
+ to="/"
82
+ search={{
83
+ afterReleaseId: undefined,
84
+ beforeReleaseId: undefined,
85
+ channelId: undefined,
86
+ enabled: undefined,
87
+ releaseId: undefined,
88
+ platform: undefined,
89
+ targetAppVersion: undefined,
90
+ bundleId: undefined,
91
+ page: undefined,
92
+ }}
93
+ />
94
+ }
95
+ tooltip="Bundles"
96
+ >
97
+ <Package />
98
+ <span>Bundles</span>
99
+ </SidebarMenuButton>
100
+ </SidebarMenuItem>
101
+ {analyticsCapability.status === "supported" ? (
102
+ <SidebarMenuItem>
103
+ <SidebarMenuButton
104
+ isActive={isAnalyticsActive}
105
+ render={<Link to="/analytics" />}
106
+ tooltip="Analytics"
107
+ >
108
+ <ChartNoAxesCombined />
109
+ <span>Analytics</span>
110
+ </SidebarMenuButton>
111
+ </SidebarMenuItem>
112
+ ) : null}
113
+ {apiKeyCapability.data?.apiKeys ? (
114
+ <SidebarMenuItem>
115
+ <SidebarMenuButton
116
+ isActive={isApiKeysActive}
117
+ render={<Link to="/api-keys" />}
118
+ tooltip="API keys"
119
+ >
120
+ <KeyRound />
121
+ <span>API keys</span>
122
+ </SidebarMenuButton>
123
+ </SidebarMenuItem>
124
+ ) : null}
125
+ <SidebarMenuItem>
126
+ <SidebarMenuButton
127
+ isActive={isSigningActive}
128
+ render={<Link to="/signing" />}
129
+ tooltip="Bundle signing"
130
+ >
131
+ <ShieldCheck />
132
+ <span>Bundle signing</span>
133
+ </SidebarMenuButton>
134
+ </SidebarMenuItem>
135
+ </SidebarMenu>
136
+ </SidebarGroupContent>
137
+ </SidebarGroup>
138
+ </SidebarContent>
139
+
140
+ <SidebarFooter>
141
+ <SidebarMenu>
142
+ <SidebarMenuItem>
143
+ <SidebarMenuButton
144
+ onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
145
+ tooltip={theme === "dark" ? "Light mode" : "Dark mode"}
146
+ >
147
+ {theme === "dark" ? (
148
+ <Sun className="h-4 w-4" />
149
+ ) : (
150
+ <Moon className="h-4 w-4" />
151
+ )}
152
+ <span>{theme === "dark" ? "Light mode" : "Dark mode"}</span>
153
+ </SidebarMenuButton>
154
+ </SidebarMenuItem>
155
+ </SidebarMenu>
156
+ </SidebarFooter>
157
+ </Sidebar>
158
+ );
159
+ }
@@ -0,0 +1,18 @@
1
+ import { cn } from "@/lib/utils";
2
+
3
+ interface BundleIdDisplayProps {
4
+ bundleId: string;
5
+ maxLength?: number;
6
+ className?: string;
7
+ fullOnMobile?: boolean;
8
+ }
9
+ export function BundleIdDisplay({ bundleId, className }: BundleIdDisplayProps) {
10
+ return (
11
+ <span
12
+ translate="no"
13
+ className={cn("break-all font-mono text-xs tabular-nums", className)}
14
+ >
15
+ {bundleId}
16
+ </span>
17
+ );
18
+ }