From 9321b63f666d3a034f156a69e34e5ae18c195cb9 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan Date: Thu, 11 Jun 2026 15:06:58 -0700 Subject: [PATCH] Share the console route tree across hosts via TanStack virtual file routes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shared console routes (integrations, policies, secrets, tools, resume, plugins) were re-declared file-by-file in every app and drifted: this host still registered /sources/* while the shared shell, command palette, and the SDK's credential-handoff URLs all point at /integrations/*, so those links landed on the router's not-found page. packages/react now owns the contract it links to: - src/routes/ holds the canonical route files, next to the pages they bind. - consoleRoutes() (src/console-routes.ts) exposes them as virtual file route nodes; an app's vite config composes them into its virtualRouteConfig, keeping its own __root (the auth shell is what genuinely differs) and its app-specific routes. Apps with an intentionally different surface for a shared path exclude it and register their own file. - The package registers its own generated console route tree (console-router.ts + routes/routeTree.gen.ts, regenerated with `bun run routes:gen`), so every Link/navigate in the shared pages and shell typechecks against the contract instead of being a loose string. host-cloudflare is the first consumer: its route dir shrinks to just __root, the old /sources/* paths are dropped, and /integrations/* now resolves (verified by driving the SPA in a browser against wrangler dev: integrations detail/add render and unknown paths still 404). TanStack Start supports the same composition via tanstackStart({ router: { virtualRouteConfig } }), so cloud, host-selfhost, and packages/app can follow. zod is pinned to exactly 4.3.6 in host-mcp, plugin-mcp, and test-servers: the new dev dependencies make bun re-resolve those workspaces, which otherwise drift to zod 4.4.x while the hoisted zod (shared with @modelcontextprotocol/sdk) stays at 4.3.6 — mixed zod instances break the SDK's schema typings. The exact pins keep every executor package on the same instance the SDK uses. --- apps/host-cloudflare/package.json | 1 + apps/host-cloudflare/vite.config.ts | 10 + apps/host-cloudflare/web/routeTree.gen.ts | 248 ++++++++++-------- apps/host-cloudflare/web/routes/secrets.tsx | 10 - bun.lock | 46 +++- packages/core/test-servers/package.json | 2 +- packages/hosts/mcp/package.json | 2 +- packages/plugins/mcp/package.json | 2 +- packages/react/package.json | 11 +- packages/react/src/console-router.ts | 24 ++ packages/react/src/console-routes.ts | 59 +++++ packages/react/src/routes/__root.tsx | 7 + .../react/src}/routes/index.tsx | 3 +- .../src/routes/integrations.$namespace.tsx | 5 +- .../routes/integrations.add.$pluginKey.tsx | 12 +- .../react/src}/routes/plugins.$pluginId.$.tsx | 0 .../react/src}/routes/policies.tsx | 3 +- .../react/src}/routes/resume.$executionId.tsx | 10 +- packages/react/src/routes/routeTree.gen.ts | 211 +++++++++++++++ packages/react/src/routes/secrets.tsx | 12 + .../react/src}/routes/tools.tsx | 3 +- packages/react/tsr.config.json | 4 + packages/react/tsup.config.ts | 17 ++ 23 files changed, 556 insertions(+), 146 deletions(-) delete mode 100644 apps/host-cloudflare/web/routes/secrets.tsx create mode 100644 packages/react/src/console-router.ts create mode 100644 packages/react/src/console-routes.ts create mode 100644 packages/react/src/routes/__root.tsx rename {apps/host-cloudflare/web => packages/react/src}/routes/index.tsx (65%) rename apps/host-cloudflare/web/routes/sources.$namespace.tsx => packages/react/src/routes/integrations.$namespace.tsx (56%) rename apps/host-cloudflare/web/routes/sources.add.$pluginKey.tsx => packages/react/src/routes/integrations.add.$pluginKey.tsx (50%) rename {apps/host-cloudflare/web => packages/react/src}/routes/plugins.$pluginId.$.tsx (100%) rename {apps/host-cloudflare/web => packages/react/src}/routes/policies.tsx (69%) rename {apps/host-cloudflare/web => packages/react/src}/routes/resume.$executionId.tsx (93%) create mode 100644 packages/react/src/routes/routeTree.gen.ts create mode 100644 packages/react/src/routes/secrets.tsx rename {apps/host-cloudflare/web => packages/react/src}/routes/tools.tsx (69%) create mode 100644 packages/react/tsr.config.json create mode 100644 packages/react/tsup.config.ts diff --git a/apps/host-cloudflare/package.json b/apps/host-cloudflare/package.json index d4bee71ba..8d7640d16 100644 --- a/apps/host-cloudflare/package.json +++ b/apps/host-cloudflare/package.json @@ -45,6 +45,7 @@ "@executor-js/vite-plugin": "workspace:*", "@tailwindcss/vite": "catalog:", "@tanstack/router-plugin": "^1.167.12", + "@tanstack/virtual-file-routes": "^1.162.0", "@types/node": "catalog:", "@types/react": "catalog:", "@types/react-dom": "catalog:", diff --git a/apps/host-cloudflare/vite.config.ts b/apps/host-cloudflare/vite.config.ts index dc371ef79..0a10b8695 100644 --- a/apps/host-cloudflare/vite.config.ts +++ b/apps/host-cloudflare/vite.config.ts @@ -4,6 +4,8 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import tailwindcss from "@tailwindcss/vite"; import { tanstackRouter } from "@tanstack/router-plugin/vite"; +import { rootRoute } from "@tanstack/virtual-file-routes"; +import { consoleRoutes } from "@executor-js/react/console-routes"; import executorVitePlugin from "@executor-js/vite-plugin"; // --------------------------------------------------------------------------- @@ -51,6 +53,14 @@ export default defineConfig({ autoCodeSplitting: true, routesDirectory: fileURLToPath(new URL("./web/routes", import.meta.url)), generatedRouteTree: fileURLToPath(new URL("./web/routeTree.gen.ts", import.meta.url)), + // The route tree is composed, not hand-mirrored: the shared console + // routes come from @executor-js/react (the package whose shell/pages + // link to them); this app only owns its root (the Cloudflare-Access + // shell). To add app-specific routes, create web/routes/app and mount + // it here with `physical("", "app")` — the directory must exist. + virtualRouteConfig: rootRoute("__root.tsx", [ + ...consoleRoutes({ dir: "../../../../packages/react/src/routes" }), + ]), }), ...react(), ], diff --git a/apps/host-cloudflare/web/routeTree.gen.ts b/apps/host-cloudflare/web/routeTree.gen.ts index 4e9e39fa4..fc0a24546 100644 --- a/apps/host-cloudflare/web/routeTree.gen.ts +++ b/apps/host-cloudflare/web/routeTree.gen.ts @@ -9,86 +9,102 @@ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { Route as rootRouteImport } from './routes/__root' -import { Route as ToolsRouteImport } from './routes/tools' -import { Route as SecretsRouteImport } from './routes/secrets' -import { Route as PoliciesRouteImport } from './routes/policies' -import { Route as IndexRouteImport } from './routes/index' -import { Route as SourcesNamespaceRouteImport } from './routes/sources.$namespace' -import { Route as ResumeExecutionIdRouteImport } from './routes/resume.$executionId' -import { Route as SourcesAddPluginKeyRouteImport } from './routes/sources.add.$pluginKey' -import { Route as PluginsPluginIdSplatRouteImport } from './routes/plugins.$pluginId.$' +import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRouteImport } from './../../../packages/react/src/routes/tools' +import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRouteImport } from './../../../packages/react/src/routes/secrets' +import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRouteImport } from './../../../packages/react/src/routes/policies' +import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRouteImport } from './../../../packages/react/src/routes/index' +import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRouteImport } from './../../../packages/react/src/routes/resume.$executionId' +import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport } from './../../../packages/react/src/routes/integrations.$namespace' +import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport } from './../../../packages/react/src/routes/plugins.$pluginId.$' +import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport } from './../../../packages/react/src/routes/integrations.add.$pluginKey' -const ToolsRoute = ToolsRouteImport.update({ - id: '/tools', - path: '/tools', - getParentRoute: () => rootRouteImport, -} as any) -const SecretsRoute = SecretsRouteImport.update({ - id: '/secrets', - path: '/secrets', - getParentRoute: () => rootRouteImport, -} as any) -const PoliciesRoute = PoliciesRouteImport.update({ - id: '/policies', - path: '/policies', - getParentRoute: () => rootRouteImport, -} as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -} as any) -const SourcesNamespaceRoute = SourcesNamespaceRouteImport.update({ - id: '/sources/$namespace', - path: '/sources/$namespace', - getParentRoute: () => rootRouteImport, -} as any) -const ResumeExecutionIdRoute = ResumeExecutionIdRouteImport.update({ - id: '/resume/$executionId', - path: '/resume/$executionId', - getParentRoute: () => rootRouteImport, -} as any) -const SourcesAddPluginKeyRoute = SourcesAddPluginKeyRouteImport.update({ - id: '/sources/add/$pluginKey', - path: '/sources/add/$pluginKey', - getParentRoute: () => rootRouteImport, -} as any) -const PluginsPluginIdSplatRoute = PluginsPluginIdSplatRouteImport.update({ - id: '/plugins/$pluginId/$', - path: '/plugins/$pluginId/$', - getParentRoute: () => rootRouteImport, -} as any) +const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute = + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRouteImport.update({ + id: '/tools', + path: '/tools', + getParentRoute: () => rootRouteImport, + } as any) +const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute = + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRouteImport.update({ + id: '/secrets', + path: '/secrets', + getParentRoute: () => rootRouteImport, + } as any) +const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute = + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRouteImport.update({ + id: '/policies', + path: '/policies', + getParentRoute: () => rootRouteImport, + } as any) +const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute = + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, + } as any) +const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute = + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRouteImport.update( + { + id: '/resume/$executionId', + path: '/resume/$executionId', + getParentRoute: () => rootRouteImport, + } as any, + ) +const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute = + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport.update( + { + id: '/integrations/$namespace', + path: '/integrations/$namespace', + getParentRoute: () => rootRouteImport, + } as any, + ) +const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute = + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport.update( + { + id: '/plugins/$pluginId/$', + path: '/plugins/$pluginId/$', + getParentRoute: () => rootRouteImport, + } as any, + ) +const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute = + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport.update( + { + id: '/integrations/add/$pluginKey', + path: '/integrations/add/$pluginKey', + getParentRoute: () => rootRouteImport, + } as any, + ) export interface FileRoutesByFullPath { - '/': typeof IndexRoute - '/policies': typeof PoliciesRoute - '/secrets': typeof SecretsRoute - '/tools': typeof ToolsRoute - '/resume/$executionId': typeof ResumeExecutionIdRoute - '/sources/$namespace': typeof SourcesNamespaceRoute - '/plugins/$pluginId/$': typeof PluginsPluginIdSplatRoute - '/sources/add/$pluginKey': typeof SourcesAddPluginKeyRoute + '/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute + '/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute + '/secrets': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute + '/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute + '/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute + '/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute + '/integrations/add/$pluginKey': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute + '/plugins/$pluginId/$': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute } export interface FileRoutesByTo { - '/': typeof IndexRoute - '/policies': typeof PoliciesRoute - '/secrets': typeof SecretsRoute - '/tools': typeof ToolsRoute - '/resume/$executionId': typeof ResumeExecutionIdRoute - '/sources/$namespace': typeof SourcesNamespaceRoute - '/plugins/$pluginId/$': typeof PluginsPluginIdSplatRoute - '/sources/add/$pluginKey': typeof SourcesAddPluginKeyRoute + '/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute + '/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute + '/secrets': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute + '/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute + '/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute + '/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute + '/integrations/add/$pluginKey': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute + '/plugins/$pluginId/$': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute } export interface FileRoutesById { __root__: typeof rootRouteImport - '/': typeof IndexRoute - '/policies': typeof PoliciesRoute - '/secrets': typeof SecretsRoute - '/tools': typeof ToolsRoute - '/resume/$executionId': typeof ResumeExecutionIdRoute - '/sources/$namespace': typeof SourcesNamespaceRoute - '/plugins/$pluginId/$': typeof PluginsPluginIdSplatRoute - '/sources/add/$pluginKey': typeof SourcesAddPluginKeyRoute + '/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute + '/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute + '/secrets': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute + '/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute + '/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute + '/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute + '/integrations/add/$pluginKey': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute + '/plugins/$pluginId/$': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath @@ -97,41 +113,41 @@ export interface FileRouteTypes { | '/policies' | '/secrets' | '/tools' + | '/integrations/$namespace' | '/resume/$executionId' - | '/sources/$namespace' + | '/integrations/add/$pluginKey' | '/plugins/$pluginId/$' - | '/sources/add/$pluginKey' fileRoutesByTo: FileRoutesByTo to: | '/' | '/policies' | '/secrets' | '/tools' + | '/integrations/$namespace' | '/resume/$executionId' - | '/sources/$namespace' + | '/integrations/add/$pluginKey' | '/plugins/$pluginId/$' - | '/sources/add/$pluginKey' id: | '__root__' | '/' | '/policies' | '/secrets' | '/tools' + | '/integrations/$namespace' | '/resume/$executionId' - | '/sources/$namespace' + | '/integrations/add/$pluginKey' | '/plugins/$pluginId/$' - | '/sources/add/$pluginKey' fileRoutesById: FileRoutesById } export interface RootRouteChildren { - IndexRoute: typeof IndexRoute - PoliciesRoute: typeof PoliciesRoute - SecretsRoute: typeof SecretsRoute - ToolsRoute: typeof ToolsRoute - ResumeExecutionIdRoute: typeof ResumeExecutionIdRoute - SourcesNamespaceRoute: typeof SourcesNamespaceRoute - PluginsPluginIdSplatRoute: typeof PluginsPluginIdSplatRoute - SourcesAddPluginKeyRoute: typeof SourcesAddPluginKeyRoute + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute } declare module '@tanstack/react-router' { @@ -140,70 +156,78 @@ declare module '@tanstack/react-router' { id: '/tools' path: '/tools' fullPath: '/tools' - preLoaderRoute: typeof ToolsRouteImport + preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRouteImport parentRoute: typeof rootRouteImport } '/secrets': { id: '/secrets' path: '/secrets' fullPath: '/secrets' - preLoaderRoute: typeof SecretsRouteImport + preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRouteImport parentRoute: typeof rootRouteImport } '/policies': { id: '/policies' path: '/policies' fullPath: '/policies' - preLoaderRoute: typeof PoliciesRouteImport + preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRouteImport parentRoute: typeof rootRouteImport } '/': { id: '/' path: '/' fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport - } - '/sources/$namespace': { - id: '/sources/$namespace' - path: '/sources/$namespace' - fullPath: '/sources/$namespace' - preLoaderRoute: typeof SourcesNamespaceRouteImport + preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRouteImport parentRoute: typeof rootRouteImport } '/resume/$executionId': { id: '/resume/$executionId' path: '/resume/$executionId' fullPath: '/resume/$executionId' - preLoaderRoute: typeof ResumeExecutionIdRouteImport + preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRouteImport parentRoute: typeof rootRouteImport } - '/sources/add/$pluginKey': { - id: '/sources/add/$pluginKey' - path: '/sources/add/$pluginKey' - fullPath: '/sources/add/$pluginKey' - preLoaderRoute: typeof SourcesAddPluginKeyRouteImport + '/integrations/$namespace': { + id: '/integrations/$namespace' + path: '/integrations/$namespace' + fullPath: '/integrations/$namespace' + preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport parentRoute: typeof rootRouteImport } '/plugins/$pluginId/$': { id: '/plugins/$pluginId/$' path: '/plugins/$pluginId/$' fullPath: '/plugins/$pluginId/$' - preLoaderRoute: typeof PluginsPluginIdSplatRouteImport + preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport + parentRoute: typeof rootRouteImport + } + '/integrations/add/$pluginKey': { + id: '/integrations/add/$pluginKey' + path: '/integrations/add/$pluginKey' + fullPath: '/integrations/add/$pluginKey' + preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport parentRoute: typeof rootRouteImport } } } const rootRouteChildren: RootRouteChildren = { - IndexRoute: IndexRoute, - PoliciesRoute: PoliciesRoute, - SecretsRoute: SecretsRoute, - ToolsRoute: ToolsRoute, - ResumeExecutionIdRoute: ResumeExecutionIdRoute, - SourcesNamespaceRoute: SourcesNamespaceRoute, - PluginsPluginIdSplatRoute: PluginsPluginIdSplatRoute, - SourcesAddPluginKeyRoute: SourcesAddPluginKeyRoute, + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute: + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute, + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute: + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute, + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute: + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute, + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute: + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute, + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute: + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute, + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute: + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute, + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute: + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute, + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute: + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) diff --git a/apps/host-cloudflare/web/routes/secrets.tsx b/apps/host-cloudflare/web/routes/secrets.tsx deleted file mode 100644 index 10bd4a10b..000000000 --- a/apps/host-cloudflare/web/routes/secrets.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { createFileRoute } from "@tanstack/react-router"; -import { SecretsPage } from "@executor-js/react/pages/secrets"; - -// The Providers/Secrets page lets self-host users inspect their credential -// backends. Credential entry happens through the per-integration Add Account -// flow (`connections.createHandoff` → `/integrations/{slug}?addAccount=1`), -// not here, so this route takes no search params. -export const Route = createFileRoute("/secrets")({ - component: () => , -}); diff --git a/bun.lock b/bun.lock index d0521840a..7f0e200f6 100644 --- a/bun.lock +++ b/bun.lock @@ -185,6 +185,7 @@ "@executor-js/vite-plugin": "workspace:*", "@tailwindcss/vite": "catalog:", "@tanstack/router-plugin": "^1.167.12", + "@tanstack/virtual-file-routes": "^1.162.0", "@types/node": "catalog:", "@types/react": "catalog:", "@types/react-dom": "catalog:", @@ -583,7 +584,7 @@ "@modelcontextprotocol/sdk": "^1.29.0", "effect": "catalog:", "graphql-yoga": "^5.17.0", - "zod": "^4.3.6", + "zod": "4.3.6", }, "devDependencies": { "@cloudflare/workers-types": "^4.20250620.0", @@ -642,7 +643,7 @@ "@executor-js/sdk": "workspace:*", "@modelcontextprotocol/sdk": "^1.12.1", "effect": "catalog:", - "zod": "^4.3.0", + "zod": "4.3.6", }, "devDependencies": { "@effect/vitest": "catalog:", @@ -879,7 +880,7 @@ "@executor-js/config": "workspace:*", "@executor-js/sdk": "workspace:*", "@modelcontextprotocol/sdk": "^1.29.0", - "zod": "^4.3.6", + "zod": "4.3.6", }, "devDependencies": { "@effect/atom-react": "catalog:", @@ -1023,6 +1024,7 @@ "@shikijs/langs": "^4.0.2", "@shikijs/themes": "^4.0.2", "@tanstack/react-router": "catalog:", + "@tanstack/virtual-file-routes": "^1.162.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", @@ -1046,10 +1048,12 @@ }, "devDependencies": { "@effect/vitest": "catalog:", + "@tanstack/router-cli": "^1.167.17", "@types/node": "catalog:", "@types/react": "catalog:", "hast-util-to-jsx-runtime": "^2.3.6", "tailwindcss": "catalog:", + "tsup": "^8.5.1", "typescript": "catalog:", "vite": "catalog:", }, @@ -2508,6 +2512,8 @@ "@tanstack/react-store": ["@tanstack/react-store@0.9.3", "", { "dependencies": { "@tanstack/store": "0.9.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg=="], + "@tanstack/router-cli": ["@tanstack/router-cli@1.167.17", "", { "dependencies": { "@tanstack/router-generator": "1.167.17", "chokidar": "^5.0.0", "yargs": "^17.7.2" }, "bin": { "tsr": "bin/tsr.cjs" } }, "sha512-kws9PNdspkHbeZG6aLDjjpgH/hg7Q565BdSW9hHrjKmXPq/at7OKxQnA76aw9dJiJqy9OmST2RdOKlMEog43+g=="], + "@tanstack/router-core": ["@tanstack/router-core@1.168.15", "", { "dependencies": { "@tanstack/history": "1.161.6", "cookie-es": "^3.0.0", "seroval": "^1.5.0", "seroval-plugins": "^1.5.0" }, "bin": { "intent": "bin/intent.js" } }, "sha512-Wr0424NDtD8fT/uALobMZ9DdcfsTyXtW5IPR++7zvW8/7RaIOeaqXpVDId8ywaGtqPWLWOfaUg2zUtYtukoXYA=="], "@tanstack/router-generator": ["@tanstack/router-generator@1.166.32", "", { "dependencies": { "@babel/types": "^7.28.5", "@tanstack/router-core": "1.168.15", "@tanstack/router-utils": "1.161.6", "@tanstack/virtual-file-routes": "1.161.7", "magic-string": "^0.30.21", "prettier": "^3.5.0", "tsx": "^4.19.2", "zod": "^3.24.2" } }, "sha512-VuusKwEXcgKq+myq1JQfZogY8scTXIIeFls50dJ/UXgCXWp5n14iFreYNlg41wURcak2oA3M+t2TVfD0xUUD6g=="], @@ -2528,7 +2534,7 @@ "@tanstack/store": ["@tanstack/store@0.9.3", "", {}, "sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw=="], - "@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.161.7", "", { "bin": { "intent": "bin/intent.js" } }, "sha512-olW33+Cn+bsCsZKPwEGhlkqS6w3M2slFv11JIobdnCFKMLG97oAI2kWKdx5/zsywTL8flpnoIgaZZPlQTFYhdQ=="], + "@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.162.0", "", {}, "sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA=="], "@turbo/darwin-64": ["@turbo/darwin-64@2.9.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-X/56SnVXIQZBLKwniGTwEQTGmtE5brSACnKMBWpY3YafuxVYefrC2acamfjgxP7BG5w3I+6jf0UrLoSzgPcSJg=="], @@ -3732,7 +3738,7 @@ "jake": ["jake@10.9.4", "", { "dependencies": { "async": "^3.2.6", "filelist": "^1.0.4", "picocolors": "^1.1.1" }, "bin": { "jake": "bin/cli.js" } }, "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA=="], - "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], "jose": ["jose@5.10.0", "", {}, "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg=="], @@ -5344,6 +5350,8 @@ "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + "@tailwindcss/node/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], @@ -5356,8 +5364,16 @@ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@tanstack/router-cli/@tanstack/router-generator": ["@tanstack/router-generator@1.167.17", "", { "dependencies": { "@babel/types": "^7.28.5", "@tanstack/router-core": "1.171.13", "@tanstack/router-utils": "1.162.2", "@tanstack/virtual-file-routes": "1.162.0", "jiti": "^2.7.0", "magic-string": "^0.30.21", "prettier": "^3.5.0", "zod": "^4.4.3" } }, "sha512-xtB9tB2Ws0tWR6Pi7nc3Qk9IYgoh1mQCKWjHqIl9tf6BNUpKoqniJoPAQ4+LGrK8FeZYU0o0p/qlZEyj9FAulA=="], + + "@tanstack/router-cli/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], + + "@tanstack/router-generator/@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.161.7", "", { "bin": { "intent": "bin/intent.js" } }, "sha512-olW33+Cn+bsCsZKPwEGhlkqS6w3M2slFv11JIobdnCFKMLG97oAI2kWKdx5/zsywTL8flpnoIgaZZPlQTFYhdQ=="], + "@tanstack/router-generator/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "@tanstack/router-plugin/@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.161.7", "", { "bin": { "intent": "bin/intent.js" } }, "sha512-olW33+Cn+bsCsZKPwEGhlkqS6w3M2slFv11JIobdnCFKMLG97oAI2kWKdx5/zsywTL8flpnoIgaZZPlQTFYhdQ=="], + "@tanstack/router-plugin/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "@tanstack/start-plugin-core/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], @@ -5400,6 +5416,8 @@ "app-builder-lib/fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], + "app-builder-lib/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + "app-builder-lib/which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="], "astro/@clack/prompts": ["@clack/prompts@1.2.0", "", { "dependencies": { "@clack/core": "1.2.0", "fast-string-width": "^1.1.0", "fast-wrap-ansi": "^0.1.3", "sisteransi": "^1.0.5" } }, "sha512-4jmztR9fMqPMjz6H/UZXj0zEmE43ha1euENwkckKKel4XpSfokExPo5AiVStdHSAlHekz4d0CA/r45Ok1E4D3w=="], @@ -5412,6 +5430,8 @@ "atmn/dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="], + "atmn/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + "better-auth/jose": ["jose@6.2.2", "", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="], "better-call/rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="], @@ -5526,6 +5546,8 @@ "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + "knip/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + "libsql/detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="], "matcher/escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], @@ -5900,6 +5922,14 @@ "@react-grab/cli/ora/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + "@tanstack/router-cli/@tanstack/router-generator/@tanstack/router-core": ["@tanstack/router-core@1.171.13", "", { "dependencies": { "@tanstack/history": "1.162.0", "cookie-es": "^3.0.0", "seroval": "^1.5.4", "seroval-plugins": "^1.5.4" } }, "sha512-+NOwEj1kO/6IGmpHRIZHasYxYWpyBQGNIZAST9aNrk9Q3YlU9SgqVnl1pbLa9qAKfeNdXQIRve0RQb/0kyDeDA=="], + + "@tanstack/router-cli/@tanstack/router-generator/@tanstack/router-utils": ["@tanstack/router-utils@1.162.2", "", { "dependencies": { "@babel/generator": "^7.28.5", "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "ansis": "^4.1.0", "babel-dead-code-elimination": "^1.0.12", "diff": "^8.0.2", "pathe": "^2.0.3", "tinyglobby": "^0.2.15" } }, "sha512-hTWqJtqIFFdvuCl8WXNyrodp2L9zo2G37xKRrcVmVRWpAB2h+U1LuRAfS4tsFTiWOIoE/B+WDVFB8JpoEdw6jQ=="], + + "@tanstack/router-cli/@tanstack/router-generator/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], + + "@tanstack/router-cli/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], + "@types/better-sqlite3/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], "@types/cacheable-request/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], @@ -6352,6 +6382,12 @@ "@react-grab/cli/ora/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + "@tanstack/router-cli/@tanstack/router-generator/@tanstack/router-core/@tanstack/history": ["@tanstack/history@1.162.0", "", {}, "sha512-79pf/RkhteYZTRgcR4F9kbk84P2N8rugQJswxfIqovlbRiT3yI7eBE+5QorIrZaOKktsgzRlXh1l/du/xpl4iA=="], + + "@tanstack/router-cli/@tanstack/router-generator/@tanstack/router-core/seroval": ["seroval@1.5.4", "", {}, "sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw=="], + + "@tanstack/router-cli/@tanstack/router-generator/@tanstack/router-core/seroval-plugins": ["seroval-plugins@1.5.4", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw=="], + "agents/yargs/cliui/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], "agents/yargs/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], diff --git a/packages/core/test-servers/package.json b/packages/core/test-servers/package.json index 0015254cc..176f0a4fe 100644 --- a/packages/core/test-servers/package.json +++ b/packages/core/test-servers/package.json @@ -16,7 +16,7 @@ "@modelcontextprotocol/sdk": "^1.29.0", "effect": "catalog:", "graphql-yoga": "^5.17.0", - "zod": "^4.3.6" + "zod": "4.3.6" }, "devDependencies": { "@cloudflare/workers-types": "^4.20250620.0", diff --git a/packages/hosts/mcp/package.json b/packages/hosts/mcp/package.json index 3b0364318..4ea803fe9 100644 --- a/packages/hosts/mcp/package.json +++ b/packages/hosts/mcp/package.json @@ -29,7 +29,7 @@ "@executor-js/sdk": "workspace:*", "@modelcontextprotocol/sdk": "^1.12.1", "effect": "catalog:", - "zod": "^4.3.0" + "zod": "4.3.6" }, "devDependencies": { "@effect/vitest": "catalog:", diff --git a/packages/plugins/mcp/package.json b/packages/plugins/mcp/package.json index 89ee101ec..6161ddf5c 100644 --- a/packages/plugins/mcp/package.json +++ b/packages/plugins/mcp/package.json @@ -66,7 +66,7 @@ "@executor-js/config": "workspace:*", "@executor-js/sdk": "workspace:*", "@modelcontextprotocol/sdk": "^1.29.0", - "zod": "^4.3.6" + "zod": "4.3.6" }, "devDependencies": { "@effect/atom-react": "catalog:", diff --git a/packages/react/package.json b/packages/react/package.json index ec5471524..6deace349 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -4,6 +4,11 @@ "private": true, "type": "module", "exports": { + "./console-routes": { + "types": "./src/console-routes.ts", + "bun": "./src/console-routes.ts", + "default": "./dist/console-routes.js" + }, "./api/oauth-popup": "./src/api/oauth-popup.ts", "./api/*": "./src/api/*.tsx", "./plugins/*": "./src/plugins/*.tsx", @@ -20,7 +25,8 @@ "test": "vitest run", "test:watch": "vitest", "typecheck": "tsgo --noEmit", - "typecheck:slow": "tsc --noEmit" + "typecheck:slow": "tsc --noEmit", + "build": "tsup" }, "dependencies": { "@base-ui/react": "^1.3.0", @@ -32,6 +38,7 @@ "@shikijs/langs": "^4.0.2", "@shikijs/themes": "^4.0.2", "@tanstack/react-router": "catalog:", + "@tanstack/virtual-file-routes": "^1.162.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", @@ -55,10 +62,12 @@ }, "devDependencies": { "@effect/vitest": "catalog:", + "@tanstack/router-cli": "^1.167.17", "@types/node": "catalog:", "@types/react": "catalog:", "hast-util-to-jsx-runtime": "^2.3.6", "tailwindcss": "catalog:", + "tsup": "^8.5.1", "typescript": "catalog:", "vite": "catalog:" } diff --git a/packages/react/src/console-router.ts b/packages/react/src/console-router.ts new file mode 100644 index 000000000..da82d3e68 --- /dev/null +++ b/packages/react/src/console-router.ts @@ -0,0 +1,24 @@ +import { createRouter } from "@tanstack/react-router"; + +import { routeTree } from "./routes/routeTree.gen"; + +// --------------------------------------------------------------------------- +// The canonical console router TYPE. Nothing imports this module — it exists +// so this package's own typecheck registers the console route tree (generated +// from src/routes by `bun run routes:gen`), which makes every `Link to=` / +// `navigate({ to })` in the shared pages and shell typecheck against the +// console route contract instead of being loose strings. A shared component +// linking to a route that isn't part of the shared console set fails HERE, in +// the package that owns the contract, not at runtime in some app. +// +// Apps are unaffected: each app's own router.tsx registers its real router +// (shared console routes + app-specific extras) for its own typecheck graph. +// --------------------------------------------------------------------------- + +export const consoleRouter = createRouter({ routeTree }); + +declare module "@tanstack/react-router" { + interface Register { + readonly router: typeof consoleRouter; + } +} diff --git a/packages/react/src/console-routes.ts b/packages/react/src/console-routes.ts new file mode 100644 index 000000000..0a7c181dd --- /dev/null +++ b/packages/react/src/console-routes.ts @@ -0,0 +1,59 @@ +import { index, route } from "@tanstack/virtual-file-routes"; +import type { VirtualRouteNode } from "@tanstack/virtual-file-routes"; + +// --------------------------------------------------------------------------- +// The shared console route contract. This package's pages and shell link to +// these paths (`Link to="/integrations/$namespace"` etc.), so every app that +// renders the shared console MUST register them — historically each app +// re-declared the same route files by hand and they drifted (host-cloudflare +// shipped `/sources/*` while the shell linked `/integrations/*`). +// +// `consoleRoutes()` makes the contract executable: app vite configs compose it +// into their TanStack `virtualRouteConfig`, mounting the canonical route files +// that live in `src/routes/` next to the pages they bind. Apps keep their own +// `__root.tsx` (the auth shell is what genuinely differs) and add app-specific +// routes alongside. An app with an intentionally different surface for one of +// these paths excludes it here and registers its own file instead. +// --------------------------------------------------------------------------- + +export const CONSOLE_ROUTE_PATHS = [ + "/", + "/integrations/$namespace", + "/integrations/add/$pluginKey", + "/policies", + "/secrets", + "/tools", + "/resume/$executionId", + "/plugins/$pluginId/$", +] as const; + +export type ConsoleRoutePath = (typeof CONSOLE_ROUTE_PATHS)[number]; + +export interface ConsoleRoutesOptions { + /** Path from the app's `routesDirectory` to this package's `src/routes`. */ + readonly dir: string; + /** Shared paths this app replaces with its own route file (or omits). */ + readonly exclude?: ReadonlyArray; +} + +export const consoleRoutes = (options: ConsoleRoutesOptions): Array => { + const excluded = new Set(options.exclude ?? []); + const file = (name: string): string => `${options.dir}/${name}`; + const entries: ReadonlyArray = [ + ["/", index(file("index.tsx"))], + [ + "/integrations/$namespace", + route("/integrations/$namespace", file("integrations.$namespace.tsx")), + ], + [ + "/integrations/add/$pluginKey", + route("/integrations/add/$pluginKey", file("integrations.add.$pluginKey.tsx")), + ], + ["/policies", route("/policies", file("policies.tsx"))], + ["/secrets", route("/secrets", file("secrets.tsx"))], + ["/tools", route("/tools", file("tools.tsx"))], + ["/resume/$executionId", route("/resume/$executionId", file("resume.$executionId.tsx"))], + ["/plugins/$pluginId/$", route("/plugins/$pluginId/$", file("plugins.$pluginId.$.tsx"))], + ]; + return entries.filter(([path]) => !excluded.has(path)).map(([, node]) => node); +}; diff --git a/packages/react/src/routes/__root.tsx b/packages/react/src/routes/__root.tsx new file mode 100644 index 000000000..63ec60603 --- /dev/null +++ b/packages/react/src/routes/__root.tsx @@ -0,0 +1,7 @@ +import { Outlet, createRootRoute } from "@tanstack/react-router"; + +// Typing-only root for the package-local console route tree (see +// console-router.ts). Apps never mount this file — each app supplies its own +// __root.tsx (the auth shell is what genuinely differs per app) in its +// virtualRouteConfig and composes the shared routes via consoleRoutes(). +export const Route = createRootRoute({ component: Outlet }); diff --git a/apps/host-cloudflare/web/routes/index.tsx b/packages/react/src/routes/index.tsx similarity index 65% rename from apps/host-cloudflare/web/routes/index.tsx rename to packages/react/src/routes/index.tsx index 2d57f82f0..9b62dcb15 100644 --- a/apps/host-cloudflare/web/routes/index.tsx +++ b/packages/react/src/routes/index.tsx @@ -1,5 +1,6 @@ import { createFileRoute } from "@tanstack/react-router"; -import { IntegrationsPage } from "@executor-js/react/pages/integrations"; + +import { IntegrationsPage } from "../pages/integrations"; export const Route = createFileRoute("/")({ component: IntegrationsPage, diff --git a/apps/host-cloudflare/web/routes/sources.$namespace.tsx b/packages/react/src/routes/integrations.$namespace.tsx similarity index 56% rename from apps/host-cloudflare/web/routes/sources.$namespace.tsx rename to packages/react/src/routes/integrations.$namespace.tsx index b8fcd190b..61345d269 100644 --- a/apps/host-cloudflare/web/routes/sources.$namespace.tsx +++ b/packages/react/src/routes/integrations.$namespace.tsx @@ -1,7 +1,8 @@ import { createFileRoute } from "@tanstack/react-router"; -import { IntegrationDetailPage } from "@executor-js/react/pages/integration-detail"; -export const Route = createFileRoute("/sources/$namespace")({ +import { IntegrationDetailPage } from "../pages/integration-detail"; + +export const Route = createFileRoute("/integrations/$namespace")({ component: () => { const { namespace } = Route.useParams(); return ; diff --git a/apps/host-cloudflare/web/routes/sources.add.$pluginKey.tsx b/packages/react/src/routes/integrations.add.$pluginKey.tsx similarity index 50% rename from apps/host-cloudflare/web/routes/sources.add.$pluginKey.tsx rename to packages/react/src/routes/integrations.add.$pluginKey.tsx index 9924b2c9e..187ef4846 100644 --- a/apps/host-cloudflare/web/routes/sources.add.$pluginKey.tsx +++ b/packages/react/src/routes/integrations.add.$pluginKey.tsx @@ -1,19 +1,23 @@ import { Schema } from "effect"; import { createFileRoute } from "@tanstack/react-router"; -import { AddIntegrationPage } from "@executor-js/react/pages/integration-add"; + +import { AddIntegrationPage } from "../pages/integration-add"; const SearchParams = Schema.toStandardSchemaV1( Schema.Struct({ url: Schema.optional(Schema.String), preset: Schema.optional(Schema.String), + namespace: Schema.optional(Schema.String), }), ); -export const Route = createFileRoute("/sources/add/$pluginKey")({ +export const Route = createFileRoute("/integrations/add/$pluginKey")({ validateSearch: SearchParams, component: () => { const { pluginKey } = Route.useParams(); - const { url, preset } = Route.useSearch(); - return ; + const { url, preset, namespace } = Route.useSearch(); + return ( + + ); }, }); diff --git a/apps/host-cloudflare/web/routes/plugins.$pluginId.$.tsx b/packages/react/src/routes/plugins.$pluginId.$.tsx similarity index 100% rename from apps/host-cloudflare/web/routes/plugins.$pluginId.$.tsx rename to packages/react/src/routes/plugins.$pluginId.$.tsx diff --git a/apps/host-cloudflare/web/routes/policies.tsx b/packages/react/src/routes/policies.tsx similarity index 69% rename from apps/host-cloudflare/web/routes/policies.tsx rename to packages/react/src/routes/policies.tsx index a9de9ff6f..0bd91a688 100644 --- a/apps/host-cloudflare/web/routes/policies.tsx +++ b/packages/react/src/routes/policies.tsx @@ -1,5 +1,6 @@ import { createFileRoute } from "@tanstack/react-router"; -import { PoliciesPage } from "@executor-js/react/pages/policies"; + +import { PoliciesPage } from "../pages/policies"; export const Route = createFileRoute("/policies")({ component: () => , diff --git a/apps/host-cloudflare/web/routes/resume.$executionId.tsx b/packages/react/src/routes/resume.$executionId.tsx similarity index 93% rename from apps/host-cloudflare/web/routes/resume.$executionId.tsx rename to packages/react/src/routes/resume.$executionId.tsx index 32a84347b..90d0962ea 100644 --- a/apps/host-cloudflare/web/routes/resume.$executionId.tsx +++ b/packages/react/src/routes/resume.$executionId.tsx @@ -3,12 +3,10 @@ import { useAtomSet, useAtomValue } from "@effect/atom-react"; import { Data, Effect, Option, Schema } from "effect"; import * as Atom from "effect/unstable/reactivity/Atom"; import { createFileRoute } from "@tanstack/react-router"; -import { - ResumeApprovalPage, - ResumeApprovalPageView, -} from "@executor-js/react/pages/resume-approval"; -import { pausedExecutionAtom } from "@executor-js/react/api/atoms"; -import type { ElicitationAction } from "@executor-js/react/components/elicitation-approval"; + +import { ResumeApprovalPage, ResumeApprovalPageView } from "../pages/resume-approval"; +import { pausedExecutionAtom } from "../api/atoms"; +import type { ElicitationAction } from "../components/elicitation-approval"; const SearchParams = Schema.toStandardSchemaV1( Schema.Struct({ diff --git a/packages/react/src/routes/routeTree.gen.ts b/packages/react/src/routes/routeTree.gen.ts new file mode 100644 index 000000000..11684c806 --- /dev/null +++ b/packages/react/src/routes/routeTree.gen.ts @@ -0,0 +1,211 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './__root' +import { Route as ToolsRouteImport } from './tools' +import { Route as SecretsRouteImport } from './secrets' +import { Route as PoliciesRouteImport } from './policies' +import { Route as IndexRouteImport } from './index' +import { Route as ResumeExecutionIdRouteImport } from './resume.$executionId' +import { Route as IntegrationsNamespaceRouteImport } from './integrations.$namespace' +import { Route as PluginsPluginIdSplatRouteImport } from './plugins.$pluginId.$' +import { Route as IntegrationsAddPluginKeyRouteImport } from './integrations.add.$pluginKey' + +const ToolsRoute = ToolsRouteImport.update({ + id: '/tools', + path: '/tools', + getParentRoute: () => rootRouteImport, +} as any) +const SecretsRoute = SecretsRouteImport.update({ + id: '/secrets', + path: '/secrets', + getParentRoute: () => rootRouteImport, +} as any) +const PoliciesRoute = PoliciesRouteImport.update({ + id: '/policies', + path: '/policies', + getParentRoute: () => rootRouteImport, +} as any) +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const ResumeExecutionIdRoute = ResumeExecutionIdRouteImport.update({ + id: '/resume/$executionId', + path: '/resume/$executionId', + getParentRoute: () => rootRouteImport, +} as any) +const IntegrationsNamespaceRoute = IntegrationsNamespaceRouteImport.update({ + id: '/integrations/$namespace', + path: '/integrations/$namespace', + getParentRoute: () => rootRouteImport, +} as any) +const PluginsPluginIdSplatRoute = PluginsPluginIdSplatRouteImport.update({ + id: '/plugins/$pluginId/$', + path: '/plugins/$pluginId/$', + getParentRoute: () => rootRouteImport, +} as any) +const IntegrationsAddPluginKeyRoute = + IntegrationsAddPluginKeyRouteImport.update({ + id: '/integrations/add/$pluginKey', + path: '/integrations/add/$pluginKey', + getParentRoute: () => rootRouteImport, + } as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/policies': typeof PoliciesRoute + '/secrets': typeof SecretsRoute + '/tools': typeof ToolsRoute + '/integrations/$namespace': typeof IntegrationsNamespaceRoute + '/resume/$executionId': typeof ResumeExecutionIdRoute + '/integrations/add/$pluginKey': typeof IntegrationsAddPluginKeyRoute + '/plugins/$pluginId/$': typeof PluginsPluginIdSplatRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/policies': typeof PoliciesRoute + '/secrets': typeof SecretsRoute + '/tools': typeof ToolsRoute + '/integrations/$namespace': typeof IntegrationsNamespaceRoute + '/resume/$executionId': typeof ResumeExecutionIdRoute + '/integrations/add/$pluginKey': typeof IntegrationsAddPluginKeyRoute + '/plugins/$pluginId/$': typeof PluginsPluginIdSplatRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/policies': typeof PoliciesRoute + '/secrets': typeof SecretsRoute + '/tools': typeof ToolsRoute + '/integrations/$namespace': typeof IntegrationsNamespaceRoute + '/resume/$executionId': typeof ResumeExecutionIdRoute + '/integrations/add/$pluginKey': typeof IntegrationsAddPluginKeyRoute + '/plugins/$pluginId/$': typeof PluginsPluginIdSplatRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/' + | '/policies' + | '/secrets' + | '/tools' + | '/integrations/$namespace' + | '/resume/$executionId' + | '/integrations/add/$pluginKey' + | '/plugins/$pluginId/$' + fileRoutesByTo: FileRoutesByTo + to: + | '/' + | '/policies' + | '/secrets' + | '/tools' + | '/integrations/$namespace' + | '/resume/$executionId' + | '/integrations/add/$pluginKey' + | '/plugins/$pluginId/$' + id: + | '__root__' + | '/' + | '/policies' + | '/secrets' + | '/tools' + | '/integrations/$namespace' + | '/resume/$executionId' + | '/integrations/add/$pluginKey' + | '/plugins/$pluginId/$' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + PoliciesRoute: typeof PoliciesRoute + SecretsRoute: typeof SecretsRoute + ToolsRoute: typeof ToolsRoute + IntegrationsNamespaceRoute: typeof IntegrationsNamespaceRoute + ResumeExecutionIdRoute: typeof ResumeExecutionIdRoute + IntegrationsAddPluginKeyRoute: typeof IntegrationsAddPluginKeyRoute + PluginsPluginIdSplatRoute: typeof PluginsPluginIdSplatRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/tools': { + id: '/tools' + path: '/tools' + fullPath: '/tools' + preLoaderRoute: typeof ToolsRouteImport + parentRoute: typeof rootRouteImport + } + '/secrets': { + id: '/secrets' + path: '/secrets' + fullPath: '/secrets' + preLoaderRoute: typeof SecretsRouteImport + parentRoute: typeof rootRouteImport + } + '/policies': { + id: '/policies' + path: '/policies' + fullPath: '/policies' + preLoaderRoute: typeof PoliciesRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + '/resume/$executionId': { + id: '/resume/$executionId' + path: '/resume/$executionId' + fullPath: '/resume/$executionId' + preLoaderRoute: typeof ResumeExecutionIdRouteImport + parentRoute: typeof rootRouteImport + } + '/integrations/$namespace': { + id: '/integrations/$namespace' + path: '/integrations/$namespace' + fullPath: '/integrations/$namespace' + preLoaderRoute: typeof IntegrationsNamespaceRouteImport + parentRoute: typeof rootRouteImport + } + '/plugins/$pluginId/$': { + id: '/plugins/$pluginId/$' + path: '/plugins/$pluginId/$' + fullPath: '/plugins/$pluginId/$' + preLoaderRoute: typeof PluginsPluginIdSplatRouteImport + parentRoute: typeof rootRouteImport + } + '/integrations/add/$pluginKey': { + id: '/integrations/add/$pluginKey' + path: '/integrations/add/$pluginKey' + fullPath: '/integrations/add/$pluginKey' + preLoaderRoute: typeof IntegrationsAddPluginKeyRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + PoliciesRoute: PoliciesRoute, + SecretsRoute: SecretsRoute, + ToolsRoute: ToolsRoute, + IntegrationsNamespaceRoute: IntegrationsNamespaceRoute, + ResumeExecutionIdRoute: ResumeExecutionIdRoute, + IntegrationsAddPluginKeyRoute: IntegrationsAddPluginKeyRoute, + PluginsPluginIdSplatRoute: PluginsPluginIdSplatRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/react/src/routes/secrets.tsx b/packages/react/src/routes/secrets.tsx new file mode 100644 index 000000000..952c8809d --- /dev/null +++ b/packages/react/src/routes/secrets.tsx @@ -0,0 +1,12 @@ +import { createFileRoute } from "@tanstack/react-router"; + +import { SecretsPage } from "../pages/secrets"; + +// The Providers/Secrets page lets users inspect their credential backends. +// Credential entry happens through the per-integration Add Account flow +// (`connections.createHandoff` → `/integrations/{slug}?addAccount=1`), not +// here, so this route takes no search params. Apps with a different secrets +// surface (e.g. cloud hides it) exclude this route and bring their own file. +export const Route = createFileRoute("/secrets")({ + component: () => , +}); diff --git a/apps/host-cloudflare/web/routes/tools.tsx b/packages/react/src/routes/tools.tsx similarity index 69% rename from apps/host-cloudflare/web/routes/tools.tsx rename to packages/react/src/routes/tools.tsx index 25929fd2b..ef8fbe62e 100644 --- a/apps/host-cloudflare/web/routes/tools.tsx +++ b/packages/react/src/routes/tools.tsx @@ -1,5 +1,6 @@ import { createFileRoute } from "@tanstack/react-router"; -import { ToolsPage } from "@executor-js/react/pages/tools"; + +import { ToolsPage } from "../pages/tools"; export const Route = createFileRoute("/tools")({ component: ToolsPage, diff --git a/packages/react/tsr.config.json b/packages/react/tsr.config.json new file mode 100644 index 000000000..b32421ec7 --- /dev/null +++ b/packages/react/tsr.config.json @@ -0,0 +1,4 @@ +{ + "routesDirectory": "./src/routes", + "generatedRouteTree": "./src/routes/routeTree.gen.ts" +} diff --git a/packages/react/tsup.config.ts b/packages/react/tsup.config.ts new file mode 100644 index 000000000..1c11b59ec --- /dev/null +++ b/packages/react/tsup.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from "tsup"; + +// Only console-routes gets built: it is imported by app vite configs, which +// vite's config loader runs under Node (it externalizes bare package imports, +// and Node cannot load raw .ts). Everything else this package exports is +// browser code compiled from src by each app's vite build. Same pattern as +// @executor-js/vite-plugin. +export default defineConfig({ + entry: { + "console-routes": "src/console-routes.ts", + }, + format: ["esm"], + dts: false, + sourcemap: true, + clean: true, + external: [/^@tanstack\//], +});