@shipfox/client-integrations 0.2.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 (243) hide show
  1. package/.storybook/main.ts +1 -0
  2. package/.storybook/preview.css +4 -0
  3. package/.storybook/preview.tsx +56 -0
  4. package/.turbo/turbo-build.log +2 -0
  5. package/.turbo/turbo-type$colon$emit.log +1 -0
  6. package/.turbo/turbo-type.log +1 -0
  7. package/CHANGELOG.md +207 -0
  8. package/LICENSE +21 -0
  9. package/dist/components/connection-picker.d.ts +7 -0
  10. package/dist/components/connection-picker.d.ts.map +1 -0
  11. package/dist/components/connection-picker.js +98 -0
  12. package/dist/components/connection-picker.js.map +1 -0
  13. package/dist/components/installed-integrations-section.d.ts +14 -0
  14. package/dist/components/installed-integrations-section.d.ts.map +1 -0
  15. package/dist/components/installed-integrations-section.js +203 -0
  16. package/dist/components/installed-integrations-section.js.map +1 -0
  17. package/dist/components/integration-delete-confirm-modal.d.ts +10 -0
  18. package/dist/components/integration-delete-confirm-modal.d.ts.map +1 -0
  19. package/dist/components/integration-delete-confirm-modal.js +61 -0
  20. package/dist/components/integration-delete-confirm-modal.js.map +1 -0
  21. package/dist/components/integration-gallery-for-workspace.d.ts +9 -0
  22. package/dist/components/integration-gallery-for-workspace.d.ts.map +1 -0
  23. package/dist/components/integration-gallery-for-workspace.js +169 -0
  24. package/dist/components/integration-gallery-for-workspace.js.map +1 -0
  25. package/dist/components/integration-gallery.d.ts +8 -0
  26. package/dist/components/integration-gallery.d.ts.map +1 -0
  27. package/dist/components/integration-gallery.js +26 -0
  28. package/dist/components/integration-gallery.js.map +1 -0
  29. package/dist/components/integration-gallery.stories.d.ts +26 -0
  30. package/dist/components/integration-gallery.stories.d.ts.map +1 -0
  31. package/dist/components/integration-gallery.stories.js +335 -0
  32. package/dist/components/integration-gallery.stories.js.map +1 -0
  33. package/dist/components/integration-usage-events.d.ts +4 -0
  34. package/dist/components/integration-usage-events.d.ts.map +1 -0
  35. package/dist/components/integration-usage-events.js +127 -0
  36. package/dist/components/integration-usage-events.js.map +1 -0
  37. package/dist/components/integration-usage-modal.d.ts +16 -0
  38. package/dist/components/integration-usage-modal.d.ts.map +1 -0
  39. package/dist/components/integration-usage-modal.js +184 -0
  40. package/dist/components/integration-usage-modal.js.map +1 -0
  41. package/dist/components/provider-grid.d.ts +14 -0
  42. package/dist/components/provider-grid.d.ts.map +1 -0
  43. package/dist/components/provider-grid.js +152 -0
  44. package/dist/components/provider-grid.js.map +1 -0
  45. package/dist/components/redirect-install-page.d.ts +19 -0
  46. package/dist/components/redirect-install-page.d.ts.map +1 -0
  47. package/dist/components/redirect-install-page.js +68 -0
  48. package/dist/components/redirect-install-page.js.map +1 -0
  49. package/dist/components/repository-picker.d.ts +15 -0
  50. package/dist/components/repository-picker.d.ts.map +1 -0
  51. package/dist/components/repository-picker.js +78 -0
  52. package/dist/components/repository-picker.js.map +1 -0
  53. package/dist/components/webhook/copyable-value.d.ts +10 -0
  54. package/dist/components/webhook/copyable-value.d.ts.map +1 -0
  55. package/dist/components/webhook/copyable-value.js +65 -0
  56. package/dist/components/webhook/copyable-value.js.map +1 -0
  57. package/dist/components/webhook/webhook-create-modal.d.ts +10 -0
  58. package/dist/components/webhook/webhook-create-modal.d.ts.map +1 -0
  59. package/dist/components/webhook/webhook-create-modal.js +233 -0
  60. package/dist/components/webhook/webhook-create-modal.js.map +1 -0
  61. package/dist/components/webhook/webhook-form-errors.d.ts +11 -0
  62. package/dist/components/webhook/webhook-form-errors.d.ts.map +1 -0
  63. package/dist/components/webhook/webhook-form-errors.js +27 -0
  64. package/dist/components/webhook/webhook-form-errors.js.map +1 -0
  65. package/dist/components/webhook/webhook-modals.stories.d.ts +25 -0
  66. package/dist/components/webhook/webhook-modals.stories.d.ts.map +1 -0
  67. package/dist/components/webhook/webhook-modals.stories.js +222 -0
  68. package/dist/components/webhook/webhook-modals.stories.js.map +1 -0
  69. package/dist/components/webhook/webhook-public-endpoint-alert.d.ts +2 -0
  70. package/dist/components/webhook/webhook-public-endpoint-alert.d.ts.map +1 -0
  71. package/dist/components/webhook/webhook-public-endpoint-alert.js +20 -0
  72. package/dist/components/webhook/webhook-public-endpoint-alert.js.map +1 -0
  73. package/dist/components/webhook/webhook-usage-details.d.ts +7 -0
  74. package/dist/components/webhook/webhook-usage-details.d.ts.map +1 -0
  75. package/dist/components/webhook/webhook-usage-details.js +49 -0
  76. package/dist/components/webhook/webhook-usage-details.js.map +1 -0
  77. package/dist/connection-status-badge.d.ts +12 -0
  78. package/dist/connection-status-badge.d.ts.map +1 -0
  79. package/dist/connection-status-badge.js +34 -0
  80. package/dist/connection-status-badge.js.map +1 -0
  81. package/dist/feature.d.ts +37 -0
  82. package/dist/feature.d.ts.map +1 -0
  83. package/dist/feature.js +48 -0
  84. package/dist/feature.js.map +1 -0
  85. package/dist/hooks/api/integrations.d.ts +265 -0
  86. package/dist/hooks/api/integrations.d.ts.map +1 -0
  87. package/dist/hooks/api/integrations.js +229 -0
  88. package/dist/hooks/api/integrations.js.map +1 -0
  89. package/dist/hooks/api/webhook-connections.d.ts +91 -0
  90. package/dist/hooks/api/webhook-connections.d.ts.map +1 -0
  91. package/dist/hooks/api/webhook-connections.js +97 -0
  92. package/dist/hooks/api/webhook-connections.js.map +1 -0
  93. package/dist/index.d.ts +17 -0
  94. package/dist/index.d.ts.map +1 -0
  95. package/dist/index.js +18 -0
  96. package/dist/index.js.map +1 -0
  97. package/dist/integration-icon.d.ts +17 -0
  98. package/dist/integration-icon.d.ts.map +1 -0
  99. package/dist/integration-icon.js +22 -0
  100. package/dist/integration-icon.js.map +1 -0
  101. package/dist/linear-callback.d.ts +17 -0
  102. package/dist/linear-callback.d.ts.map +1 -0
  103. package/dist/linear-callback.js +113 -0
  104. package/dist/linear-callback.js.map +1 -0
  105. package/dist/pages/gitea-form-errors.d.ts +11 -0
  106. package/dist/pages/gitea-form-errors.d.ts.map +1 -0
  107. package/dist/pages/gitea-form-errors.js +35 -0
  108. package/dist/pages/gitea-form-errors.js.map +1 -0
  109. package/dist/pages/gitea-install-page.d.ts +2 -0
  110. package/dist/pages/gitea-install-page.d.ts.map +1 -0
  111. package/dist/pages/gitea-install-page.js +139 -0
  112. package/dist/pages/gitea-install-page.js.map +1 -0
  113. package/dist/pages/github-install-page.d.ts +2 -0
  114. package/dist/pages/github-install-page.d.ts.map +1 -0
  115. package/dist/pages/github-install-page.js +11 -0
  116. package/dist/pages/github-install-page.js.map +1 -0
  117. package/dist/pages/linear-callback-page.d.ts +2 -0
  118. package/dist/pages/linear-callback-page.d.ts.map +1 -0
  119. package/dist/pages/linear-callback-page.js +192 -0
  120. package/dist/pages/linear-callback-page.js.map +1 -0
  121. package/dist/pages/linear-install-page.d.ts +2 -0
  122. package/dist/pages/linear-install-page.d.ts.map +1 -0
  123. package/dist/pages/linear-install-page.js +24 -0
  124. package/dist/pages/linear-install-page.js.map +1 -0
  125. package/dist/pages/sentry-callback-page.d.ts +2 -0
  126. package/dist/pages/sentry-callback-page.d.ts.map +1 -0
  127. package/dist/pages/sentry-callback-page.js +256 -0
  128. package/dist/pages/sentry-callback-page.js.map +1 -0
  129. package/dist/pages/sentry-install-page.d.ts +2 -0
  130. package/dist/pages/sentry-install-page.d.ts.map +1 -0
  131. package/dist/pages/sentry-install-page.js +17 -0
  132. package/dist/pages/sentry-install-page.js.map +1 -0
  133. package/dist/pages/source-control-onboarding-page.d.ts +2 -0
  134. package/dist/pages/source-control-onboarding-page.d.ts.map +1 -0
  135. package/dist/pages/source-control-onboarding-page.js +37 -0
  136. package/dist/pages/source-control-onboarding-page.js.map +1 -0
  137. package/dist/provider-catalog.d.ts +15 -0
  138. package/dist/provider-catalog.d.ts.map +1 -0
  139. package/dist/provider-catalog.js +28 -0
  140. package/dist/provider-catalog.js.map +1 -0
  141. package/dist/routes/gitea.d.ts +6 -0
  142. package/dist/routes/gitea.d.ts.map +1 -0
  143. package/dist/routes/gitea.js +7 -0
  144. package/dist/routes/gitea.js.map +1 -0
  145. package/dist/routes/github-callback.d.ts +6 -0
  146. package/dist/routes/github-callback.d.ts.map +1 -0
  147. package/dist/routes/github-callback.js +129 -0
  148. package/dist/routes/github-callback.js.map +1 -0
  149. package/dist/routes/github.d.ts +6 -0
  150. package/dist/routes/github.d.ts.map +1 -0
  151. package/dist/routes/github.js +7 -0
  152. package/dist/routes/github.js.map +1 -0
  153. package/dist/routes/integrations.d.ts +6 -0
  154. package/dist/routes/integrations.d.ts.map +1 -0
  155. package/dist/routes/integrations.js +7 -0
  156. package/dist/routes/integrations.js.map +1 -0
  157. package/dist/routes/linear-callback.d.ts +6 -0
  158. package/dist/routes/linear-callback.d.ts.map +1 -0
  159. package/dist/routes/linear-callback.js +7 -0
  160. package/dist/routes/linear-callback.js.map +1 -0
  161. package/dist/routes/linear.d.ts +6 -0
  162. package/dist/routes/linear.d.ts.map +1 -0
  163. package/dist/routes/linear.js +7 -0
  164. package/dist/routes/linear.js.map +1 -0
  165. package/dist/routes/sentry-callback.d.ts +6 -0
  166. package/dist/routes/sentry-callback.d.ts.map +1 -0
  167. package/dist/routes/sentry-callback.js +7 -0
  168. package/dist/routes/sentry-callback.js.map +1 -0
  169. package/dist/routes/sentry.d.ts +6 -0
  170. package/dist/routes/sentry.d.ts.map +1 -0
  171. package/dist/routes/sentry.js +7 -0
  172. package/dist/routes/sentry.js.map +1 -0
  173. package/dist/sentry-callback.d.ts +35 -0
  174. package/dist/sentry-callback.d.ts.map +1 -0
  175. package/dist/sentry-callback.js +132 -0
  176. package/dist/sentry-callback.js.map +1 -0
  177. package/dist/tsconfig.test.tsbuildinfo +1 -0
  178. package/package.json +120 -0
  179. package/src/components/connection-picker.tsx +89 -0
  180. package/src/components/installed-integrations-section.tsx +198 -0
  181. package/src/components/integration-delete-confirm-modal.tsx +62 -0
  182. package/src/components/integration-gallery-for-workspace.tsx +193 -0
  183. package/src/components/integration-gallery.stories.tsx +302 -0
  184. package/src/components/integration-gallery.test.tsx +692 -0
  185. package/src/components/integration-gallery.tsx +49 -0
  186. package/src/components/integration-usage-events.test.ts +80 -0
  187. package/src/components/integration-usage-events.ts +116 -0
  188. package/src/components/integration-usage-modal.tsx +178 -0
  189. package/src/components/provider-grid.tsx +163 -0
  190. package/src/components/redirect-install-page.test.tsx +98 -0
  191. package/src/components/redirect-install-page.tsx +68 -0
  192. package/src/components/repository-picker.tsx +102 -0
  193. package/src/components/webhook/copyable-value.test.tsx +41 -0
  194. package/src/components/webhook/copyable-value.tsx +72 -0
  195. package/src/components/webhook/webhook-create-modal.tsx +247 -0
  196. package/src/components/webhook/webhook-form-errors.test.ts +45 -0
  197. package/src/components/webhook/webhook-form-errors.ts +32 -0
  198. package/src/components/webhook/webhook-modals.stories.tsx +225 -0
  199. package/src/components/webhook/webhook-public-endpoint-alert.tsx +15 -0
  200. package/src/components/webhook/webhook-usage-details.tsx +44 -0
  201. package/src/connection-status-badge.tsx +38 -0
  202. package/src/feature.ts +47 -0
  203. package/src/hooks/api/integrations.test.ts +105 -0
  204. package/src/hooks/api/integrations.ts +287 -0
  205. package/src/hooks/api/webhook-connections.ts +115 -0
  206. package/src/index.ts +16 -0
  207. package/src/integration-icon.react.test.tsx +40 -0
  208. package/src/integration-icon.test.ts +19 -0
  209. package/src/integration-icon.tsx +26 -0
  210. package/src/linear-callback.test.ts +227 -0
  211. package/src/linear-callback.ts +136 -0
  212. package/src/pages/gitea-form-errors.test.ts +55 -0
  213. package/src/pages/gitea-form-errors.ts +36 -0
  214. package/src/pages/gitea-install-page.tsx +108 -0
  215. package/src/pages/github-install-page.tsx +11 -0
  216. package/src/pages/linear-callback-page.test.tsx +157 -0
  217. package/src/pages/linear-callback-page.tsx +175 -0
  218. package/src/pages/linear-install-page.tsx +26 -0
  219. package/src/pages/sentry-callback-page.test.tsx +205 -0
  220. package/src/pages/sentry-callback-page.tsx +238 -0
  221. package/src/pages/sentry-install-page.tsx +17 -0
  222. package/src/pages/source-control-onboarding-page.test.tsx +46 -0
  223. package/src/pages/source-control-onboarding-page.tsx +26 -0
  224. package/src/provider-catalog.test.ts +42 -0
  225. package/src/provider-catalog.ts +49 -0
  226. package/src/routes/gitea.tsx +4 -0
  227. package/src/routes/github-callback.tsx +114 -0
  228. package/src/routes/github.tsx +4 -0
  229. package/src/routes/integrations.tsx +4 -0
  230. package/src/routes/linear-callback.tsx +4 -0
  231. package/src/routes/linear.tsx +4 -0
  232. package/src/routes/sentry-callback.tsx +4 -0
  233. package/src/routes/sentry.tsx +4 -0
  234. package/src/sentry-callback.test.ts +239 -0
  235. package/src/sentry-callback.ts +142 -0
  236. package/test/render.tsx +111 -0
  237. package/test/setup.ts +3 -0
  238. package/tsconfig.build.json +9 -0
  239. package/tsconfig.build.tsbuildinfo +1 -0
  240. package/tsconfig.json +3 -0
  241. package/tsconfig.test.json +8 -0
  242. package/vercel.json +8 -0
  243. package/vitest.config.ts +75 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/pages/source-control-onboarding-page.tsx"],"sourcesContent":["import {useActiveWorkspace} from '@shipfox/client-auth';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport {ProviderGrid} from '#components/provider-grid.js';\nimport {useIntegrationProvidersQuery} from '#hooks/api/integrations.js';\n\nexport function SourceControlOnboardingPage() {\n const workspace = useActiveWorkspace();\n const providersQuery = useIntegrationProvidersQuery({capability: 'source_control'});\n\n return (\n <div className=\"mx-auto flex w-full max-w-[640px] flex-col gap-20\">\n <header className=\"flex flex-col gap-8\">\n <Header variant=\"h1\">Install source control</Header>\n <Text size=\"md\" className=\"text-foreground-neutral-muted\">\n Shipfox needs a source control integration to import your repositories.\n </Text>\n </header>\n\n <ProviderGrid\n providersQuery={providersQuery}\n workspaceId={workspace.id}\n emptyMessage=\"Enable at least one source-control provider in the application settings.\"\n />\n </div>\n );\n}\n"],"names":["useActiveWorkspace","Header","Text","ProviderGrid","useIntegrationProvidersQuery","SourceControlOnboardingPage","workspace","providersQuery","capability","div","className","header","variant","size","workspaceId","id","emptyMessage"],"mappings":";AAAA,SAAQA,kBAAkB,QAAO,uBAAuB;AACxD,SAAQC,MAAM,EAAEC,IAAI,QAAO,+BAA+B;AAC1D,SAAQC,YAAY,QAAO,+BAA+B;AAC1D,SAAQC,4BAA4B,QAAO,6BAA6B;AAExE,OAAO,SAASC;IACd,MAAMC,YAAYN;IAClB,MAAMO,iBAAiBH,6BAA6B;QAACI,YAAY;IAAgB;IAEjF,qBACE,MAACC;QAAIC,WAAU;;0BACb,MAACC;gBAAOD,WAAU;;kCAChB,KAACT;wBAAOW,SAAQ;kCAAK;;kCACrB,KAACV;wBAAKW,MAAK;wBAAKH,WAAU;kCAAgC;;;;0BAK5D,KAACP;gBACCI,gBAAgBA;gBAChBO,aAAaR,UAAUS,EAAE;gBACzBC,cAAa;;;;AAIrB"}
@@ -0,0 +1,15 @@
1
+ import type { IconName } from '@shipfox/react-ui/icon';
2
+ export type ProviderSetupPath = '/workspaces/$wid/integrations/github' | '/workspaces/$wid/integrations/gitea' | '/workspaces/$wid/integrations/sentry' | '/workspaces/$wid/integrations/linear';
3
+ interface RouteProviderCatalogEntry {
4
+ kind: 'redirect-install' | 'direct-connect';
5
+ iconName: IconName;
6
+ setupPath: ProviderSetupPath;
7
+ }
8
+ interface ModalProviderCatalogEntry {
9
+ kind: 'modal-connect';
10
+ iconName: IconName;
11
+ }
12
+ export type ProviderCatalogEntry = RouteProviderCatalogEntry | ModalProviderCatalogEntry;
13
+ export declare const PROVIDER_CATALOG: Record<string, ProviderCatalogEntry>;
14
+ export {};
15
+ //# sourceMappingURL=provider-catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-catalog.d.ts","sourceRoot":"","sources":["../src/provider-catalog.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,wBAAwB,CAAC;AAIrD,MAAM,MAAM,iBAAiB,GACzB,sCAAsC,GACtC,qCAAqC,GACrC,sCAAsC,GACtC,sCAAsC,CAAC;AAE3C,UAAU,yBAAyB;IACjC,IAAI,EAAE,kBAAkB,GAAG,gBAAgB,CAAC;IAC5C,QAAQ,EAAE,QAAQ,CAAC;IACnB,SAAS,EAAE,iBAAiB,CAAC;CAC9B;AAED,UAAU,yBAAyB;IACjC,IAAI,EAAE,eAAe,CAAC;IACtB,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED,MAAM,MAAM,oBAAoB,GAAG,yBAAyB,GAAG,yBAAyB,CAAC;AAEzF,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAyBjE,CAAC"}
@@ -0,0 +1,28 @@
1
+ export const PROVIDER_CATALOG = {
2
+ github: {
3
+ kind: 'redirect-install',
4
+ iconName: 'github',
5
+ setupPath: '/workspaces/$wid/integrations/github'
6
+ },
7
+ sentry: {
8
+ kind: 'redirect-install',
9
+ iconName: 'sentry',
10
+ setupPath: '/workspaces/$wid/integrations/sentry'
11
+ },
12
+ linear: {
13
+ kind: 'redirect-install',
14
+ iconName: 'linear',
15
+ setupPath: '/workspaces/$wid/integrations/linear'
16
+ },
17
+ gitea: {
18
+ kind: 'direct-connect',
19
+ iconName: 'gitea',
20
+ setupPath: '/workspaces/$wid/integrations/gitea'
21
+ },
22
+ webhook: {
23
+ kind: 'modal-connect',
24
+ iconName: 'webhookLine'
25
+ }
26
+ };
27
+
28
+ //# sourceMappingURL=provider-catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/provider-catalog.ts"],"sourcesContent":["import type {IconName} from '@shipfox/react-ui/icon';\n\n// Literal union (not string) so `<Link to={catalog.setupPath}>` stays typed\n// against TanStack Router's route tree.\nexport type ProviderSetupPath =\n | '/workspaces/$wid/integrations/github'\n | '/workspaces/$wid/integrations/gitea'\n | '/workspaces/$wid/integrations/sentry'\n | '/workspaces/$wid/integrations/linear';\n\ninterface RouteProviderCatalogEntry {\n kind: 'redirect-install' | 'direct-connect';\n iconName: IconName;\n setupPath: ProviderSetupPath;\n}\n\ninterface ModalProviderCatalogEntry {\n kind: 'modal-connect';\n iconName: IconName;\n}\n\nexport type ProviderCatalogEntry = RouteProviderCatalogEntry | ModalProviderCatalogEntry;\n\nexport const PROVIDER_CATALOG: Record<string, ProviderCatalogEntry> = {\n github: {\n kind: 'redirect-install',\n iconName: 'github',\n setupPath: '/workspaces/$wid/integrations/github',\n },\n sentry: {\n kind: 'redirect-install',\n iconName: 'sentry',\n setupPath: '/workspaces/$wid/integrations/sentry',\n },\n linear: {\n kind: 'redirect-install',\n iconName: 'linear',\n setupPath: '/workspaces/$wid/integrations/linear',\n },\n gitea: {\n kind: 'direct-connect',\n iconName: 'gitea',\n setupPath: '/workspaces/$wid/integrations/gitea',\n },\n webhook: {\n kind: 'modal-connect',\n iconName: 'webhookLine',\n },\n};\n"],"names":["PROVIDER_CATALOG","github","kind","iconName","setupPath","sentry","linear","gitea","webhook"],"mappings":"AAuBA,OAAO,MAAMA,mBAAyD;IACpEC,QAAQ;QACNC,MAAM;QACNC,UAAU;QACVC,WAAW;IACb;IACAC,QAAQ;QACNH,MAAM;QACNC,UAAU;QACVC,WAAW;IACb;IACAE,QAAQ;QACNJ,MAAM;QACNC,UAAU;QACVC,WAAW;IACb;IACAG,OAAO;QACLL,MAAM;QACNC,UAAU;QACVC,WAAW;IACb;IACAI,SAAS;QACPN,MAAM;QACNC,UAAU;IACZ;AACF,EAAE"}
@@ -0,0 +1,6 @@
1
+ import { GiteaInstallPage } from '#pages/gitea-install-page.js';
2
+ declare const _default: import("@shipfox/client-shell/runtime").RouteImpl<{
3
+ readonly component: typeof GiteaInstallPage;
4
+ }>;
5
+ export default _default;
6
+ //# sourceMappingURL=gitea.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitea.d.ts","sourceRoot":"","sources":["../../src/routes/gitea.tsx"],"names":[],"mappings":"AACA,OAAO,EAAC,gBAAgB,EAAC,MAAM,8BAA8B,CAAC;;;;AAE9D,wBAA0D"}
@@ -0,0 +1,7 @@
1
+ import { defineRoute } from '@shipfox/client-shell/runtime';
2
+ import { GiteaInstallPage } from '#pages/gitea-install-page.js';
3
+ export default defineRoute({
4
+ component: GiteaInstallPage
5
+ });
6
+
7
+ //# sourceMappingURL=gitea.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/routes/gitea.tsx"],"sourcesContent":["import {defineRoute} from '@shipfox/client-shell/runtime';\nimport {GiteaInstallPage} from '#pages/gitea-install-page.js';\n\nexport default defineRoute({component: GiteaInstallPage});\n"],"names":["defineRoute","GiteaInstallPage","component"],"mappings":"AAAA,SAAQA,WAAW,QAAO,gCAAgC;AAC1D,SAAQC,gBAAgB,QAAO,+BAA+B;AAE9D,eAAeD,YAAY;IAACE,WAAWD;AAAgB,GAAG"}
@@ -0,0 +1,6 @@
1
+ declare const _default: import("@shipfox/client-shell/runtime").RouteImpl<{
2
+ readonly component: typeof GithubCallbackRoute;
3
+ }>;
4
+ export default _default;
5
+ declare function GithubCallbackRoute(): import("react").JSX.Element;
6
+ //# sourceMappingURL=github-callback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-callback.d.ts","sourceRoot":"","sources":["../../src/routes/github-callback.tsx"],"names":[],"mappings":";;;AAkBA,wBAA6D;AAE7D,iBAAS,mBAAmB,gCAqD3B"}
@@ -0,0 +1,129 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { ApiError, apiRequest } from '@shipfox/client-api';
3
+ import { useRefreshAuth } from '@shipfox/client-auth';
4
+ import { defineRoute } from '@shipfox/client-shell/runtime';
5
+ import { FullPageLoader } from '@shipfox/react-ui/loader';
6
+ import { toast } from '@shipfox/react-ui/toast';
7
+ import { useNavigate, useSearch } from '@tanstack/react-router';
8
+ import { useEffect } from 'react';
9
+ const callbackRequests = new Map();
10
+ const toastedCallbacks = new Set();
11
+ export default defineRoute({
12
+ component: GithubCallbackRoute
13
+ });
14
+ function GithubCallbackRoute() {
15
+ const search = useSearch({
16
+ strict: false
17
+ });
18
+ const navigate = useNavigate();
19
+ const refreshAuth = useRefreshAuth();
20
+ useEffect(()=>{
21
+ const missing = missingCallbackParams(search);
22
+ if (missing.length > 0) {
23
+ toast.error(`GitHub callback is missing: ${missing.join(', ')}. ${missing.includes('code') ? 'Enable "Request user authorization (OAuth) during installation" on the Shipfox GitHub App.' : ''}`);
24
+ navigate({
25
+ to: '/',
26
+ replace: true
27
+ });
28
+ return;
29
+ }
30
+ const params = githubCallbackParams(search);
31
+ if (!params) {
32
+ toast.error('GitHub callback is missing required parameters.');
33
+ navigate({
34
+ to: '/',
35
+ replace: true
36
+ });
37
+ return;
38
+ }
39
+ let disposed = false;
40
+ const key = encodeCallbackQuery(params);
41
+ const request = callbackRequests.get(key) ?? refreshAuth().then(async (session)=>{
42
+ await apiRequest(`/integrations/github/callback/api?${key}`, {
43
+ headers: {
44
+ authorization: `Bearer ${session.token}`
45
+ }
46
+ });
47
+ });
48
+ callbackRequests.set(key, request);
49
+ request.then(()=>{
50
+ if (disposed) return;
51
+ if (!toastedCallbacks.has(key)) {
52
+ toastedCallbacks.add(key);
53
+ toast.success('GitHub installed.');
54
+ }
55
+ navigate({
56
+ to: '/',
57
+ replace: true
58
+ });
59
+ }).catch((error)=>{
60
+ if (disposed) return;
61
+ if (!toastedCallbacks.has(key)) {
62
+ toastedCallbacks.add(key);
63
+ toast.error(githubCallbackErrorMessage(error));
64
+ }
65
+ navigate({
66
+ to: '/',
67
+ replace: true
68
+ });
69
+ });
70
+ return ()=>{
71
+ disposed = true;
72
+ };
73
+ }, [
74
+ navigate,
75
+ refreshAuth,
76
+ search
77
+ ]);
78
+ return /*#__PURE__*/ _jsx(FullPageLoader, {});
79
+ }
80
+ function missingCallbackParams(search) {
81
+ const missing = [];
82
+ if (!stringParam(search.code)) missing.push('code');
83
+ if (!numberParam(search.installation_id)) missing.push('installation_id');
84
+ if (!stringParam(search.state)) missing.push('state');
85
+ return missing;
86
+ }
87
+ function githubCallbackParams(search) {
88
+ const code = stringParam(search.code);
89
+ const installationId = numberParam(search.installation_id);
90
+ const state = stringParam(search.state);
91
+ if (!code || installationId === undefined || !state) return undefined;
92
+ const setupAction = stringParam(search.setup_action);
93
+ return setupAction ? {
94
+ code,
95
+ installationId,
96
+ state,
97
+ setupAction
98
+ } : {
99
+ code,
100
+ installationId,
101
+ state
102
+ };
103
+ }
104
+ function encodeCallbackQuery(params) {
105
+ const search = new URLSearchParams();
106
+ search.set('code', params.code);
107
+ search.set('installation_id', params.installationId.toString());
108
+ search.set('state', params.state);
109
+ if (params.setupAction) search.set('setup_action', params.setupAction);
110
+ return search.toString();
111
+ }
112
+ function stringParam(value) {
113
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
114
+ }
115
+ function numberParam(value) {
116
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
117
+ if (typeof value === 'string' && value.length > 0) {
118
+ const parsed = Number(value);
119
+ return Number.isFinite(parsed) ? parsed : undefined;
120
+ }
121
+ return undefined;
122
+ }
123
+ function githubCallbackErrorMessage(error) {
124
+ if (error instanceof ApiError) return error.message;
125
+ if (error instanceof Error) return error.message;
126
+ return 'Could not install GitHub.';
127
+ }
128
+
129
+ //# sourceMappingURL=github-callback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/routes/github-callback.tsx"],"sourcesContent":["import {ApiError, apiRequest} from '@shipfox/client-api';\nimport {useRefreshAuth} from '@shipfox/client-auth';\nimport {defineRoute} from '@shipfox/client-shell/runtime';\nimport {FullPageLoader} from '@shipfox/react-ui/loader';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {useNavigate, useSearch} from '@tanstack/react-router';\nimport {useEffect} from 'react';\n\ninterface GithubCallbackParams {\n code: string;\n installationId: number;\n state: string;\n setupAction?: string;\n}\n\nconst callbackRequests = new Map<string, Promise<void>>();\nconst toastedCallbacks = new Set<string>();\n\nexport default defineRoute({component: GithubCallbackRoute});\n\nfunction GithubCallbackRoute() {\n const search = useSearch({strict: false});\n const navigate = useNavigate();\n const refreshAuth = useRefreshAuth();\n\n useEffect(() => {\n const missing = missingCallbackParams(search);\n if (missing.length > 0) {\n toast.error(\n `GitHub callback is missing: ${missing.join(', ')}. ${missing.includes('code') ? 'Enable \"Request user authorization (OAuth) during installation\" on the Shipfox GitHub App.' : ''}`,\n );\n navigate({to: '/', replace: true});\n return;\n }\n const params = githubCallbackParams(search);\n if (!params) {\n toast.error('GitHub callback is missing required parameters.');\n navigate({to: '/', replace: true});\n return;\n }\n let disposed = false;\n const key = encodeCallbackQuery(params);\n const request =\n callbackRequests.get(key) ??\n refreshAuth().then(async (session) => {\n await apiRequest(`/integrations/github/callback/api?${key}`, {\n headers: {authorization: `Bearer ${session.token}`},\n });\n });\n callbackRequests.set(key, request);\n request\n .then(() => {\n if (disposed) return;\n if (!toastedCallbacks.has(key)) {\n toastedCallbacks.add(key);\n toast.success('GitHub installed.');\n }\n navigate({to: '/', replace: true});\n })\n .catch((error: unknown) => {\n if (disposed) return;\n if (!toastedCallbacks.has(key)) {\n toastedCallbacks.add(key);\n toast.error(githubCallbackErrorMessage(error));\n }\n navigate({to: '/', replace: true});\n });\n return () => {\n disposed = true;\n };\n }, [navigate, refreshAuth, search]);\n\n return <FullPageLoader />;\n}\n\nfunction missingCallbackParams(search: Record<string, unknown>): string[] {\n const missing: string[] = [];\n if (!stringParam(search.code)) missing.push('code');\n if (!numberParam(search.installation_id)) missing.push('installation_id');\n if (!stringParam(search.state)) missing.push('state');\n return missing;\n}\nfunction githubCallbackParams(search: Record<string, unknown>): GithubCallbackParams | undefined {\n const code = stringParam(search.code);\n const installationId = numberParam(search.installation_id);\n const state = stringParam(search.state);\n if (!code || installationId === undefined || !state) return undefined;\n const setupAction = stringParam(search.setup_action);\n return setupAction ? {code, installationId, state, setupAction} : {code, installationId, state};\n}\nfunction encodeCallbackQuery(params: GithubCallbackParams): string {\n const search = new URLSearchParams();\n search.set('code', params.code);\n search.set('installation_id', params.installationId.toString());\n search.set('state', params.state);\n if (params.setupAction) search.set('setup_action', params.setupAction);\n return search.toString();\n}\nfunction stringParam(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\nfunction numberParam(value: unknown): number | undefined {\n if (typeof value === 'number' && Number.isFinite(value)) return value;\n if (typeof value === 'string' && value.length > 0) {\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n }\n return undefined;\n}\nfunction githubCallbackErrorMessage(error: unknown): string {\n if (error instanceof ApiError) return error.message;\n if (error instanceof Error) return error.message;\n return 'Could not install GitHub.';\n}\n"],"names":["ApiError","apiRequest","useRefreshAuth","defineRoute","FullPageLoader","toast","useNavigate","useSearch","useEffect","callbackRequests","Map","toastedCallbacks","Set","component","GithubCallbackRoute","search","strict","navigate","refreshAuth","missing","missingCallbackParams","length","error","join","includes","to","replace","params","githubCallbackParams","disposed","key","encodeCallbackQuery","request","get","then","session","headers","authorization","token","set","has","add","success","catch","githubCallbackErrorMessage","stringParam","code","push","numberParam","installation_id","state","installationId","undefined","setupAction","setup_action","URLSearchParams","toString","value","Number","isFinite","parsed","message","Error"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { GithubInstallPage } from '#pages/github-install-page.js';
2
+ declare const _default: import("@shipfox/client-shell/runtime").RouteImpl<{
3
+ readonly component: typeof GithubInstallPage;
4
+ }>;
5
+ export default _default;
6
+ //# sourceMappingURL=github.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../src/routes/github.tsx"],"names":[],"mappings":"AACA,OAAO,EAAC,iBAAiB,EAAC,MAAM,+BAA+B,CAAC;;;;AAEhE,wBAA2D"}
@@ -0,0 +1,7 @@
1
+ import { defineRoute } from '@shipfox/client-shell/runtime';
2
+ import { GithubInstallPage } from '#pages/github-install-page.js';
3
+ export default defineRoute({
4
+ component: GithubInstallPage
5
+ });
6
+
7
+ //# sourceMappingURL=github.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/routes/github.tsx"],"sourcesContent":["import {defineRoute} from '@shipfox/client-shell/runtime';\nimport {GithubInstallPage} from '#pages/github-install-page.js';\n\nexport default defineRoute({component: GithubInstallPage});\n"],"names":["defineRoute","GithubInstallPage","component"],"mappings":"AAAA,SAAQA,WAAW,QAAO,gCAAgC;AAC1D,SAAQC,iBAAiB,QAAO,gCAAgC;AAEhE,eAAeD,YAAY;IAACE,WAAWD;AAAiB,GAAG"}
@@ -0,0 +1,6 @@
1
+ import { SourceControlOnboardingPage } from '#pages/source-control-onboarding-page.js';
2
+ declare const _default: import("@shipfox/client-shell/runtime").RouteImpl<{
3
+ readonly component: typeof SourceControlOnboardingPage;
4
+ }>;
5
+ export default _default;
6
+ //# sourceMappingURL=integrations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integrations.d.ts","sourceRoot":"","sources":["../../src/routes/integrations.tsx"],"names":[],"mappings":"AACA,OAAO,EAAC,2BAA2B,EAAC,MAAM,0CAA0C,CAAC;;;;AAErF,wBAAqE"}
@@ -0,0 +1,7 @@
1
+ import { defineRoute } from '@shipfox/client-shell/runtime';
2
+ import { SourceControlOnboardingPage } from '#pages/source-control-onboarding-page.js';
3
+ export default defineRoute({
4
+ component: SourceControlOnboardingPage
5
+ });
6
+
7
+ //# sourceMappingURL=integrations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/routes/integrations.tsx"],"sourcesContent":["import {defineRoute} from '@shipfox/client-shell/runtime';\nimport {SourceControlOnboardingPage} from '#pages/source-control-onboarding-page.js';\n\nexport default defineRoute({component: SourceControlOnboardingPage});\n"],"names":["defineRoute","SourceControlOnboardingPage","component"],"mappings":"AAAA,SAAQA,WAAW,QAAO,gCAAgC;AAC1D,SAAQC,2BAA2B,QAAO,2CAA2C;AAErF,eAAeD,YAAY;IAACE,WAAWD;AAA2B,GAAG"}
@@ -0,0 +1,6 @@
1
+ import { LinearCallbackPage } from '#pages/linear-callback-page.js';
2
+ declare const _default: import("@shipfox/client-shell/runtime").RouteImpl<{
3
+ readonly component: typeof LinearCallbackPage;
4
+ }>;
5
+ export default _default;
6
+ //# sourceMappingURL=linear-callback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linear-callback.d.ts","sourceRoot":"","sources":["../../src/routes/linear-callback.tsx"],"names":[],"mappings":"AACA,OAAO,EAAC,kBAAkB,EAAC,MAAM,gCAAgC,CAAC;;;;AAElE,wBAA4D"}
@@ -0,0 +1,7 @@
1
+ import { defineRoute } from '@shipfox/client-shell/runtime';
2
+ import { LinearCallbackPage } from '#pages/linear-callback-page.js';
3
+ export default defineRoute({
4
+ component: LinearCallbackPage
5
+ });
6
+
7
+ //# sourceMappingURL=linear-callback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/routes/linear-callback.tsx"],"sourcesContent":["import {defineRoute} from '@shipfox/client-shell/runtime';\nimport {LinearCallbackPage} from '#pages/linear-callback-page.js';\n\nexport default defineRoute({component: LinearCallbackPage});\n"],"names":["defineRoute","LinearCallbackPage","component"],"mappings":"AAAA,SAAQA,WAAW,QAAO,gCAAgC;AAC1D,SAAQC,kBAAkB,QAAO,iCAAiC;AAElE,eAAeD,YAAY;IAACE,WAAWD;AAAkB,GAAG"}
@@ -0,0 +1,6 @@
1
+ import { LinearInstallPage } from '#pages/linear-install-page.js';
2
+ declare const _default: import("@shipfox/client-shell/runtime").RouteImpl<{
3
+ readonly component: typeof LinearInstallPage;
4
+ }>;
5
+ export default _default;
6
+ //# sourceMappingURL=linear.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linear.d.ts","sourceRoot":"","sources":["../../src/routes/linear.tsx"],"names":[],"mappings":"AACA,OAAO,EAAC,iBAAiB,EAAC,MAAM,+BAA+B,CAAC;;;;AAEhE,wBAA2D"}
@@ -0,0 +1,7 @@
1
+ import { defineRoute } from '@shipfox/client-shell/runtime';
2
+ import { LinearInstallPage } from '#pages/linear-install-page.js';
3
+ export default defineRoute({
4
+ component: LinearInstallPage
5
+ });
6
+
7
+ //# sourceMappingURL=linear.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/routes/linear.tsx"],"sourcesContent":["import {defineRoute} from '@shipfox/client-shell/runtime';\nimport {LinearInstallPage} from '#pages/linear-install-page.js';\n\nexport default defineRoute({component: LinearInstallPage});\n"],"names":["defineRoute","LinearInstallPage","component"],"mappings":"AAAA,SAAQA,WAAW,QAAO,gCAAgC;AAC1D,SAAQC,iBAAiB,QAAO,gCAAgC;AAEhE,eAAeD,YAAY;IAACE,WAAWD;AAAiB,GAAG"}
@@ -0,0 +1,6 @@
1
+ import { SentryCallbackPage } from '#pages/sentry-callback-page.js';
2
+ declare const _default: import("@shipfox/client-shell/runtime").RouteImpl<{
3
+ readonly component: typeof SentryCallbackPage;
4
+ }>;
5
+ export default _default;
6
+ //# sourceMappingURL=sentry-callback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sentry-callback.d.ts","sourceRoot":"","sources":["../../src/routes/sentry-callback.tsx"],"names":[],"mappings":"AACA,OAAO,EAAC,kBAAkB,EAAC,MAAM,gCAAgC,CAAC;;;;AAElE,wBAA4D"}
@@ -0,0 +1,7 @@
1
+ import { defineRoute } from '@shipfox/client-shell/runtime';
2
+ import { SentryCallbackPage } from '#pages/sentry-callback-page.js';
3
+ export default defineRoute({
4
+ component: SentryCallbackPage
5
+ });
6
+
7
+ //# sourceMappingURL=sentry-callback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/routes/sentry-callback.tsx"],"sourcesContent":["import {defineRoute} from '@shipfox/client-shell/runtime';\nimport {SentryCallbackPage} from '#pages/sentry-callback-page.js';\n\nexport default defineRoute({component: SentryCallbackPage});\n"],"names":["defineRoute","SentryCallbackPage","component"],"mappings":"AAAA,SAAQA,WAAW,QAAO,gCAAgC;AAC1D,SAAQC,kBAAkB,QAAO,iCAAiC;AAElE,eAAeD,YAAY;IAACE,WAAWD;AAAkB,GAAG"}
@@ -0,0 +1,6 @@
1
+ import { SentryInstallPage } from '#pages/sentry-install-page.js';
2
+ declare const _default: import("@shipfox/client-shell/runtime").RouteImpl<{
3
+ readonly component: typeof SentryInstallPage;
4
+ }>;
5
+ export default _default;
6
+ //# sourceMappingURL=sentry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sentry.d.ts","sourceRoot":"","sources":["../../src/routes/sentry.tsx"],"names":[],"mappings":"AACA,OAAO,EAAC,iBAAiB,EAAC,MAAM,+BAA+B,CAAC;;;;AAEhE,wBAA2D"}
@@ -0,0 +1,7 @@
1
+ import { defineRoute } from '@shipfox/client-shell/runtime';
2
+ import { SentryInstallPage } from '#pages/sentry-install-page.js';
3
+ export default defineRoute({
4
+ component: SentryInstallPage
5
+ });
6
+
7
+ //# sourceMappingURL=sentry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/routes/sentry.tsx"],"sourcesContent":["import {defineRoute} from '@shipfox/client-shell/runtime';\nimport {SentryInstallPage} from '#pages/sentry-install-page.js';\n\nexport default defineRoute({component: SentryInstallPage});\n"],"names":["defineRoute","SentryInstallPage","component"],"mappings":"AAAA,SAAQA,WAAW,QAAO,gCAAgC;AAC1D,SAAQC,iBAAiB,QAAO,gCAAgC;AAEhE,eAAeD,YAAY;IAACE,WAAWD;AAAiB,GAAG"}
@@ -0,0 +1,35 @@
1
+ export declare const SENTRY_INSTALL_WORKSPACE_KEY = "shipfox.sentry-install.workspace-id";
2
+ type WorkspaceStorage = Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>;
3
+ export declare function saveSentryInstallWorkspace(storage: WorkspaceStorage, workspaceId: string): void;
4
+ export declare function readSentryInstallWorkspace(storage: WorkspaceStorage): string | undefined;
5
+ export declare function clearSentryInstallWorkspace(storage: WorkspaceStorage): void;
6
+ export interface SentryCallbackParams {
7
+ code: string;
8
+ installationId: string;
9
+ /** Display-only: rendered in the confirm copy, never sent to the API. */
10
+ orgSlug?: string | undefined;
11
+ }
12
+ export declare function parseSentryCallbackParams(search: Record<string, unknown>): SentryCallbackParams | undefined;
13
+ export interface SentryWorkspaceOption {
14
+ id: string;
15
+ name: string;
16
+ }
17
+ export type SentryWorkspacePreselection = {
18
+ kind: 'pick';
19
+ preselectedId: string | undefined;
20
+ } | {
21
+ kind: 'none';
22
+ };
23
+ export declare function preselectSentryWorkspace(storedId: string | undefined, workspaces: SentryWorkspaceOption[]): SentryWorkspacePreselection;
24
+ export type SentryConnectFailure = {
25
+ kind: 'retryable';
26
+ message: string;
27
+ retryAfterSeconds?: number | undefined;
28
+ } | {
29
+ kind: 'terminal';
30
+ message: string;
31
+ startOver: boolean;
32
+ };
33
+ export declare function classifySentryConnectError(error: unknown): SentryConnectFailure;
34
+ export {};
35
+ //# sourceMappingURL=sentry-callback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sentry-callback.d.ts","sourceRoot":"","sources":["../src/sentry-callback.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,4BAA4B,wCAAwC,CAAC;AAElF,KAAK,gBAAgB,GAAG,IAAI,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,GAAG,YAAY,CAAC,CAAC;AAK5E,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAM/F;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,GAAG,SAAS,CAMxF;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAM3E;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC9B;AAED,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,oBAAoB,GAAG,SAAS,CAQlC;AAED,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,2BAA2B,GACnC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAA;CAAC,GACjD;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAAC;AAKnB,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,UAAU,EAAE,qBAAqB,EAAE,GAClC,2BAA2B,CAO7B;AAED,MAAM,MAAM,oBAAoB,GAC5B;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAC,GAC5E;IAAC,IAAI,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAC,CAAC;AAE5D,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,oBAAoB,CA8C/E"}
@@ -0,0 +1,132 @@
1
+ import { ApiError } from '@shipfox/client-api';
2
+ export const SENTRY_INSTALL_WORKSPACE_KEY = 'shipfox.sentry-install.workspace-id';
3
+ // Storage helpers swallow failures (quota, private mode, disabled storage):
4
+ // the handoff is an optimization, never a requirement — the callback always
5
+ // asks for explicit confirmation and only uses the stored id to pre-select.
6
+ export function saveSentryInstallWorkspace(storage, workspaceId) {
7
+ try {
8
+ storage.setItem(SENTRY_INSTALL_WORKSPACE_KEY, workspaceId);
9
+ } catch {
10
+ // The callback falls back to its sole-workspace pre-selection.
11
+ }
12
+ }
13
+ export function readSentryInstallWorkspace(storage) {
14
+ try {
15
+ return storage.getItem(SENTRY_INSTALL_WORKSPACE_KEY) ?? undefined;
16
+ } catch {
17
+ return undefined;
18
+ }
19
+ }
20
+ export function clearSentryInstallWorkspace(storage) {
21
+ try {
22
+ storage.removeItem(SENTRY_INSTALL_WORKSPACE_KEY);
23
+ } catch {
24
+ // A tab-scoped leftover key is harmless: it only pre-selects.
25
+ }
26
+ }
27
+ export function parseSentryCallbackParams(search) {
28
+ const code = stringParam(search.code);
29
+ // Sentry documents camelCase params; accept snake_case defensively.
30
+ const installationId = stringParam(search.installationId) ?? stringParam(search.installation_id);
31
+ if (!code || !installationId) return undefined;
32
+ const orgSlug = stringParam(search.orgSlug) ?? stringParam(search.org_slug);
33
+ return {
34
+ code,
35
+ installationId,
36
+ orgSlug
37
+ };
38
+ }
39
+ // Sentry's redirect carries no state token, so the callback can never prove it
40
+ // belongs to an install the user started — installation always requires an
41
+ // explicit click. The stored id (and a sole workspace) only pre-select.
42
+ export function preselectSentryWorkspace(storedId, workspaces) {
43
+ if (workspaces.length === 0) return {
44
+ kind: 'none'
45
+ };
46
+ if (storedId && workspaces.some((workspace)=>workspace.id === storedId)) {
47
+ return {
48
+ kind: 'pick',
49
+ preselectedId: storedId
50
+ };
51
+ }
52
+ if (workspaces.length === 1) return {
53
+ kind: 'pick',
54
+ preselectedId: workspaces[0]?.id
55
+ };
56
+ return {
57
+ kind: 'pick',
58
+ preselectedId: undefined
59
+ };
60
+ }
61
+ export function classifySentryConnectError(error) {
62
+ if (error instanceof ApiError) {
63
+ if (error.code === 'sentry-installation-already-linked') {
64
+ return {
65
+ kind: 'terminal',
66
+ message: 'This Sentry org is already installed in another workspace.',
67
+ // Retrying or starting over would fail identically.
68
+ startOver: false
69
+ };
70
+ }
71
+ if (error.code === 'sentry-verification-in-progress') {
72
+ // A concurrent signed webhook is still verifying the install. The grant code
73
+ // is untouched, so the existing backoff re-calls and finds the verified row.
74
+ return {
75
+ kind: 'retryable',
76
+ message: 'Finishing Sentry verification. This only takes a moment.',
77
+ retryAfterSeconds: retryAfterSeconds(error.details)
78
+ };
79
+ }
80
+ if (error.code === 'rate-limited') {
81
+ return {
82
+ kind: 'retryable',
83
+ message: 'Sentry is rate limiting requests. Try again in a moment.',
84
+ retryAfterSeconds: retryAfterSeconds(error.details)
85
+ };
86
+ }
87
+ if (error.code === 'timeout' || error.code === 'provider-unavailable') {
88
+ return {
89
+ kind: 'retryable',
90
+ message: 'Sentry is unreachable. Try again.'
91
+ };
92
+ }
93
+ if (error.status === 0 || error.code === 'network-error') {
94
+ // A network failure (apiRequest throws status 0) never reached the
95
+ // server, so the grant code is untouched and a plain Retry can recover.
96
+ // Must precede the status < 500 branch, which would otherwise treat it as
97
+ // a spent code and force a full restart.
98
+ return {
99
+ kind: 'retryable',
100
+ message: 'Could not reach Shipfox. Check your connection and try again.'
101
+ };
102
+ }
103
+ if (error.status < 500) {
104
+ // 4xx (access-denied, malformed-provider-response, validation): the grant
105
+ // code is spent or rejected — only a fresh install can recover.
106
+ return {
107
+ kind: 'terminal',
108
+ message: error.message,
109
+ startOver: true
110
+ };
111
+ }
112
+ }
113
+ return {
114
+ kind: 'retryable',
115
+ message: 'Could not install Sentry. Try again.'
116
+ };
117
+ }
118
+ function retryAfterSeconds(details) {
119
+ // client-api's toApiError stores the whole response body as ApiError.details,
120
+ // so the structured payload lives one level deeper at details.details (mirrors
121
+ // project-error.ts apiDetails()).
122
+ if (typeof details !== 'object' || details === null) return undefined;
123
+ const inner = details.details;
124
+ if (typeof inner !== 'object' || inner === null) return undefined;
125
+ const value = inner.retry_after_seconds;
126
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
127
+ }
128
+ function stringParam(value) {
129
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
130
+ }
131
+
132
+ //# sourceMappingURL=sentry-callback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/sentry-callback.ts"],"sourcesContent":["import {ApiError} from '@shipfox/client-api';\n\nexport const SENTRY_INSTALL_WORKSPACE_KEY = 'shipfox.sentry-install.workspace-id';\n\ntype WorkspaceStorage = Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>;\n\n// Storage helpers swallow failures (quota, private mode, disabled storage):\n// the handoff is an optimization, never a requirement — the callback always\n// asks for explicit confirmation and only uses the stored id to pre-select.\nexport function saveSentryInstallWorkspace(storage: WorkspaceStorage, workspaceId: string): void {\n try {\n storage.setItem(SENTRY_INSTALL_WORKSPACE_KEY, workspaceId);\n } catch {\n // The callback falls back to its sole-workspace pre-selection.\n }\n}\n\nexport function readSentryInstallWorkspace(storage: WorkspaceStorage): string | undefined {\n try {\n return storage.getItem(SENTRY_INSTALL_WORKSPACE_KEY) ?? undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function clearSentryInstallWorkspace(storage: WorkspaceStorage): void {\n try {\n storage.removeItem(SENTRY_INSTALL_WORKSPACE_KEY);\n } catch {\n // A tab-scoped leftover key is harmless: it only pre-selects.\n }\n}\n\nexport interface SentryCallbackParams {\n code: string;\n installationId: string;\n /** Display-only: rendered in the confirm copy, never sent to the API. */\n orgSlug?: string | undefined;\n}\n\nexport function parseSentryCallbackParams(\n search: Record<string, unknown>,\n): SentryCallbackParams | undefined {\n const code = stringParam(search.code);\n // Sentry documents camelCase params; accept snake_case defensively.\n const installationId = stringParam(search.installationId) ?? stringParam(search.installation_id);\n if (!code || !installationId) return undefined;\n\n const orgSlug = stringParam(search.orgSlug) ?? stringParam(search.org_slug);\n return {code, installationId, orgSlug};\n}\n\nexport interface SentryWorkspaceOption {\n id: string;\n name: string;\n}\n\nexport type SentryWorkspacePreselection =\n | {kind: 'pick'; preselectedId: string | undefined}\n | {kind: 'none'};\n\n// Sentry's redirect carries no state token, so the callback can never prove it\n// belongs to an install the user started — installation always requires an\n// explicit click. The stored id (and a sole workspace) only pre-select.\nexport function preselectSentryWorkspace(\n storedId: string | undefined,\n workspaces: SentryWorkspaceOption[],\n): SentryWorkspacePreselection {\n if (workspaces.length === 0) return {kind: 'none'};\n if (storedId && workspaces.some((workspace) => workspace.id === storedId)) {\n return {kind: 'pick', preselectedId: storedId};\n }\n if (workspaces.length === 1) return {kind: 'pick', preselectedId: workspaces[0]?.id};\n return {kind: 'pick', preselectedId: undefined};\n}\n\nexport type SentryConnectFailure =\n | {kind: 'retryable'; message: string; retryAfterSeconds?: number | undefined}\n | {kind: 'terminal'; message: string; startOver: boolean};\n\nexport function classifySentryConnectError(error: unknown): SentryConnectFailure {\n if (error instanceof ApiError) {\n if (error.code === 'sentry-installation-already-linked') {\n return {\n kind: 'terminal',\n message: 'This Sentry org is already installed in another workspace.',\n // Retrying or starting over would fail identically.\n startOver: false,\n };\n }\n if (error.code === 'sentry-verification-in-progress') {\n // A concurrent signed webhook is still verifying the install. The grant code\n // is untouched, so the existing backoff re-calls and finds the verified row.\n return {\n kind: 'retryable',\n message: 'Finishing Sentry verification. This only takes a moment.',\n retryAfterSeconds: retryAfterSeconds(error.details),\n };\n }\n if (error.code === 'rate-limited') {\n return {\n kind: 'retryable',\n message: 'Sentry is rate limiting requests. Try again in a moment.',\n retryAfterSeconds: retryAfterSeconds(error.details),\n };\n }\n if (error.code === 'timeout' || error.code === 'provider-unavailable') {\n return {kind: 'retryable', message: 'Sentry is unreachable. Try again.'};\n }\n if (error.status === 0 || error.code === 'network-error') {\n // A network failure (apiRequest throws status 0) never reached the\n // server, so the grant code is untouched and a plain Retry can recover.\n // Must precede the status < 500 branch, which would otherwise treat it as\n // a spent code and force a full restart.\n return {\n kind: 'retryable',\n message: 'Could not reach Shipfox. Check your connection and try again.',\n };\n }\n if (error.status < 500) {\n // 4xx (access-denied, malformed-provider-response, validation): the grant\n // code is spent or rejected — only a fresh install can recover.\n return {kind: 'terminal', message: error.message, startOver: true};\n }\n }\n return {kind: 'retryable', message: 'Could not install Sentry. Try again.'};\n}\n\nfunction retryAfterSeconds(details: unknown): number | undefined {\n // client-api's toApiError stores the whole response body as ApiError.details,\n // so the structured payload lives one level deeper at details.details (mirrors\n // project-error.ts apiDetails()).\n if (typeof details !== 'object' || details === null) return undefined;\n const inner = (details as Record<string, unknown>).details;\n if (typeof inner !== 'object' || inner === null) return undefined;\n const value = (inner as Record<string, unknown>).retry_after_seconds;\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\nfunction stringParam(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n"],"names":["ApiError","SENTRY_INSTALL_WORKSPACE_KEY","saveSentryInstallWorkspace","storage","workspaceId","setItem","readSentryInstallWorkspace","getItem","undefined","clearSentryInstallWorkspace","removeItem","parseSentryCallbackParams","search","code","stringParam","installationId","installation_id","orgSlug","org_slug","preselectSentryWorkspace","storedId","workspaces","length","kind","some","workspace","id","preselectedId","classifySentryConnectError","error","message","startOver","retryAfterSeconds","details","status","inner","value","retry_after_seconds","Number","isFinite"],"mappings":"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"}