Resolves the slug against the tenant catalog and forwards into the integration detail page's existing add-connection flow. Unknown slugs get a not-found state. Makes the link survive sign-in: reserve connect as an org slug so cloud's callback stops reading it as an org selector, and return self-host's login to the requested path (its gate swaps the page in without ever writing a returnTo).
Unified · Split
| @@ -23,6 +23,7 @@ import { Route as ApiKeysRouteImport } from './routes/app/api-keys' · 22 unmodified lines | |||
| 23 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRouteImport } from './../../../packages/react/src/routes/toolkits.$toolkitSlug' | 23 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRouteImport } from './../../../packages/react/src/routes/toolkits.$toolkitSlug' |
| 24 | import { Route as ResumeDotexecutionIdRouteImport } from './routes/app/resume.$executionId' | 24 | import { Route as ResumeDotexecutionIdRouteImport } from './routes/app/resume.$executionId' |
| 25 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport } from './../../../packages/react/src/routes/integrations.$namespace' | 25 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport } from './../../../packages/react/src/routes/integrations.$namespace' |
| 26 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRouteImport } from './../../../packages/react/src/routes/connect.$integrationSlug' | ||
| 26 | import { Route as Billing_DotplansRouteImport } from './routes/app/billing_.plans' | 27 | import { Route as Billing_DotplansRouteImport } from './routes/app/billing_.plans' |
| 27 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport } from './../../../packages/react/src/routes/integrations.add.$pluginKey' | 28 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport } from './../../../packages/react/src/routes/integrations.add.$pluginKey' |
| 28 | 29 | ||
| @@ -107,6 +108,14 @@ const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRout · 78 unmodified lines | |||
| 107 | getParentRoute: () => rootRouteImport, | 108 | getParentRoute: () => rootRouteImport, |
| 108 | } as any, | 109 | } as any, |
| 109 | ) | 110 | ) |
| 111 | const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRoute = | ||
| 112 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRouteImport.update( | ||
| 113 | { | ||
| 114 | id: '/{-$orgSlug}/connect/$integrationSlug', | ||
| 115 | path: '/{-$orgSlug}/connect/$integrationSlug', | ||
| 116 | getParentRoute: () => rootRouteImport, | ||
| 117 | } as any, | ||
| 118 | ) | ||
| 110 | const Billing_DotplansRoute = Billing_DotplansRouteImport.update({ | 119 | const Billing_DotplansRoute = Billing_DotplansRouteImport.update({ |
| 111 | id: '/{-$orgSlug}/billing_/plans', | 120 | id: '/{-$orgSlug}/billing_/plans', |
| 112 | path: '/{-$orgSlug}/billing/plans', | 121 | path: '/{-$orgSlug}/billing/plans', |
| @@ -134,6 +143,7 @@ export interface FileRoutesByFullPath { · 21 unmodified lines | |||
| 134 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute | 143 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute |
| 135 | '/{-$orgSlug}/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute | 144 | '/{-$orgSlug}/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute |
| 136 | '/{-$orgSlug}/billing/plans': typeof Billing_DotplansRoute | 145 | '/{-$orgSlug}/billing/plans': typeof Billing_DotplansRoute |
| 146 | '/{-$orgSlug}/connect/$integrationSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRoute | ||
| 137 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute | 147 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute |
| 138 | '/{-$orgSlug}/resume/$executionId': typeof ResumeDotexecutionIdRoute | 148 | '/{-$orgSlug}/resume/$executionId': typeof ResumeDotexecutionIdRoute |
| 139 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute | 149 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute |
| @@ -152,6 +162,7 @@ export interface FileRoutesByTo { · 12 unmodified lines | |||
| 152 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute | 162 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute |
| 153 | '/{-$orgSlug}': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute | 163 | '/{-$orgSlug}': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute |
| 154 | '/{-$orgSlug}/billing/plans': typeof Billing_DotplansRoute | 164 | '/{-$orgSlug}/billing/plans': typeof Billing_DotplansRoute |
| 165 | '/{-$orgSlug}/connect/$integrationSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRoute | ||
| 155 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute | 166 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute |
| 156 | '/{-$orgSlug}/resume/$executionId': typeof ResumeDotexecutionIdRoute | 167 | '/{-$orgSlug}/resume/$executionId': typeof ResumeDotexecutionIdRoute |
| 157 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute | 168 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute |
| @@ -171,6 +182,7 @@ export interface FileRoutesById { · 13 unmodified lines | |||
| 171 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute | 182 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute |
| 172 | '/{-$orgSlug}/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute | 183 | '/{-$orgSlug}/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute |
| 173 | '/{-$orgSlug}/billing_/plans': typeof Billing_DotplansRoute | 184 | '/{-$orgSlug}/billing_/plans': typeof Billing_DotplansRoute |
| 185 | '/{-$orgSlug}/connect/$integrationSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRoute | ||
| 174 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute | 186 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute |
| 175 | '/{-$orgSlug}/resume/$executionId': typeof ResumeDotexecutionIdRoute | 187 | '/{-$orgSlug}/resume/$executionId': typeof ResumeDotexecutionIdRoute |
| 176 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute | 188 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute |
| @@ -191,6 +203,7 @@ export interface FileRouteTypes { · 14 unmodified lines | |||
| 191 | | '/{-$orgSlug}/tools' | 203 | | '/{-$orgSlug}/tools' |
| 192 | | '/{-$orgSlug}/' | 204 | | '/{-$orgSlug}/' |
| 193 | | '/{-$orgSlug}/billing/plans' | 205 | | '/{-$orgSlug}/billing/plans' |
| 206 | | '/{-$orgSlug}/connect/$integrationSlug' | ||
| 194 | | '/{-$orgSlug}/integrations/$namespace' | 207 | | '/{-$orgSlug}/integrations/$namespace' |
| 195 | | '/{-$orgSlug}/resume/$executionId' | 208 | | '/{-$orgSlug}/resume/$executionId' |
| 196 | | '/{-$orgSlug}/toolkits/$toolkitSlug' | 209 | | '/{-$orgSlug}/toolkits/$toolkitSlug' |
| @@ -209,6 +222,7 @@ export interface FileRouteTypes { · 12 unmodified lines | |||
| 209 | | '/{-$orgSlug}/tools' | 222 | | '/{-$orgSlug}/tools' |
| 210 | | '/{-$orgSlug}' | 223 | | '/{-$orgSlug}' |
| 211 | | '/{-$orgSlug}/billing/plans' | 224 | | '/{-$orgSlug}/billing/plans' |
| 225 | | '/{-$orgSlug}/connect/$integrationSlug' | ||
| 212 | | '/{-$orgSlug}/integrations/$namespace' | 226 | | '/{-$orgSlug}/integrations/$namespace' |
| 213 | | '/{-$orgSlug}/resume/$executionId' | 227 | | '/{-$orgSlug}/resume/$executionId' |
| 214 | | '/{-$orgSlug}/toolkits/$toolkitSlug' | 228 | | '/{-$orgSlug}/toolkits/$toolkitSlug' |
| @@ -227,6 +241,7 @@ export interface FileRouteTypes { · 12 unmodified lines | |||
| 227 | | '/{-$orgSlug}/tools' | 241 | | '/{-$orgSlug}/tools' |
| 228 | | '/{-$orgSlug}/' | 242 | | '/{-$orgSlug}/' |
| 229 | | '/{-$orgSlug}/billing_/plans' | 243 | | '/{-$orgSlug}/billing_/plans' |
| 244 | | '/{-$orgSlug}/connect/$integrationSlug' | ||
| 230 | | '/{-$orgSlug}/integrations/$namespace' | 245 | | '/{-$orgSlug}/integrations/$namespace' |
| 231 | | '/{-$orgSlug}/resume/$executionId' | 246 | | '/{-$orgSlug}/resume/$executionId' |
| 232 | | '/{-$orgSlug}/toolkits/$toolkitSlug' | 247 | | '/{-$orgSlug}/toolkits/$toolkitSlug' |
| @@ -246,6 +261,7 @@ export interface RootRouteChildren { · 13 unmodified lines | |||
| 246 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute | 261 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute |
| 247 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute | 262 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute |
| 248 | Billing_DotplansRoute: typeof Billing_DotplansRoute | 263 | Billing_DotplansRoute: typeof Billing_DotplansRoute |
| 264 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRoute | ||
| 249 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute | 265 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute |
| 250 | ResumeDotexecutionIdRoute: typeof ResumeDotexecutionIdRoute | 266 | ResumeDotexecutionIdRoute: typeof ResumeDotexecutionIdRoute |
| 251 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute | 267 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute |
| @@ -351,6 +367,13 @@ declare module '@tanstack/react-router' { · 99 unmodified lines | |||
| 351 | preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport | 367 | preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport |
| 352 | parentRoute: typeof rootRouteImport | 368 | parentRoute: typeof rootRouteImport |
| 353 | } | 369 | } |
| 370 | '/{-$orgSlug}/connect/$integrationSlug': { | ||
| 371 | id: '/{-$orgSlug}/connect/$integrationSlug' | ||
| 372 | path: '/{-$orgSlug}/connect/$integrationSlug' | ||
| 373 | fullPath: '/{-$orgSlug}/connect/$integrationSlug' | ||
| 374 | preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRouteImport | ||
| 375 | parentRoute: typeof rootRouteImport | ||
| 376 | } | ||
| 354 | '/{-$orgSlug}/billing_/plans': { | 377 | '/{-$orgSlug}/billing_/plans': { |
| 355 | id: '/{-$orgSlug}/billing_/plans' | 378 | id: '/{-$orgSlug}/billing_/plans' |
| 356 | path: '/{-$orgSlug}/billing/plans' | 379 | path: '/{-$orgSlug}/billing/plans' |
| @@ -400,6 +423,8 @@ const rootRouteChildren: RootRouteChildren = { · 43 unmodified lines | |||
| 400 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute: | 423 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute: |
| 401 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute, | 424 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute, |
| 402 | Billing_DotplansRoute: Billing_DotplansRoute, | 425 | Billing_DotplansRoute: Billing_DotplansRoute, |
| 426 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRoute: | ||
| 427 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRoute, | ||
| 403 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute: | 428 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute: |
| 404 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute, | 429 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute, |
| 405 | ResumeDotexecutionIdRoute: ResumeDotexecutionIdRoute, | 430 | ResumeDotexecutionIdRoute: ResumeDotexecutionIdRoute, |
| @@ -17,6 +17,7 @@ import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRouteImp · 16 unmodified lines | |||
| 17 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRouteImport } from './../../../packages/react/src/routes/toolkits.$toolkitSlug' | 17 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRouteImport } from './../../../packages/react/src/routes/toolkits.$toolkitSlug' |
| 18 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRouteImport } from './../../../packages/react/src/routes/resume.$executionId' | 18 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRouteImport } from './../../../packages/react/src/routes/resume.$executionId' |
| 19 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport } from './../../../packages/react/src/routes/integrations.$namespace' | 19 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport } from './../../../packages/react/src/routes/integrations.$namespace' |
| 20 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRouteImport } from './../../../packages/react/src/routes/connect.$integrationSlug' | ||
| 20 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport } from './../../../packages/react/src/routes/plugins.$pluginId.$' | 21 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport } from './../../../packages/react/src/routes/plugins.$pluginId.$' |
| 21 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport } from './../../../packages/react/src/routes/integrations.add.$pluginKey' | 22 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport } from './../../../packages/react/src/routes/integrations.add.$pluginKey' |
| 22 | 23 | ||
| @@ -75,6 +76,14 @@ const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRout · 52 unmodified lines | |||
| 75 | getParentRoute: () => rootRouteImport, | 76 | getParentRoute: () => rootRouteImport, |
| 76 | } as any, | 77 | } as any, |
| 77 | ) | 78 | ) |
| 79 | const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRoute = | ||
| 80 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRouteImport.update( | ||
| 81 | { | ||
| 82 | id: '/{-$orgSlug}/connect/$integrationSlug', | ||
| 83 | path: '/{-$orgSlug}/connect/$integrationSlug', | ||
| 84 | getParentRoute: () => rootRouteImport, | ||
| 85 | } as any, | ||
| 86 | ) | ||
| 78 | const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute = | 87 | const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute = |
| 79 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport.update( | 88 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport.update( |
| 80 | { | 89 | { |
| @@ -98,6 +107,7 @@ export interface FileRoutesByFullPath { · 17 unmodified lines | |||
| 98 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren | 107 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren |
| 99 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute | 108 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute |
| 100 | '/{-$orgSlug}/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute | 109 | '/{-$orgSlug}/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute |
| 110 | '/{-$orgSlug}/connect/$integrationSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRoute | ||
| 101 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute | 111 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute |
| 102 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute | 112 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute |
| 103 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute | 113 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute |
| @@ -110,6 +120,7 @@ export interface FileRoutesByTo { · 6 unmodified lines | |||
| 110 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren | 120 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren |
| 111 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute | 121 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute |
| 112 | '/{-$orgSlug}': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute | 122 | '/{-$orgSlug}': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute |
| 123 | '/{-$orgSlug}/connect/$integrationSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRoute | ||
| 113 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute | 124 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute |
| 114 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute | 125 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute |
| 115 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute | 126 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute |
| @@ -123,6 +134,7 @@ export interface FileRoutesById { · 7 unmodified lines | |||
| 123 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren | 134 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren |
| 124 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute | 135 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute |
| 125 | '/{-$orgSlug}/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute | 136 | '/{-$orgSlug}/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute |
| 137 | '/{-$orgSlug}/connect/$integrationSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRoute | ||
| 126 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute | 138 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute |
| 127 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute | 139 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute |
| 128 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute | 140 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute |
| @@ -137,6 +149,7 @@ export interface FileRouteTypes { · 8 unmodified lines | |||
| 137 | | '/{-$orgSlug}/toolkits' | 149 | | '/{-$orgSlug}/toolkits' |
| 138 | | '/{-$orgSlug}/tools' | 150 | | '/{-$orgSlug}/tools' |
| 139 | | '/{-$orgSlug}/' | 151 | | '/{-$orgSlug}/' |
| 152 | | '/{-$orgSlug}/connect/$integrationSlug' | ||
| 140 | | '/{-$orgSlug}/integrations/$namespace' | 153 | | '/{-$orgSlug}/integrations/$namespace' |
| 141 | | '/{-$orgSlug}/resume/$executionId' | 154 | | '/{-$orgSlug}/resume/$executionId' |
| 142 | | '/{-$orgSlug}/toolkits/$toolkitSlug' | 155 | | '/{-$orgSlug}/toolkits/$toolkitSlug' |
| @@ -149,6 +162,7 @@ export interface FileRouteTypes { · 6 unmodified lines | |||
| 149 | | '/{-$orgSlug}/toolkits' | 162 | | '/{-$orgSlug}/toolkits' |
| 150 | | '/{-$orgSlug}/tools' | 163 | | '/{-$orgSlug}/tools' |
| 151 | | '/{-$orgSlug}' | 164 | | '/{-$orgSlug}' |
| 165 | | '/{-$orgSlug}/connect/$integrationSlug' | ||
| 152 | | '/{-$orgSlug}/integrations/$namespace' | 166 | | '/{-$orgSlug}/integrations/$namespace' |
| 153 | | '/{-$orgSlug}/resume/$executionId' | 167 | | '/{-$orgSlug}/resume/$executionId' |
| 154 | | '/{-$orgSlug}/toolkits/$toolkitSlug' | 168 | | '/{-$orgSlug}/toolkits/$toolkitSlug' |
| @@ -161,6 +175,7 @@ export interface FileRouteTypes { · 6 unmodified lines | |||
| 161 | | '/{-$orgSlug}/toolkits' | 175 | | '/{-$orgSlug}/toolkits' |
| 162 | | '/{-$orgSlug}/tools' | 176 | | '/{-$orgSlug}/tools' |
| 163 | | '/{-$orgSlug}/' | 177 | | '/{-$orgSlug}/' |
| 178 | | '/{-$orgSlug}/connect/$integrationSlug' | ||
| 164 | | '/{-$orgSlug}/integrations/$namespace' | 179 | | '/{-$orgSlug}/integrations/$namespace' |
| 165 | | '/{-$orgSlug}/resume/$executionId' | 180 | | '/{-$orgSlug}/resume/$executionId' |
| 166 | | '/{-$orgSlug}/toolkits/$toolkitSlug' | 181 | | '/{-$orgSlug}/toolkits/$toolkitSlug' |
| @@ -174,6 +189,7 @@ export interface RootRouteChildren { · 7 unmodified lines | |||
| 174 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren | 189 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren |
| 175 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute | 190 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute |
| 176 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute | 191 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute |
| 192 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRoute | ||
| 177 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute | 193 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute |
| 178 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute | 194 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute |
| 179 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute | 195 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute |
| @@ -238,6 +254,13 @@ declare module '@tanstack/react-router' { · 58 unmodified lines | |||
| 238 | preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport | 254 | preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport |
| 239 | parentRoute: typeof rootRouteImport | 255 | parentRoute: typeof rootRouteImport |
| 240 | } | 256 | } |
| 257 | '/{-$orgSlug}/connect/$integrationSlug': { | ||
| 258 | id: '/{-$orgSlug}/connect/$integrationSlug' | ||
| 259 | path: '/{-$orgSlug}/connect/$integrationSlug' | ||
| 260 | fullPath: '/{-$orgSlug}/connect/$integrationSlug' | ||
| 261 | preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRouteImport | ||
| 262 | parentRoute: typeof rootRouteImport | ||
| 263 | } | ||
| 241 | '/{-$orgSlug}/plugins/$pluginId/$': { | 264 | '/{-$orgSlug}/plugins/$pluginId/$': { |
| 242 | id: '/{-$orgSlug}/plugins/$pluginId/$' | 265 | id: '/{-$orgSlug}/plugins/$pluginId/$' |
| 243 | path: '/{-$orgSlug}/plugins/$pluginId/$' | 266 | path: '/{-$orgSlug}/plugins/$pluginId/$' |
| @@ -281,6 +304,8 @@ const rootRouteChildren: RootRouteChildren = { · 37 unmodified lines | |||
| 281 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute, | 304 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute, |
| 282 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute: | 305 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute: |
| 283 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute, | 306 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute, |
| 307 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRoute: | ||
| 308 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRoute, | ||
| 284 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute: | 309 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute: |
| 285 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute, | 310 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute, |
| 286 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute: | 311 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute: |
| @@ -1,6 +1,12 @@ | |||
| 1 | import { describe, expect, it } from "@effect/vitest"; | 1 | import { describe, expect, it } from "@effect/vitest"; |
| 2 | 2 | ||
| 3 | import { isSafeReturnTo, loginPath, mcpAuthorizeResumeTarget, safeReturnTo } from "./return-to"; | 3 | import { |
| 4 | isSafeReturnTo, | ||
| 5 | loginPath, | ||
| 6 | mcpAuthorizeResumeTarget, | ||
| 7 | postLoginTarget, | ||
| 8 | safeReturnTo, | ||
| 9 | } from "./return-to"; | ||
| 4 | 10 | ||
| 5 | describe("isSafeReturnTo", () => { | 11 | describe("isSafeReturnTo", () => { |
| 6 | const safe = [ | 12 | const safe = [ |
| @@ -73,6 +79,44 @@ describe("mcpAuthorizeResumeTarget", () => { · 66 unmodified lines | |||
| 73 | }); | 79 | }); |
| 74 | }); | 80 | }); |
| 75 | 81 | ||
| 82 | describe("postLoginTarget", () => { | ||
| 83 | // Self-host renders the login page IN PLACE of the requested route without | ||
| 84 | // navigating, so the live location is the only record of where the person was | ||
| 85 | // headed. A connect deep link must survive sign-in on that basis alone. | ||
| 86 | it("returns to the deep link the person actually opened", () => { | ||
| 87 | expect(postLoginTarget({ pathname: "/connect/linear", search: "" })).toBe("/connect/linear"); | ||
| 88 | expect(postLoginTarget({ pathname: "/integrations/sentry", search: "?addAccount=1" })).toBe( | ||
| 89 | "/integrations/sentry?addAccount=1", | ||
| 90 | ); | ||
| 91 | }); | ||
| 92 | |||
| 93 | it("lands on the dashboard when signing in from the bare login page", () => { | ||
| 94 | // No deep link to resume, and echoing /login back would re-render the form. | ||
| 95 | expect(postLoginTarget({ pathname: "/login", search: "" })).toBe("/"); | ||
| 96 | }); | ||
| 97 | |||
| 98 | it("prefers an explicit returnTo over the current location", () => { | ||
| 99 | expect(postLoginTarget({ pathname: "/login", search: "?returnTo=%2Fconnect%2Flinear" })).toBe( | ||
| 100 | "/connect/linear", | ||
| 101 | ); | ||
| 102 | }); | ||
| 103 | |||
| 104 | it("prefers an MCP authorize resume over everything else", () => { | ||
| 105 | const target = postLoginTarget({ | ||
| 106 | pathname: "/login", | ||
| 107 | search: | ||
| 108 | "?response_type=code&client_id=abc&redirect_uri=http%3A%2F%2Flocalhost%3A3118%2Fcallback", | ||
| 109 | }); | ||
| 110 | expect(target.startsWith("/api/auth/mcp/authorize?")).toBe(true); | ||
| 111 | }); | ||
| 112 | |||
| 113 | it("never honors an unsafe location", () => { | ||
| 114 | // `safeReturnTo` vets the echoed path too — an app-plane path is not a | ||
| 115 | // place to land a freshly signed-in browser. | ||
| 116 | expect(postLoginTarget({ pathname: "/api/auth/logout", search: "" })).toBe("/"); | ||
| 117 | }); | ||
| 118 | }); | ||
| 119 | |||
| 76 | describe("loginPath", () => { | 120 | describe("loginPath", () => { |
| 77 | it("omits returnTo for the root", () => { | 121 | it("omits returnTo for the root", () => { |
| 78 | expect(loginPath("/")).toBe("/login"); | 122 | expect(loginPath("/")).toBe("/login"); |
| @@ -32,3 +32,32 @@ export const mcpAuthorizeResumeTarget = (search: string): string | null => { · 31 unmodified lines | |||
| 32 | if (!params.get("client_id") || !params.get("redirect_uri")) return null; | 32 | if (!params.get("client_id") || !params.get("redirect_uri")) return null; |
| 33 | return `${MCP_AUTHORIZE_PATH}?${params.toString()}`; | 33 | return `${MCP_AUTHORIZE_PATH}?${params.toString()}`; |
| 34 | }; | 34 | }; |
| 35 | |||
| 36 | const LOGIN_PATH = "/login"; | ||
| 37 | |||
| 38 | /** | ||
| 39 | * Where the self-host login page sends someone after a successful sign-in, in | ||
| 40 | * priority order: | ||
| 41 | * | ||
| 42 | * 1. an interrupted MCP OAuth authorize (the params ARE the request), | ||
| 43 | * 2. an explicit safe `returnTo` (e.g. the integration OAuth callback), | ||
| 44 | * 3. the URL they actually opened, and | ||
| 45 | * 4. the dashboard. | ||
| 46 | * | ||
| 47 | * Step 3 is what makes a deep link like `/connect/linear` survive sign-in here. | ||
| 48 | * Unlike cloud — which redirects to `/login?returnTo=…` — self-host's gate | ||
| 49 | * swaps the login page in WITHOUT navigating, so the address bar still holds | ||
| 50 | * the requested URL and no `returnTo` was ever written. `/login` itself is | ||
| 51 | * excluded so signing in from the bare login page lands on the dashboard rather | ||
| 52 | * than looping back to the form. | ||
| 53 | */ | ||
| 54 | export const postLoginTarget = (location: { | ||
| 55 | readonly pathname: string; | ||
| 56 | readonly search: string; | ||
| 57 | }): string => | ||
| 58 | mcpAuthorizeResumeTarget(location.search) ?? | ||
| 59 | safeReturnTo(new URLSearchParams(location.search).get("returnTo")) ?? | ||
| 60 | (location.pathname === LOGIN_PATH | ||
| 61 | ? null | ||
| 62 | : safeReturnTo(`${location.pathname}${location.search}`)) ?? | ||
| 63 | "/"; | ||
| @@ -6,7 +6,7 @@ import { Label } from "@executor-js/react/components/label"; · 5 unmodified lines | |||
| 6 | 6 | ||
| 7 | import { authClient } from "./auth-client"; | 7 | import { authClient } from "./auth-client"; |
| 8 | import { AuthLayout } from "./auth-layout"; | 8 | import { AuthLayout } from "./auth-layout"; |
| 9 | import { mcpAuthorizeResumeTarget, safeReturnTo } from "../src/auth/return-to"; | 9 | import { postLoginTarget } from "../src/auth/return-to"; |
| 10 | 10 | ||
| 11 | // Self-host login: email + password sign-in via Better Auth. On success we | 11 | // Self-host login: email + password sign-in via Better Auth. On success we |
| 12 | // reload so the shared AuthProvider re-reads /account/me and the AuthGate swaps | 12 | // reload so the shared AuthProvider re-reads /account/me and the AuthGate swaps |
| @@ -17,15 +17,10 @@ import { mcpAuthorizeResumeTarget, safeReturnTo } from "../src/auth/return-to"; · 4 unmodified lines | |||
| 17 | // redeeming an invite — either the full /join/<code> link, or by entering the | 17 | // redeeming an invite — either the full /join/<code> link, or by entering the |
| 18 | // code here ("Have an invite code?"), which forwards to the same join page. | 18 | // code here ("Have an invite code?"), which forwards to the same join page. |
| 19 | export const LoginPage = () => { | 19 | export const LoginPage = () => { |
| 20 | const search = window.location.search; | 20 | // Where to go after sign-in. The gate renders this page IN PLACE of the |
| 21 | // Where to go after sign-in: resume an interrupted MCP OAuth authorize if we | 21 | // requested route without navigating, so the live location is what carries a |
| 22 | // arrived from one (Better Auth redirects it here with the OAuth params), | 22 | // deep link (`/connect/linear`) across sign-in — see `postLoginTarget`. |
| 23 | // otherwise honor a safe returnTo (e.g. an integration OAuth callback), else | 23 | const postLogin = postLoginTarget(window.location); |
| 24 | // land on the dashboard. | ||
| 25 | const postLogin = | ||
| 26 | mcpAuthorizeResumeTarget(search) ?? | ||
| 27 | safeReturnTo(new URLSearchParams(search).get("returnTo")) ?? | ||
| 28 | "/"; | ||
| 29 | const [mode, setMode] = useState<"signin" | "code">("signin"); | 24 | const [mode, setMode] = useState<"signin" | "code">("signin"); |
| 30 | const [email, setEmail] = useState(""); | 25 | const [email, setEmail] = useState(""); |
| 31 | const [password, setPassword] = useState(""); | 26 | const [password, setPassword] = useState(""); |
| @@ -20,6 +20,7 @@ import { Route as JoinDotcodeRouteImport } from './routes/public/join.$code' · 19 unmodified lines | |||
| 20 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRouteImport } from './../../../packages/react/src/routes/toolkits.$toolkitSlug' | 20 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRouteImport } from './../../../packages/react/src/routes/toolkits.$toolkitSlug' |
| 21 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRouteImport } from './../../../packages/react/src/routes/resume.$executionId' | 21 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRouteImport } from './../../../packages/react/src/routes/resume.$executionId' |
| 22 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport } from './../../../packages/react/src/routes/integrations.$namespace' | 22 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport } from './../../../packages/react/src/routes/integrations.$namespace' |
| 23 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRouteImport } from './../../../packages/react/src/routes/connect.$integrationSlug' | ||
| 23 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport } from './../../../packages/react/src/routes/plugins.$pluginId.$' | 24 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport } from './../../../packages/react/src/routes/plugins.$pluginId.$' |
| 24 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport } from './../../../packages/react/src/routes/integrations.add.$pluginKey' | 25 | import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport } from './../../../packages/react/src/routes/integrations.add.$pluginKey' |
| 25 | 26 | ||
| @@ -93,6 +94,14 @@ const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRout · 67 unmodified lines | |||
| 93 | getParentRoute: () => rootRouteImport, | 94 | getParentRoute: () => rootRouteImport, |
| 94 | } as any, | 95 | } as any, |
| 95 | ) | 96 | ) |
| 97 | const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRoute = | ||
| 98 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRouteImport.update( | ||
| 99 | { | ||
| 100 | id: '/{-$orgSlug}/connect/$integrationSlug', | ||
| 101 | path: '/{-$orgSlug}/connect/$integrationSlug', | ||
| 102 | getParentRoute: () => rootRouteImport, | ||
| 103 | } as any, | ||
| 104 | ) | ||
| 96 | const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute = | 105 | const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute = |
| 97 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport.update( | 106 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport.update( |
| 98 | { | 107 | { |
| @@ -119,6 +128,7 @@ export interface FileRoutesByFullPath { · 20 unmodified lines | |||
| 119 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren | 128 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren |
| 120 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute | 129 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute |
| 121 | '/{-$orgSlug}/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute | 130 | '/{-$orgSlug}/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute |
| 131 | '/{-$orgSlug}/connect/$integrationSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRoute | ||
| 122 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute | 132 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute |
| 123 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute | 133 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute |
| 124 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute | 134 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute |
| @@ -134,6 +144,7 @@ export interface FileRoutesByTo { · 9 unmodified lines | |||
| 134 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren | 144 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren |
| 135 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute | 145 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute |
| 136 | '/{-$orgSlug}': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute | 146 | '/{-$orgSlug}': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute |
| 147 | '/{-$orgSlug}/connect/$integrationSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRoute | ||
| 137 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute | 148 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute |
| 138 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute | 149 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute |
| 139 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute | 150 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute |
| @@ -150,6 +161,7 @@ export interface FileRoutesById { · 10 unmodified lines | |||
| 150 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren | 161 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren |
| 151 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute | 162 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute |
| 152 | '/{-$orgSlug}/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute | 163 | '/{-$orgSlug}/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute |
| 164 | '/{-$orgSlug}/connect/$integrationSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRoute | ||
| 153 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute | 165 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute |
| 154 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute | 166 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute |
| 155 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute | 167 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute |
| @@ -167,6 +179,7 @@ export interface FileRouteTypes { · 11 unmodified lines | |||
| 167 | | '/{-$orgSlug}/toolkits' | 179 | | '/{-$orgSlug}/toolkits' |
| 168 | | '/{-$orgSlug}/tools' | 180 | | '/{-$orgSlug}/tools' |
| 169 | | '/{-$orgSlug}/' | 181 | | '/{-$orgSlug}/' |
| 182 | | '/{-$orgSlug}/connect/$integrationSlug' | ||
| 170 | | '/{-$orgSlug}/integrations/$namespace' | 183 | | '/{-$orgSlug}/integrations/$namespace' |
| 171 | | '/{-$orgSlug}/resume/$executionId' | 184 | | '/{-$orgSlug}/resume/$executionId' |
| 172 | | '/{-$orgSlug}/toolkits/$toolkitSlug' | 185 | | '/{-$orgSlug}/toolkits/$toolkitSlug' |
| @@ -182,6 +195,7 @@ export interface FileRouteTypes { · 9 unmodified lines | |||
| 182 | | '/{-$orgSlug}/toolkits' | 195 | | '/{-$orgSlug}/toolkits' |
| 183 | | '/{-$orgSlug}/tools' | 196 | | '/{-$orgSlug}/tools' |
| 184 | | '/{-$orgSlug}' | 197 | | '/{-$orgSlug}' |
| 198 | | '/{-$orgSlug}/connect/$integrationSlug' | ||
| 185 | | '/{-$orgSlug}/integrations/$namespace' | 199 | | '/{-$orgSlug}/integrations/$namespace' |
| 186 | | '/{-$orgSlug}/resume/$executionId' | 200 | | '/{-$orgSlug}/resume/$executionId' |
| 187 | | '/{-$orgSlug}/toolkits/$toolkitSlug' | 201 | | '/{-$orgSlug}/toolkits/$toolkitSlug' |
| @@ -197,6 +211,7 @@ export interface FileRouteTypes { · 9 unmodified lines | |||
| 197 | | '/{-$orgSlug}/toolkits' | 211 | | '/{-$orgSlug}/toolkits' |
| 198 | | '/{-$orgSlug}/tools' | 212 | | '/{-$orgSlug}/tools' |
| 199 | | '/{-$orgSlug}/' | 213 | | '/{-$orgSlug}/' |
| 214 | | '/{-$orgSlug}/connect/$integrationSlug' | ||
| 200 | | '/{-$orgSlug}/integrations/$namespace' | 215 | | '/{-$orgSlug}/integrations/$namespace' |
| 201 | | '/{-$orgSlug}/resume/$executionId' | 216 | | '/{-$orgSlug}/resume/$executionId' |
| 202 | | '/{-$orgSlug}/toolkits/$toolkitSlug' | 217 | | '/{-$orgSlug}/toolkits/$toolkitSlug' |
| @@ -213,6 +228,7 @@ export interface RootRouteChildren { · 10 unmodified lines | |||
| 213 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren | 228 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren |
| 214 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute | 229 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute |
| 215 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute | 230 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute |
| 231 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRoute | ||
| 216 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute | 232 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute |
| 217 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute | 233 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute |
| 218 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute | 234 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute |
| @@ -298,6 +314,13 @@ declare module '@tanstack/react-router' { · 79 unmodified lines | |||
| 298 | preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport | 314 | preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport |
| 299 | parentRoute: typeof rootRouteImport | 315 | parentRoute: typeof rootRouteImport |
| 300 | } | 316 | } |
| 317 | '/{-$orgSlug}/connect/$integrationSlug': { | ||
| 318 | id: '/{-$orgSlug}/connect/$integrationSlug' | ||
| 319 | path: '/{-$orgSlug}/connect/$integrationSlug' | ||
| 320 | fullPath: '/{-$orgSlug}/connect/$integrationSlug' | ||
| 321 | preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRouteImport | ||
| 322 | parentRoute: typeof rootRouteImport | ||
| 323 | } | ||
| 301 | '/{-$orgSlug}/plugins/$pluginId/$': { | 324 | '/{-$orgSlug}/plugins/$pluginId/$': { |
| 302 | id: '/{-$orgSlug}/plugins/$pluginId/$' | 325 | id: '/{-$orgSlug}/plugins/$pluginId/$' |
| 303 | path: '/{-$orgSlug}/plugins/$pluginId/$' | 326 | path: '/{-$orgSlug}/plugins/$pluginId/$' |
| @@ -344,6 +367,8 @@ const rootRouteChildren: RootRouteChildren = { · 40 unmodified lines | |||
| 344 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute, | 367 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute, |
| 345 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute: | 368 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute: |
| 346 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute, | 369 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute, |
| 370 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRoute: | ||
| 371 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesConnectDotintegrationSlugRoute, | ||
| 347 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute: | 372 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute: |
| 348 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute, | 373 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute, |
| 349 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute: | 374 | DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute: |
| @@ -0,0 +1,168 @@ | |||
| 1 | // The connect deep link: a customer sends an end user a direct link for ONE | ||
| 2 | // integration ("connect your Linear") instead of pointing them at the dashboard | ||
| 3 | // to hunt for it. `/connect/<integration slug>` resolves the slug against the | ||
| 4 | // tenant's catalog and lands the person in the SAME connect flow the | ||
| 5 | // integration detail page's own button opens. | ||
| 6 | // | ||
| 7 | // The slug — not a plugin id — is the stable identity here: it is what a | ||
| 8 | // customer can write down and template into an email. | ||
| 9 | // | ||
| 10 | // Covered: | ||
| 11 | // 1. Signed in, known slug → the Add connection flow is open on the right | ||
| 12 | // integration, with the auth methods the spec declares | ||
| 13 | // 2. Unknown slug → a clear not-found state, not a blank page or a | ||
| 14 | // crash (an end user WILL mistype these, and links outlive integrations) | ||
| 15 | // 3. Already connected → the deep link shows the existing connection and | ||
| 16 | // still offers to add another account | ||
| 17 | import { randomBytes, randomUUID } from "node:crypto"; | ||
| 18 | |||
| 19 | import { expect } from "@effect/vitest"; | ||
| 20 | import { Effect } from "effect"; | ||
| 21 | import { AccountHttpApi } from "@executor-js/api"; | ||
| 22 | import { composePluginApi } from "@executor-js/api/server"; | ||
| 23 | import { openApiHttpPlugin } from "@executor-js/plugin-openapi/api"; | ||
| 24 | import { | ||
| 25 | AuthTemplateSlug, | ||
| 26 | ConnectionName, | ||
| 27 | IntegrationSlug, | ||
| 28 | ProviderItemId, | ||
| 29 | } from "@executor-js/sdk/shared"; | ||
| 30 | |||
| 31 | import { scenario } from "../src/scenario"; | ||
| 32 | import { Api, Browser, Target } from "../src/services"; | ||
| 33 | |||
| 34 | const api = composePluginApi([openApiHttpPlugin()] as const); | ||
| 35 | |||
| 36 | /** OpenAPI 3 spec with a single operation — enough to be a real catalog entry. */ | ||
| 37 | const greetSpec = (baseUrl: string): string => | ||
| 38 | JSON.stringify({ | ||
| 39 | openapi: "3.0.3", | ||
| 40 | info: { title: "Deep Link API", version: "1.0.0" }, | ||
| 41 | servers: [{ url: baseUrl }], | ||
| 42 | paths: { | ||
| 43 | "/greet": { | ||
| 44 | get: { | ||
| 45 | operationId: "getGreeting", | ||
| 46 | summary: "Return a greeting message", | ||
| 47 | responses: { "200": { description: "A greeting" } }, | ||
| 48 | }, | ||
| 49 | }, | ||
| 50 | }, | ||
| 51 | }); | ||
| 52 | |||
| 53 | scenario( | ||
| 54 | "Connect · /connect/<slug> lands an end user in that integration's connect flow", | ||
| 55 | { timeout: 180_000 }, | ||
| 56 | Effect.gen(function* () { | ||
| 57 | const target = yield* Target; | ||
| 58 | const browser = yield* Browser; | ||
| 59 | const { client } = yield* Api; | ||
| 60 | |||
| 61 | const identity = yield* target.newIdentity(); | ||
| 62 | const apiClient = yield* client(api, identity); | ||
| 63 | |||
| 64 | // Selfhost shares one bootstrap-admin identity, so a unique slug keeps | ||
| 65 | // repeated and parallel runs out of each other's catalogs. | ||
| 66 | const slug = `connect-dl-${randomBytes(4).toString("hex")}`; | ||
| 67 | const specBaseUrl = "http://127.0.0.1:59999"; // never contacted here | ||
| 68 | |||
| 69 | // Org-scoped hosts canonicalize console URLs onto the active org's slug, so | ||
| 70 | // the deep link may legitimately arrive at either `/connect/x` or | ||
| 71 | // `/<org>/connect/x`. Read the slug the shell will canonicalize onto. | ||
| 72 | const accountClient = yield* client(AccountHttpApi, identity); | ||
| 73 | const me = yield* accountClient.account.me(); | ||
| 74 | const orgSlug = me.organization?.slug ?? null; | ||
| 75 | const detailPath = orgSlug ? `/${orgSlug}/integrations/${slug}` : `/integrations/${slug}`; | ||
| 76 | |||
| 77 | yield* Effect.ensuring( | ||
| 78 | Effect.gen(function* () { | ||
| 79 | const added = yield* apiClient.openapi.addSpec({ | ||
| 80 | payload: { | ||
| 81 | spec: { kind: "blob", value: greetSpec(specBaseUrl) }, | ||
| 82 | slug, | ||
| 83 | name: "Deep Link API", | ||
| 84 | baseUrl: specBaseUrl, | ||
| 85 | authenticationTemplate: [ | ||
| 86 | { | ||
| 87 | slug: "apiKey", | ||
| 88 | type: "apiKey", | ||
| 89 | headers: { "x-api-key": [{ type: "variable", name: "token" }] }, | ||
| 90 | }, | ||
| 91 | ], | ||
| 92 | }, | ||
| 93 | }); | ||
| 94 | expect(added.slug, "the integration keeps the requested slug").toBe(slug); | ||
| 95 | |||
| 96 | yield* browser.session(identity, async ({ page, step }) => { | ||
| 97 | await step("Open the connect deep link for the seeded integration", async () => { | ||
| 98 | await page.goto(`/connect/${slug}`, { waitUntil: "networkidle" }); | ||
| 99 | // It forwards into the integration's OWN detail route rather than | ||
| 100 | // rendering a parallel connect UI, carrying the add-account handoff. | ||
| 101 | await page.waitForURL((url) => url.pathname === detailPath, { timeout: 30_000 }); | ||
| 102 | expect(new URL(page.url()).searchParams.get("addAccount")).toBe("1"); | ||
| 103 | }); | ||
| 104 | |||
| 105 | await step("The Add connection flow is open for that integration", async () => { | ||
| 106 | await page | ||
| 107 | .getByRole("heading", { name: /Add connection/ }) | ||
| 108 | .waitFor({ timeout: 20_000 }); | ||
| 109 | // The right integration, and the declared auth method is offered — | ||
| 110 | // proof it resolved the slug rather than opening a generic dialog. | ||
| 111 | await page | ||
| 112 | .getByRole("dialog") | ||
| 113 | .getByRole("textbox", { name: "x-api-key" }) | ||
| 114 | .waitFor({ timeout: 20_000 }); | ||
| 115 | }); | ||
| 116 | |||
| 117 | await step("An unknown slug is a clear not-found, not a blank page", async () => { | ||
| 118 | await page.goto("/connect/zz-no-such-integration", { waitUntil: "networkidle" }); | ||
| 119 | await page | ||
| 120 | .getByText("Unknown integration: zz-no-such-integration") | ||
| 121 | .waitFor({ timeout: 30_000 }); | ||
| 122 | await page.getByRole("link", { name: "Back to integrations" }).waitFor(); | ||
| 123 | }); | ||
| 124 | }); | ||
| 125 | |||
| 126 | // Already connected: the deep link must show the existing connection | ||
| 127 | // and still let the person add another account. | ||
| 128 | const providers = yield* apiClient.providers.list(); | ||
| 129 | expect(providers.length, "a credential provider is available").toBeGreaterThan(0); | ||
| 130 | yield* apiClient.connections.create({ | ||
| 131 | payload: { | ||
| 132 | owner: "org", | ||
| 133 | name: ConnectionName.make("existing"), | ||
| 134 | integration: IntegrationSlug.make(slug), | ||
| 135 | template: AuthTemplateSlug.make("apiKey"), | ||
| 136 | from: { provider: providers[0]!, id: ProviderItemId.make(randomUUID()) }, | ||
| 137 | }, | ||
| 138 | }); | ||
| 139 | |||
| 140 | yield* browser.session(identity, async ({ page, step }) => { | ||
| 141 | await step("The deep link surfaces the existing connection", async () => { | ||
| 142 | await page.goto(`/connect/${slug}`, { waitUntil: "networkidle" }); | ||
| 143 | await page.waitForURL((url) => url.pathname === detailPath, { timeout: 30_000 }); | ||
| 144 | // The connect flow still opens (this link means "connect me"), and | ||
| 145 | // the already-saved account is visible behind it. | ||
| 146 | await page | ||
| 147 | .getByRole("heading", { name: /Add connection/ }) | ||
| 148 | .waitFor({ timeout: 20_000 }); | ||
| 149 | await page.keyboard.press("Escape"); | ||
| 150 | await page.getByText("existing").first().waitFor({ timeout: 20_000 }); | ||
| 151 | }); | ||
| 152 | }); | ||
| 153 | }), | ||
| 154 | Effect.gen(function* () { | ||
| 155 | yield* apiClient.connections | ||
| 156 | .remove({ | ||
| 157 | params: { | ||
| 158 | owner: "org", | ||
| 159 | integration: IntegrationSlug.make(slug), | ||
| 160 | name: ConnectionName.make("existing"), | ||
| 161 | }, | ||
| 162 | }) | ||
| 163 | .pipe(Effect.ignore); | ||
| 164 | yield* apiClient.openapi.removeSpec({ params: { slug } }).pipe(Effect.ignore); | ||
| 165 | }).pipe(Effect.ignore), | ||
| 166 | ); | ||
| 167 | }), | ||
| 168 | ); | ||
| @@ -17,6 +17,7 @@ import { Route as DotDotDotDotDotDotReactSrcRoutesPoliciesRouteImport } from './ · 16 unmodified lines | |||
| 17 | import { Route as DotDotDotDotDotDotReactSrcRoutesToolkitsDottoolkitSlugRouteImport } from './../../react/src/routes/toolkits.$toolkitSlug' | 17 | import { Route as DotDotDotDotDotDotReactSrcRoutesToolkitsDottoolkitSlugRouteImport } from './../../react/src/routes/toolkits.$toolkitSlug' |
| 18 | import { Route as DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRouteImport } from './../../react/src/routes/resume.$executionId' | 18 | import { Route as DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRouteImport } from './../../react/src/routes/resume.$executionId' |
| 19 | import { Route as DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRouteImport } from './../../react/src/routes/integrations.$namespace' | 19 | import { Route as DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRouteImport } from './../../react/src/routes/integrations.$namespace' |
| 20 | import { Route as DotDotDotDotDotDotReactSrcRoutesConnectDotintegrationSlugRouteImport } from './../../react/src/routes/connect.$integrationSlug' | ||
| 20 | import { Route as DotDotDotDotDotDotReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport } from './../../react/src/routes/plugins.$pluginId.$' | 21 | import { Route as DotDotDotDotDotDotReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport } from './../../react/src/routes/plugins.$pluginId.$' |
| 21 | import { Route as DotDotDotDotDotDotReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport } from './../../react/src/routes/integrations.add.$pluginKey' | 22 | import { Route as DotDotDotDotDotDotReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport } from './../../react/src/routes/integrations.add.$pluginKey' |
| 22 | 23 | ||
| @@ -67,6 +68,12 @@ const DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRoute = · 44 unmodified lines | |||
| 67 | path: '/{-$orgSlug}/integrations/$namespace', | 68 | path: '/{-$orgSlug}/integrations/$namespace', |
| 68 | getParentRoute: () => rootRouteImport, | 69 | getParentRoute: () => rootRouteImport, |
| 69 | } as any) | 70 | } as any) |
| 71 | const DotDotDotDotDotDotReactSrcRoutesConnectDotintegrationSlugRoute = | ||
| 72 | DotDotDotDotDotDotReactSrcRoutesConnectDotintegrationSlugRouteImport.update({ | ||
| 73 | id: '/{-$orgSlug}/connect/$integrationSlug', | ||
| 74 | path: '/{-$orgSlug}/connect/$integrationSlug', | ||
| 75 | getParentRoute: () => rootRouteImport, | ||
| 76 | } as any) | ||
| 70 | const DotDotDotDotDotDotReactSrcRoutesPluginsDotpluginIdDotsplatRoute = | 77 | const DotDotDotDotDotDotReactSrcRoutesPluginsDotpluginIdDotsplatRoute = |
| 71 | DotDotDotDotDotDotReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport.update({ | 78 | DotDotDotDotDotDotReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport.update({ |
| 72 | id: '/{-$orgSlug}/plugins/$pluginId/$', | 79 | id: '/{-$orgSlug}/plugins/$pluginId/$', |
| @@ -88,6 +95,7 @@ export interface FileRoutesByFullPath { · 15 unmodified lines | |||
| 88 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotReactSrcRoutesToolkitsRouteWithChildren | 95 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotReactSrcRoutesToolkitsRouteWithChildren |
| 89 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotReactSrcRoutesToolsRoute | 96 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotReactSrcRoutesToolsRoute |
| 90 | '/{-$orgSlug}/': typeof DotDotDotDotDotDotReactSrcRoutesIndexRoute | 97 | '/{-$orgSlug}/': typeof DotDotDotDotDotDotReactSrcRoutesIndexRoute |
| 98 | '/{-$orgSlug}/connect/$integrationSlug': typeof DotDotDotDotDotDotReactSrcRoutesConnectDotintegrationSlugRoute | ||
| 91 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRoute | 99 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRoute |
| 92 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRoute | 100 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRoute |
| 93 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotReactSrcRoutesToolkitsDottoolkitSlugRoute | 101 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotReactSrcRoutesToolkitsDottoolkitSlugRoute |
| @@ -100,6 +108,7 @@ export interface FileRoutesByTo { · 6 unmodified lines | |||
| 100 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotReactSrcRoutesToolkitsRouteWithChildren | 108 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotReactSrcRoutesToolkitsRouteWithChildren |
| 101 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotReactSrcRoutesToolsRoute | 109 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotReactSrcRoutesToolsRoute |
| 102 | '/{-$orgSlug}': typeof DotDotDotDotDotDotReactSrcRoutesIndexRoute | 110 | '/{-$orgSlug}': typeof DotDotDotDotDotDotReactSrcRoutesIndexRoute |
| 111 | '/{-$orgSlug}/connect/$integrationSlug': typeof DotDotDotDotDotDotReactSrcRoutesConnectDotintegrationSlugRoute | ||
| 103 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRoute | 112 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRoute |
| 104 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRoute | 113 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRoute |
| 105 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotReactSrcRoutesToolkitsDottoolkitSlugRoute | 114 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotReactSrcRoutesToolkitsDottoolkitSlugRoute |
| @@ -113,6 +122,7 @@ export interface FileRoutesById { · 7 unmodified lines | |||
| 113 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotReactSrcRoutesToolkitsRouteWithChildren | 122 | '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotReactSrcRoutesToolkitsRouteWithChildren |
| 114 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotReactSrcRoutesToolsRoute | 123 | '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotReactSrcRoutesToolsRoute |
| 115 | '/{-$orgSlug}/': typeof DotDotDotDotDotDotReactSrcRoutesIndexRoute | 124 | '/{-$orgSlug}/': typeof DotDotDotDotDotDotReactSrcRoutesIndexRoute |
| 125 | '/{-$orgSlug}/connect/$integrationSlug': typeof DotDotDotDotDotDotReactSrcRoutesConnectDotintegrationSlugRoute | ||
| 116 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRoute | 126 | '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRoute |
| 117 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRoute | 127 | '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRoute |
| 118 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotReactSrcRoutesToolkitsDottoolkitSlugRoute | 128 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotReactSrcRoutesToolkitsDottoolkitSlugRoute |
| @@ -127,6 +137,7 @@ export interface FileRouteTypes { · 8 unmodified lines | |||
| 127 | | '/{-$orgSlug}/toolkits' | 137 | | '/{-$orgSlug}/toolkits' |
| 128 | | '/{-$orgSlug}/tools' | 138 | | '/{-$orgSlug}/tools' |
| 129 | | '/{-$orgSlug}/' | 139 | | '/{-$orgSlug}/' |
| 140 | | '/{-$orgSlug}/connect/$integrationSlug' | ||
| 130 | | '/{-$orgSlug}/integrations/$namespace' | 141 | | '/{-$orgSlug}/integrations/$namespace' |
| 131 | | '/{-$orgSlug}/resume/$executionId' | 142 | | '/{-$orgSlug}/resume/$executionId' |
| 132 | | '/{-$orgSlug}/toolkits/$toolkitSlug' | 143 | | '/{-$orgSlug}/toolkits/$toolkitSlug' |
| @@ -139,6 +150,7 @@ export interface FileRouteTypes { · 6 unmodified lines | |||
| 139 | | '/{-$orgSlug}/toolkits' | 150 | | '/{-$orgSlug}/toolkits' |
| 140 | | '/{-$orgSlug}/tools' | 151 | | '/{-$orgSlug}/tools' |
| 141 | | '/{-$orgSlug}' | 152 | | '/{-$orgSlug}' |
| 153 | | '/{-$orgSlug}/connect/$integrationSlug' | ||
| 142 | | '/{-$orgSlug}/integrations/$namespace' | 154 | | '/{-$orgSlug}/integrations/$namespace' |
| 143 | | '/{-$orgSlug}/resume/$executionId' | 155 | | '/{-$orgSlug}/resume/$executionId' |
| 144 | | '/{-$orgSlug}/toolkits/$toolkitSlug' | 156 | | '/{-$orgSlug}/toolkits/$toolkitSlug' |
| @@ -151,6 +163,7 @@ export interface FileRouteTypes { · 6 unmodified lines | |||
| 151 | | '/{-$orgSlug}/toolkits' | 163 | | '/{-$orgSlug}/toolkits' |
| 152 | | '/{-$orgSlug}/tools' | 164 | | '/{-$orgSlug}/tools' |
| 153 | | '/{-$orgSlug}/' | 165 | | '/{-$orgSlug}/' |
| 166 | | '/{-$orgSlug}/connect/$integrationSlug' | ||
| 154 | | '/{-$orgSlug}/integrations/$namespace' | 167 | | '/{-$orgSlug}/integrations/$namespace' |
| 155 | | '/{-$orgSlug}/resume/$executionId' | 168 | | '/{-$orgSlug}/resume/$executionId' |
| 156 | | '/{-$orgSlug}/toolkits/$toolkitSlug' | 169 | | '/{-$orgSlug}/toolkits/$toolkitSlug' |
| @@ -164,6 +177,7 @@ export interface RootRouteChildren { · 7 unmodified lines | |||
| 164 | DotDotDotDotDotDotReactSrcRoutesToolkitsRoute: typeof DotDotDotDotDotDotReactSrcRoutesToolkitsRouteWithChildren | 177 | DotDotDotDotDotDotReactSrcRoutesToolkitsRoute: typeof DotDotDotDotDotDotReactSrcRoutesToolkitsRouteWithChildren |
| 165 | DotDotDotDotDotDotReactSrcRoutesToolsRoute: typeof DotDotDotDotDotDotReactSrcRoutesToolsRoute | 178 | DotDotDotDotDotDotReactSrcRoutesToolsRoute: typeof DotDotDotDotDotDotReactSrcRoutesToolsRoute |
| 166 | DotDotDotDotDotDotReactSrcRoutesIndexRoute: typeof DotDotDotDotDotDotReactSrcRoutesIndexRoute | 179 | DotDotDotDotDotDotReactSrcRoutesIndexRoute: typeof DotDotDotDotDotDotReactSrcRoutesIndexRoute |
| 180 | DotDotDotDotDotDotReactSrcRoutesConnectDotintegrationSlugRoute: typeof DotDotDotDotDotDotReactSrcRoutesConnectDotintegrationSlugRoute | ||
| 167 | DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRoute: typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRoute | 181 | DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRoute: typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRoute |
| 168 | DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRoute: typeof DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRoute | 182 | DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRoute: typeof DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRoute |
| 169 | DotDotDotDotDotDotReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute: typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute | 183 | DotDotDotDotDotDotReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute: typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute |
| @@ -228,6 +242,13 @@ declare module '@tanstack/react-router' { · 58 unmodified lines | |||
| 228 | preLoaderRoute: typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRouteImport | 242 | preLoaderRoute: typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRouteImport |
| 229 | parentRoute: typeof rootRouteImport | 243 | parentRoute: typeof rootRouteImport |
| 230 | } | 244 | } |
| 245 | '/{-$orgSlug}/connect/$integrationSlug': { | ||
| 246 | id: '/{-$orgSlug}/connect/$integrationSlug' | ||
| 247 | path: '/{-$orgSlug}/connect/$integrationSlug' | ||
| 248 | fullPath: '/{-$orgSlug}/connect/$integrationSlug' | ||
| 249 | preLoaderRoute: typeof DotDotDotDotDotDotReactSrcRoutesConnectDotintegrationSlugRouteImport | ||
| 250 | parentRoute: typeof rootRouteImport | ||
| 251 | } | ||
| 231 | '/{-$orgSlug}/plugins/$pluginId/$': { | 252 | '/{-$orgSlug}/plugins/$pluginId/$': { |
| 232 | id: '/{-$orgSlug}/plugins/$pluginId/$' | 253 | id: '/{-$orgSlug}/plugins/$pluginId/$' |
| 233 | path: '/{-$orgSlug}/plugins/$pluginId/$' | 254 | path: '/{-$orgSlug}/plugins/$pluginId/$' |
| @@ -270,6 +291,8 @@ const rootRouteChildren: RootRouteChildren = { · 36 unmodified lines | |||
| 270 | DotDotDotDotDotDotReactSrcRoutesToolsRoute, | 291 | DotDotDotDotDotDotReactSrcRoutesToolsRoute, |
| 271 | DotDotDotDotDotDotReactSrcRoutesIndexRoute: | 292 | DotDotDotDotDotDotReactSrcRoutesIndexRoute: |
| 272 | DotDotDotDotDotDotReactSrcRoutesIndexRoute, | 293 | DotDotDotDotDotDotReactSrcRoutesIndexRoute, |
| 294 | DotDotDotDotDotDotReactSrcRoutesConnectDotintegrationSlugRoute: | ||
| 295 | DotDotDotDotDotDotReactSrcRoutesConnectDotintegrationSlugRoute, | ||
| 273 | DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRoute: | 296 | DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRoute: |
| 274 | DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRoute, | 297 | DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRoute, |
| 275 | DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRoute: | 298 | DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRoute: |
| @@ -57,7 +57,20 @@ describe("isValidOrgSlug", () => { · 56 unmodified lines | |||
| 57 | }); | 57 | }); |
| 58 | 58 | ||
| 59 | it("reserves the segments routing depends on", () => { | 59 | it("reserves the segments routing depends on", () => { |
| 60 | for (const critical of ["api", "mcp", "integrations", "policies", "login", "cdn-cgi"]) { | 60 | for (const critical of [ |
| 61 | "api", | ||
| 62 | "mcp", | ||
| 63 | "integrations", | ||
| 64 | "policies", | ||
| 65 | "login", | ||
| 66 | "cdn-cgi", | ||
| 67 | // The connect deep link's root. Cloud's post-auth callback reads the | ||
| 68 | // first path segment as an org selector, so leaving `connect` claimable | ||
| 69 | // made `/connect/<slug>` look like a request for an org named "connect" | ||
| 70 | // — which suppressed the active-membership fallback and could drop the | ||
| 71 | // deep link into org onboarding after sign-in. | ||
| 72 | "connect", | ||
| 73 | ]) { | ||
| 61 | expect(RESERVED_ORG_SLUGS.has(critical), critical).toBe(true); | 74 | expect(RESERVED_ORG_SLUGS.has(critical), critical).toBe(true); |
| 62 | } | 75 | } |
| 63 | }); | 76 | }); |
| @@ -17,8 +17,8 @@ const ORG_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){1,47}$/; · 16 unmodified lines | |||
| 17 | * predictably will): | 17 | * predictably will): |
| 18 | * - App planes: api, mcp, .well-known (cloud `app-paths.ts`, selfhost | 18 | * - App planes: api, mcp, .well-known (cloud `app-paths.ts`, selfhost |
| 19 | * envelope, cloudflare `run_worker_first`) | 19 | * envelope, cloudflare `run_worker_first`) |
| 20 | * - Console routes: integrations, policies, secrets, tools, resume, | 20 | * - Console routes: connect, integrations, policies, secrets, tools, |
| 21 | * plugins (the shared contract), plus host extras: | 21 | * resume, plugins (the shared contract), plus host extras: |
| 22 | * api-keys, org, billing, create-org, setup-mcp (cloud), | 22 | * api-keys, org, billing, create-org, setup-mcp (cloud), |
| 23 | * admin, join, docs (selfhost), login (Better Auth | 23 | * admin, join, docs (selfhost), login (Better Auth |
| 24 | * `mcp({ loginPage })` + cloud/selfhost login UX) | 24 | * `mcp({ loginPage })` + cloud/selfhost login UX) |
| @@ -41,6 +41,7 @@ export const RESERVED_ORG_SLUGS: ReadonlySet<string> = new Set([ · 16 unmodified lines | |||
| 41 | "mcp", | 41 | "mcp", |
| 42 | ".well-known", | 42 | ".well-known", |
| 43 | // console + host routes | 43 | // console + host routes |
| 44 | "connect", | ||
| 44 | "integrations", | 45 | "integrations", |
| 45 | "policies", | 46 | "policies", |
| 46 | "secrets", | 47 | "secrets", |
| @@ -34,6 +34,7 @@ export const ORG_SLUG_SEGMENT = "{-$orgSlug}"; · 33 unmodified lines | |||
| 34 | * are these paths prefixed with `/{-$orgSlug}`. */ | 34 | * are these paths prefixed with `/{-$orgSlug}`. */ |
| 35 | export const CONSOLE_ROUTE_PATHS = [ | 35 | export const CONSOLE_ROUTE_PATHS = [ |
| 36 | "/", | 36 | "/", |
| 37 | "/connect/$integrationSlug", | ||
| 37 | "/integrations/$namespace", | 38 | "/integrations/$namespace", |
| 38 | "/integrations/add/$pluginKey", | 39 | "/integrations/add/$pluginKey", |
| 39 | "/policies", | 40 | "/policies", |
| @@ -62,6 +63,10 @@ export const consoleRoutes = (options: ConsoleRoutesOptions): Array<VirtualRoute · 22 unmodified lines | |||
| 62 | const file = (name: string): string => `${options.dir}/${name}`; | 63 | const file = (name: string): string => `${options.dir}/${name}`; |
| 63 | const entries: ReadonlyArray<readonly [ConsoleRoutePath, VirtualRouteNode]> = [ | 64 | const entries: ReadonlyArray<readonly [ConsoleRoutePath, VirtualRouteNode]> = [ |
| 64 | ["/", index(file("index.tsx"))], | 65 | ["/", index(file("index.tsx"))], |
| 66 | [ | ||
| 67 | "/connect/$integrationSlug", | ||
| 68 | route("/connect/$integrationSlug", file("connect.$integrationSlug.tsx")), | ||
| 69 | ], | ||
| 65 | [ | 70 | [ |
| 66 | "/integrations/$namespace", | 71 | "/integrations/$namespace", |
| 67 | route("/integrations/$namespace", file("integrations.$namespace.tsx")), | 72 | route("/integrations/$namespace", file("integrations.$namespace.tsx")), |
| @@ -0,0 +1,109 @@ | |||
| 1 | import { useEffect } from "react"; | ||
| 2 | import { Link, useNavigate } from "@tanstack/react-router"; | ||
| 3 | import { useAtomValue, useAtomRefresh } from "@effect/atom-react"; | ||
| 4 | import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; | ||
| 5 | import { IntegrationSlug } from "@executor-js/sdk/shared"; | ||
| 6 | |||
| 7 | import { integrationAtom, integrationsOptimisticAtom } from "../api/atoms"; | ||
| 8 | import { ErrorState } from "../components/error-state"; | ||
| 9 | import { Skeleton } from "../components/skeleton"; | ||
| 10 | import { isAsyncResultLoading } from "../lib/async-result"; | ||
| 11 | import { useExecutorDocumentTitle } from "../lib/document-title"; | ||
| 12 | |||
| 13 | // --------------------------------------------------------------------------- | ||
| 14 | // `/connect/<integration slug>` — the stable, shareable deep link that drops a | ||
| 15 | // person straight into the connect flow for ONE integration ("connect your | ||
| 16 | // Linear") instead of the dashboard. | ||
| 17 | // | ||
| 18 | // This page owns no connect UI of its own. It resolves the slug against the | ||
| 19 | // tenant's catalog and forwards into the integration detail page's existing | ||
| 20 | // account handoff (`?addAccount=1`), which is the same surface the detail | ||
| 21 | // page's own "Add connection" button opens — multi-method tab strips, OAuth | ||
| 22 | // popups, existing-connection lists and "add another account" all come for | ||
| 23 | // free, and stay correct as that flow evolves. | ||
| 24 | // | ||
| 25 | // Deliberately keyed on the INTEGRATION SLUG, not a plugin id: the slug is the | ||
| 26 | // catalog identity a customer can write down, whereas plugin routes are on | ||
| 27 | // their way out. The redirect is `replace: true` so the deep link does not | ||
| 28 | // linger in history and Back leaves the connect flow rather than bouncing. | ||
| 29 | // --------------------------------------------------------------------------- | ||
| 30 | |||
| 31 | export function ConnectIntegrationPage(props: { readonly integrationSlug: string }) { | ||
| 32 | const { integrationSlug } = props; | ||
| 33 | const slug = IntegrationSlug.make(integrationSlug); | ||
| 34 | const integration = useAtomValue(integrationAtom(slug)); | ||
| 35 | const refreshIntegrations = useAtomRefresh(integrationsOptimisticAtom); | ||
| 36 | const navigate = useNavigate(); | ||
| 37 | |||
| 38 | useExecutorDocumentTitle("Connect"); | ||
| 39 | |||
| 40 | // The catalog read is a client-side find over the integrations list, so an | ||
| 41 | // unknown slug resolves to Success(null) rather than a failure — the missing | ||
| 42 | // case is a not-found state, and only a genuine fetch failure is an error. | ||
| 43 | const resolved = AsyncResult.isSuccess(integration) ? integration.value : null; | ||
| 44 | const loading = isAsyncResultLoading(integration); | ||
| 45 | const failed = AsyncResult.isFailure(integration); | ||
| 46 | const missing = !loading && !failed && resolved === null; | ||
| 47 | |||
| 48 | useEffect(() => { | ||
| 49 | if (resolved === null) return; | ||
| 50 | void navigate({ | ||
| 51 | to: "/{-$orgSlug}/integrations/$namespace", | ||
| 52 | params: { namespace: String(resolved.slug) }, | ||
| 53 | // The NUMBER 1, so the router serializes the canonical `?addAccount=1` | ||
| 54 | // that agent-generated handoff links already use (a string would be | ||
| 55 | // quoted as `%221%22`). | ||
| 56 | search: { tab: "accounts", addAccount: 1 }, | ||
| 57 | replace: true, | ||
| 58 | }); | ||
| 59 | }, [resolved, navigate]); | ||
| 60 | |||
| 61 | if (failed) { | ||
| 62 | return ( | ||
| 63 | <ConnectStateFrame> | ||
| 64 | <ErrorState message="Failed to load integrations" onRetry={refreshIntegrations} /> | ||
| 65 | </ConnectStateFrame> | ||
| 66 | ); | ||
| 67 | } | ||
| 68 | |||
| 69 | if (missing) { | ||
| 70 | return ( | ||
| 71 | <ConnectStateFrame> | ||
| 72 | <div className="flex flex-col items-center justify-center rounded-2xl border border-dashed border-border py-20"> | ||
| 73 | <p className="mb-1 text-sm font-medium text-foreground/70"> | ||
| 74 | Unknown integration: {integrationSlug} | ||
| 75 | </p> | ||
| 76 | <p className="mb-5 text-xs text-muted-foreground"> | ||
| 77 | This integration is not in your workspace. | ||
| 78 | </p> | ||
| 79 | <Link | ||
| 80 | to="/{-$orgSlug}" | ||
| 81 | className="inline-flex items-center gap-1.5 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90" | ||
| 82 | > | ||
| 83 | Back to integrations | ||
| 84 | </Link> | ||
| 85 | </div> | ||
| 86 | </ConnectStateFrame> | ||
| 87 | ); | ||
| 88 | } | ||
| 89 | |||
| 90 | // Loading, or resolved and mid-redirect: the same quiet placeholder, so the | ||
| 91 | // hand-off never flashes an empty frame between catalog load and navigation. | ||
| 92 | return ( | ||
| 93 | <ConnectStateFrame> | ||
| 94 | <div className="space-y-3"> | ||
| 95 | <Skeleton className="h-8 w-56" /> | ||
| 96 | <Skeleton className="h-4 w-80" /> | ||
| 97 | <Skeleton className="h-32 w-full" /> | ||
| 98 | </div> | ||
| 99 | </ConnectStateFrame> | ||
| 100 | ); | ||
| 101 | } | ||
| 102 | |||
| 103 | function ConnectStateFrame(props: { readonly children: React.ReactNode }) { | ||
| 104 | return ( | ||
| 105 | <div className="relative min-h-0 flex-1 overflow-y-auto"> | ||
| 106 | <div className="mx-auto max-w-4xl px-6 py-10 lg:px-10 lg:py-14">{props.children}</div> | ||
| 107 | </div> | ||
| 108 | ); | ||
| 109 | } | ||
| @@ -60,6 +60,10 @@ type ToolRow = { · 59 unmodified lines | |||
| 60 | export function IntegrationDetailPage(props: { | 60 | export function IntegrationDetailPage(props: { |
| 61 | namespace: string; | 61 | namespace: string; |
| 62 | tab?: IntegrationDetailSearchTab; | 62 | tab?: IntegrationDetailSearchTab; |
| 63 | /** Route-validated `addAccount=1`: open the add-connection flow on arrival. | ||
| 64 | * Set by the `/connect/<slug>` deep link, which navigates client-side and so | ||
| 65 | * cannot rely on the raw location search below. */ | ||
| 66 | addAccount?: boolean; | ||
| 63 | }) { | 67 | }) { |
| 64 | const { namespace } = props; | 68 | const { namespace } = props; |
| 65 | const slug = IntegrationSlug.make(namespace); | 69 | const slug = IntegrationSlug.make(namespace); |
| @@ -122,9 +126,11 @@ export function IntegrationDetailPage(props: { · 56 unmodified lines | |||
| 122 | const canRefresh = integrationData?.canRefresh ?? false; | 126 | const canRefresh = integrationData?.canRefresh ?? false; |
| 123 | const canRemove = integrationData?.canRemove ?? false; | 127 | const canRemove = integrationData?.canRemove ?? false; |
| 124 | const urlAccountHandoff = useMemo<IntegrationAccountHandoff | null>(() => { | 128 | const urlAccountHandoff = useMemo<IntegrationAccountHandoff | null>(() => { |
| 125 | if (locationSearch.length === 0) return null; | ||
| 126 | const search = new URLSearchParams(locationSearch); | 129 | const search = new URLSearchParams(locationSearch); |
| 127 | if (search.get("addAccount") !== "1") return null; | 130 | // The route-validated flag and the raw `addAccount=1` are the same request; |
| 131 | // either one opens the flow. The extra prefill fields below are read from | ||
| 132 | // the raw search, which is populated on the full page loads that carry them. | ||
| 133 | if (!props.addAccount && search.get("addAccount") !== "1") return null; | ||
| 128 | const owner = search.get("owner"); | 134 | const owner = search.get("owner"); |
| 129 | const template = search.get("template"); | 135 | const template = search.get("template"); |
| 130 | const label = search.get("label"); | 136 | const label = search.get("label"); |
| @@ -149,13 +155,13 @@ export function IntegrationDetailPage(props: { · 18 unmodified lines | |||
| 149 | }; | 155 | }; |
| 150 | })(); | 156 | })(); |
| 151 | return { | 157 | return { |
| 152 | key: locationSearch, | 158 | key: `${String(props.addAccount)}:${locationSearch}`, |
| 153 | ...(owner === "org" || owner === "user" ? { owner } : {}), | 159 | ...(owner === "org" || owner === "user" ? { owner } : {}), |
| 154 | ...(template != null && template.length > 0 ? { template } : {}), | 160 | ...(template != null && template.length > 0 ? { template } : {}), |
| 155 | ...(label != null && label.length > 0 ? { label } : {}), | 161 | ...(label != null && label.length > 0 ? { label } : {}), |
| 156 | ...(oauthClient !== undefined ? { oauthClient } : {}), | 162 | ...(oauthClient !== undefined ? { oauthClient } : {}), |
| 157 | }; | 163 | }; |
| 158 | }, [locationSearch]); | 164 | }, [locationSearch, props.addAccount]); |
| 159 | const accountHandoff = manualAccountHandoff ?? urlAccountHandoff; | 165 | const accountHandoff = manualAccountHandoff ?? urlAccountHandoff; |
| 160 | 166 | ||
| 161 | useEffect(() => { | 167 | useEffect(() => { |
| @@ -0,0 +1,121 @@ | |||
| 1 | import { describe, expect, it } from "@effect/vitest"; | ||
| 2 | import { | ||
| 3 | createMemoryHistory, | ||
| 4 | createRootRoute, | ||
| 5 | createRoute, | ||
| 6 | createRouter, | ||
| 7 | } from "@tanstack/react-router"; | ||
| 8 | |||
| 9 | import { CONSOLE_ROUTE_PATHS } from "../console-routes"; | ||
| 10 | import { isAddAccountRequested } from "./integrations.$namespace"; | ||
| 11 | |||
| 12 | // Routing semantics of the `/connect/<integration slug>` deep link — the URL a | ||
| 13 | // customer hands to an end user ("connect your Linear"). The page component is | ||
| 14 | // exercised end-to-end in e2e/scenarios/connect-deep-link.test.ts; this locks | ||
| 15 | // the URL contract itself: which shapes match, what the params resolve to, and | ||
| 16 | // that the org-slug prefix round-trips. | ||
| 17 | |||
| 18 | type RouteMatch = { readonly routeId: string; readonly params: Record<string, string> }; | ||
| 19 | type SpikeRouter = { | ||
| 20 | matchRoutes(pathname: string): ReadonlyArray<RouteMatch>; | ||
| 21 | buildLocation(options: unknown): { readonly href: string }; | ||
| 22 | }; | ||
| 23 | |||
| 24 | const CONNECT_ROUTE_ID = "/{-$orgSlug}/connect/$integrationSlug"; | ||
| 25 | |||
| 26 | const make = (): SpikeRouter => { | ||
| 27 | const rootRoute = createRootRoute(); | ||
| 28 | const orgScope = createRoute({ getParentRoute: () => rootRoute, path: "/{-$orgSlug}" }); | ||
| 29 | const indexRoute = createRoute({ getParentRoute: () => orgScope, path: "/" }); | ||
| 30 | const connectRoute = createRoute({ | ||
| 31 | getParentRoute: () => orgScope, | ||
| 32 | path: "/connect/$integrationSlug", | ||
| 33 | }); | ||
| 34 | const nsRoute = createRoute({ | ||
| 35 | getParentRoute: () => orgScope, | ||
| 36 | path: "/integrations/$namespace", | ||
| 37 | }); | ||
| 38 | const routeTree = rootRoute.addChildren([ | ||
| 39 | orgScope.addChildren([indexRoute, connectRoute, nsRoute]), | ||
| 40 | ]); | ||
| 41 | // A test-local tree is not the package's registered console tree, so widen | ||
| 42 | // away the global Register types — this asserts runtime semantics. | ||
| 43 | // oxlint-disable-next-line executor/no-double-cast -- boundary: a test-local route tree must escape the package-global router Register types | ||
| 44 | return createRouter({ | ||
| 45 | routeTree, | ||
| 46 | history: createMemoryHistory({ initialEntries: ["/"] }), | ||
| 47 | }) as unknown as SpikeRouter; | ||
| 48 | }; | ||
| 49 | |||
| 50 | describe("connect deep link", () => { | ||
| 51 | it("is part of the shared console route contract", () => { | ||
| 52 | // Every host mounts the contract, so the deep link exists on cloud, | ||
| 53 | // self-host, cloudflare and local alike. | ||
| 54 | expect(CONSOLE_ROUTE_PATHS).toContain("/connect/$integrationSlug"); | ||
| 55 | }); | ||
| 56 | |||
| 57 | it("matches the bare link owner.com would generate", () => { | ||
| 58 | const matches = make().matchRoutes("/connect/linear"); | ||
| 59 | const match = matches.find((m) => m.routeId === CONNECT_ROUTE_ID); | ||
| 60 | expect(match?.params).toEqual({ integrationSlug: "linear" }); | ||
| 61 | }); | ||
| 62 | |||
| 63 | it("matches the org-prefixed form without eating the slug", () => { | ||
| 64 | const matches = make().matchRoutes("/acme/connect/linear"); | ||
| 65 | const match = matches.find((m) => m.routeId === CONNECT_ROUTE_ID); | ||
| 66 | expect(match?.params).toEqual({ orgSlug: "acme", integrationSlug: "linear" }); | ||
| 67 | }); | ||
| 68 | |||
| 69 | it("keeps slugs that look like other console words intact", () => { | ||
| 70 | // The integration slug is a free-form catalog id; "tools" or "integrations" | ||
| 71 | // as a slug must still land on connect, not on those pages. | ||
| 72 | for (const slug of ["tools", "integrations", "connect"]) { | ||
| 73 | const match = make() | ||
| 74 | .matchRoutes(`/connect/${slug}`) | ||
| 75 | .find((m) => m.routeId === CONNECT_ROUTE_ID); | ||
| 76 | expect(match?.params).toEqual({ integrationSlug: slug }); | ||
| 77 | } | ||
| 78 | }); | ||
| 79 | |||
| 80 | it("builds the link with and without an org slug", () => { | ||
| 81 | const router = make(); | ||
| 82 | expect( | ||
| 83 | router.buildLocation({ to: CONNECT_ROUTE_ID, params: { integrationSlug: "linear" } }).href, | ||
| 84 | ).toBe("/connect/linear"); | ||
| 85 | expect( | ||
| 86 | router.buildLocation({ | ||
| 87 | to: CONNECT_ROUTE_ID, | ||
| 88 | params: { orgSlug: "acme", integrationSlug: "linear" }, | ||
| 89 | }).href, | ||
| 90 | ).toBe("/acme/connect/linear"); | ||
| 91 | }); | ||
| 92 | |||
| 93 | it("forwards into the integration's existing connect flow", () => { | ||
| 94 | // The page redirects here rather than rendering its own connect UI; the | ||
| 95 | // handoff is carried as validated search so it survives client navigation. | ||
| 96 | // The number 1 keeps the canonical `?addAccount=1` that agent-generated | ||
| 97 | // handoff links already use — a string would serialize as `%221%22`. | ||
| 98 | const href = make().buildLocation({ | ||
| 99 | to: "/{-$orgSlug}/integrations/$namespace", | ||
| 100 | params: { namespace: "linear" }, | ||
| 101 | search: { tab: "accounts", addAccount: 1 }, | ||
| 102 | }).href; | ||
| 103 | expect(href).toBe("/integrations/linear?tab=accounts&addAccount=1"); | ||
| 104 | }); | ||
| 105 | }); | ||
| 106 | |||
| 107 | describe("addAccount flag", () => { | ||
| 108 | it("accepts every spelling the two link sources produce", () => { | ||
| 109 | // A full page load parses `?addAccount=1` to the number; a client-side | ||
| 110 | // navigation may carry the string. Both mean "open the connect flow". | ||
| 111 | expect(isAddAccountRequested(1)).toBe(true); | ||
| 112 | expect(isAddAccountRequested("1")).toBe(true); | ||
| 113 | expect(isAddAccountRequested(true)).toBe(true); | ||
| 114 | }); | ||
| 115 | |||
| 116 | it("ignores anything else instead of failing the page", () => { | ||
| 117 | for (const value of [undefined, null, 0, "0", "", "banana", {}]) { | ||
| 118 | expect(isAddAccountRequested(value)).toBe(false); | ||
| 119 | } | ||
| 120 | }); | ||
| 121 | }); | ||
| @@ -0,0 +1,10 @@ | |||
| 1 | import { createFileRoute } from "@tanstack/react-router"; | ||
| 2 | |||
| 3 | import { ConnectIntegrationPage } from "../pages/connect-integration"; | ||
| 4 | |||
| 5 | export const Route = createFileRoute("/{-$orgSlug}/connect/$integrationSlug")({ | ||
| 6 | component: () => { | ||
| 7 | const { integrationSlug } = Route.useParams(); | ||
| 8 | return <ConnectIntegrationPage integrationSlug={integrationSlug} />; | ||
| 9 | }, | ||
| 10 | }); | ||
| @@ -4,21 +4,40 @@ import { createFileRoute } from "@tanstack/react-router"; · 3 unmodified lines | |||
| 4 | import { IntegrationDetailPage } from "../pages/integration-detail"; | 4 | import { IntegrationDetailPage } from "../pages/integration-detail"; |
| 5 | import type { IntegrationDetailSearchTab } from "../lib/integration-detail-tabs"; | 5 | import type { IntegrationDetailSearchTab } from "../lib/integration-detail-tabs"; |
| 6 | 6 | ||
| 7 | // `addAccount=1` opens the add-connection flow on arrival. It is declared here | ||
| 8 | // (rather than only read off `window.location`) because unlisted keys do not | ||
| 9 | // survive validation: a CLIENT-SIDE navigation carrying the handoff — the | ||
| 10 | // `/connect/<slug>` deep link — would otherwise have it stripped before the | ||
| 11 | // page could see it. The richer handoff fields stay URL-only; they arrive on | ||
| 12 | // full page loads from agent-generated links. | ||
| 13 | // | ||
| 14 | // Typed as unknown and coerced, not as a literal, for two reasons. A URL flag | ||
| 15 | // must never hard-fail a page: a hand-edited `?addAccount=banana` should be | ||
| 16 | // ignored, not rendered as a validation error. And the router's search | ||
| 17 | // serializer quotes a string "1" (`addAccount=%221%22`) while leaving the | ||
| 18 | // number 1 bare, so accepting both is what keeps ONE canonical URL — | ||
| 19 | // `?addAccount=1` — shared by deep links and agent-generated handoff links. | ||
| 7 | const SearchParams = Schema.toStandardSchemaV1( | 20 | const SearchParams = Schema.toStandardSchemaV1( |
| 8 | Schema.Struct({ | 21 | Schema.Struct({ |
| 9 | tab: Schema.optional(Schema.Literals(["accounts", "source", "tools"])), | 22 | tab: Schema.optional(Schema.Literals(["accounts", "source", "tools"])), |
| 23 | addAccount: Schema.optional(Schema.Unknown), | ||
| 10 | }), | 24 | }), |
| 11 | ); | 25 | ); |
| 12 | 26 | ||
| 27 | /** The truthy spellings of the `addAccount` URL flag. */ | ||
| 28 | export const isAddAccountRequested = (value: unknown): boolean => | ||
| 29 | value === 1 || value === "1" || value === true; | ||
| 30 | |||
| 13 | export const Route = createFileRoute("/{-$orgSlug}/integrations/$namespace")({ | 31 | export const Route = createFileRoute("/{-$orgSlug}/integrations/$namespace")({ |
| 14 | validateSearch: SearchParams, | 32 | validateSearch: SearchParams, |
| 15 | component: () => { | 33 | component: () => { |
| 16 | const { namespace } = Route.useParams(); | 34 | const { namespace } = Route.useParams(); |
| 17 | const { tab } = Route.useSearch(); | 35 | const { tab, addAccount } = Route.useSearch(); |
| 18 | return ( | 36 | return ( |
| 19 | <IntegrationDetailPage | 37 | <IntegrationDetailPage |
| 20 | namespace={namespace} | 38 | namespace={namespace} |
| 21 | tab={tab as IntegrationDetailSearchTab | undefined} | 39 | tab={tab as IntegrationDetailSearchTab | undefined} |
| 40 | addAccount={isAddAccountRequested(addAccount)} | ||
| 22 | /> | 41 | /> |
| 23 | ); | 42 | ); |
| 24 | }, | 43 | }, |
| @@ -17,6 +17,7 @@ import { Route as DotPoliciesRouteImport } from './policies' · 16 unmodified lines | |||
| 17 | import { Route as DotToolkitsDottoolkitSlugRouteImport } from './toolkits.$toolkitSlug' | 17 | import { Route as DotToolkitsDottoolkitSlugRouteImport } from './toolkits.$toolkitSlug' |
| 18 | import { Route as DotResumeDotexecutionIdRouteImport } from './resume.$executionId' | 18 | import { Route as DotResumeDotexecutionIdRouteImport } from './resume.$executionId' |
| 19 | import { Route as DotIntegrationsDotnamespaceRouteImport } from './integrations.$namespace' | 19 | import { Route as DotIntegrationsDotnamespaceRouteImport } from './integrations.$namespace' |
| 20 | import { Route as DotConnectDotintegrationSlugRouteImport } from './connect.$integrationSlug' | ||
| 20 | import { Route as DotPluginsDotpluginIdDotsplatRouteImport } from './plugins.$pluginId.$' | 21 | import { Route as DotPluginsDotpluginIdDotsplatRouteImport } from './plugins.$pluginId.$' |
| 21 | import { Route as DotIntegrationsDotaddDotpluginKeyRouteImport } from './integrations.add.$pluginKey' | 22 | import { Route as DotIntegrationsDotaddDotpluginKeyRouteImport } from './integrations.add.$pluginKey' |
| 22 | 23 | ||
| @@ -62,6 +63,12 @@ const DotIntegrationsDotnamespaceRoute = · 39 unmodified lines | |||
| 62 | path: '/{-$orgSlug}/integrations/$namespace', | 63 | path: '/{-$orgSlug}/integrations/$namespace', |
| 63 | getParentRoute: () => rootRouteImport, | 64 | getParentRoute: () => rootRouteImport, |
| 64 | } as any) | 65 | } as any) |
| 66 | const DotConnectDotintegrationSlugRoute = | ||
| 67 | DotConnectDotintegrationSlugRouteImport.update({ | ||
| 68 | id: '/{-$orgSlug}/connect/$integrationSlug', | ||
| 69 | path: '/{-$orgSlug}/connect/$integrationSlug', | ||
| 70 | getParentRoute: () => rootRouteImport, | ||
| 71 | } as any) | ||
| 65 | const DotPluginsDotpluginIdDotsplatRoute = | 72 | const DotPluginsDotpluginIdDotsplatRoute = |
| 66 | DotPluginsDotpluginIdDotsplatRouteImport.update({ | 73 | DotPluginsDotpluginIdDotsplatRouteImport.update({ |
| 67 | id: '/{-$orgSlug}/plugins/$pluginId/$', | 74 | id: '/{-$orgSlug}/plugins/$pluginId/$', |
| @@ -81,6 +88,7 @@ export interface FileRoutesByFullPath { · 13 unmodified lines | |||
| 81 | '/{-$orgSlug}/toolkits': typeof DotToolkitsRouteWithChildren | 88 | '/{-$orgSlug}/toolkits': typeof DotToolkitsRouteWithChildren |
| 82 | '/{-$orgSlug}/tools': typeof DotToolsRoute | 89 | '/{-$orgSlug}/tools': typeof DotToolsRoute |
| 83 | '/{-$orgSlug}/': typeof DotIndexRoute | 90 | '/{-$orgSlug}/': typeof DotIndexRoute |
| 91 | '/{-$orgSlug}/connect/$integrationSlug': typeof DotConnectDotintegrationSlugRoute | ||
| 84 | '/{-$orgSlug}/integrations/$namespace': typeof DotIntegrationsDotnamespaceRoute | 92 | '/{-$orgSlug}/integrations/$namespace': typeof DotIntegrationsDotnamespaceRoute |
| 85 | '/{-$orgSlug}/resume/$executionId': typeof DotResumeDotexecutionIdRoute | 93 | '/{-$orgSlug}/resume/$executionId': typeof DotResumeDotexecutionIdRoute |
| 86 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotToolkitsDottoolkitSlugRoute | 94 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotToolkitsDottoolkitSlugRoute |
| @@ -93,6 +101,7 @@ export interface FileRoutesByTo { · 6 unmodified lines | |||
| 93 | '/{-$orgSlug}/toolkits': typeof DotToolkitsRouteWithChildren | 101 | '/{-$orgSlug}/toolkits': typeof DotToolkitsRouteWithChildren |
| 94 | '/{-$orgSlug}/tools': typeof DotToolsRoute | 102 | '/{-$orgSlug}/tools': typeof DotToolsRoute |
| 95 | '/{-$orgSlug}': typeof DotIndexRoute | 103 | '/{-$orgSlug}': typeof DotIndexRoute |
| 104 | '/{-$orgSlug}/connect/$integrationSlug': typeof DotConnectDotintegrationSlugRoute | ||
| 96 | '/{-$orgSlug}/integrations/$namespace': typeof DotIntegrationsDotnamespaceRoute | 105 | '/{-$orgSlug}/integrations/$namespace': typeof DotIntegrationsDotnamespaceRoute |
| 97 | '/{-$orgSlug}/resume/$executionId': typeof DotResumeDotexecutionIdRoute | 106 | '/{-$orgSlug}/resume/$executionId': typeof DotResumeDotexecutionIdRoute |
| 98 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotToolkitsDottoolkitSlugRoute | 107 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotToolkitsDottoolkitSlugRoute |
| @@ -106,6 +115,7 @@ export interface FileRoutesById { · 7 unmodified lines | |||
| 106 | '/{-$orgSlug}/toolkits': typeof DotToolkitsRouteWithChildren | 115 | '/{-$orgSlug}/toolkits': typeof DotToolkitsRouteWithChildren |
| 107 | '/{-$orgSlug}/tools': typeof DotToolsRoute | 116 | '/{-$orgSlug}/tools': typeof DotToolsRoute |
| 108 | '/{-$orgSlug}/': typeof DotIndexRoute | 117 | '/{-$orgSlug}/': typeof DotIndexRoute |
| 118 | '/{-$orgSlug}/connect/$integrationSlug': typeof DotConnectDotintegrationSlugRoute | ||
| 109 | '/{-$orgSlug}/integrations/$namespace': typeof DotIntegrationsDotnamespaceRoute | 119 | '/{-$orgSlug}/integrations/$namespace': typeof DotIntegrationsDotnamespaceRoute |
| 110 | '/{-$orgSlug}/resume/$executionId': typeof DotResumeDotexecutionIdRoute | 120 | '/{-$orgSlug}/resume/$executionId': typeof DotResumeDotexecutionIdRoute |
| 111 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotToolkitsDottoolkitSlugRoute | 121 | '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotToolkitsDottoolkitSlugRoute |
| @@ -120,6 +130,7 @@ export interface FileRouteTypes { · 8 unmodified lines | |||
| 120 | | '/{-$orgSlug}/toolkits' | 130 | | '/{-$orgSlug}/toolkits' |
| 121 | | '/{-$orgSlug}/tools' | 131 | | '/{-$orgSlug}/tools' |
| 122 | | '/{-$orgSlug}/' | 132 | | '/{-$orgSlug}/' |
| 133 | | '/{-$orgSlug}/connect/$integrationSlug' | ||
| 123 | | '/{-$orgSlug}/integrations/$namespace' | 134 | | '/{-$orgSlug}/integrations/$namespace' |
| 124 | | '/{-$orgSlug}/resume/$executionId' | 135 | | '/{-$orgSlug}/resume/$executionId' |
| 125 | | '/{-$orgSlug}/toolkits/$toolkitSlug' | 136 | | '/{-$orgSlug}/toolkits/$toolkitSlug' |
| @@ -132,6 +143,7 @@ export interface FileRouteTypes { · 6 unmodified lines | |||
| 132 | | '/{-$orgSlug}/toolkits' | 143 | | '/{-$orgSlug}/toolkits' |
| 133 | | '/{-$orgSlug}/tools' | 144 | | '/{-$orgSlug}/tools' |
| 134 | | '/{-$orgSlug}' | 145 | | '/{-$orgSlug}' |
| 146 | | '/{-$orgSlug}/connect/$integrationSlug' | ||
| 135 | | '/{-$orgSlug}/integrations/$namespace' | 147 | | '/{-$orgSlug}/integrations/$namespace' |
| 136 | | '/{-$orgSlug}/resume/$executionId' | 148 | | '/{-$orgSlug}/resume/$executionId' |
| 137 | | '/{-$orgSlug}/toolkits/$toolkitSlug' | 149 | | '/{-$orgSlug}/toolkits/$toolkitSlug' |
| @@ -144,6 +156,7 @@ export interface FileRouteTypes { · 6 unmodified lines | |||
| 144 | | '/{-$orgSlug}/toolkits' | 156 | | '/{-$orgSlug}/toolkits' |
| 145 | | '/{-$orgSlug}/tools' | 157 | | '/{-$orgSlug}/tools' |
| 146 | | '/{-$orgSlug}/' | 158 | | '/{-$orgSlug}/' |
| 159 | | '/{-$orgSlug}/connect/$integrationSlug' | ||
| 147 | | '/{-$orgSlug}/integrations/$namespace' | 160 | | '/{-$orgSlug}/integrations/$namespace' |
| 148 | | '/{-$orgSlug}/resume/$executionId' | 161 | | '/{-$orgSlug}/resume/$executionId' |
| 149 | | '/{-$orgSlug}/toolkits/$toolkitSlug' | 162 | | '/{-$orgSlug}/toolkits/$toolkitSlug' |
| @@ -157,6 +170,7 @@ export interface RootRouteChildren { · 7 unmodified lines | |||
| 157 | DotToolkitsRoute: typeof DotToolkitsRouteWithChildren | 170 | DotToolkitsRoute: typeof DotToolkitsRouteWithChildren |
| 158 | DotToolsRoute: typeof DotToolsRoute | 171 | DotToolsRoute: typeof DotToolsRoute |
| 159 | DotIndexRoute: typeof DotIndexRoute | 172 | DotIndexRoute: typeof DotIndexRoute |
| 173 | DotConnectDotintegrationSlugRoute: typeof DotConnectDotintegrationSlugRoute | ||
| 160 | DotIntegrationsDotnamespaceRoute: typeof DotIntegrationsDotnamespaceRoute | 174 | DotIntegrationsDotnamespaceRoute: typeof DotIntegrationsDotnamespaceRoute |
| 161 | DotResumeDotexecutionIdRoute: typeof DotResumeDotexecutionIdRoute | 175 | DotResumeDotexecutionIdRoute: typeof DotResumeDotexecutionIdRoute |
| 162 | DotIntegrationsDotaddDotpluginKeyRoute: typeof DotIntegrationsDotaddDotpluginKeyRoute | 176 | DotIntegrationsDotaddDotpluginKeyRoute: typeof DotIntegrationsDotaddDotpluginKeyRoute |
| @@ -221,6 +235,13 @@ declare module '@tanstack/react-router' { · 58 unmodified lines | |||
| 221 | preLoaderRoute: typeof DotIntegrationsDotnamespaceRouteImport | 235 | preLoaderRoute: typeof DotIntegrationsDotnamespaceRouteImport |
| 222 | parentRoute: typeof rootRouteImport | 236 | parentRoute: typeof rootRouteImport |
| 223 | } | 237 | } |
| 238 | '/{-$orgSlug}/connect/$integrationSlug': { | ||
| 239 | id: '/{-$orgSlug}/connect/$integrationSlug' | ||
| 240 | path: '/{-$orgSlug}/connect/$integrationSlug' | ||
| 241 | fullPath: '/{-$orgSlug}/connect/$integrationSlug' | ||
| 242 | preLoaderRoute: typeof DotConnectDotintegrationSlugRouteImport | ||
| 243 | parentRoute: typeof rootRouteImport | ||
| 244 | } | ||
| 224 | '/{-$orgSlug}/plugins/$pluginId/$': { | 245 | '/{-$orgSlug}/plugins/$pluginId/$': { |
| 225 | id: '/{-$orgSlug}/plugins/$pluginId/$' | 246 | id: '/{-$orgSlug}/plugins/$pluginId/$' |
| 226 | path: '/{-$orgSlug}/plugins/$pluginId/$' | 247 | path: '/{-$orgSlug}/plugins/$pluginId/$' |
| @@ -256,6 +277,7 @@ const rootRouteChildren: RootRouteChildren = { · 29 unmodified lines | |||
| 256 | DotToolkitsRoute: DotToolkitsRouteWithChildren, | 277 | DotToolkitsRoute: DotToolkitsRouteWithChildren, |
| 257 | DotToolsRoute: DotToolsRoute, | 278 | DotToolsRoute: DotToolsRoute, |
| 258 | DotIndexRoute: DotIndexRoute, | 279 | DotIndexRoute: DotIndexRoute, |
| 280 | DotConnectDotintegrationSlugRoute: DotConnectDotintegrationSlugRoute, | ||
| 259 | DotIntegrationsDotnamespaceRoute: DotIntegrationsDotnamespaceRoute, | 281 | DotIntegrationsDotnamespaceRoute: DotIntegrationsDotnamespaceRoute, |
| 260 | DotResumeDotexecutionIdRoute: DotResumeDotexecutionIdRoute, | 282 | DotResumeDotexecutionIdRoute: DotResumeDotexecutionIdRoute, |
| 261 | DotIntegrationsDotaddDotpluginKeyRoute: | 283 | DotIntegrationsDotaddDotpluginKeyRoute: |