@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
package/src/lib/api.ts ADDED
@@ -0,0 +1,444 @@
1
+ import type {
2
+ ChannelDeleteInput,
3
+ ChannelInsertInput,
4
+ ReleasePolicyPatch,
5
+ } from "@hot-updater/plugin-core";
6
+ import type { BundleEventInsightsWindow } from "@hot-updater/server";
7
+ import {
8
+ type QueryClient,
9
+ type QueryKey,
10
+ useMutation,
11
+ useQuery,
12
+ useQueryClient,
13
+ } from "@tanstack/react-query";
14
+
15
+ import {
16
+ createChannel as createChannelApi,
17
+ deleteChannel as deleteChannelApi,
18
+ deleteBundle as deleteBundleApi,
19
+ deleteBundles as deleteBundlesApi,
20
+ deleteRelease as deleteReleaseApi,
21
+ getBundle,
22
+ getBundleChildCounts,
23
+ getBundleChildren,
24
+ getBundleEventInsights as getBundleEventInsightsApi,
25
+ getBundleEventSummary as getBundleEventSummaryApi,
26
+ getBundles,
27
+ getChannels,
28
+ getConfig,
29
+ getConfigLoaded,
30
+ getInstallationHistory as getInstallationHistoryApi,
31
+ getRelease,
32
+ getReleaseCatalogDiagnostics,
33
+ getReleases,
34
+ preflightRelease as preflightReleaseApi,
35
+ promoteRelease as promoteReleaseApi,
36
+ searchInstallations as searchInstallationsApi,
37
+ updateRelease as updateReleaseApi,
38
+ } from "./api-rpc";
39
+
40
+ type BundleFilters = {
41
+ platform?: "ios" | "android";
42
+ page?: number;
43
+ limit?: string;
44
+ after?: string;
45
+ before?: string;
46
+ };
47
+
48
+ type BundlesQueryData = Awaited<ReturnType<typeof getBundles>>;
49
+
50
+ const INSIGHTS_STALE_TIME_MS = 30_000;
51
+
52
+ export type BundleEventSummary = Awaited<
53
+ ReturnType<typeof getBundleEventSummaryApi>
54
+ >;
55
+ export type BundleEventInsights = Awaited<
56
+ ReturnType<typeof getBundleEventInsightsApi>
57
+ >;
58
+ export type InstallationSearchResult = Awaited<
59
+ ReturnType<typeof searchInstallationsApi>
60
+ >;
61
+ export type InstallationHistoryResult = Awaited<
62
+ ReturnType<typeof getInstallationHistoryApi>
63
+ >;
64
+ export type InstallationSearchRow = InstallationSearchResult["data"][number];
65
+ export type InstallationHistoryRow = InstallationHistoryResult["data"][number];
66
+
67
+ const bundleListQueryKey = ["bundles"] as const;
68
+ const releaseListQueryKey = ["releases"] as const;
69
+
70
+ export const queryKeys = {
71
+ config: ["config"] as const,
72
+ channels: ["channels"] as const,
73
+ configLoaded: ["config-loaded"] as const,
74
+ bundles: {
75
+ all: bundleListQueryKey,
76
+ list: (filters?: BundleFilters) =>
77
+ [...bundleListQueryKey, filters ?? {}] as const,
78
+ },
79
+ releases: {
80
+ all: releaseListQueryKey,
81
+ list: (filters?: ReleaseFilters) =>
82
+ [...releaseListQueryKey, filters ?? {}] as const,
83
+ },
84
+ release: (releaseId: string) => ["release", releaseId] as const,
85
+ releaseCatalog: (scopeKey: string) => ["release-catalog", scopeKey] as const,
86
+ bundleChildren: {
87
+ all: ["bundle-children"] as const,
88
+ list: (baseBundleId: string) => ["bundle-children", baseBundleId] as const,
89
+ counts: (bundleIds: string[]) =>
90
+ ["bundle-children", "counts", ...bundleIds] as const,
91
+ },
92
+ bundle: (bundleId: string) => ["bundle", bundleId] as const,
93
+ bundleEventSummary: (bundleId: string) =>
94
+ ["bundle-event-summary", bundleId] as const,
95
+ bundleEventInsights: (input: {
96
+ bundleId: string;
97
+ window: BundleEventInsightsWindow;
98
+ limit?: number;
99
+ offset?: number;
100
+ }) => ["bundle-event-insights", input] as const,
101
+
102
+ installations: {
103
+ search: (input: { query: string; limit?: number; offset?: number }) =>
104
+ ["installations", "search", input] as const,
105
+ history: (input: { installId: string; limit?: number; offset?: number }) =>
106
+ ["installations", "history", input] as const,
107
+ },
108
+ };
109
+
110
+ export type ReleaseFilters = {
111
+ afterReleaseId?: string;
112
+ beforeReleaseId?: string;
113
+ bundleId?: string;
114
+ channelId?: string;
115
+ enabled?: boolean;
116
+ platform?: "ios" | "android";
117
+ limit?: number;
118
+ page?: number;
119
+ targetAppVersion?: string;
120
+ };
121
+
122
+ function removeBundleFromQueryData(
123
+ data: BundlesQueryData | undefined,
124
+ bundleId: string,
125
+ ) {
126
+ if (!data) {
127
+ return data;
128
+ }
129
+
130
+ return {
131
+ ...data,
132
+ data: data.data.filter((bundle) => bundle.id !== bundleId),
133
+ };
134
+ }
135
+
136
+ function removeBundlesFromQueryData(
137
+ data: BundlesQueryData | undefined,
138
+ bundleIds: readonly string[],
139
+ ) {
140
+ if (!data) {
141
+ return data;
142
+ }
143
+
144
+ const bundleIdSet = new Set(bundleIds);
145
+ return {
146
+ ...data,
147
+ data: data.data.filter((bundle) => !bundleIdSet.has(bundle.id)),
148
+ };
149
+ }
150
+
151
+ const invalidateInBackground = (
152
+ queryClient: QueryClient,
153
+ queryKey: QueryKey,
154
+ ) => {
155
+ void queryClient.invalidateQueries({ queryKey }).catch(() => undefined);
156
+ };
157
+
158
+ // Query Hooks
159
+ export function useConfigQuery() {
160
+ return useQuery({
161
+ queryKey: queryKeys.config,
162
+ queryFn: () => getConfig(),
163
+ staleTime: Infinity,
164
+ });
165
+ }
166
+
167
+ export function useChannelsQuery() {
168
+ return useQuery({
169
+ queryKey: queryKeys.channels,
170
+ queryFn: () => getChannels(),
171
+ staleTime: Infinity,
172
+ });
173
+ }
174
+
175
+ export function useConfigLoadedQuery() {
176
+ return useQuery({
177
+ queryKey: queryKeys.configLoaded,
178
+ queryFn: () => getConfigLoaded(),
179
+ staleTime: Infinity,
180
+ });
181
+ }
182
+
183
+ export function useBundlesQuery(filters?: BundleFilters) {
184
+ return useQuery({
185
+ queryKey: queryKeys.bundles.list(filters),
186
+ queryFn: () => getBundles({ data: filters }),
187
+ staleTime: Infinity,
188
+ placeholderData: (previousData) => previousData,
189
+ });
190
+ }
191
+
192
+ export function useReleasesQuery(filters?: ReleaseFilters) {
193
+ return useQuery({
194
+ queryKey: queryKeys.releases.list(filters),
195
+ queryFn: () => getReleases({ data: filters }),
196
+ placeholderData: (previousData) => previousData,
197
+ });
198
+ }
199
+
200
+ export function useReleaseQuery(releaseId: string) {
201
+ return useQuery({
202
+ enabled: releaseId.length > 0,
203
+ queryFn: () => getRelease({ data: { releaseId } }),
204
+ queryKey: queryKeys.release(releaseId),
205
+ });
206
+ }
207
+
208
+ export function useReleaseCatalogDiagnosticsQuery(scopeKey: string) {
209
+ return useQuery({
210
+ enabled: scopeKey.length > 0,
211
+ queryFn: () => getReleaseCatalogDiagnostics({ data: { scopeKey } }),
212
+ queryKey: queryKeys.releaseCatalog(scopeKey),
213
+ });
214
+ }
215
+
216
+ export function useBundleQuery(bundleId: string) {
217
+ return useQuery({
218
+ queryKey: queryKeys.bundle(bundleId),
219
+ queryFn: () => getBundle({ data: { bundleId } }),
220
+ staleTime: Infinity,
221
+ enabled: !!bundleId,
222
+ });
223
+ }
224
+
225
+ export function useBundleEventSummaryQuery(bundleId: string, enabled: boolean) {
226
+ return useQuery({
227
+ queryKey: queryKeys.bundleEventSummary(bundleId),
228
+ queryFn: () => getBundleEventSummaryApi({ data: { bundleId } }),
229
+ staleTime: INSIGHTS_STALE_TIME_MS,
230
+ refetchOnWindowFocus: true,
231
+ enabled: enabled && bundleId.length > 0,
232
+ });
233
+ }
234
+
235
+ export function useBundleEventInsightsQuery(
236
+ input: {
237
+ bundleId: string;
238
+ window: BundleEventInsightsWindow;
239
+ limit?: number;
240
+ offset?: number;
241
+ },
242
+ enabled: boolean,
243
+ ) {
244
+ return useQuery({
245
+ queryKey: queryKeys.bundleEventInsights(input),
246
+ queryFn: () => getBundleEventInsightsApi({ data: input }),
247
+ staleTime: INSIGHTS_STALE_TIME_MS,
248
+ refetchOnWindowFocus: true,
249
+ enabled: enabled && input.bundleId.length > 0,
250
+ });
251
+ }
252
+
253
+ export function useInstallationSearchQuery(
254
+ input: {
255
+ query: string;
256
+ limit?: number;
257
+ offset?: number;
258
+ },
259
+ enabled: boolean,
260
+ ) {
261
+ return useQuery({
262
+ queryKey: queryKeys.installations.search(input),
263
+ queryFn: () => searchInstallationsApi({ data: input }),
264
+ staleTime: INSIGHTS_STALE_TIME_MS,
265
+ refetchOnWindowFocus: true,
266
+ enabled: enabled && input.query.trim().length > 0,
267
+ });
268
+ }
269
+
270
+ export function useInstallationHistoryQuery(
271
+ input: {
272
+ installId: string;
273
+ limit?: number;
274
+ offset?: number;
275
+ },
276
+ enabled: boolean,
277
+ ) {
278
+ return useQuery({
279
+ queryKey: queryKeys.installations.history(input),
280
+ queryFn: () => getInstallationHistoryApi({ data: input }),
281
+ staleTime: INSIGHTS_STALE_TIME_MS,
282
+ refetchOnWindowFocus: true,
283
+ enabled: enabled && input.installId.length > 0,
284
+ });
285
+ }
286
+
287
+ export function useBundleChildrenQuery(baseBundleId: string) {
288
+ return useQuery({
289
+ queryKey: queryKeys.bundleChildren.list(baseBundleId),
290
+ queryFn: () => getBundleChildren({ data: { baseBundleId } }),
291
+ staleTime: Infinity,
292
+ enabled: !!baseBundleId,
293
+ });
294
+ }
295
+
296
+ export function useBundleChildCountsQuery(bundleIds: string[]) {
297
+ const normalizedBundleIds = [...bundleIds].sort((left, right) =>
298
+ left.localeCompare(right),
299
+ );
300
+
301
+ return useQuery({
302
+ queryKey: queryKeys.bundleChildren.counts(normalizedBundleIds),
303
+ queryFn: () =>
304
+ getBundleChildCounts({ data: { bundleIds: normalizedBundleIds } }),
305
+ staleTime: Infinity,
306
+ enabled: normalizedBundleIds.length > 0,
307
+ });
308
+ }
309
+
310
+ // Mutation Hooks
311
+ export function useCreateChannelMutation() {
312
+ const queryClient = useQueryClient();
313
+
314
+ return useMutation({
315
+ mutationFn: (input: ChannelInsertInput) =>
316
+ createChannelApi({ data: input }).then((response) => response.data),
317
+ onSuccess: async () => {
318
+ await queryClient.invalidateQueries({ queryKey: queryKeys.channels });
319
+ },
320
+ });
321
+ }
322
+
323
+ export function useDeleteChannelMutation() {
324
+ const queryClient = useQueryClient();
325
+
326
+ return useMutation({
327
+ mutationFn: (input: ChannelDeleteInput) =>
328
+ deleteChannelApi({ data: input }).then((response) => response.data),
329
+ onSettled: async () => {
330
+ await queryClient.invalidateQueries({ queryKey: queryKeys.channels });
331
+ },
332
+ });
333
+ }
334
+
335
+ export function useDeleteBundleMutation() {
336
+ const queryClient = useQueryClient();
337
+
338
+ return useMutation({
339
+ mutationFn: (params: { bundleId: string }) =>
340
+ deleteBundleApi({ data: params }),
341
+ onSuccess: (_, vars) => {
342
+ queryClient.removeQueries({ queryKey: queryKeys.bundle(vars.bundleId) });
343
+ queryClient.setQueriesData(
344
+ { queryKey: queryKeys.bundles.all },
345
+ (data: BundlesQueryData | undefined) =>
346
+ removeBundleFromQueryData(data, vars.bundleId),
347
+ );
348
+
349
+ invalidateInBackground(queryClient, queryKeys.bundles.all);
350
+ invalidateInBackground(queryClient, queryKeys.bundleChildren.all);
351
+ },
352
+ });
353
+ }
354
+
355
+ export function useDeleteBundlesMutation() {
356
+ const queryClient = useQueryClient();
357
+
358
+ return useMutation({
359
+ mutationFn: (params: { bundleIds: string[] }) =>
360
+ deleteBundlesApi({ data: params }),
361
+ onSuccess: (_, vars) => {
362
+ for (const bundleId of vars.bundleIds) {
363
+ queryClient.removeQueries({ queryKey: queryKeys.bundle(bundleId) });
364
+ }
365
+ queryClient.setQueriesData(
366
+ { queryKey: queryKeys.bundles.all },
367
+ (data: BundlesQueryData | undefined) =>
368
+ removeBundlesFromQueryData(data, vars.bundleIds),
369
+ );
370
+
371
+ invalidateInBackground(queryClient, queryKeys.bundles.all);
372
+ invalidateInBackground(queryClient, queryKeys.bundleChildren.all);
373
+ },
374
+ });
375
+ }
376
+
377
+ export function useUpdateReleaseMutation() {
378
+ const queryClient = useQueryClient();
379
+ return useMutation({
380
+ mutationFn: (input: {
381
+ expectedRevision: number;
382
+ patch: ReleasePolicyPatch;
383
+ releaseId: string;
384
+ }) => updateReleaseApi({ data: input }),
385
+ onSuccess: async (_, input) => {
386
+ await Promise.all([
387
+ queryClient.invalidateQueries({ queryKey: queryKeys.releases.all }),
388
+ queryClient.invalidateQueries({
389
+ queryKey: queryKeys.release(input.releaseId),
390
+ }),
391
+ queryClient.invalidateQueries({ queryKey: ["release-catalog"] }),
392
+ queryClient.invalidateQueries({ queryKey: queryKeys.channels }),
393
+ ]);
394
+ },
395
+ });
396
+ }
397
+
398
+ export function usePreflightReleaseMutation() {
399
+ return useMutation({
400
+ mutationFn: (input: {
401
+ expectedRevision: number;
402
+ patch: ReleasePolicyPatch;
403
+ releaseId: string;
404
+ }) => preflightReleaseApi({ data: input }),
405
+ });
406
+ }
407
+
408
+ export function useDeleteReleaseMutation() {
409
+ const queryClient = useQueryClient();
410
+ return useMutation({
411
+ mutationFn: (input: { expectedRevision: number; releaseId: string }) =>
412
+ deleteReleaseApi({ data: input }),
413
+ onSuccess: async (_, input) => {
414
+ queryClient.removeQueries({
415
+ queryKey: queryKeys.release(input.releaseId),
416
+ });
417
+ await Promise.all([
418
+ queryClient.invalidateQueries({ queryKey: queryKeys.releases.all }),
419
+ queryClient.invalidateQueries({ queryKey: ["release-catalog"] }),
420
+ queryClient.invalidateQueries({ queryKey: queryKeys.channels }),
421
+ ]);
422
+ },
423
+ });
424
+ }
425
+
426
+ export function usePromoteReleaseMutation() {
427
+ const queryClient = useQueryClient();
428
+ return useMutation({
429
+ mutationFn: (input: {
430
+ action: "copy" | "move";
431
+ expectedRevision: number;
432
+ releaseId: string;
433
+ targetChannel: string;
434
+ }) => promoteReleaseApi({ data: input }),
435
+ onSuccess: async () => {
436
+ await Promise.all([
437
+ queryClient.invalidateQueries({ queryKey: queryKeys.releases.all }),
438
+ queryClient.invalidateQueries({ queryKey: ["release"] }),
439
+ queryClient.invalidateQueries({ queryKey: ["release-catalog"] }),
440
+ queryClient.invalidateQueries({ queryKey: queryKeys.channels }),
441
+ ]);
442
+ },
443
+ });
444
+ }
@@ -0,0 +1,16 @@
1
+ import { createServerFn } from "@tanstack/react-start";
2
+ import { getRequest } from "@tanstack/react-start/server";
3
+
4
+ export const getConsoleAccessRpc = createServerFn({ method: "GET" }).handler(
5
+ async () => {
6
+ const { getConsoleAccess } = await import("./server/auth.server");
7
+ return getConsoleAccess(getRequest());
8
+ },
9
+ );
10
+
11
+ export const getConsoleAuthProvidersRpc = createServerFn({
12
+ method: "GET",
13
+ }).handler(async () => {
14
+ const { getConsoleAuthProviders } = await import("./server/auth.server");
15
+ return getConsoleAuthProviders(getRequest());
16
+ });
@@ -0,0 +1,14 @@
1
+ import { useQuery } from "@tanstack/react-query";
2
+
3
+ import { getBundleSigningInspectionRpc } from "./bundle-signing-rpc";
4
+
5
+ export const bundleSigningQueryKey = ["bundle-signing"] as const;
6
+
7
+ export const getBundleSigningInspectionQueryOptions = () => ({
8
+ queryFn: () => getBundleSigningInspectionRpc(),
9
+ queryKey: bundleSigningQueryKey,
10
+ staleTime: 30_000,
11
+ });
12
+
13
+ export const useBundleSigningInspectionQuery = () =>
14
+ useQuery(getBundleSigningInspectionQueryOptions());
@@ -0,0 +1,16 @@
1
+ import { createServerFn } from "@tanstack/react-start";
2
+
3
+ export type { BundleSigningInspection } from "./server/bundle-signing.server";
4
+
5
+ export const getBundleSigningInspection = async () => {
6
+ const [{ prepareConfig }, { inspectBundleSigning }] = await Promise.all([
7
+ import("./server/config.server"),
8
+ import("./server/bundle-signing.server"),
9
+ ]);
10
+ const { config } = await prepareConfig();
11
+ return inspectBundleSigning(config.signing);
12
+ };
13
+
14
+ export const getBundleSigningInspectionRpc = createServerFn({
15
+ method: "GET",
16
+ }).handler(getBundleSigningInspection);
@@ -0,0 +1 @@
1
+ export const DEFAULT_PAGE_LIMIT = 20;
package/src/lib/git.ts ADDED
@@ -0,0 +1,51 @@
1
+ const normalizeGitUrl = (gitUrl: string) => {
2
+ const trimmedGitUrl = gitUrl.trim();
3
+
4
+ if (trimmedGitUrl.startsWith("git@")) {
5
+ const sshMatch = trimmedGitUrl.match(/^git@([^:]+):(.+?)(?:\.git)?$/);
6
+
7
+ if (sshMatch) {
8
+ return `https://${sshMatch[1]}/${sshMatch[2]}`;
9
+ }
10
+ }
11
+
12
+ if (trimmedGitUrl.startsWith("ssh://")) {
13
+ try {
14
+ const url = new URL(trimmedGitUrl);
15
+ return `https://${url.hostname}${url.pathname}`
16
+ .replace(/\/+$/, "")
17
+ .replace(/\.git$/, "");
18
+ } catch {
19
+ return trimmedGitUrl.replace(/\/+$/, "").replace(/\.git$/, "");
20
+ }
21
+ }
22
+
23
+ return trimmedGitUrl.replace(/\/+$/, "").replace(/\.git$/, "");
24
+ };
25
+
26
+ export const getCommitUrl = (
27
+ gitUrl: string | undefined,
28
+ commitHash: string,
29
+ ) => {
30
+ if (!gitUrl?.trim()) {
31
+ return null;
32
+ }
33
+
34
+ const normalizedGitUrl = normalizeGitUrl(gitUrl);
35
+
36
+ try {
37
+ const { hostname } = new URL(normalizedGitUrl);
38
+
39
+ if (hostname.includes("gitlab")) {
40
+ return `${normalizedGitUrl}/-/commit/${commitHash}`;
41
+ }
42
+
43
+ if (hostname.includes("bitbucket")) {
44
+ return `${normalizedGitUrl}/commits/${commitHash}`;
45
+ }
46
+ } catch {
47
+ // Fall back to the default commit route for custom git hosts.
48
+ }
49
+
50
+ return `${normalizedGitUrl}/commit/${commitHash}`;
51
+ };
@@ -0,0 +1,144 @@
1
+ import { type QueryClient, useQuery } from "@tanstack/react-query";
2
+ import { redirect } from "@tanstack/react-router";
3
+
4
+ import {
5
+ type ActiveInstallationInput,
6
+ parseActiveInstallationInput,
7
+ } from "./insights-input";
8
+ import {
9
+ getActiveInstallationOverviewRpc,
10
+ getInsightsCapabilitiesRpc,
11
+ getInsightsOverviewRpc,
12
+ type InsightsCapabilities,
13
+ } from "./insights-rpc";
14
+
15
+ export type InsightsCapabilityState =
16
+ | { readonly status: "unresolved" }
17
+ | { readonly status: "unsupported" }
18
+ | {
19
+ readonly status: "supported";
20
+ readonly mode: "bounded";
21
+ readonly maxMatchingRows: number;
22
+ }
23
+ | { readonly status: "error"; readonly error: Error };
24
+
25
+ type InsightsCapabilityQueryResult =
26
+ | { readonly status: "pending" }
27
+ | { readonly status: "success"; readonly data: InsightsCapabilities }
28
+ | { readonly status: "error"; readonly error: Error };
29
+
30
+ export type ProtectedInsightsRouteDecision =
31
+ | "loading"
32
+ | "redirect"
33
+ | "allow"
34
+ | "error";
35
+
36
+ export const insightsQueryKeys = {
37
+ capabilities: ["insights", "capabilities"] as const,
38
+ overview: ["insights", "overview"] as const,
39
+ activeInstallations: (input: ActiveInstallationInput) =>
40
+ [
41
+ "insights",
42
+ "active-installations",
43
+ input.window,
44
+ input.userId ?? null,
45
+ ] as const,
46
+ };
47
+
48
+ const INSIGHTS_STALE_TIME_MS = 30_000;
49
+
50
+ export const getInsightsCapabilityState = (
51
+ query: InsightsCapabilityQueryResult,
52
+ ): InsightsCapabilityState => {
53
+ switch (query.status) {
54
+ case "pending":
55
+ return { status: "unresolved" };
56
+ case "error":
57
+ return { status: "error", error: query.error };
58
+ case "success":
59
+ if (!query.data.capabilities.insights) {
60
+ return { status: "unsupported" };
61
+ }
62
+ return {
63
+ status: "supported",
64
+ mode: "bounded",
65
+ maxMatchingRows: query.data.capabilities.maxMatchingRows,
66
+ };
67
+ }
68
+ };
69
+
70
+ export const getProtectedInsightsRouteDecision = (
71
+ capability: InsightsCapabilityState,
72
+ ): ProtectedInsightsRouteDecision => {
73
+ switch (capability.status) {
74
+ case "unresolved":
75
+ return "loading";
76
+ case "unsupported":
77
+ return "redirect";
78
+ case "supported":
79
+ return "allow";
80
+ case "error":
81
+ return "error";
82
+ }
83
+ };
84
+
85
+ export const isInsightsQueryEnabled = (
86
+ capability: InsightsCapabilityState,
87
+ ): boolean => capability.status === "supported";
88
+
89
+ export const getInsightsCapabilitiesQueryOptions = () => ({
90
+ queryKey: insightsQueryKeys.capabilities,
91
+ queryFn: () => getInsightsCapabilitiesRpc(),
92
+ staleTime: Infinity,
93
+ });
94
+
95
+ export const useInsightsCapabilitiesQuery = () =>
96
+ useQuery(getInsightsCapabilitiesQueryOptions());
97
+
98
+ export const ensureInsightsRouteAccess = async (
99
+ queryClient: QueryClient,
100
+ ): Promise<void> => {
101
+ const result = await queryClient.ensureQueryData(
102
+ getInsightsCapabilitiesQueryOptions(),
103
+ );
104
+
105
+ if (!result.capabilities.insights) {
106
+ throw redirect({
107
+ to: "/",
108
+ search: {},
109
+ replace: true,
110
+ });
111
+ }
112
+ };
113
+
114
+ export const getInsightsOverviewQueryOptions = (
115
+ capability: InsightsCapabilityState,
116
+ ) => ({
117
+ queryKey: insightsQueryKeys.overview,
118
+ queryFn: () => getInsightsOverviewRpc(),
119
+ staleTime: INSIGHTS_STALE_TIME_MS,
120
+ refetchOnWindowFocus: true,
121
+ enabled: isInsightsQueryEnabled(capability),
122
+ });
123
+
124
+ export const useInsightsOverviewQuery = (capability: InsightsCapabilityState) =>
125
+ useQuery(getInsightsOverviewQueryOptions(capability));
126
+
127
+ export const getActiveInstallationQueryOptions = (
128
+ capability: InsightsCapabilityState,
129
+ input: ActiveInstallationInput,
130
+ ) => {
131
+ const normalized = parseActiveInstallationInput(input);
132
+ return {
133
+ queryKey: insightsQueryKeys.activeInstallations(normalized),
134
+ queryFn: () => getActiveInstallationOverviewRpc({ data: normalized }),
135
+ staleTime: INSIGHTS_STALE_TIME_MS,
136
+ refetchOnWindowFocus: true,
137
+ enabled: isInsightsQueryEnabled(capability),
138
+ };
139
+ };
140
+
141
+ export const useActiveInstallationQuery = (
142
+ capability: InsightsCapabilityState,
143
+ input: ActiveInstallationInput,
144
+ ) => useQuery(getActiveInstallationQueryOptions(capability, input));
@@ -0,0 +1,19 @@
1
+ export type InsightsErrorCopy = {
2
+ readonly title: string;
3
+ readonly description: string;
4
+ };
5
+
6
+ export const getInsightsErrorCopy = (
7
+ error: Error,
8
+ fallbackTitle: string,
9
+ ): InsightsErrorCopy => {
10
+ const match = /^Bundle event scan exceeded (\d+) rows\.$/.exec(error.message);
11
+ if (!match) {
12
+ return { title: fallbackTitle, description: error.message };
13
+ }
14
+ const limit = Number(match[1]);
15
+ return {
16
+ title: "Insights report limit reached",
17
+ description: `This query matched more than ${limit.toLocaleString()} reports. Narrow the query and try again.`,
18
+ };
19
+ };