From cdfbc6e613474bf8a4ea8250741daee9410fe632 Mon Sep 17 00:00:00 2001 From: Peter Wielander Date: Tue, 26 May 2026 16:47:27 +0200 Subject: [PATCH] feat(next): add /_workflow web ui during dev Adds a `/_workflow` redirect during `next dev` that boots the `@workflow/web` observability dashboard on a random port and redirects the browser to it. Co-Authored-By: Claude Opus 4.7 (1M context) --- .changeset/next-dashboard-route.md | 5 ++++ packages/next/package.json | 1 + packages/next/src/index.ts | 47 ++++++++++++++++++++++++++++++ pnpm-lock.yaml | 15 ++++------ 4 files changed, 58 insertions(+), 10 deletions(-) create mode 100644 .changeset/next-dashboard-route.md diff --git a/.changeset/next-dashboard-route.md b/.changeset/next-dashboard-route.md new file mode 100644 index 0000000000..f5ddebeb73 --- /dev/null +++ b/.changeset/next-dashboard-route.md @@ -0,0 +1,5 @@ +--- +'@workflow/next': minor +--- + +Add `/_workflow` route in dev mode that opens the workflow observability dashboard. diff --git a/packages/next/package.json b/packages/next/package.json index 345accb584..91fb42702d 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -37,6 +37,7 @@ "@workflow/builders": "workspace:*", "@workflow/core": "workspace:*", "@workflow/swc-plugin": "workspace:*", + "@workflow/web": "workspace:*", "semver": "catalog:", "watchpack": "2.5.1" }, diff --git a/packages/next/src/index.ts b/packages/next/src/index.ts index 3efa9a40a5..6c33c357bd 100644 --- a/packages/next/src/index.ts +++ b/packages/next/src/index.ts @@ -33,6 +33,26 @@ const workflowSerdeComputedPropertyPattern = const PSEUDO_EXTERNAL_PACKAGES = new Set(['server-only', 'client-only']); const warnedAutoRemovedServerExternalPackages = new Set(); +let dashboardServerPromise: Promise | undefined; + +function ensureDashboardServer(): Promise { + if (!dashboardServerPromise) { + dashboardServerPromise = (async () => { + const { startServer } = (await import('@workflow/web/server')) as { + startServer: (port?: number) => Promise; + }; + const server = await startServer(0); + const address = server.address(); + const port = typeof address === 'object' && address ? address.port : 3456; + return `http://localhost:${port}`; + })().catch((error: unknown) => { + dashboardServerPromise = undefined; + throw error; + }); + } + return dashboardServerPromise; +} + interface WorkflowPatternMatch { hasUseWorkflow: boolean; hasUseStep: boolean; @@ -629,6 +649,33 @@ export function withWorkflow( process.env.WORKFLOW_NEXT_PRIVATE_BUILT = '1'; } + if (phase === 'phase-development-server') { + const dashboardUrl = await ensureDashboardServer().catch( + (error: unknown) => { + console.error('Failed to start workflow dashboard:', error); + return undefined; + } + ); + + if (dashboardUrl) { + const existingRedirects = nextConfig.redirects; + nextConfig.redirects = async () => { + const userRedirects = existingRedirects + ? await existingRedirects() + : []; + return [ + ...userRedirects, + { + source: '/_workflow', + destination: dashboardUrl, + permanent: false, + basePath: false, + }, + ]; + }; + } + } + return nextConfig; }; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a32d75e3a8..dbbe42b603 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -754,6 +754,9 @@ importers: '@workflow/swc-plugin': specifier: workspace:* version: link:../swc-plugin-workflow + '@workflow/web': + specifier: workspace:* + version: link:../web semver: specifier: 'catalog:' version: 7.7.4 @@ -23203,14 +23206,6 @@ snapshots: optionalDependencies: vite: 7.3.2(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.3) - '@vitest/mocker@4.0.18(vite@7.3.2(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.3))': - dependencies: - '@vitest/spy': 4.0.18 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 7.3.2(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.3) - '@vitest/mocker@4.0.18(vite@7.3.2(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.3))': dependencies: '@vitest/spy': 4.0.18 @@ -32243,7 +32238,7 @@ snapshots: vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@22.19.0)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.32.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.3): dependencies: '@vitest/expect': 4.0.18 - '@vitest/mocker': 4.0.18(vite@7.3.2(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.3)) + '@vitest/mocker': 4.0.18(vite@7.3.2(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.3)) '@vitest/pretty-format': 4.0.18 '@vitest/runner': 4.0.18 '@vitest/snapshot': 4.0.18 @@ -32282,7 +32277,7 @@ snapshots: vitest@4.0.18(@opentelemetry/api@1.9.1)(@types/node@22.19.0)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.32.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.3): dependencies: '@vitest/expect': 4.0.18 - '@vitest/mocker': 4.0.18(vite@7.3.2(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.3)) + '@vitest/mocker': 4.0.18(vite@7.3.2(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.3)) '@vitest/pretty-format': 4.0.18 '@vitest/runner': 4.0.18 '@vitest/snapshot': 4.0.18