@shipfox/client-integrations 12.0.2 → 13.0.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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +34 -0
- package/dist/components/callback-status-shell.d.ts +4 -3
- package/dist/components/callback-status-shell.d.ts.map +1 -1
- package/dist/components/callback-status-shell.js +11 -11
- package/dist/components/callback-status-shell.js.map +1 -1
- package/dist/components/installed-integrations-section.d.ts +2 -1
- package/dist/components/installed-integrations-section.d.ts.map +1 -1
- package/dist/components/installed-integrations-section.js +7 -6
- package/dist/components/installed-integrations-section.js.map +1 -1
- package/dist/components/integration-gallery-for-workspace.d.ts.map +1 -1
- package/dist/components/integration-gallery-for-workspace.js +11 -2
- package/dist/components/integration-gallery-for-workspace.js.map +1 -1
- package/dist/components/integration-gallery.stories.js +10 -9
- package/dist/components/integration-gallery.stories.js.map +1 -1
- package/dist/components/provider-grid.d.ts +2 -2
- package/dist/components/provider-grid.d.ts.map +1 -1
- package/dist/components/provider-grid.js +4 -4
- package/dist/components/provider-grid.js.map +1 -1
- package/dist/components/redirect-install-page.d.ts +1 -1
- package/dist/components/redirect-install-page.js +2 -2
- package/dist/components/redirect-install-page.js.map +1 -1
- package/dist/components/webhook/webhook-modals.stories.js +2 -2
- package/dist/components/webhook/webhook-modals.stories.js.map +1 -1
- package/dist/feature.d.ts +7 -7
- package/dist/feature.js +7 -7
- package/dist/feature.js.map +1 -1
- package/dist/hooks/api/integrations.js +1 -1
- package/dist/hooks/api/integrations.js.map +1 -1
- package/dist/pages/gitea-install-page.js +4 -4
- package/dist/pages/gitea-install-page.js.map +1 -1
- package/dist/pages/linear-callback-page.d.ts.map +1 -1
- package/dist/pages/linear-callback-page.js +58 -12
- package/dist/pages/linear-callback-page.js.map +1 -1
- package/dist/pages/sentry-callback-page.d.ts.map +1 -1
- package/dist/pages/sentry-callback-page.js +22 -9
- package/dist/pages/sentry-callback-page.js.map +1 -1
- package/dist/pages/slack-callback-page.d.ts.map +1 -1
- package/dist/pages/slack-callback-page.js +50 -18
- package/dist/pages/slack-callback-page.js.map +1 -1
- package/dist/pages/source-control-onboarding-page.js +1 -1
- package/dist/pages/source-control-onboarding-page.js.map +1 -1
- package/dist/provider-catalog.d.ts +1 -1
- package/dist/provider-catalog.d.ts.map +1 -1
- package/dist/provider-catalog.js +5 -5
- package/dist/provider-catalog.js.map +1 -1
- package/dist/sentry-callback.js +2 -2
- package/dist/sentry-callback.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/dist/workspace-navigation.d.ts +13 -0
- package/dist/workspace-navigation.d.ts.map +1 -0
- package/dist/workspace-navigation.js +17 -0
- package/dist/workspace-navigation.js.map +1 -0
- package/package.json +13 -13
- package/src/components/callback-status-shell.tsx +14 -10
- package/src/components/installed-integrations-section.tsx +16 -9
- package/src/components/integration-gallery-for-workspace.tsx +20 -10
- package/src/components/integration-gallery.stories.tsx +10 -10
- package/src/components/integration-gallery.test.tsx +30 -15
- package/src/components/provider-grid.tsx +6 -6
- package/src/components/redirect-install-page.test.tsx +3 -3
- package/src/components/redirect-install-page.tsx +2 -2
- package/src/components/webhook/webhook-modals.stories.tsx +2 -2
- package/src/feature.ts +7 -7
- package/src/hooks/api/integrations.ts +1 -1
- package/src/pages/gitea-install-page.tsx +2 -2
- package/src/pages/linear-callback-page.test.tsx +25 -12
- package/src/pages/linear-callback-page.tsx +73 -12
- package/src/pages/sentry-callback-page.test.tsx +8 -5
- package/src/pages/sentry-callback-page.tsx +32 -8
- package/src/pages/slack-callback-page.test.tsx +78 -0
- package/src/pages/slack-callback-page.tsx +56 -18
- package/src/pages/source-control-onboarding-page.test.tsx +4 -4
- package/src/pages/source-control-onboarding-page.tsx +1 -1
- package/src/provider-catalog.test.ts +4 -4
- package/src/provider-catalog.ts +10 -10
- package/src/sentry-callback.ts +2 -2
- package/src/workspace-navigation.test.ts +43 -0
- package/src/workspace-navigation.ts +38 -0
- package/test/render.tsx +2 -1
- package/tsconfig.build.tsbuildinfo +1 -1
package/dist/provider-catalog.js
CHANGED
|
@@ -3,27 +3,27 @@ export const PROVIDER_CATALOG = {
|
|
|
3
3
|
github: {
|
|
4
4
|
kind: 'redirect-install',
|
|
5
5
|
iconName: PROVIDER_ICONS.github,
|
|
6
|
-
setupPath: '/
|
|
6
|
+
setupPath: '/w/$workspaceSlug/integrations/github'
|
|
7
7
|
},
|
|
8
8
|
sentry: {
|
|
9
9
|
kind: 'redirect-install',
|
|
10
10
|
iconName: PROVIDER_ICONS.sentry,
|
|
11
|
-
setupPath: '/
|
|
11
|
+
setupPath: '/w/$workspaceSlug/integrations/sentry'
|
|
12
12
|
},
|
|
13
13
|
linear: {
|
|
14
14
|
kind: 'redirect-install',
|
|
15
15
|
iconName: PROVIDER_ICONS.linear,
|
|
16
|
-
setupPath: '/
|
|
16
|
+
setupPath: '/w/$workspaceSlug/integrations/linear'
|
|
17
17
|
},
|
|
18
18
|
slack: {
|
|
19
19
|
kind: 'redirect-install',
|
|
20
20
|
iconName: PROVIDER_ICONS.slack,
|
|
21
|
-
setupPath: '/
|
|
21
|
+
setupPath: '/w/$workspaceSlug/integrations/slack'
|
|
22
22
|
},
|
|
23
23
|
gitea: {
|
|
24
24
|
kind: 'direct-connect',
|
|
25
25
|
iconName: PROVIDER_ICONS.gitea,
|
|
26
|
-
setupPath: '/
|
|
26
|
+
setupPath: '/w/$workspaceSlug/integrations/gitea'
|
|
27
27
|
},
|
|
28
28
|
webhook: {
|
|
29
29
|
kind: 'modal-connect',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/provider-catalog.ts"],"sourcesContent":["import {PROVIDER_ICONS} from '@shipfox/integration-icons';\nimport 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 | '/
|
|
1
|
+
{"version":3,"sources":["../src/provider-catalog.ts"],"sourcesContent":["import {PROVIDER_ICONS} from '@shipfox/integration-icons';\nimport 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 | '/w/$workspaceSlug/integrations/github'\n | '/w/$workspaceSlug/integrations/gitea'\n | '/w/$workspaceSlug/integrations/sentry'\n | '/w/$workspaceSlug/integrations/linear'\n | '/w/$workspaceSlug/integrations/slack';\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: PROVIDER_ICONS.github,\n setupPath: '/w/$workspaceSlug/integrations/github',\n },\n sentry: {\n kind: 'redirect-install',\n iconName: PROVIDER_ICONS.sentry,\n setupPath: '/w/$workspaceSlug/integrations/sentry',\n },\n linear: {\n kind: 'redirect-install',\n iconName: PROVIDER_ICONS.linear,\n setupPath: '/w/$workspaceSlug/integrations/linear',\n },\n slack: {\n kind: 'redirect-install',\n iconName: PROVIDER_ICONS.slack,\n setupPath: '/w/$workspaceSlug/integrations/slack',\n },\n gitea: {\n kind: 'direct-connect',\n iconName: PROVIDER_ICONS.gitea,\n setupPath: '/w/$workspaceSlug/integrations/gitea',\n },\n webhook: {\n kind: 'modal-connect',\n iconName: PROVIDER_ICONS.webhook,\n },\n};\n"],"names":["PROVIDER_ICONS","PROVIDER_CATALOG","github","kind","iconName","setupPath","sentry","linear","slack","gitea","webhook"],"mappings":"AAAA,SAAQA,cAAc,QAAO,6BAA6B;AAyB1D,OAAO,MAAMC,mBAAyD;IACpEC,QAAQ;QACNC,MAAM;QACNC,UAAUJ,eAAeE,MAAM;QAC/BG,WAAW;IACb;IACAC,QAAQ;QACNH,MAAM;QACNC,UAAUJ,eAAeM,MAAM;QAC/BD,WAAW;IACb;IACAE,QAAQ;QACNJ,MAAM;QACNC,UAAUJ,eAAeO,MAAM;QAC/BF,WAAW;IACb;IACAG,OAAO;QACLL,MAAM;QACNC,UAAUJ,eAAeQ,KAAK;QAC9BH,WAAW;IACb;IACAI,OAAO;QACLN,MAAM;QACNC,UAAUJ,eAAeS,KAAK;QAC9BJ,WAAW;IACb;IACAK,SAAS;QACPP,MAAM;QACNC,UAAUJ,eAAeU,OAAO;IAClC;AACF,EAAE"}
|
package/dist/sentry-callback.js
CHANGED
|
@@ -11,7 +11,7 @@ const sentryInstallWorkspaceStorageKey = {
|
|
|
11
11
|
parse: (value)=>value || undefined
|
|
12
12
|
};
|
|
13
13
|
// Storage helpers swallow failures (quota, private mode, disabled storage):
|
|
14
|
-
// the handoff is an optimization, never a requirement
|
|
14
|
+
// the handoff is an optimization, never a requirement: the callback always
|
|
15
15
|
// asks for explicit confirmation and only uses the stored id to pre-select.
|
|
16
16
|
export function saveSentryInstallWorkspace(storage, workspaceId) {
|
|
17
17
|
installWorkspaceStorage(storage).write(workspaceId);
|
|
@@ -38,7 +38,7 @@ export function parseSentryCallbackParams(search) {
|
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
40
|
// Sentry's redirect carries no state token, so the callback can never prove it
|
|
41
|
-
// belongs to an install the user started
|
|
41
|
+
// belongs to an install the user started. Installation always requires an
|
|
42
42
|
// explicit click. The stored id (and a sole workspace) only pre-select.
|
|
43
43
|
export function preselectSentryWorkspace(storedId, workspaces) {
|
|
44
44
|
if (workspaces.length === 0) return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/sentry-callback.ts"],"sourcesContent":["import {ApiError} from '@shipfox/client-api';\nimport {\n type BrowserStorage,\n type BrowserStorageKey,\n createTypedBrowserStorage,\n} from '@shipfox/client-ui';\n\nexport const SENTRY_INSTALL_WORKSPACE_KEY = 'shipfox.sentry-install.workspace-id';\n\ntype WorkspaceStorage = BrowserStorage | undefined;\n\nconst sentryInstallWorkspaceStorageKey = {\n key: SENTRY_INSTALL_WORKSPACE_KEY,\n lifetime: 'session',\n // This is a one-shot navigation hint, not private workspace state. The\n // callback validates it against the authenticated workspace list before use.\n principalScope: 'global',\n serialize: (workspaceId: string) => workspaceId,\n parse: (value: string) => value || undefined,\n} satisfies BrowserStorageKey<string>;\n\n// Storage helpers swallow failures (quota, private mode, disabled storage):\n// the handoff is an optimization, never a requirement
|
|
1
|
+
{"version":3,"sources":["../src/sentry-callback.ts"],"sourcesContent":["import {ApiError} from '@shipfox/client-api';\nimport {\n type BrowserStorage,\n type BrowserStorageKey,\n createTypedBrowserStorage,\n} from '@shipfox/client-ui';\n\nexport const SENTRY_INSTALL_WORKSPACE_KEY = 'shipfox.sentry-install.workspace-id';\n\ntype WorkspaceStorage = BrowserStorage | undefined;\n\nconst sentryInstallWorkspaceStorageKey = {\n key: SENTRY_INSTALL_WORKSPACE_KEY,\n lifetime: 'session',\n // This is a one-shot navigation hint, not private workspace state. The\n // callback validates it against the authenticated workspace list before use.\n principalScope: 'global',\n serialize: (workspaceId: string) => workspaceId,\n parse: (value: string) => value || undefined,\n} satisfies BrowserStorageKey<string>;\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 installWorkspaceStorage(storage).write(workspaceId);\n}\n\nexport function readSentryInstallWorkspace(storage: WorkspaceStorage): string | undefined {\n return installWorkspaceStorage(storage).read();\n}\n\nexport function clearSentryInstallWorkspace(storage: WorkspaceStorage): void {\n installWorkspaceStorage(storage).remove();\n}\n\nfunction installWorkspaceStorage(storage: WorkspaceStorage) {\n return createTypedBrowserStorage(() => storage, sentryInstallWorkspaceStorageKey);\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 (the client transport reports 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 return {\n kind: 'terminal',\n message:\n 'Sentry did not accept this install. Start the install again from workspace settings.',\n startOver: true,\n };\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","createTypedBrowserStorage","SENTRY_INSTALL_WORKSPACE_KEY","sentryInstallWorkspaceStorageKey","key","lifetime","principalScope","serialize","workspaceId","parse","value","undefined","saveSentryInstallWorkspace","storage","installWorkspaceStorage","write","readSentryInstallWorkspace","read","clearSentryInstallWorkspace","remove","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","retry_after_seconds","Number","isFinite"],"mappings":"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"}
|