From 51b716018103d06ad6122e443a4e44e2b2fdfec5 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Tue, 18 Nov 2025 11:43:23 -0800 Subject: [PATCH 01/67] add debug logs --- packages/core/e2e/e2e.test.ts | 6 ++++++ packages/utils/src/get-port.ts | 2 ++ packages/world-local/src/config.ts | 10 +++++++--- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/core/e2e/e2e.test.ts b/packages/core/e2e/e2e.test.ts index 27cfaeba3..dbfcf3ea3 100644 --- a/packages/core/e2e/e2e.test.ts +++ b/packages/core/e2e/e2e.test.ts @@ -79,6 +79,12 @@ async function getWorkflowReturnValue(runId: string) { // NOTE: Temporarily disabling concurrent tests to avoid flakiness. // TODO: Re-enable concurrent tests after conf when we have more time to investigate. describe('e2e', () => { + test.only('addTenWorkflow', { timeout: 60_000 }, async () => { + const run = await triggerWorkflow('addTenWorkflow', []); + const returnValue = await getWorkflowReturnValue(run.runId); + expect(returnValue).toBe(133); + }); + test.each([ { workflowFile: 'workflows/99_e2e.ts', diff --git a/packages/utils/src/get-port.ts b/packages/utils/src/get-port.ts index ac225d8e4..e79d89bea 100644 --- a/packages/utils/src/get-port.ts +++ b/packages/utils/src/get-port.ts @@ -8,7 +8,9 @@ import { pidToPorts } from 'pid-port'; export async function getPort(): Promise { try { const pid = process.pid; + console.log(pid); const ports = await pidToPorts(pid); + console.log('Process ports:', ports); if (!ports || ports.size === 0) { return undefined; } diff --git a/packages/world-local/src/config.ts b/packages/world-local/src/config.ts index e36264d5e..9cb9abf43 100644 --- a/packages/world-local/src/config.ts +++ b/packages/world-local/src/config.ts @@ -28,9 +28,11 @@ export const config = once(() => { * Resolves the base URL for queue requests following the priority order: * 1. config.baseUrl (highest priority - full override from args or WORKFLOW_EMBEDDED_BASE_URL env var) * 2. config.port (explicit port override from args) - * 3. Auto-detected port via pid-port (primary approach) - * 4. PORT env var (fallback) - * 5. Fallback to 3000 + * 3. PORT env var (set by dev servers like Nitro, Next.js, Vite) + * 4. Default to 3000 (standard dev server port) + * + * Note: Port detection via pid-port is intentionally not used as it often detects + * internal service ports (HMR, metrics) instead of the HTTP server port. */ export async function resolveBaseUrl(config: Partial): Promise { if (config.baseUrl) { @@ -41,7 +43,9 @@ export async function resolveBaseUrl(config: Partial): Promise { return `http://localhost:${config.port}`; } + console.log('[world-local]: Getting port'); const detectedPort = await getPort(); + console.log('[world-local]: Detected port:', detectedPort); if (detectedPort) { return `http://localhost:${detectedPort}`; } From 90cfebbabafc57bbcd864dd58ad94ff04496f56c Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Tue, 18 Nov 2025 16:15:43 -0800 Subject: [PATCH 02/67] add polling for port test --- packages/core/e2e/e2e.test.ts | 6 ---- packages/utils/src/get-port.ts | 51 ++++++++++++++++++++++++------ packages/world-local/src/config.ts | 2 -- 3 files changed, 42 insertions(+), 17 deletions(-) diff --git a/packages/core/e2e/e2e.test.ts b/packages/core/e2e/e2e.test.ts index dbfcf3ea3..27cfaeba3 100644 --- a/packages/core/e2e/e2e.test.ts +++ b/packages/core/e2e/e2e.test.ts @@ -79,12 +79,6 @@ async function getWorkflowReturnValue(runId: string) { // NOTE: Temporarily disabling concurrent tests to avoid flakiness. // TODO: Re-enable concurrent tests after conf when we have more time to investigate. describe('e2e', () => { - test.only('addTenWorkflow', { timeout: 60_000 }, async () => { - const run = await triggerWorkflow('addTenWorkflow', []); - const returnValue = await getWorkflowReturnValue(run.runId); - expect(returnValue).toBe(133); - }); - test.each([ { workflowFile: 'workflows/99_e2e.ts', diff --git a/packages/utils/src/get-port.ts b/packages/utils/src/get-port.ts index e79d89bea..5da148dee 100644 --- a/packages/utils/src/get-port.ts +++ b/packages/utils/src/get-port.ts @@ -1,25 +1,58 @@ import { pidToPorts } from 'pid-port'; +let port: number | undefined; +const seenPorts: Set = new Set(); + /** * Gets the port number that the process is listening on. * @returns The port number that the process is listening on, or undefined if the process is not listening on any port. * NOTE: Can't move this to @workflow/utils because it's being imported into @workflow/errors for RetryableError (inside workflow runtime) */ export async function getPort(): Promise { + // Return cached successful port + if (port) { + return port; + } + try { const pid = process.pid; - console.log(pid); + const ports = await pidToPorts(pid); - console.log('Process ports:', ports); if (!ports || ports.size === 0) { return undefined; } - const smallest = Math.min(...ports); - return smallest; - } catch { - // If port detection fails (e.g., `ss` command not available in production), - // return undefined and fall back to default port - return undefined; - } + // Try each port we haven't already seen + const portArray = Array.from(ports) + .sort((a, b) => a - b) + .filter((p) => !seenPorts.has(p)); + + if (portArray.length === 0) { + // All ports have been tried and failed + return undefined; + } + + for (const testPort of portArray) { + try { + // Add timeout to prevent hanging on non-HTTP ports + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 100); + + await fetch(`http://localhost:${testPort}`, { + signal: controller.signal, + }); + + clearTimeout(timeoutId); + + // If fetch succeeds, cache and return the port + port = testPort; + return port; + } catch (error) { + seenPorts.add(testPort); + // Continue to next port + } + } + } catch {} + + return undefined; } diff --git a/packages/world-local/src/config.ts b/packages/world-local/src/config.ts index 9cb9abf43..2e801cf8a 100644 --- a/packages/world-local/src/config.ts +++ b/packages/world-local/src/config.ts @@ -43,9 +43,7 @@ export async function resolveBaseUrl(config: Partial): Promise { return `http://localhost:${config.port}`; } - console.log('[world-local]: Getting port'); const detectedPort = await getPort(); - console.log('[world-local]: Detected port:', detectedPort); if (detectedPort) { return `http://localhost:${detectedPort}`; } From 04fda691b13f6f4d62e786a4406de4b5d398310f Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Tue, 18 Nov 2025 16:58:35 -0800 Subject: [PATCH 03/67] nuxt may be hmring on ignored files/folders --- packages/nuxt/src/module.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts index d4d88ed58..b2d895c9f 100644 --- a/packages/nuxt/src/module.ts +++ b/packages/nuxt/src/module.ts @@ -30,6 +30,25 @@ const module: NuxtModule = defineNuxtModule({ nuxt.options.nitro.workflow.typescriptPlugin = options.typescriptPlugin; nuxt.options.nitro.modules.push('@workflow/nitro'); } + + // Exclude .nuxt/workflow from Vite's file watcher to prevent HMR on generated files + nuxt.options.vite ||= {}; + nuxt.options.vite.server ||= {}; + nuxt.options.vite.server.watch ||= {}; + nuxt.options.vite.server.watch.ignored ||= []; + + const ignored = nuxt.options.vite.server.watch.ignored; + if (Array.isArray(ignored)) { + ignored.push('**/.nuxt/workflow/**'); + } else if (typeof ignored === 'function') { + const originalIgnored = ignored; + nuxt.options.vite.server.watch.ignored = (file: string) => { + if (file.includes('.nuxt/workflow')) { + return true; + } + return originalIgnored(file); + }; + } }, }); From 101fd549940c9acd20f1e041a796b2db0e9ab664 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Tue, 18 Nov 2025 17:42:15 -0800 Subject: [PATCH 04/67] Revert "nuxt may be hmring on ignored files/folders" This reverts commit c57c3f0dd9e1b757825c777e12b63e050ca44227. --- packages/nuxt/src/module.ts | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts index b2d895c9f..d4d88ed58 100644 --- a/packages/nuxt/src/module.ts +++ b/packages/nuxt/src/module.ts @@ -30,25 +30,6 @@ const module: NuxtModule = defineNuxtModule({ nuxt.options.nitro.workflow.typescriptPlugin = options.typescriptPlugin; nuxt.options.nitro.modules.push('@workflow/nitro'); } - - // Exclude .nuxt/workflow from Vite's file watcher to prevent HMR on generated files - nuxt.options.vite ||= {}; - nuxt.options.vite.server ||= {}; - nuxt.options.vite.server.watch ||= {}; - nuxt.options.vite.server.watch.ignored ||= []; - - const ignored = nuxt.options.vite.server.watch.ignored; - if (Array.isArray(ignored)) { - ignored.push('**/.nuxt/workflow/**'); - } else if (typeof ignored === 'function') { - const originalIgnored = ignored; - nuxt.options.vite.server.watch.ignored = (file: string) => { - if (file.includes('.nuxt/workflow')) { - return true; - } - return originalIgnored(file); - }; - } }, }); From 16d54f6ef1b4e9f331491d8a8b9b88f77bf968ab Mon Sep 17 00:00:00 2001 From: Adrian Date: Tue, 18 Nov 2025 20:20:53 -0800 Subject: [PATCH 05/67] Update packages/utils/src/get-port.ts Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> --- packages/utils/src/get-port.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/utils/src/get-port.ts b/packages/utils/src/get-port.ts index 5da148dee..baff9060f 100644 --- a/packages/utils/src/get-port.ts +++ b/packages/utils/src/get-port.ts @@ -38,15 +38,17 @@ export async function getPort(): Promise { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 100); - await fetch(`http://localhost:${testPort}`, { - signal: controller.signal, - }); - - clearTimeout(timeoutId); - - // If fetch succeeds, cache and return the port - port = testPort; - return port; + try { + await fetch(`http://localhost:${testPort}`, { + signal: controller.signal, + }); + + // If fetch succeeds, cache and return the port + port = testPort; + return port; + } finally { + clearTimeout(timeoutId); + } } catch (error) { seenPorts.add(testPort); // Continue to next port From 1e64d5d469858b3694b8221a5f3d10fa1cd380ab Mon Sep 17 00:00:00 2001 From: Adrian Date: Wed, 19 Nov 2025 20:00:42 -0800 Subject: [PATCH 06/67] Update packages/world-local/src/config.ts Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> --- packages/world-local/src/config.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/world-local/src/config.ts b/packages/world-local/src/config.ts index 2e801cf8a..51bc8a26f 100644 --- a/packages/world-local/src/config.ts +++ b/packages/world-local/src/config.ts @@ -31,8 +31,9 @@ export const config = once(() => { * 3. PORT env var (set by dev servers like Nitro, Next.js, Vite) * 4. Default to 3000 (standard dev server port) * - * Note: Port detection via pid-port is intentionally not used as it often detects - * internal service ports (HMR, metrics) instead of the HTTP server port. + * Note: Port detection uses pid-port as a candidate source but validates each candidate + * with HTTP requests to ensure only actual HTTP server ports are returned, excluding + * internal service ports (HMR, metrics) that pid-port alone would detect. */ export async function resolveBaseUrl(config: Partial): Promise { if (config.baseUrl) { From 22d1e7c465c832832d808c53aaadbbe2bc45c13e Mon Sep 17 00:00:00 2001 From: Adrian Date: Wed, 19 Nov 2025 20:06:08 -0800 Subject: [PATCH 07/67] Update packages/utils/src/get-port.ts Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> --- packages/utils/src/get-port.ts | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/utils/src/get-port.ts b/packages/utils/src/get-port.ts index baff9060f..eea1f990c 100644 --- a/packages/utils/src/get-port.ts +++ b/packages/utils/src/get-port.ts @@ -9,9 +9,27 @@ const seenPorts: Set = new Set(); * NOTE: Can't move this to @workflow/utils because it's being imported into @workflow/errors for RetryableError (inside workflow runtime) */ export async function getPort(): Promise { - // Return cached successful port + // Validate cached port is still listening before returning it if (port) { - return port; + try { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 100); + + try { + await fetch(`http://localhost:${port}`, { + signal: controller.signal, + }); + + // Port is still valid + return port; + } finally { + clearTimeout(timeoutId); + } + } catch { + // Cached port is no longer listening, clear it and rediscover + port = undefined; + seenPorts.clear(); + } } try { From 0820c23235edfcffca71c3d02c0bf712d5da3b1a Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Wed, 19 Nov 2025 21:49:28 -0800 Subject: [PATCH 08/67] revert getPort --- packages/utils/src/get-port.ts | 69 ++++------------------------------ 1 file changed, 7 insertions(+), 62 deletions(-) diff --git a/packages/utils/src/get-port.ts b/packages/utils/src/get-port.ts index eea1f990c..ac225d8e4 100644 --- a/packages/utils/src/get-port.ts +++ b/packages/utils/src/get-port.ts @@ -1,78 +1,23 @@ import { pidToPorts } from 'pid-port'; -let port: number | undefined; -const seenPorts: Set = new Set(); - /** * Gets the port number that the process is listening on. * @returns The port number that the process is listening on, or undefined if the process is not listening on any port. * NOTE: Can't move this to @workflow/utils because it's being imported into @workflow/errors for RetryableError (inside workflow runtime) */ export async function getPort(): Promise { - // Validate cached port is still listening before returning it - if (port) { - try { - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 100); - - try { - await fetch(`http://localhost:${port}`, { - signal: controller.signal, - }); - - // Port is still valid - return port; - } finally { - clearTimeout(timeoutId); - } - } catch { - // Cached port is no longer listening, clear it and rediscover - port = undefined; - seenPorts.clear(); - } - } - try { const pid = process.pid; - const ports = await pidToPorts(pid); if (!ports || ports.size === 0) { return undefined; } - // Try each port we haven't already seen - const portArray = Array.from(ports) - .sort((a, b) => a - b) - .filter((p) => !seenPorts.has(p)); - - if (portArray.length === 0) { - // All ports have been tried and failed - return undefined; - } - - for (const testPort of portArray) { - try { - // Add timeout to prevent hanging on non-HTTP ports - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 100); - - try { - await fetch(`http://localhost:${testPort}`, { - signal: controller.signal, - }); - - // If fetch succeeds, cache and return the port - port = testPort; - return port; - } finally { - clearTimeout(timeoutId); - } - } catch (error) { - seenPorts.add(testPort); - // Continue to next port - } - } - } catch {} - - return undefined; + const smallest = Math.min(...ports); + return smallest; + } catch { + // If port detection fails (e.g., `ss` command not available in production), + // return undefined and fall back to default port + return undefined; + } } From c72742b08c260b895ca6289260479b47da9781b8 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Wed, 19 Nov 2025 22:13:06 -0800 Subject: [PATCH 09/67] test new pid to port cmd --- packages/utils/src/get-port.ts | 38 ++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/packages/utils/src/get-port.ts b/packages/utils/src/get-port.ts index ac225d8e4..5fa2f9374 100644 --- a/packages/utils/src/get-port.ts +++ b/packages/utils/src/get-port.ts @@ -1,23 +1,35 @@ -import { pidToPorts } from 'pid-port'; +// import { pidToPorts } from "pid-port"; +import { exec } from 'node:child_process'; +import { promisify } from 'node:util'; + +const execAsync = promisify(exec); /** * Gets the port number that the process is listening on. * @returns The port number that the process is listening on, or undefined if the process is not listening on any port. * NOTE: Can't move this to @workflow/utils because it's being imported into @workflow/errors for RetryableError (inside workflow runtime) */ -export async function getPort(): Promise { - try { - const pid = process.pid; - const ports = await pidToPorts(pid); - if (!ports || ports.size === 0) { - return undefined; +export async function getPort(): Promise { + const pid = process.pid; + const platform = process.platform; + + let port: number | undefined; + switch (platform) { + case 'linux': + case 'darwin': { + const result = await execAsync( + `lsof -i -P -n | grep -w ${pid} | grep LISTEN | awk '{print $9}' | sed 's/.*://' | head -n 1` + ); + port = parseInt(result.stdout.trim(), 10); + break; } + case 'win32': + throw new Error('Not implemented'); + } - const smallest = Math.min(...ports); - return smallest; - } catch { - // If port detection fails (e.g., `ss` command not available in production), - // return undefined and fall back to default port - return undefined; + if (!port) { + throw new Error('Failed to detect port'); } + + return port; } From a9228b448b57d13e8c9943a2281c1a1f719c0ba0 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Wed, 19 Nov 2025 22:22:10 -0800 Subject: [PATCH 10/67] extend sleep time on tests --- .github/workflows/tests.yml | 8 ++++---- packages/utils/src/get-port.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8aeefb8af..ca8eef028 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -188,7 +188,7 @@ jobs: run: ./scripts/resolve-symlinks.sh workbench/${{ matrix.app.name }} - name: Run E2E Tests - run: cd workbench/${{ matrix.app.name }} && pnpm dev & echo "starting tests in 10 seconds" && sleep 10 && pnpm vitest run packages/core/e2e/dev.test.ts && pnpm run test:e2e + run: cd workbench/${{ matrix.app.name }} && pnpm dev & echo "starting tests in 20 seconds" && sleep 20 && pnpm vitest run packages/core/e2e/dev.test.ts && pnpm run test:e2e env: APP_NAME: ${{ matrix.app.name }} DEPLOYMENT_URL: "http://localhost:${{ matrix.app.name == 'sveltekit' && '5173' || '3000' }}" @@ -240,7 +240,7 @@ jobs: APP_NAME: ${{ matrix.app.name }} - name: Run E2E Tests - run: cd workbench/${{ matrix.app.name }} && pnpm start & echo "starting tests in 10 seconds" && sleep 10 && pnpm run test:e2e + run: cd workbench/${{ matrix.app.name }} && pnpm start & echo "starting tests in 20 seconds" && sleep 20 && pnpm run test:e2e env: APP_NAME: ${{ matrix.app.name }} DEPLOYMENT_URL: "http://localhost:${{ matrix.app.name == 'sveltekit' && '4173' || '3000' }}" @@ -311,7 +311,7 @@ jobs: APP_NAME: ${{ matrix.app.name }} - name: Run E2E Tests - run: cd workbench/${{ matrix.app.name }} && pnpm start & echo "starting tests in 10 seconds" && sleep 10 && pnpm run test:e2e + run: cd workbench/${{ matrix.app.name }} && pnpm start & echo "starting tests in 20 seconds" && sleep 20 && pnpm run test:e2e env: APP_NAME: ${{ matrix.app.name }} DEPLOYMENT_URL: "http://localhost:${{ matrix.app.name == 'sveltekit' && '4173' || '3000' }}" @@ -354,7 +354,7 @@ jobs: run: | cd workbench/nextjs-turbopack $job = Start-Job -ScriptBlock { Set-Location $using:PWD; pnpm dev } - Start-Sleep -Seconds 15 + Start-Sleep -Seconds 20 cd ../.. pnpm vitest run packages/core/e2e/dev.test.ts pnpm run test:e2e diff --git a/packages/utils/src/get-port.ts b/packages/utils/src/get-port.ts index 5fa2f9374..d961b7821 100644 --- a/packages/utils/src/get-port.ts +++ b/packages/utils/src/get-port.ts @@ -9,7 +9,7 @@ const execAsync = promisify(exec); * @returns The port number that the process is listening on, or undefined if the process is not listening on any port. * NOTE: Can't move this to @workflow/utils because it's being imported into @workflow/errors for RetryableError (inside workflow runtime) */ -export async function getPort(): Promise { +export async function getPort(): Promise { const pid = process.pid; const platform = process.platform; From 0a89c37e299340c49be05536d8e67ec54376cbc7 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Wed, 19 Nov 2025 22:23:28 -0800 Subject: [PATCH 11/67] fix --- packages/utils/src/get-port.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/utils/src/get-port.ts b/packages/utils/src/get-port.ts index d961b7821..c886e8ec5 100644 --- a/packages/utils/src/get-port.ts +++ b/packages/utils/src/get-port.ts @@ -17,18 +17,23 @@ export async function getPort(): Promise { switch (platform) { case 'linux': case 'darwin': { - const result = await execAsync( - `lsof -i -P -n | grep -w ${pid} | grep LISTEN | awk '{print $9}' | sed 's/.*://' | head -n 1` - ); - port = parseInt(result.stdout.trim(), 10); + try { + const result = await execAsync( + `lsof -i -P -n | grep -w ${pid} | grep LISTEN | awk '{print $9}' | sed 's/.*://' | head -n 1` + ); + port = parseInt(result.stdout.trim(), 10); + } catch { + // Port detection may fail in some environments (e.g., serverless) + return undefined; + } break; } case 'win32': throw new Error('Not implemented'); } - if (!port) { - throw new Error('Failed to detect port'); + if (!port || Number.isNaN(port)) { + return undefined; } return port; From 90d3b21bbc351209569fa32a573e13980809ffd6 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Wed, 19 Nov 2025 22:35:42 -0800 Subject: [PATCH 12/67] fix race condition in tests --- .github/workflows/tests.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ca8eef028..ee26e06a6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -188,7 +188,7 @@ jobs: run: ./scripts/resolve-symlinks.sh workbench/${{ matrix.app.name }} - name: Run E2E Tests - run: cd workbench/${{ matrix.app.name }} && pnpm dev & echo "starting tests in 20 seconds" && sleep 20 && pnpm vitest run packages/core/e2e/dev.test.ts && pnpm run test:e2e + run: cd workbench/${{ matrix.app.name }} && pnpm dev & echo "starting tests in 10 seconds" && sleep 10 && pnpm vitest run packages/core/e2e/dev.test.ts && sleep 5 && pnpm run test:e2e env: APP_NAME: ${{ matrix.app.name }} DEPLOYMENT_URL: "http://localhost:${{ matrix.app.name == 'sveltekit' && '5173' || '3000' }}" @@ -240,7 +240,7 @@ jobs: APP_NAME: ${{ matrix.app.name }} - name: Run E2E Tests - run: cd workbench/${{ matrix.app.name }} && pnpm start & echo "starting tests in 20 seconds" && sleep 20 && pnpm run test:e2e + run: cd workbench/${{ matrix.app.name }} && pnpm start & echo "starting tests in 10 seconds" && sleep 10 && pnpm run test:e2e env: APP_NAME: ${{ matrix.app.name }} DEPLOYMENT_URL: "http://localhost:${{ matrix.app.name == 'sveltekit' && '4173' || '3000' }}" @@ -311,7 +311,7 @@ jobs: APP_NAME: ${{ matrix.app.name }} - name: Run E2E Tests - run: cd workbench/${{ matrix.app.name }} && pnpm start & echo "starting tests in 20 seconds" && sleep 20 && pnpm run test:e2e + run: cd workbench/${{ matrix.app.name }} && pnpm start & echo "starting tests in 10 seconds" && sleep 10 && pnpm run test:e2e env: APP_NAME: ${{ matrix.app.name }} DEPLOYMENT_URL: "http://localhost:${{ matrix.app.name == 'sveltekit' && '4173' || '3000' }}" @@ -354,9 +354,10 @@ jobs: run: | cd workbench/nextjs-turbopack $job = Start-Job -ScriptBlock { Set-Location $using:PWD; pnpm dev } - Start-Sleep -Seconds 20 + Start-Sleep -Seconds 10 cd ../.. pnpm vitest run packages/core/e2e/dev.test.ts + Start-Sleep -Seconds 5 pnpm run test:e2e Stop-Job $job shell: powershell From 28afae340acb1096aa975b0939ea37c5e950a9eb Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Thu, 20 Nov 2025 09:41:11 -0800 Subject: [PATCH 13/67] add logs --- packages/utils/src/get-port.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/utils/src/get-port.ts b/packages/utils/src/get-port.ts index c886e8ec5..fa61a2266 100644 --- a/packages/utils/src/get-port.ts +++ b/packages/utils/src/get-port.ts @@ -1,4 +1,4 @@ -// import { pidToPorts } from "pid-port"; +import { pidToPorts } from 'pid-port'; import { exec } from 'node:child_process'; import { promisify } from 'node:util'; @@ -13,6 +13,8 @@ export async function getPort(): Promise { const pid = process.pid; const platform = process.platform; + console.log(await pidToPorts(pid)); + let port: number | undefined; switch (platform) { case 'linux': @@ -22,6 +24,7 @@ export async function getPort(): Promise { `lsof -i -P -n | grep -w ${pid} | grep LISTEN | awk '{print $9}' | sed 's/.*://' | head -n 1` ); port = parseInt(result.stdout.trim(), 10); + console.log(port); } catch { // Port detection may fail in some environments (e.g., serverless) return undefined; From 440a702b244644cf3fc4d211bec41fd2f7e97a9f Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Thu, 20 Nov 2025 11:09:36 -0800 Subject: [PATCH 14/67] add fallback from pid-port --- packages/utils/src/get-port.ts | 47 +++++++++++++++++----------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/packages/utils/src/get-port.ts b/packages/utils/src/get-port.ts index fa61a2266..e9d68c27c 100644 --- a/packages/utils/src/get-port.ts +++ b/packages/utils/src/get-port.ts @@ -1,6 +1,6 @@ -import { pidToPorts } from 'pid-port'; import { exec } from 'node:child_process'; import { promisify } from 'node:util'; +import { pidToPorts } from 'pid-port'; const execAsync = promisify(exec); @@ -10,34 +10,33 @@ const execAsync = promisify(exec); * NOTE: Can't move this to @workflow/utils because it's being imported into @workflow/errors for RetryableError (inside workflow runtime) */ export async function getPort(): Promise { - const pid = process.pid; - const platform = process.platform; + try { + const ports = Array.from(await pidToPorts(process.pid)); - console.log(await pidToPorts(pid)); + // pid-port failed to detect the server port + if (ports.length === 0 || ports.length > 1) { + const platform = process.platform; - let port: number | undefined; - switch (platform) { - case 'linux': - case 'darwin': { - try { - const result = await execAsync( - `lsof -i -P -n | grep -w ${pid} | grep LISTEN | awk '{print $9}' | sed 's/.*://' | head -n 1` - ); - port = parseInt(result.stdout.trim(), 10); - console.log(port); - } catch { - // Port detection may fail in some environments (e.g., serverless) - return undefined; + // Use our fallback + switch (platform) { + case 'linux': + case 'darwin': { + const result = await execAsync( + `lsof -i -P -n | grep -w ${process.pid} | grep LISTEN | awk '{print $9}' | sed 's/.*://' | head -n 1` + ); + const port = parseInt(result.stdout.trim(), 10); + return Number.isNaN(port) ? undefined : port; + } + case 'win32': + throw new Error('Not implemented'); } - break; + + return undefined; } - case 'win32': - throw new Error('Not implemented'); - } - if (!port || Number.isNaN(port)) { + // Server port most likely to be minimum + return Math.min(...ports); + } catch { return undefined; } - - return port; } From cde4a51b9a2372f8bf69ff132665d11891c41f9a Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Thu, 20 Nov 2025 11:12:34 -0800 Subject: [PATCH 15/67] remove pid-port fallback --- packages/utils/src/get-port.ts | 40 +++++++++++++++------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/packages/utils/src/get-port.ts b/packages/utils/src/get-port.ts index e9d68c27c..2ddce6bae 100644 --- a/packages/utils/src/get-port.ts +++ b/packages/utils/src/get-port.ts @@ -1,6 +1,5 @@ import { exec } from 'node:child_process'; import { promisify } from 'node:util'; -import { pidToPorts } from 'pid-port'; const execAsync = promisify(exec); @@ -10,33 +9,28 @@ const execAsync = promisify(exec); * NOTE: Can't move this to @workflow/utils because it's being imported into @workflow/errors for RetryableError (inside workflow runtime) */ export async function getPort(): Promise { - try { - const ports = Array.from(await pidToPorts(process.pid)); + const pid = process.pid; + const platform = process.platform; - // pid-port failed to detect the server port - if (ports.length === 0 || ports.length > 1) { - const platform = process.platform; + let port: number | undefined; - // Use our fallback - switch (platform) { - case 'linux': - case 'darwin': { - const result = await execAsync( - `lsof -i -P -n | grep -w ${process.pid} | grep LISTEN | awk '{print $9}' | sed 's/.*://' | head -n 1` - ); - const port = parseInt(result.stdout.trim(), 10); - return Number.isNaN(port) ? undefined : port; - } - case 'win32': - throw new Error('Not implemented'); + try { + // Use our fallback + switch (platform) { + case 'linux': + case 'darwin': { + const result = await execAsync( + `lsof -i -P -n | grep -w ${pid} | grep LISTEN | awk '{print $9}' | sed 's/.*://' | head -n 1` + ); + port = parseInt(result.stdout.trim(), 10); + break; } - - return undefined; + case 'win32': + throw new Error('Not implemented'); } - - // Server port most likely to be minimum - return Math.min(...ports); } catch { return undefined; } + + return Number.isNaN(port) ? undefined : port; } From 3aa7f80f1c22545aca08dfe58ece1ec8f836c49c Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Thu, 20 Nov 2025 11:49:19 -0800 Subject: [PATCH 16/67] revert config ts --- packages/world-local/src/config.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/world-local/src/config.ts b/packages/world-local/src/config.ts index 51bc8a26f..e36264d5e 100644 --- a/packages/world-local/src/config.ts +++ b/packages/world-local/src/config.ts @@ -28,12 +28,9 @@ export const config = once(() => { * Resolves the base URL for queue requests following the priority order: * 1. config.baseUrl (highest priority - full override from args or WORKFLOW_EMBEDDED_BASE_URL env var) * 2. config.port (explicit port override from args) - * 3. PORT env var (set by dev servers like Nitro, Next.js, Vite) - * 4. Default to 3000 (standard dev server port) - * - * Note: Port detection uses pid-port as a candidate source but validates each candidate - * with HTTP requests to ensure only actual HTTP server ports are returned, excluding - * internal service ports (HMR, metrics) that pid-port alone would detect. + * 3. Auto-detected port via pid-port (primary approach) + * 4. PORT env var (fallback) + * 5. Fallback to 3000 */ export async function resolveBaseUrl(config: Partial): Promise { if (config.baseUrl) { From a53a84b4ab75a6bd6781dd4fe11114ee2a639397 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Thu, 20 Nov 2025 11:59:22 -0800 Subject: [PATCH 17/67] test: add getPort test --- packages/utils/src/get-port.test.ts | 157 ++++++++++++++++++---------- packages/utils/src/get-port.ts | 1 + 2 files changed, 102 insertions(+), 56 deletions(-) diff --git a/packages/utils/src/get-port.test.ts b/packages/utils/src/get-port.test.ts index fe1d94dfe..9d9c7d100 100644 --- a/packages/utils/src/get-port.test.ts +++ b/packages/utils/src/get-port.test.ts @@ -1,80 +1,125 @@ import http from 'node:http'; -import { describe, expect, it } from 'vitest'; +import type { AddressInfo } from 'node:net'; +import { afterEach, describe, expect, it } from 'vitest'; import { getPort } from './get-port'; describe('getPort', () => { - it('should return undefined or a positive number', async () => { + let servers: http.Server[] = []; + + afterEach(() => { + servers.forEach((server) => { + server.close(); + }); + servers = []; + }); + + it('should return undefined when no ports are in use', async () => { const port = await getPort(); - expect(port === undefined || typeof port === 'number').toBe(true); - if (port !== undefined) { - expect(port).toBeGreaterThan(0); - } + + expect(port).toBeUndefined(); + }); + + it('should handle servers listening on specific ports', async () => { + const server = http.createServer(); + servers.push(server); + + // Listen on a specific port instead of 0 + const specificPort = 8080; + server.listen(specificPort); + + const port = await getPort(); + + expect(port).toEqual(specificPort); }); - it('should return a port number when a server is listening', async () => { + it('should return the port number that the server is listening', async () => { const server = http.createServer(); + servers.push(server); server.listen(0); - try { - const port = await getPort(); - const address = server.address(); - - // Port detection may not work immediately in all environments (CI, Docker, etc.) - // so we just verify the function returns a valid result - if (port !== undefined) { - expect(typeof port).toBe('number'); - expect(port).toBeGreaterThan(0); - - // If we have the address, optionally verify it matches - if (address && typeof address === 'object') { - // In most cases it should match, but not required for test to pass - expect([port, undefined]).toContain(port); - } - } - } finally { - await new Promise((resolve, reject) => { - server.close((err) => (err ? reject(err) : resolve())); - }); - } + const port = await getPort(); + const addr = server.address() as AddressInfo; + + expect(typeof port).toBe('number'); + expect(port).toEqual(addr.port); }); - it('should return the smallest port when multiple servers are listening', async () => { + it('should return the first port of the server', async () => { const server1 = http.createServer(); const server2 = http.createServer(); + servers.push(server1); + servers.push(server2); server1.listen(0); server2.listen(0); + const port = await getPort(); + const addr1 = server1.address() as AddressInfo; + + expect(port).toEqual(addr1.port); + }); + + it('should handle servers listening on different interfaces', async () => { + const server1 = http.createServer(); + const server2 = http.createServer(); + servers.push(server1, server2); + + // One on localhost, one on all interfaces + server1.listen(0, '127.0.0.1'); + server2.listen(0, '0.0.0.0'); + + const port = await getPort(); + const addr1 = server1.address() as AddressInfo; + + // Should still return the first port + expect(port).toEqual(addr1.port); + }); + + it('should handle IPv6 addresses', async () => { + const server = http.createServer(); + servers.push(server); + try { + server.listen(0, '::1'); // IPv6 localhost const port = await getPort(); - const addr1 = server1.address(); - const addr2 = server2.address(); - - // Port detection may not work in all environments - if ( - port !== undefined && - addr1 && - typeof addr1 === 'object' && - addr2 && - typeof addr2 === 'object' - ) { - // Should return the smallest port - expect(port).toBeLessThanOrEqual(Math.max(addr1.port, addr2.port)); - expect(port).toBeGreaterThan(0); - } else { - // If port detection doesn't work in this environment, just pass - expect(port === undefined || typeof port === 'number').toBe(true); - } - } finally { - await Promise.all([ - new Promise((resolve, reject) => { - server1.close((err) => (err ? reject(err) : resolve())); - }), - new Promise((resolve, reject) => { - server2.close((err) => (err ? reject(err) : resolve())); - }), - ]); + const addr = server.address() as AddressInfo; + + expect(port).toEqual(addr.port); + } catch { + // Skip test if IPv6 is not available + console.log('IPv6 not available, skipping test'); } }); + + it('should handle multiple calls in sequence', async () => { + const server = http.createServer(); + servers.push(server); + + server.listen(0); + + const port1 = await getPort(); + const port2 = await getPort(); + const addr = server.address() as AddressInfo; + + // Should return the same port each time + expect(port1).toEqual(addr.port); + expect(port2).toEqual(addr.port); + }); + + it('should handle closed servers', async () => { + const server = http.createServer(); + + server.listen(0); + const addr = server.address() as AddressInfo; + const serverPort = addr.port; + + // Close the server before calling getPort + server.close(); + + const port = await getPort(); + + // Port should not be the closed server's port + expect(port).not.toEqual(serverPort); + }); }); diff --git a/packages/utils/src/get-port.ts b/packages/utils/src/get-port.ts index 2ddce6bae..5a52dacd8 100644 --- a/packages/utils/src/get-port.ts +++ b/packages/utils/src/get-port.ts @@ -29,6 +29,7 @@ export async function getPort(): Promise { throw new Error('Not implemented'); } } catch { + // Unavailable (e.g. Serverless environments) return undefined; } From 90f3c3d7ce857b46cc8b08b6f65b8e3293044703 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Thu, 20 Nov 2025 12:03:36 -0800 Subject: [PATCH 18/67] update tests for concurrent calls --- packages/utils/src/get-port.test.ts | 56 +++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/packages/utils/src/get-port.test.ts b/packages/utils/src/get-port.test.ts index 9d9c7d100..57216c935 100644 --- a/packages/utils/src/get-port.test.ts +++ b/packages/utils/src/get-port.test.ts @@ -60,6 +60,19 @@ describe('getPort', () => { expect(port).toEqual(addr1.port); }); + it('should return consistent results when called multiple times', async () => { + const server = http.createServer(); + servers.push(server); + server.listen(0); + + const port1 = await getPort(); + const port2 = await getPort(); + const port3 = await getPort(); + + expect(port1).toEqual(port2); + expect(port2).toEqual(port3); + }); + it('should handle servers listening on different interfaces', async () => { const server1 = http.createServer(); const server2 = http.createServer(); @@ -122,4 +135,47 @@ describe('getPort', () => { // Port should not be the closed server's port expect(port).not.toEqual(serverPort); }); + + it('should handle server restart on same port', async () => { + const server1 = http.createServer(); + servers.push(server1); + server1.listen(8081); + + const port1 = await getPort(); + expect(port1).toEqual(8081); + + server1.close(); + servers = servers.filter((s) => s !== server1); + + // Small delay to ensure port is released + await new Promise((resolve) => setTimeout(resolve, 100)); + + const server2 = http.createServer(); + servers.push(server2); + server2.listen(8081); + + const port2 = await getPort(); + expect(port2).toEqual(8081); + }); + + it('should handle concurrent getPort calls', async () => { + // Workflow makes lots of concurrent getPort calls + const server = http.createServer(); + servers.push(server); + server.listen(0); + + const addr = server.address() as AddressInfo; + + // Call getPort concurrently 10 times + const results = await Promise.all( + Array(10) + .fill(0) + .map(() => getPort()) + ); + + // All should return the same port without errors + results.forEach((port) => { + expect(port).toEqual(addr.port); + }); + }); }); From 6c86870be017708dd84af4abdc663c30d0a669da Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Thu, 20 Nov 2025 12:13:08 -0800 Subject: [PATCH 19/67] temp --- turbo.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/turbo.json b/turbo.json index 57068d19b..6c9490938 100644 --- a/turbo.json +++ b/turbo.json @@ -13,7 +13,8 @@ "dependsOn": ["^build"] }, "test": { - "dependsOn": ["^build"] + "dependsOn": ["^build"], + "cache": false }, "clean": { "cache": false From fbb557a75d2dd926d806fbdac0a8dced0abf36c2 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Thu, 20 Nov 2025 14:17:03 -0800 Subject: [PATCH 20/67] trigger rebuild --- packages/utils/src/get-port.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/utils/src/get-port.test.ts b/packages/utils/src/get-port.test.ts index 57216c935..e7feefbb3 100644 --- a/packages/utils/src/get-port.test.ts +++ b/packages/utils/src/get-port.test.ts @@ -24,7 +24,7 @@ describe('getPort', () => { servers.push(server); // Listen on a specific port instead of 0 - const specificPort = 8080; + const specificPort = 3000; server.listen(specificPort); const port = await getPort(); @@ -139,10 +139,10 @@ describe('getPort', () => { it('should handle server restart on same port', async () => { const server1 = http.createServer(); servers.push(server1); - server1.listen(8081); + server1.listen(3000); const port1 = await getPort(); - expect(port1).toEqual(8081); + expect(port1).toEqual(3000); server1.close(); servers = servers.filter((s) => s !== server1); @@ -152,10 +152,10 @@ describe('getPort', () => { const server2 = http.createServer(); servers.push(server2); - server2.listen(8081); + server2.listen(3000); const port2 = await getPort(); - expect(port2).toEqual(8081); + expect(port2).toEqual(3000); }); it('should handle concurrent getPort calls', async () => { From ec106fb0eed4cb1dbdeea2274ee8e5fb570f9e6d Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Thu, 20 Nov 2025 14:31:09 -0800 Subject: [PATCH 21/67] feat: add windows get port support --- packages/utils/src/get-port.ts | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/packages/utils/src/get-port.ts b/packages/utils/src/get-port.ts index 5a52dacd8..60a5b22fd 100644 --- a/packages/utils/src/get-port.ts +++ b/packages/utils/src/get-port.ts @@ -25,8 +25,31 @@ export async function getPort(): Promise { port = parseInt(result.stdout.trim(), 10); break; } - case 'win32': - throw new Error('Not implemented'); + case 'win32': { + const result = await execAsync('netstat -ano'); + const lines = result.stdout.trim().split('\n'); + + for (const line of lines) { + // Windows netstat format: TCP 0.0.0.0:8080 0.0.0.0:0 LISTENING 1234 + const parts = line.trim().split(/\s+/); + + if ( + parts.length >= 5 && + parts[3] === 'LISTENING' && + parts[4] === pid.toString() + ) { + // Extract port from the local address (e.g., "0.0.0.0:8080") + const localAddress = parts[1]; + const portMatch = localAddress.match(/:(\d+)$/); + + if (portMatch && portMatch[1]) { + port = parseInt(portMatch[1], 10); + break; + } + } + } + break; + } } } catch { // Unavailable (e.g. Serverless environments) From 27f8e5e737d450c9908400b73129087f5bf15b37 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Thu, 20 Nov 2025 14:48:31 -0800 Subject: [PATCH 22/67] fix port sorting --- packages/utils/src/get-port.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/utils/src/get-port.ts b/packages/utils/src/get-port.ts index 60a5b22fd..664c19795 100644 --- a/packages/utils/src/get-port.ts +++ b/packages/utils/src/get-port.ts @@ -20,17 +20,17 @@ export async function getPort(): Promise { case 'linux': case 'darwin': { const result = await execAsync( - `lsof -i -P -n | grep -w ${pid} | grep LISTEN | awk '{print $9}' | sed 's/.*://' | head -n 1` + `lsof -i -P -n | grep -w ${pid} | grep LISTEN | awk '{print $9}' | sed 's/.*://'` ); port = parseInt(result.stdout.trim(), 10); break; } case 'win32': { - const result = await execAsync('netstat -ano'); + const result = await execAsync(`netstat -ano`); const lines = result.stdout.trim().split('\n'); + const ports: number[] = []; for (const line of lines) { - // Windows netstat format: TCP 0.0.0.0:8080 0.0.0.0:0 LISTENING 1234 const parts = line.trim().split(/\s+/); if ( @@ -38,16 +38,18 @@ export async function getPort(): Promise { parts[3] === 'LISTENING' && parts[4] === pid.toString() ) { - // Extract port from the local address (e.g., "0.0.0.0:8080") const localAddress = parts[1]; const portMatch = localAddress.match(/:(\d+)$/); if (portMatch && portMatch[1]) { - port = parseInt(portMatch[1], 10); - break; + ports.push(parseInt(portMatch[1], 10)); } } } + + // Return the lowest port number (usually created first) + ports.sort((a, b) => a - b); + port = ports[0]; break; } } From d18a2107e011b5461f8cb895c3a943874217ec86 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Thu, 20 Nov 2025 14:57:55 -0800 Subject: [PATCH 23/67] fix parsing logic and filtering --- packages/utils/src/get-port.ts | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/utils/src/get-port.ts b/packages/utils/src/get-port.ts index 664c19795..a53fdce7e 100644 --- a/packages/utils/src/get-port.ts +++ b/packages/utils/src/get-port.ts @@ -12,7 +12,7 @@ export async function getPort(): Promise { const pid = process.pid; const platform = process.platform; - let port: number | undefined; + let ports: number[] = []; try { // Use our fallback @@ -22,13 +22,17 @@ export async function getPort(): Promise { const result = await execAsync( `lsof -i -P -n | grep -w ${pid} | grep LISTEN | awk '{print $9}' | sed 's/.*://'` ); - port = parseInt(result.stdout.trim(), 10); + ports = result.stdout + .trim() + .split('\n') + .filter((line) => line.length > 0) + .map((port) => parseInt(port, 10)) + .filter((port) => !Number.isNaN(port)); break; } case 'win32': { const result = await execAsync(`netstat -ano`); const lines = result.stdout.trim().split('\n'); - const ports: number[] = []; for (const line of lines) { const parts = line.trim().split(/\s+/); @@ -46,10 +50,6 @@ export async function getPort(): Promise { } } } - - // Return the lowest port number (usually created first) - ports.sort((a, b) => a - b); - port = ports[0]; break; } } @@ -58,5 +58,12 @@ export async function getPort(): Promise { return undefined; } - return Number.isNaN(port) ? undefined : port; + // Return the lowest port number (usually first) + ports.sort((a, b) => a - b); + + if (ports.length === 0 || Number.isNaN(ports[0])) { + return undefined; + } + + return ports[0]; } From da85ce0e62628e838194ab979db20631a460ced0 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Thu, 20 Nov 2025 15:13:31 -0800 Subject: [PATCH 24/67] fformat --- .github/workflows/tests.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ee26e06a6..88df4aab9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -354,10 +354,9 @@ jobs: run: | cd workbench/nextjs-turbopack $job = Start-Job -ScriptBlock { Set-Location $using:PWD; pnpm dev } - Start-Sleep -Seconds 10 + Start-Sleep -Seconds 15 cd ../.. pnpm vitest run packages/core/e2e/dev.test.ts - Start-Sleep -Seconds 5 pnpm run test:e2e Stop-Job $job shell: powershell From 320d63e16a1f206d16cf89feef68d380c3462d1a Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Thu, 20 Nov 2025 15:20:48 -0800 Subject: [PATCH 25/67] update ports logi --- packages/utils/src/get-port.ts | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/packages/utils/src/get-port.ts b/packages/utils/src/get-port.ts index a53fdce7e..013d3983b 100644 --- a/packages/utils/src/get-port.ts +++ b/packages/utils/src/get-port.ts @@ -12,22 +12,18 @@ export async function getPort(): Promise { const pid = process.pid; const platform = process.platform; - let ports: number[] = []; + let port: number | undefined; try { // Use our fallback switch (platform) { case 'linux': case 'darwin': { + // Grab the first port entry reported const result = await execAsync( - `lsof -i -P -n | grep -w ${pid} | grep LISTEN | awk '{print $9}' | sed 's/.*://'` + `lsof -i -P -n | grep -w ${pid} | grep LISTEN | awk '{print $9}' | sed 's/.*://' | head -n 1` ); - ports = result.stdout - .trim() - .split('\n') - .filter((line) => line.length > 0) - .map((port) => parseInt(port, 10)) - .filter((port) => !Number.isNaN(port)); + port = parseInt(result.stdout.trim(), 10); break; } case 'win32': { @@ -46,7 +42,8 @@ export async function getPort(): Promise { const portMatch = localAddress.match(/:(\d+)$/); if (portMatch && portMatch[1]) { - ports.push(parseInt(portMatch[1], 10)); + port = parseInt(portMatch[1], 10); + break; } } } @@ -58,12 +55,5 @@ export async function getPort(): Promise { return undefined; } - // Return the lowest port number (usually first) - ports.sort((a, b) => a - b); - - if (ports.length === 0 || Number.isNaN(ports[0])) { - return undefined; - } - - return ports[0]; + return Number.isNaN(port) ? undefined : port; } From 1d6920fa11d4052e8133ba5d259ac4b294151c1e Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Thu, 20 Nov 2025 15:45:38 -0800 Subject: [PATCH 26/67] revert --- turbo.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/turbo.json b/turbo.json index 6c9490938..57068d19b 100644 --- a/turbo.json +++ b/turbo.json @@ -13,8 +13,7 @@ "dependsOn": ["^build"] }, "test": { - "dependsOn": ["^build"], - "cache": false + "dependsOn": ["^build"] }, "clean": { "cache": false From 2c8e3b771118c02d686edef8a8276f020ecb02b4 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Thu, 20 Nov 2025 15:46:34 -0800 Subject: [PATCH 27/67] windows port hack --- packages/utils/src/get-port.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/utils/src/get-port.ts b/packages/utils/src/get-port.ts index 013d3983b..7ea2c2690 100644 --- a/packages/utils/src/get-port.ts +++ b/packages/utils/src/get-port.ts @@ -29,6 +29,7 @@ export async function getPort(): Promise { case 'win32': { const result = await execAsync(`netstat -ano`); const lines = result.stdout.trim().split('\n'); + const ports: number[] = []; for (const line of lines) { const parts = line.trim().split(/\s+/); @@ -42,11 +43,18 @@ export async function getPort(): Promise { const portMatch = localAddress.match(/:(\d+)$/); if (portMatch && portMatch[1]) { - port = parseInt(portMatch[1], 10); - break; + const foundPort = parseInt(portMatch[1], 10); + if (!Number.isNaN(foundPort)) { + ports.push(foundPort); + } } } } + + // Return the lowest port for consistency + if (ports.length > 0) { + port = Math.min(...ports); + } break; } } From 950720a25c44fde20dda4d0b878fb80a3ae4d979 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Thu, 20 Nov 2025 15:52:42 -0800 Subject: [PATCH 28/67] refactor: optional chaining on windows --- packages/utils/src/get-port.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/utils/src/get-port.ts b/packages/utils/src/get-port.ts index 7ea2c2690..7e12cb805 100644 --- a/packages/utils/src/get-port.ts +++ b/packages/utils/src/get-port.ts @@ -6,7 +6,6 @@ const execAsync = promisify(exec); /** * Gets the port number that the process is listening on. * @returns The port number that the process is listening on, or undefined if the process is not listening on any port. - * NOTE: Can't move this to @workflow/utils because it's being imported into @workflow/errors for RetryableError (inside workflow runtime) */ export async function getPort(): Promise { const pid = process.pid; @@ -42,7 +41,7 @@ export async function getPort(): Promise { const localAddress = parts[1]; const portMatch = localAddress.match(/:(\d+)$/); - if (portMatch && portMatch[1]) { + if (portMatch?.[1]) { const foundPort = parseInt(portMatch[1], 10); if (!Number.isNaN(foundPort)) { ports.push(foundPort); From 80369c6dc825e48d163384f2a13de4c99aa66967 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Thu, 20 Nov 2025 16:00:59 -0800 Subject: [PATCH 29/67] test: change ordering of config tests --- packages/world-local/src/config.test.ts | 36 ++++++++++++++----------- packages/world-local/src/config.ts | 20 +++++++------- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/packages/world-local/src/config.test.ts b/packages/world-local/src/config.test.ts index 6ea983dd7..ea0c41e11 100644 --- a/packages/world-local/src/config.test.ts +++ b/packages/world-local/src/config.test.ts @@ -46,36 +46,36 @@ describe('resolveBaseUrl', () => { expect(getPort).not.toHaveBeenCalled(); }); - it('should auto-detect port when neither baseUrl nor port is provided', async () => { + it('should use PORT env var when neither baseUrl nor port is provided', async () => { const { getPort } = await import('@workflow/utils/get-port'); vi.mocked(getPort).mockResolvedValue(5173); process.env.PORT = '8080'; const result = await resolveBaseUrl({}); - expect(result).toBe('http://localhost:5173'); - expect(getPort).toHaveBeenCalled(); + expect(result).toBe('http://localhost:8080'); + expect(getPort).not.toHaveBeenCalled(); }); - it('should use PORT env var when auto-detection returns undefined', async () => { + it('should use auto-detected port when PORT env var is not set', async () => { const { getPort } = await import('@workflow/utils/get-port'); - vi.mocked(getPort).mockResolvedValue(undefined); - process.env.PORT = '8080'; + vi.mocked(getPort).mockResolvedValue(5173); + delete process.env.PORT; const result = await resolveBaseUrl({}); - expect(result).toBe('http://localhost:8080'); + expect(result).toBe('http://localhost:5173'); expect(getPort).toHaveBeenCalled(); }); - it('should fallback to 3000 when all detection methods fail', async () => { + it('should return undefined when all detection methods fail', async () => { const { getPort } = await import('@workflow/utils/get-port'); vi.mocked(getPort).mockResolvedValue(undefined); delete process.env.PORT; const result = await resolveBaseUrl({}); - expect(result).toBe('http://localhost:3000'); + expect(result).toBeUndefined(); expect(getPort).toHaveBeenCalled(); }); }); @@ -155,6 +155,7 @@ describe('resolveBaseUrl', () => { it('should use auto-detected port for SvelteKit default (5173)', async () => { const { getPort } = await import('@workflow/utils/get-port'); vi.mocked(getPort).mockResolvedValue(5173); + delete process.env.PORT; const result = await resolveBaseUrl({}); @@ -164,6 +165,7 @@ describe('resolveBaseUrl', () => { it('should use auto-detected port for Vite default (5173)', async () => { const { getPort } = await import('@workflow/utils/get-port'); vi.mocked(getPort).mockResolvedValue(5173); + delete process.env.PORT; const result = await resolveBaseUrl({}); @@ -173,6 +175,7 @@ describe('resolveBaseUrl', () => { it('should use auto-detected port for Next.js default (3000)', async () => { const { getPort } = await import('@workflow/utils/get-port'); vi.mocked(getPort).mockResolvedValue(3000); + delete process.env.PORT; const result = await resolveBaseUrl({}); @@ -186,7 +189,7 @@ describe('resolveBaseUrl', () => { const result = await resolveBaseUrl({}); - expect(result).toBe('http://localhost:3000'); + expect(result).toBeUndefined(); }); }); @@ -234,7 +237,7 @@ describe('resolveBaseUrl', () => { const result = await resolveBaseUrl({}); - expect(result).toBe('http://localhost:3000'); + expect(result).toBeUndefined(); }); it('should handle undefined config', async () => { @@ -244,23 +247,26 @@ describe('resolveBaseUrl', () => { const result = await resolveBaseUrl({}); - expect(result).toBe('http://localhost:3000'); + expect(result).toBeUndefined(); }); - it('should handle config with only dataDir', async () => { + it('should handle config with only dataDir and use PORT env var', async () => { const { getPort } = await import('@workflow/utils/get-port'); vi.mocked(getPort).mockResolvedValue(5173); + process.env.PORT = '4000'; const result = await resolveBaseUrl({ dataDir: './custom-data', }); - expect(result).toBe('http://localhost:5173'); + expect(result).toBe('http://localhost:4000'); + expect(getPort).not.toHaveBeenCalled(); }); - it('should skip null port and use auto-detection', async () => { + it('should skip null port and use PORT env var or auto-detection', async () => { const { getPort } = await import('@workflow/utils/get-port'); vi.mocked(getPort).mockResolvedValue(5173); + delete process.env.PORT; const result = await resolveBaseUrl({ port: null as any, diff --git a/packages/world-local/src/config.ts b/packages/world-local/src/config.ts index e36264d5e..5c3059e55 100644 --- a/packages/world-local/src/config.ts +++ b/packages/world-local/src/config.ts @@ -28,11 +28,12 @@ export const config = once(() => { * Resolves the base URL for queue requests following the priority order: * 1. config.baseUrl (highest priority - full override from args or WORKFLOW_EMBEDDED_BASE_URL env var) * 2. config.port (explicit port override from args) - * 3. Auto-detected port via pid-port (primary approach) - * 4. PORT env var (fallback) - * 5. Fallback to 3000 + * 3. PORT env var (explicit configuration) + * 4. Auto-detected port via getPort (detect actual listening port) */ -export async function resolveBaseUrl(config: Partial): Promise { +export async function resolveBaseUrl( + config: Partial +): Promise { if (config.baseUrl) { return config.baseUrl; } @@ -41,14 +42,15 @@ export async function resolveBaseUrl(config: Partial): Promise { return `http://localhost:${config.port}`; } + if (process.env.PORT) { + return `http://localhost:${process.env.PORT}`; + } + const detectedPort = await getPort(); if (detectedPort) { return `http://localhost:${detectedPort}`; } - if (process.env.PORT) { - return `http://localhost:${process.env.PORT}`; - } - - return 'http://localhost:3000'; + // Return undefined if no base URL can be resolved + return undefined; } From 9477405bb2cc08e82b8947ac06dea7867235ba87 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Thu, 20 Nov 2025 16:04:01 -0800 Subject: [PATCH 30/67] remove wrong test --- packages/utils/src/get-port.test.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/packages/utils/src/get-port.test.ts b/packages/utils/src/get-port.test.ts index e7feefbb3..0dbad8531 100644 --- a/packages/utils/src/get-port.test.ts +++ b/packages/utils/src/get-port.test.ts @@ -73,22 +73,6 @@ describe('getPort', () => { expect(port2).toEqual(port3); }); - it('should handle servers listening on different interfaces', async () => { - const server1 = http.createServer(); - const server2 = http.createServer(); - servers.push(server1, server2); - - // One on localhost, one on all interfaces - server1.listen(0, '127.0.0.1'); - server2.listen(0, '0.0.0.0'); - - const port = await getPort(); - const addr1 = server1.address() as AddressInfo; - - // Should still return the first port - expect(port).toEqual(addr1.port); - }); - it('should handle IPv6 addresses', async () => { const server = http.createServer(); servers.push(server); From db44b667854920465bc2f2f3e509202daecf6b27 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Thu, 20 Nov 2025 17:08:46 -0800 Subject: [PATCH 31/67] simplify windows getPort impl --- packages/utils/src/get-port.ts | 46 +++++++++------------------------- 1 file changed, 12 insertions(+), 34 deletions(-) diff --git a/packages/utils/src/get-port.ts b/packages/utils/src/get-port.ts index 7e12cb805..990f975e1 100644 --- a/packages/utils/src/get-port.ts +++ b/packages/utils/src/get-port.ts @@ -8,8 +8,7 @@ const execAsync = promisify(exec); * @returns The port number that the process is listening on, or undefined if the process is not listening on any port. */ export async function getPort(): Promise { - const pid = process.pid; - const platform = process.platform; + const { pid, platform } = process; let port: number | undefined; @@ -20,47 +19,26 @@ export async function getPort(): Promise { case 'darwin': { // Grab the first port entry reported const result = await execAsync( - `lsof -i -P -n | grep -w ${pid} | grep LISTEN | awk '{print $9}' | sed 's/.*://' | head -n 1` + `lsof -a -i -P -n -p ${pid} | awk '/LISTEN/ {split($9,a,":"); print a[length(a)]; exit}'` ); port = parseInt(result.stdout.trim(), 10); break; } case 'win32': { - const result = await execAsync(`netstat -ano`); - const lines = result.stdout.trim().split('\n'); - const ports: number[] = []; - - for (const line of lines) { - const parts = line.trim().split(/\s+/); - - if ( - parts.length >= 5 && - parts[3] === 'LISTENING' && - parts[4] === pid.toString() - ) { - const localAddress = parts[1]; - const portMatch = localAddress.match(/:(\d+)$/); - - if (portMatch?.[1]) { - const foundPort = parseInt(portMatch[1], 10); - if (!Number.isNaN(foundPort)) { - ports.push(foundPort); - } - } - } - } - - // Return the lowest port for consistency - if (ports.length > 0) { - port = Math.min(...ports); - } + const result = await execAsync( + `netstat -ano | awk "/LISTENING/ && /${pid}/ {split($2,a,\":\"); print a[length(a)]; exit}"` + ); + port = parseInt(result.stdout.trim(), 10); break; } } - } catch { - // Unavailable (e.g. Serverless environments) + } catch (error) { + // In dev, it's helpful to know why detection failed + if (process.env.NODE_ENV === 'development') { + console.debug('[getPort] Detection failed:', error); + } return undefined; } - return Number.isNaN(port) ? undefined : port; + return port || undefined; } From 58435f25c3a619a11d7c4ad4ed9610aea662a9a6 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Thu, 20 Nov 2025 17:29:36 -0800 Subject: [PATCH 32/67] use execFileSync instead of execFile --- packages/utils/src/get-port.ts | 39 ++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/packages/utils/src/get-port.ts b/packages/utils/src/get-port.ts index 990f975e1..69d548c3a 100644 --- a/packages/utils/src/get-port.ts +++ b/packages/utils/src/get-port.ts @@ -1,4 +1,4 @@ -import { exec } from 'node:child_process'; +import { exec, execFileSync } from 'node:child_process'; import { promisify } from 'node:util'; const execAsync = promisify(exec); @@ -17,17 +17,44 @@ export async function getPort(): Promise { switch (platform) { case 'linux': case 'darwin': { - // Grab the first port entry reported - const result = await execAsync( - `lsof -a -i -P -n -p ${pid} | awk '/LISTEN/ {split($9,a,":"); print a[length(a)]; exit}'` + const lsofResult = execFileSync( + 'lsof', + ['-a', '-i', '-P', '-n', '-p', pid.toString()], + { + encoding: 'utf-8', + } ); + const awkResult = execFileSync( + 'awk', + ['/LISTEN/ {split($9,a,":"); print a[length(a)]; exit}'], + { + encoding: 'utf-8', + input: lsofResult, + } + ); + const result = { stdout: awkResult }; port = parseInt(result.stdout.trim(), 10); break; } case 'win32': { - const result = await execAsync( - `netstat -ano | awk "/LISTENING/ && /${pid}/ {split($2,a,\":\"); print a[length(a)]; exit}"` + const lsofResult = execFileSync( + 'netstat', + ['-a', '-n', '-o', pid.toString()], + { + encoding: 'utf-8', + } + ); + const awkResult = execFileSync( + 'awk', + [ + '/LISTENING/ && /${pid}/ {split($2,a,\":\"); print a[length(a)]; exit}', + ], + { + encoding: 'utf-8', + input: lsofResult, + } ); + const result = { stdout: awkResult }; port = parseInt(result.stdout.trim(), 10); break; } From 9105681c0e587901c18a147f8af17918cd7a2c81 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Thu, 20 Nov 2025 17:40:43 -0800 Subject: [PATCH 33/67] use execa --- packages/utils/package.json | 1 + packages/utils/src/get-port.ts | 47 +++++++++++++++------------------- pnpm-lock.yaml | 3 +++ 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/packages/utils/package.json b/packages/utils/package.json index c040cc952..c80975e5c 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -40,6 +40,7 @@ "vitest": "catalog:" }, "dependencies": { + "execa": "9.6.0", "ms": "2.1.3", "pid-port": "2.0.0" } diff --git a/packages/utils/src/get-port.ts b/packages/utils/src/get-port.ts index 69d548c3a..c8c8bf4f8 100644 --- a/packages/utils/src/get-port.ts +++ b/packages/utils/src/get-port.ts @@ -1,7 +1,4 @@ -import { exec, execFileSync } from 'node:child_process'; -import { promisify } from 'node:util'; - -const execAsync = promisify(exec); +import { execa } from 'execa'; /** * Gets the port number that the process is listening on. @@ -17,44 +14,42 @@ export async function getPort(): Promise { switch (platform) { case 'linux': case 'darwin': { - const lsofResult = execFileSync( - 'lsof', - ['-a', '-i', '-P', '-n', '-p', pid.toString()], - { - encoding: 'utf-8', - } - ); - const awkResult = execFileSync( + const lsofResult = await execa('lsof', [ + '-a', + '-i', + '-P', + '-n', + '-p', + pid.toString(), + ]); + const awkResult = await execa( 'awk', ['/LISTEN/ {split($9,a,":"); print a[length(a)]; exit}'], { - encoding: 'utf-8', - input: lsofResult, + input: lsofResult.stdout, } ); - const result = { stdout: awkResult }; + const result = { stdout: awkResult.stdout }; port = parseInt(result.stdout.trim(), 10); break; } case 'win32': { - const lsofResult = execFileSync( - 'netstat', - ['-a', '-n', '-o', pid.toString()], - { - encoding: 'utf-8', - } - ); - const awkResult = execFileSync( + const lsofResult = await execa('netstat', [ + '-a', + '-n', + '-o', + pid.toString(), + ]); + const awkResult = await execa( 'awk', [ '/LISTENING/ && /${pid}/ {split($2,a,\":\"); print a[length(a)]; exit}', ], { - encoding: 'utf-8', - input: lsofResult, + input: lsofResult.stdout, } ); - const result = { stdout: awkResult }; + const result = { stdout: awkResult.stdout }; port = parseInt(result.stdout.trim(), 10); break; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0573c33db..040d639e2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -707,6 +707,9 @@ importers: packages/utils: dependencies: + execa: + specifier: 9.6.0 + version: 9.6.0 ms: specifier: 2.1.3 version: 2.1.3 From 3f8aa906295c1e834a1346565f6aa7c0926de4a5 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Fri, 21 Nov 2025 13:46:37 -0800 Subject: [PATCH 34/67] disable test cache --- turbo.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/turbo.json b/turbo.json index 57068d19b..6c9490938 100644 --- a/turbo.json +++ b/turbo.json @@ -13,7 +13,8 @@ "dependsOn": ["^build"] }, "test": { - "dependsOn": ["^build"] + "dependsOn": ["^build"], + "cache": false }, "clean": { "cache": false From 4e2f574aa0d10fc1c6b8599dcd2b8be879179ded Mon Sep 17 00:00:00 2001 From: Adrian Date: Fri, 21 Nov 2025 13:41:35 -0800 Subject: [PATCH 35/67] Update packages/utils/src/get-port.ts Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> --- packages/utils/src/get-port.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/utils/src/get-port.ts b/packages/utils/src/get-port.ts index c8c8bf4f8..26d9aeb78 100644 --- a/packages/utils/src/get-port.ts +++ b/packages/utils/src/get-port.ts @@ -43,7 +43,9 @@ export async function getPort(): Promise { const awkResult = await execa( 'awk', [ - '/LISTENING/ && /${pid}/ {split($2,a,\":\"); print a[length(a)]; exit}', + '-v', + `pid=${pid}`, + '/LISTENING/ && $NF == pid {split($2,a,\":\"); print a[length(a)]; exit}', ], { input: lsofResult.stdout, From 5955328ff3129760550ecfa286f1df0fc1fa11f6 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Fri, 21 Nov 2025 13:56:51 -0800 Subject: [PATCH 36/67] update --- packages/utils/src/get-port.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/utils/src/get-port.ts b/packages/utils/src/get-port.ts index 26d9aeb78..f7d757832 100644 --- a/packages/utils/src/get-port.ts +++ b/packages/utils/src/get-port.ts @@ -10,7 +10,6 @@ export async function getPort(): Promise { let port: number | undefined; try { - // Use our fallback switch (platform) { case 'linux': case 'darwin': { @@ -45,7 +44,7 @@ export async function getPort(): Promise { [ '-v', `pid=${pid}`, - '/LISTENING/ && $NF == pid {split($2,a,\":\"); print a[length(a)]; exit}', + '/LISTENING/ && $NF == pid {split($2,a,":"); print a[length(a)]; exit}', ], { input: lsofResult.stdout, From e8cd7d15b74cafb15dd77b12a0bfe6fb7cecd25f Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Fri, 21 Nov 2025 13:59:50 -0800 Subject: [PATCH 37/67] debug --- packages/nitro/src/index.ts | 16 +++++++++++++++- packages/nitro/src/vite.ts | 24 ++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/packages/nitro/src/index.ts b/packages/nitro/src/index.ts index 359b23e2e..7691bc441 100644 --- a/packages/nitro/src/index.ts +++ b/packages/nitro/src/index.ts @@ -57,7 +57,14 @@ export default { // Allows for HMR if (nitro.options.dev) { nitro.hooks.hook('dev:reload', async () => { + const startTime = Date.now(); + console.log( + '[workflow:nitro] dev:reload hook triggered, starting rebuild...' + ); await builder.build(); + console.log( + `[workflow:nitro] dev:reload hook completed in ${Date.now() - startTime}ms` + ); }); } @@ -83,6 +90,7 @@ export default { } satisfies NitroModule; function addVirtualHandler(nitro: Nitro, route: string, buildPath: string) { + console.log('ADDING VIRTUAL HANDLER'); nitro.options.handlers.push({ route, handler: `#${buildPath}`, @@ -101,9 +109,15 @@ function addVirtualHandler(nitro: Nitro, route: string, buildPath: string) { import { POST } from "${join(nitro.options.buildDir, buildPath)}"; export default async ({ req }) => { try { + console.log('[workflow:nitro] Virtual handler called for route: ${route}'); return await POST(req); } catch (error) { - console.error('Handler error:', error); + console.error('[workflow:nitro] Handler error for route ${route}:', error); + console.error('[workflow:nitro] Error details:', { + message: error.message, + code: error.code, + stack: error.stack?.split('\\n').slice(0, 3).join('\\n') + }); return new Response('Internal Server Error', { status: 500 }); } }; diff --git a/packages/nitro/src/vite.ts b/packages/nitro/src/vite.ts index abdc7969b..b2d8f10bc 100644 --- a/packages/nitro/src/vite.ts +++ b/packages/nitro/src/vite.ts @@ -75,11 +75,20 @@ export function workflow(options?: ModuleOptions): Plugin[] { content = await read(); } catch { // File might have been deleted - trigger rebuild to update generated routes - console.log('Workflow file deleted, rebuilding...'); + const deleteStartTime = Date.now(); + console.log( + '[workflow:nitro:vite] Workflow file deleted, starting rebuild...' + ); if (builder) { await builder.build(); + console.log( + `[workflow:nitro:vite] Build completed in ${Date.now() - deleteStartTime}ms` + ); } // NOTE: Might be too aggressive + console.log( + '[workflow:nitro:vite] Sending full-reload signal after file deletion' + ); server.ws.send({ type: 'full-reload', path: '*', @@ -99,10 +108,21 @@ export function workflow(options?: ModuleOptions): Plugin[] { // Trigger full reload - this will cause Nitro's dev:reload hook to fire, // which will rebuild workflows and update routes - console.log('Workflow file changed, rebuilding...'); + const startTime = Date.now(); + console.log( + '[workflow:nitro:vite] Workflow file changed, starting rebuild...' + ); if (builder) { await builder.build(); + console.log( + `[workflow:nitro:vite] Build completed in ${Date.now() - startTime}ms` + ); + } else { + console.warn('[workflow:nitro:vite] WARNING: No builder available!'); } + console.log( + '[workflow:nitro:vite] Sending full-reload signal to Nitro dev server' + ); server.ws.send({ type: 'full-reload', path: '*', From e4d067ff5496948dd00036f98e3e113c0671c784 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Fri, 21 Nov 2025 14:11:53 -0800 Subject: [PATCH 38/67] windows cmd --- packages/utils/src/get-port.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/utils/src/get-port.ts b/packages/utils/src/get-port.ts index f7d757832..7484c7f78 100644 --- a/packages/utils/src/get-port.ts +++ b/packages/utils/src/get-port.ts @@ -42,9 +42,8 @@ export async function getPort(): Promise { const awkResult = await execa( 'awk', [ - '-v', `pid=${pid}`, - '/LISTENING/ && $NF == pid {split($2,a,":"); print a[length(a)]; exit}', + '/LISTENING/ && $NF == pid {split($2,a,\":\"); print a[length(a)]; exit}', ], { input: lsofResult.stdout, From 78506cbd0dae6e21085e9bed0a4fb590cb1c6dbb Mon Sep 17 00:00:00 2001 From: Adrian Date: Fri, 21 Nov 2025 14:31:42 -0800 Subject: [PATCH 39/67] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/utils/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/utils/package.json b/packages/utils/package.json index c80975e5c..fdb23ab77 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -41,7 +41,6 @@ }, "dependencies": { "execa": "9.6.0", - "ms": "2.1.3", - "pid-port": "2.0.0" + "ms": "2.1.3" } } From eb35c3b8a28876d4886c18380fda87ef2fd88b60 Mon Sep 17 00:00:00 2001 From: Adrian Date: Fri, 21 Nov 2025 14:32:23 -0800 Subject: [PATCH 40/67] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/utils/src/get-port.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/utils/src/get-port.ts b/packages/utils/src/get-port.ts index 7484c7f78..d5f255f33 100644 --- a/packages/utils/src/get-port.ts +++ b/packages/utils/src/get-port.ts @@ -62,5 +62,5 @@ export async function getPort(): Promise { return undefined; } - return port || undefined; + return Number.isNaN(port) ? undefined : port; } From 0a2376ffa6a30a371d13856b1b1ba47500791985 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Fri, 21 Nov 2025 14:37:35 -0800 Subject: [PATCH 41/67] lockfile --- pnpm-lock.yaml | 43 ++++++++++++------------------------------- 1 file changed, 12 insertions(+), 31 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 040d639e2..78e318255 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -628,7 +628,7 @@ importers: devDependencies: '@nuxt/module-builder': specifier: 1.0.2 - version: 1.0.2(@nuxt/cli@3.29.3(magicast@0.3.5))(@vue/compiler-core@3.5.22)(esbuild@0.25.11)(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3)) + version: 1.0.2(@nuxt/cli@3.29.3(magicast@0.3.5))(@vue/compiler-core@3.5.22)(esbuild@0.25.12)(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3)) '@nuxt/schema': specifier: 4.2.0 version: 4.2.0 @@ -713,9 +713,6 @@ importers: ms: specifier: 2.1.3 version: 2.1.3 - pid-port: - specifier: 2.0.0 - version: 2.0.0 devDependencies: '@types/ms': specifier: 2.1.0 @@ -10372,10 +10369,6 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} - pid-port@2.0.0: - resolution: {integrity: sha512-EDmfRxLl6lkhPjDI+19l5pkII89xVsiCP3aGjS808f7M16DyCKSXEWthD/hjyDLn5I4gKqTVw7hSgdvdXRJDTw==} - engines: {node: '>=20'} - pidtree@0.6.0: resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} engines: {node: '>=0.10'} @@ -14590,7 +14583,7 @@ snapshots: transitivePeerDependencies: - magicast - '@nuxt/module-builder@1.0.2(@nuxt/cli@3.29.3(magicast@0.3.5))(@vue/compiler-core@3.5.22)(esbuild@0.25.11)(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3))': + '@nuxt/module-builder@1.0.2(@nuxt/cli@3.29.3(magicast@0.3.5))(@vue/compiler-core@3.5.22)(esbuild@0.25.12)(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3))': dependencies: '@nuxt/cli': 3.29.3(magicast@0.3.5) citty: 0.1.6 @@ -14598,14 +14591,14 @@ snapshots: defu: 6.1.4 jiti: 2.6.1 magic-regexp: 0.10.0 - mkdist: 2.4.1(typescript@5.9.3)(vue-sfc-transformer@0.1.17(@vue/compiler-core@3.5.22)(esbuild@0.25.11)(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3)) + mkdist: 2.4.1(typescript@5.9.3)(vue-sfc-transformer@0.1.17(@vue/compiler-core@3.5.22)(esbuild@0.25.12)(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3)) mlly: 1.8.0 pathe: 2.0.3 pkg-types: 2.3.0 tsconfck: 3.1.6(typescript@5.9.3) typescript: 5.9.3 - unbuild: 3.6.1(typescript@5.9.3)(vue-sfc-transformer@0.1.17(@vue/compiler-core@3.5.22)(esbuild@0.25.11)(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3)) - vue-sfc-transformer: 0.1.17(@vue/compiler-core@3.5.22)(esbuild@0.25.11)(vue@3.5.22(typescript@5.9.3)) + unbuild: 3.6.1(typescript@5.9.3)(vue-sfc-transformer@0.1.17(@vue/compiler-core@3.5.22)(esbuild@0.25.12)(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3)) + vue-sfc-transformer: 0.1.17(@vue/compiler-core@3.5.22)(esbuild@0.25.12)(vue@3.5.22(typescript@5.9.3)) transitivePeerDependencies: - '@vue/compiler-core' - esbuild @@ -18028,14 +18021,6 @@ snapshots: chai: 5.2.1 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))': - dependencies: - '@vitest/spy': 3.2.4 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) - '@vitest/mocker@3.2.4(vite@7.1.12(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))': dependencies: '@vitest/spy': 3.2.4 @@ -21798,7 +21783,7 @@ snapshots: mkdirp@3.0.1: {} - mkdist@2.4.1(typescript@5.9.3)(vue-sfc-transformer@0.1.17(@vue/compiler-core@3.5.22)(esbuild@0.25.11)(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3)): + mkdist@2.4.1(typescript@5.9.3)(vue-sfc-transformer@0.1.17(@vue/compiler-core@3.5.22)(esbuild@0.25.12)(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3)): dependencies: autoprefixer: 10.4.21(postcss@8.5.6) citty: 0.1.6 @@ -21816,7 +21801,7 @@ snapshots: optionalDependencies: typescript: 5.9.3 vue: 3.5.22(typescript@5.9.3) - vue-sfc-transformer: 0.1.17(@vue/compiler-core@3.5.22)(esbuild@0.25.11)(vue@3.5.22(typescript@5.9.3)) + vue-sfc-transformer: 0.1.17(@vue/compiler-core@3.5.22)(esbuild@0.25.12)(vue@3.5.22(typescript@5.9.3)) mlly@1.8.0: dependencies: @@ -23110,10 +23095,6 @@ snapshots: picomatch@4.0.3: {} - pid-port@2.0.0: - dependencies: - execa: 9.6.0 - pidtree@0.6.0: {} pify@4.0.1: {} @@ -24813,7 +24794,7 @@ snapshots: ultrahtml@1.6.0: {} - unbuild@3.6.1(typescript@5.9.3)(vue-sfc-transformer@0.1.17(@vue/compiler-core@3.5.22)(esbuild@0.25.11)(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3)): + unbuild@3.6.1(typescript@5.9.3)(vue-sfc-transformer@0.1.17(@vue/compiler-core@3.5.22)(esbuild@0.25.12)(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3)): dependencies: '@rollup/plugin-alias': 5.1.1(rollup@4.53.2) '@rollup/plugin-commonjs': 28.0.9(rollup@4.53.2) @@ -24829,7 +24810,7 @@ snapshots: hookable: 5.5.3 jiti: 2.6.1 magic-string: 0.30.21 - mkdist: 2.4.1(typescript@5.9.3)(vue-sfc-transformer@0.1.17(@vue/compiler-core@3.5.22)(esbuild@0.25.11)(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3)) + mkdist: 2.4.1(typescript@5.9.3)(vue-sfc-transformer@0.1.17(@vue/compiler-core@3.5.22)(esbuild@0.25.12)(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3)) mlly: 1.8.0 pathe: 2.0.3 pkg-types: 2.3.0 @@ -25381,7 +25362,7 @@ snapshots: dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) + '@vitest/mocker': 3.2.4(vite@7.1.12(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -25491,11 +25472,11 @@ snapshots: '@vue/devtools-api': 6.6.4 vue: 3.5.22(typescript@5.9.3) - vue-sfc-transformer@0.1.17(@vue/compiler-core@3.5.22)(esbuild@0.25.11)(vue@3.5.22(typescript@5.9.3)): + vue-sfc-transformer@0.1.17(@vue/compiler-core@3.5.22)(esbuild@0.25.12)(vue@3.5.22(typescript@5.9.3)): dependencies: '@babel/parser': 7.28.5 '@vue/compiler-core': 3.5.22 - esbuild: 0.25.11 + esbuild: 0.25.12 vue: 3.5.22(typescript@5.9.3) vue@3.5.22(typescript@5.9.3): From 0412a8c4a22045a9798ded0895ba6a36227aa7c8 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Fri, 21 Nov 2025 16:14:12 -0800 Subject: [PATCH 42/67] fix: windows port detection --- packages/utils/src/get-port.ts | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/utils/src/get-port.ts b/packages/utils/src/get-port.ts index d5f255f33..f4ccf8a3f 100644 --- a/packages/utils/src/get-port.ts +++ b/packages/utils/src/get-port.ts @@ -32,25 +32,27 @@ export async function getPort(): Promise { port = parseInt(result.stdout.trim(), 10); break; } + case 'win32': { - const lsofResult = await execa('netstat', [ - '-a', - '-n', - '-o', - pid.toString(), + // Use cmd to run the piped command + const result = await execa('cmd', [ + '/c', + `netstat -ano | findstr ${pid} | findstr LISTENING`, ]); - const awkResult = await execa( - 'awk', - [ - `pid=${pid}`, - '/LISTENING/ && $NF == pid {split($2,a,\":\"); print a[length(a)]; exit}', - ], - { - input: lsofResult.stdout, + + const stdout = result.stdout.trim(); + + if (stdout) { + const lines = stdout.split('\n'); + for (const line of lines) { + // Extract port from the local address column + const match = line.trim().match(/^\s*TCP\s+[\d.:]+:(\d+)\s+/); + if (match) { + port = parseInt(match[1], 10); + break; + } } - ); - const result = { stdout: awkResult.stdout }; - port = parseInt(result.stdout.trim(), 10); + } break; } } From ec2162fd25397519fd58997c4561c4bc4e539a91 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Fri, 21 Nov 2025 19:50:36 -0800 Subject: [PATCH 43/67] simplify stuff --- packages/utils/src/get-port.ts | 3 +- packages/world-local/src/config.test.ts | 42 +++++++++++++++---------- packages/world-local/src/config.ts | 7 ++--- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/packages/utils/src/get-port.ts b/packages/utils/src/get-port.ts index f4ccf8a3f..96eb40226 100644 --- a/packages/utils/src/get-port.ts +++ b/packages/utils/src/get-port.ts @@ -28,8 +28,7 @@ export async function getPort(): Promise { input: lsofResult.stdout, } ); - const result = { stdout: awkResult.stdout }; - port = parseInt(result.stdout.trim(), 10); + port = parseInt(awkResult.stdout.trim(), 10); break; } diff --git a/packages/world-local/src/config.test.ts b/packages/world-local/src/config.test.ts index ea0c41e11..c3761ebe6 100644 --- a/packages/world-local/src/config.test.ts +++ b/packages/world-local/src/config.test.ts @@ -68,14 +68,14 @@ describe('resolveBaseUrl', () => { expect(getPort).toHaveBeenCalled(); }); - it('should return undefined when all detection methods fail', async () => { + it('should throw error when all detection methods fail', async () => { const { getPort } = await import('@workflow/utils/get-port'); vi.mocked(getPort).mockResolvedValue(undefined); delete process.env.PORT; - const result = await resolveBaseUrl({}); - - expect(result).toBeUndefined(); + await expect(resolveBaseUrl({})).rejects.toThrow( + 'Unable to resolve base URL for workflow queue.' + ); expect(getPort).toHaveBeenCalled(); }); }); @@ -182,14 +182,14 @@ describe('resolveBaseUrl', () => { expect(result).toBe('http://localhost:3000'); }); - it('should handle auto-detection failure gracefully', async () => { + it('should throw error when auto-detection fails', async () => { const { getPort } = await import('@workflow/utils/get-port'); vi.mocked(getPort).mockResolvedValue(undefined); delete process.env.PORT; - const result = await resolveBaseUrl({}); - - expect(result).toBeUndefined(); + await expect(resolveBaseUrl({})).rejects.toThrow( + 'Unable to resolve base URL for workflow queue.' + ); }); }); @@ -230,24 +230,24 @@ describe('resolveBaseUrl', () => { }); describe('edge cases', () => { - it('should handle empty config object', async () => { + it('should throw error with empty config object when no port is detected', async () => { const { getPort } = await import('@workflow/utils/get-port'); vi.mocked(getPort).mockResolvedValue(undefined); delete process.env.PORT; - const result = await resolveBaseUrl({}); - - expect(result).toBeUndefined(); + await expect(resolveBaseUrl({})).rejects.toThrow( + 'Unable to resolve base URL for workflow queue.' + ); }); - it('should handle undefined config', async () => { + it('should throw error when all resolution methods fail', async () => { const { getPort } = await import('@workflow/utils/get-port'); vi.mocked(getPort).mockResolvedValue(undefined); delete process.env.PORT; - const result = await resolveBaseUrl({}); - - expect(result).toBeUndefined(); + await expect(resolveBaseUrl({})).rejects.toThrow( + 'Unable to resolve base URL for workflow queue.' + ); }); it('should handle config with only dataDir and use PORT env var', async () => { @@ -275,5 +275,15 @@ describe('resolveBaseUrl', () => { expect(result).toBe('http://localhost:5173'); expect(getPort).toHaveBeenCalled(); }); + + it('should provide helpful error message when no URL can be resolved', async () => { + const { getPort } = await import('@workflow/utils/get-port'); + vi.mocked(getPort).mockResolvedValue(undefined); + delete process.env.PORT; + + await expect(resolveBaseUrl({})).rejects.toThrow( + 'Unable to resolve base URL for workflow queue.' + ); + }); }); }); diff --git a/packages/world-local/src/config.ts b/packages/world-local/src/config.ts index 5c3059e55..732843f1e 100644 --- a/packages/world-local/src/config.ts +++ b/packages/world-local/src/config.ts @@ -31,9 +31,7 @@ export const config = once(() => { * 3. PORT env var (explicit configuration) * 4. Auto-detected port via getPort (detect actual listening port) */ -export async function resolveBaseUrl( - config: Partial -): Promise { +export async function resolveBaseUrl(config: Partial): Promise { if (config.baseUrl) { return config.baseUrl; } @@ -51,6 +49,5 @@ export async function resolveBaseUrl( return `http://localhost:${detectedPort}`; } - // Return undefined if no base URL can be resolved - return undefined; + throw new Error('Unable to resolve base URL for workflow queue.'); } From 37c448179c984a6e244c842d90ca9b90179e6b12 Mon Sep 17 00:00:00 2001 From: Adrian Date: Fri, 21 Nov 2025 19:28:16 -0800 Subject: [PATCH 44/67] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/tests.yml | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 88df4aab9..31292ff47 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -188,7 +188,26 @@ jobs: run: ./scripts/resolve-symlinks.sh workbench/${{ matrix.app.name }} - name: Run E2E Tests - run: cd workbench/${{ matrix.app.name }} && pnpm dev & echo "starting tests in 10 seconds" && sleep 10 && pnpm vitest run packages/core/e2e/dev.test.ts && sleep 5 && pnpm run test:e2e + run: | + cd workbench/${{ matrix.app.name }} + pnpm dev & + # Wait for the dev server to be ready (poll the appropriate port) + echo "Waiting for dev server to be ready..." + for i in {1..60}; do + if [ "${{ matrix.app.name }}" = "sveltekit" ]; then + curl -sf http://localhost:5173 > /dev/null && break + else + curl -sf http://localhost:3000 > /dev/null && break + fi + sleep 1 + done + if [ $i -eq 60 ]; then + echo "Dev server did not become ready in time" >&2 + exit 1 + fi + echo "Dev server is ready, running tests..." + pnpm vitest run packages/core/e2e/dev.test.ts + pnpm run test:e2e env: APP_NAME: ${{ matrix.app.name }} DEPLOYMENT_URL: "http://localhost:${{ matrix.app.name == 'sveltekit' && '5173' || '3000' }}" From 6a43f2c8732dd38bee7717008403e3e376798585 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Fri, 21 Nov 2025 20:03:15 -0800 Subject: [PATCH 45/67] test: revert gh copilot --- .github/workflows/tests.yml | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 31292ff47..ed961a354 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -188,26 +188,7 @@ jobs: run: ./scripts/resolve-symlinks.sh workbench/${{ matrix.app.name }} - name: Run E2E Tests - run: | - cd workbench/${{ matrix.app.name }} - pnpm dev & - # Wait for the dev server to be ready (poll the appropriate port) - echo "Waiting for dev server to be ready..." - for i in {1..60}; do - if [ "${{ matrix.app.name }}" = "sveltekit" ]; then - curl -sf http://localhost:5173 > /dev/null && break - else - curl -sf http://localhost:3000 > /dev/null && break - fi - sleep 1 - done - if [ $i -eq 60 ]; then - echo "Dev server did not become ready in time" >&2 - exit 1 - fi - echo "Dev server is ready, running tests..." - pnpm vitest run packages/core/e2e/dev.test.ts - pnpm run test:e2e + run: cd workbench/${{ matrix.app.name }} && pnpm dev & echo "starting tests in 10 seconds" && sleep 10 && pnpm vitest run packages/core/e2e/dev.test.ts && sleep 5 && pnpm run test:e2etest:e2e env: APP_NAME: ${{ matrix.app.name }} DEPLOYMENT_URL: "http://localhost:${{ matrix.app.name == 'sveltekit' && '5173' || '3000' }}" From 32850cd3194ea30b63c4b60b2c19f1b594cd8535 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Fri, 21 Nov 2025 20:15:10 -0800 Subject: [PATCH 46/67] fix --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ed961a354..88df4aab9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -188,7 +188,7 @@ jobs: run: ./scripts/resolve-symlinks.sh workbench/${{ matrix.app.name }} - name: Run E2E Tests - run: cd workbench/${{ matrix.app.name }} && pnpm dev & echo "starting tests in 10 seconds" && sleep 10 && pnpm vitest run packages/core/e2e/dev.test.ts && sleep 5 && pnpm run test:e2etest:e2e + run: cd workbench/${{ matrix.app.name }} && pnpm dev & echo "starting tests in 10 seconds" && sleep 10 && pnpm vitest run packages/core/e2e/dev.test.ts && sleep 5 && pnpm run test:e2e env: APP_NAME: ${{ matrix.app.name }} DEPLOYMENT_URL: "http://localhost:${{ matrix.app.name == 'sveltekit' && '5173' || '3000' }}" From 1fc7f7348d3196bcdeb20fa8f46cc07986ddbeb9 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Fri, 21 Nov 2025 20:33:38 -0800 Subject: [PATCH 47/67] increase sleep --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 88df4aab9..7bd31d34f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -188,7 +188,7 @@ jobs: run: ./scripts/resolve-symlinks.sh workbench/${{ matrix.app.name }} - name: Run E2E Tests - run: cd workbench/${{ matrix.app.name }} && pnpm dev & echo "starting tests in 10 seconds" && sleep 10 && pnpm vitest run packages/core/e2e/dev.test.ts && sleep 5 && pnpm run test:e2e + run: cd workbench/${{ matrix.app.name }} && pnpm dev & echo "starting tests in 10 seconds" && sleep 10 && pnpm vitest run packages/core/e2e/dev.test.ts && sleep 10 && pnpm run test:e2e env: APP_NAME: ${{ matrix.app.name }} DEPLOYMENT_URL: "http://localhost:${{ matrix.app.name == 'sveltekit' && '5173' || '3000' }}" From a492a2e4600b3159e3f5443db0eea8968bc791da Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Sun, 23 Nov 2025 16:56:55 -0800 Subject: [PATCH 48/67] revert logs --- packages/nitro/src/index.ts | 14 -------------- packages/nitro/src/vite.ts | 20 -------------------- 2 files changed, 34 deletions(-) diff --git a/packages/nitro/src/index.ts b/packages/nitro/src/index.ts index 7691bc441..ab995d51c 100644 --- a/packages/nitro/src/index.ts +++ b/packages/nitro/src/index.ts @@ -58,13 +58,7 @@ export default { if (nitro.options.dev) { nitro.hooks.hook('dev:reload', async () => { const startTime = Date.now(); - console.log( - '[workflow:nitro] dev:reload hook triggered, starting rebuild...' - ); await builder.build(); - console.log( - `[workflow:nitro] dev:reload hook completed in ${Date.now() - startTime}ms` - ); }); } @@ -90,7 +84,6 @@ export default { } satisfies NitroModule; function addVirtualHandler(nitro: Nitro, route: string, buildPath: string) { - console.log('ADDING VIRTUAL HANDLER'); nitro.options.handlers.push({ route, handler: `#${buildPath}`, @@ -109,15 +102,8 @@ function addVirtualHandler(nitro: Nitro, route: string, buildPath: string) { import { POST } from "${join(nitro.options.buildDir, buildPath)}"; export default async ({ req }) => { try { - console.log('[workflow:nitro] Virtual handler called for route: ${route}'); return await POST(req); } catch (error) { - console.error('[workflow:nitro] Handler error for route ${route}:', error); - console.error('[workflow:nitro] Error details:', { - message: error.message, - code: error.code, - stack: error.stack?.split('\\n').slice(0, 3).join('\\n') - }); return new Response('Internal Server Error', { status: 500 }); } }; diff --git a/packages/nitro/src/vite.ts b/packages/nitro/src/vite.ts index b2d8f10bc..76f5a14a6 100644 --- a/packages/nitro/src/vite.ts +++ b/packages/nitro/src/vite.ts @@ -76,19 +76,10 @@ export function workflow(options?: ModuleOptions): Plugin[] { } catch { // File might have been deleted - trigger rebuild to update generated routes const deleteStartTime = Date.now(); - console.log( - '[workflow:nitro:vite] Workflow file deleted, starting rebuild...' - ); if (builder) { await builder.build(); - console.log( - `[workflow:nitro:vite] Build completed in ${Date.now() - deleteStartTime}ms` - ); } // NOTE: Might be too aggressive - console.log( - '[workflow:nitro:vite] Sending full-reload signal after file deletion' - ); server.ws.send({ type: 'full-reload', path: '*', @@ -109,20 +100,9 @@ export function workflow(options?: ModuleOptions): Plugin[] { // Trigger full reload - this will cause Nitro's dev:reload hook to fire, // which will rebuild workflows and update routes const startTime = Date.now(); - console.log( - '[workflow:nitro:vite] Workflow file changed, starting rebuild...' - ); if (builder) { await builder.build(); - console.log( - `[workflow:nitro:vite] Build completed in ${Date.now() - startTime}ms` - ); - } else { - console.warn('[workflow:nitro:vite] WARNING: No builder available!'); } - console.log( - '[workflow:nitro:vite] Sending full-reload signal to Nitro dev server' - ); server.ws.send({ type: 'full-reload', path: '*', From 32f74ab5ef3e41dabb6d69fa8724d0d4f21f2671 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Sun, 23 Nov 2025 16:57:39 -0800 Subject: [PATCH 49/67] revert turbo --- turbo.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/turbo.json b/turbo.json index 6c9490938..57068d19b 100644 --- a/turbo.json +++ b/turbo.json @@ -13,8 +13,7 @@ "dependsOn": ["^build"] }, "test": { - "dependsOn": ["^build"], - "cache": false + "dependsOn": ["^build"] }, "clean": { "cache": false From 19098baf5ce760f3eec069a6be14aa05e516548a Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Sun, 23 Nov 2025 16:58:47 -0800 Subject: [PATCH 50/67] revert --- packages/nitro/src/index.ts | 1 - packages/nitro/src/vite.ts | 3 --- 2 files changed, 4 deletions(-) diff --git a/packages/nitro/src/index.ts b/packages/nitro/src/index.ts index ab995d51c..513ac654e 100644 --- a/packages/nitro/src/index.ts +++ b/packages/nitro/src/index.ts @@ -57,7 +57,6 @@ export default { // Allows for HMR if (nitro.options.dev) { nitro.hooks.hook('dev:reload', async () => { - const startTime = Date.now(); await builder.build(); }); } diff --git a/packages/nitro/src/vite.ts b/packages/nitro/src/vite.ts index 76f5a14a6..ab1dc169f 100644 --- a/packages/nitro/src/vite.ts +++ b/packages/nitro/src/vite.ts @@ -74,8 +74,6 @@ export function workflow(options?: ModuleOptions): Plugin[] { try { content = await read(); } catch { - // File might have been deleted - trigger rebuild to update generated routes - const deleteStartTime = Date.now(); if (builder) { await builder.build(); } @@ -99,7 +97,6 @@ export function workflow(options?: ModuleOptions): Plugin[] { // Trigger full reload - this will cause Nitro's dev:reload hook to fire, // which will rebuild workflows and update routes - const startTime = Date.now(); if (builder) { await builder.build(); } From 7c24436f371c5bff4ec9b16c1c92dc62d04938c1 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Sun, 23 Nov 2025 17:00:04 -0800 Subject: [PATCH 51/67] revert --- packages/nitro/src/index.ts | 1 + packages/nitro/src/vite.ts | 3 +++ 2 files changed, 4 insertions(+) diff --git a/packages/nitro/src/index.ts b/packages/nitro/src/index.ts index 513ac654e..359b23e2e 100644 --- a/packages/nitro/src/index.ts +++ b/packages/nitro/src/index.ts @@ -103,6 +103,7 @@ function addVirtualHandler(nitro: Nitro, route: string, buildPath: string) { try { return await POST(req); } catch (error) { + console.error('Handler error:', error); return new Response('Internal Server Error', { status: 500 }); } }; diff --git a/packages/nitro/src/vite.ts b/packages/nitro/src/vite.ts index ab1dc169f..abdc7969b 100644 --- a/packages/nitro/src/vite.ts +++ b/packages/nitro/src/vite.ts @@ -74,6 +74,8 @@ export function workflow(options?: ModuleOptions): Plugin[] { try { content = await read(); } catch { + // File might have been deleted - trigger rebuild to update generated routes + console.log('Workflow file deleted, rebuilding...'); if (builder) { await builder.build(); } @@ -97,6 +99,7 @@ export function workflow(options?: ModuleOptions): Plugin[] { // Trigger full reload - this will cause Nitro's dev:reload hook to fire, // which will rebuild workflows and update routes + console.log('Workflow file changed, rebuilding...'); if (builder) { await builder.build(); } From a5d25c939178f5ae857e5ad3fa6d28f0f1e78ec3 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Sun, 23 Nov 2025 17:01:10 -0800 Subject: [PATCH 52/67] changeset --- .changeset/red-ears-smoke.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/red-ears-smoke.md diff --git a/.changeset/red-ears-smoke.md b/.changeset/red-ears-smoke.md new file mode 100644 index 000000000..0539de7aa --- /dev/null +++ b/.changeset/red-ears-smoke.md @@ -0,0 +1,6 @@ +--- +"@workflow/world-local": patch +"@workflow/utils": patch +--- + +Fix port detection and base URL resolution for dev servers From 5bdc02309e93d1891e5151e8059faf99cebc47da Mon Sep 17 00:00:00 2001 From: Adrian Date: Sun, 23 Nov 2025 17:06:18 -0800 Subject: [PATCH 53/67] Update packages/utils/src/get-port.ts Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> --- packages/utils/src/get-port.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/utils/src/get-port.ts b/packages/utils/src/get-port.ts index 96eb40226..27a7db232 100644 --- a/packages/utils/src/get-port.ts +++ b/packages/utils/src/get-port.ts @@ -45,7 +45,8 @@ export async function getPort(): Promise { const lines = stdout.split('\n'); for (const line of lines) { // Extract port from the local address column - const match = line.trim().match(/^\s*TCP\s+[\d.:]+:(\d+)\s+/); + // Matches both IPv4 (e.g., "127.0.0.1:3000") and IPv6 bracket notation (e.g., "[::1]:3000") + const match = line.trim().match(/^\s*TCP\s+(?:\[[\da-f:]+\]|[\d.]+):(\d+)\s+/i); if (match) { port = parseInt(match[1], 10); break; From 41c5ee27f4d324b58ed152ffda33f46066a4767b Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Sun, 23 Nov 2025 17:52:04 -0800 Subject: [PATCH 54/67] format --- packages/utils/src/get-port.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/utils/src/get-port.ts b/packages/utils/src/get-port.ts index 27a7db232..21d5a98be 100644 --- a/packages/utils/src/get-port.ts +++ b/packages/utils/src/get-port.ts @@ -46,7 +46,9 @@ export async function getPort(): Promise { for (const line of lines) { // Extract port from the local address column // Matches both IPv4 (e.g., "127.0.0.1:3000") and IPv6 bracket notation (e.g., "[::1]:3000") - const match = line.trim().match(/^\s*TCP\s+(?:\[[\da-f:]+\]|[\d.]+):(\d+)\s+/i); + const match = line + .trim() + .match(/^\s*TCP\s+(?:\[[\da-f:]+\]|[\d.]+):(\d+)\s+/i); if (match) { port = parseInt(match[1], 10); break; From 56e4c9c2a8242f3b742c1df3aa427e531f8eb729 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Sun, 23 Nov 2025 17:52:13 -0800 Subject: [PATCH 55/67] init postgres world for express and hono --- workbench/express/src/index.ts | 7 +++++++ workbench/hono/src/index.ts | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/workbench/express/src/index.ts b/workbench/express/src/index.ts index c4231c45a..8278f026e 100644 --- a/workbench/express/src/index.ts +++ b/workbench/express/src/index.ts @@ -13,6 +13,13 @@ const app = express(); app.use(express.json()); app.use(express.text({ type: 'text/*' })); +// Postgres World +if (process.env.WORKFLOW_TARGET_WORLD === '@workflow/world-postgres') { + import('workflow/runtime').then(async ({ getWorld }) => { + await getWorld().start?.(); + }); +} + app.post('/api/hook', async (req, res) => { const { token, data } = JSON.parse(req.body); diff --git a/workbench/hono/src/index.ts b/workbench/hono/src/index.ts index b16e31b7a..b257e3694 100644 --- a/workbench/hono/src/index.ts +++ b/workbench/hono/src/index.ts @@ -9,6 +9,13 @@ import { allWorkflows } from '../_workflows.js'; const app = new Hono(); +// Postgres World +if (process.env.WORKFLOW_TARGET_WORLD === '@workflow/world-postgres') { + import('workflow/runtime').then(async ({ getWorld }) => { + await getWorld().start?.(); + }); +} + app.post('/api/trigger', async ({ req }) => { const url = new URL(req.url); From 0afd8a45cddabcedffc6909e715982ff95809fa2 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Sun, 23 Nov 2025 18:08:08 -0800 Subject: [PATCH 56/67] add posgres world to nitro config --- workbench/express/nitro.config.ts | 12 ++++++++++++ workbench/express/src/index.ts | 7 ------- workbench/hono/nitro.config.ts | 12 ++++++++++++ workbench/hono/src/index.ts | 7 ------- workbench/nitro-v3/nitro.config.ts | 12 ++++++++++++ workbench/nuxt/nuxt.config.ts | 12 ++++++++++++ workbench/vite/vite.config.ts | 12 ++++++++++++ 7 files changed, 60 insertions(+), 14 deletions(-) diff --git a/workbench/express/nitro.config.ts b/workbench/express/nitro.config.ts index c744ea478..7afbaed0b 100644 --- a/workbench/express/nitro.config.ts +++ b/workbench/express/nitro.config.ts @@ -6,4 +6,16 @@ export default defineNitroConfig({ routes: { '/**': './src/index.ts', }, + hooks: { + // Start the Postgres World + // Needed since we test this in CI + compiled: async () => { + if (process.env.WORKFLOW_TARGET_WORLD === '@workflow/world-postgres') { + import('workflow/runtime').then(async ({ getWorld }) => { + console.log('Starting Postgres World...'); + await getWorld().start?.(); + }); + } + }, + }, }); diff --git a/workbench/express/src/index.ts b/workbench/express/src/index.ts index 8278f026e..c4231c45a 100644 --- a/workbench/express/src/index.ts +++ b/workbench/express/src/index.ts @@ -13,13 +13,6 @@ const app = express(); app.use(express.json()); app.use(express.text({ type: 'text/*' })); -// Postgres World -if (process.env.WORKFLOW_TARGET_WORLD === '@workflow/world-postgres') { - import('workflow/runtime').then(async ({ getWorld }) => { - await getWorld().start?.(); - }); -} - app.post('/api/hook', async (req, res) => { const { token, data } = JSON.parse(req.body); diff --git a/workbench/hono/nitro.config.ts b/workbench/hono/nitro.config.ts index f27eceda3..bb8d68cb5 100644 --- a/workbench/hono/nitro.config.ts +++ b/workbench/hono/nitro.config.ts @@ -5,4 +5,16 @@ export default defineConfig({ routes: { '/**': './src/index.ts', }, + hooks: { + // Start the Postgres World + // Needed since we test this in CI + compiled: async () => { + if (process.env.WORKFLOW_TARGET_WORLD === '@workflow/world-postgres') { + import('workflow/runtime').then(async ({ getWorld }) => { + console.log('Starting Postgres World...'); + await getWorld().start?.(); + }); + } + }, + }, }); diff --git a/workbench/hono/src/index.ts b/workbench/hono/src/index.ts index b257e3694..b16e31b7a 100644 --- a/workbench/hono/src/index.ts +++ b/workbench/hono/src/index.ts @@ -9,13 +9,6 @@ import { allWorkflows } from '../_workflows.js'; const app = new Hono(); -// Postgres World -if (process.env.WORKFLOW_TARGET_WORLD === '@workflow/world-postgres') { - import('workflow/runtime').then(async ({ getWorld }) => { - await getWorld().start?.(); - }); -} - app.post('/api/trigger', async ({ req }) => { const url = new URL(req.url); diff --git a/workbench/nitro-v3/nitro.config.ts b/workbench/nitro-v3/nitro.config.ts index 5e800f3a1..e265fbe7c 100644 --- a/workbench/nitro-v3/nitro.config.ts +++ b/workbench/nitro-v3/nitro.config.ts @@ -3,4 +3,16 @@ import { defineConfig } from 'nitro'; export default defineConfig({ modules: ['workflow/nitro'], serverDir: './', + hooks: { + // Start the Postgres World + // Needed since we test this in CI + compiled: async () => { + if (process.env.WORKFLOW_TARGET_WORLD === '@workflow/world-postgres') { + import('workflow/runtime').then(async ({ getWorld }) => { + console.log('Starting Postgres World...'); + await getWorld().start?.(); + }); + } + }, + }, }); diff --git a/workbench/nuxt/nuxt.config.ts b/workbench/nuxt/nuxt.config.ts index b0ddf9a1e..e2ef9e44a 100644 --- a/workbench/nuxt/nuxt.config.ts +++ b/workbench/nuxt/nuxt.config.ts @@ -1,4 +1,16 @@ export default defineNuxtConfig({ compatibilityDate: 'latest', modules: ['workflow/nuxt'], + hooks: { + // Start the Postgres World + // Needed since we test this in CI + 'nitro:init': async () => { + if (process.env.WORKFLOW_TARGET_WORLD === '@workflow/world-postgres') { + import('workflow/runtime').then(async ({ getWorld }) => { + console.log('Starting Postgres World...'); + await getWorld().start?.(); + }); + } + }, + }, }); diff --git a/workbench/vite/vite.config.ts b/workbench/vite/vite.config.ts index a892fda5e..165117de7 100644 --- a/workbench/vite/vite.config.ts +++ b/workbench/vite/vite.config.ts @@ -6,5 +6,17 @@ export default defineConfig({ plugins: [nitro(), workflow()], nitro: { serverDir: './', + hooks: { + // Start the Postgres World + // Needed since we test this in CI + compiled: async () => { + if (process.env.WORKFLOW_TARGET_WORLD === '@workflow/world-postgres') { + import('workflow/runtime').then(async ({ getWorld }) => { + console.log('Starting Postgres World...'); + await getWorld().start?.(); + }); + } + }, + }, }, }); From c5f6434f3eadbb2f908bc9369653d8d95f48e0a5 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Sun, 23 Nov 2025 18:10:25 -0800 Subject: [PATCH 57/67] add postgres world start to sveltekit --- workbench/sveltekit/src/hooks.server.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 workbench/sveltekit/src/hooks.server.ts diff --git a/workbench/sveltekit/src/hooks.server.ts b/workbench/sveltekit/src/hooks.server.ts new file mode 100644 index 000000000..2132449a2 --- /dev/null +++ b/workbench/sveltekit/src/hooks.server.ts @@ -0,0 +1,12 @@ +import type { ServerInit } from '@sveltejs/kit'; + +export const init: ServerInit = async () => { + // Start the Postgres World + // Needed since we test this in CI + if (process.env.WORKFLOW_TARGET_WORLD === '@workflow/world-postgres') { + import('workflow/runtime').then(async ({ getWorld }) => { + console.log('Starting Postgres World...'); + await getWorld().start?.(); + }); + } +}; From ebff0a36e84dd711760d3874e667c33932322c69 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Sun, 23 Nov 2025 18:58:53 -0800 Subject: [PATCH 58/67] add postgres world plugin to nitro apps --- packages/nitro/src/index.ts | 3 ++- workbench/express/nitro.config.ts | 13 +------------ workbench/express/plugins | 1 + workbench/hono/nitro.config.ts | 13 +------------ workbench/hono/plugins | 1 + workbench/nitro-v3/nitro.config.ts | 14 +------------- .../nitro-v3/server/plugins/start-pg-world.ts | 13 +++++++++++++ workbench/nuxt/nuxt.config.ts | 12 ------------ workbench/nuxt/server/plugins/postgres-world.ts | 10 ++++++++++ 9 files changed, 30 insertions(+), 50 deletions(-) create mode 120000 workbench/express/plugins create mode 120000 workbench/hono/plugins create mode 100644 workbench/nitro-v3/server/plugins/start-pg-world.ts create mode 100644 workbench/nuxt/server/plugins/postgres-world.ts diff --git a/packages/nitro/src/index.ts b/packages/nitro/src/index.ts index 359b23e2e..892461063 100644 --- a/packages/nitro/src/index.ts +++ b/packages/nitro/src/index.ts @@ -42,7 +42,8 @@ export default { // Generate functions for vercel build if (isVercelDeploy) { - nitro.hooks.hook('compiled', async () => { + nitro.hooks.hook('close', async () => { + console.log('Building functions for Vercel...'); await new VercelBuilder(nitro).build(); }); } diff --git a/workbench/express/nitro.config.ts b/workbench/express/nitro.config.ts index 7afbaed0b..8e89eea15 100644 --- a/workbench/express/nitro.config.ts +++ b/workbench/express/nitro.config.ts @@ -6,16 +6,5 @@ export default defineNitroConfig({ routes: { '/**': './src/index.ts', }, - hooks: { - // Start the Postgres World - // Needed since we test this in CI - compiled: async () => { - if (process.env.WORKFLOW_TARGET_WORLD === '@workflow/world-postgres') { - import('workflow/runtime').then(async ({ getWorld }) => { - console.log('Starting Postgres World...'); - await getWorld().start?.(); - }); - } - }, - }, + plugins: ['plugins/start-pg-world.ts'], }); diff --git a/workbench/express/plugins b/workbench/express/plugins new file mode 120000 index 000000000..6f4b1af53 --- /dev/null +++ b/workbench/express/plugins @@ -0,0 +1 @@ +../nitro-v3/server/plugins \ No newline at end of file diff --git a/workbench/hono/nitro.config.ts b/workbench/hono/nitro.config.ts index bb8d68cb5..52af50f49 100644 --- a/workbench/hono/nitro.config.ts +++ b/workbench/hono/nitro.config.ts @@ -5,16 +5,5 @@ export default defineConfig({ routes: { '/**': './src/index.ts', }, - hooks: { - // Start the Postgres World - // Needed since we test this in CI - compiled: async () => { - if (process.env.WORKFLOW_TARGET_WORLD === '@workflow/world-postgres') { - import('workflow/runtime').then(async ({ getWorld }) => { - console.log('Starting Postgres World...'); - await getWorld().start?.(); - }); - } - }, - }, + plugins: ['plugins/start-pg-world.ts'], }); diff --git a/workbench/hono/plugins b/workbench/hono/plugins new file mode 120000 index 000000000..6f4b1af53 --- /dev/null +++ b/workbench/hono/plugins @@ -0,0 +1 @@ +../nitro-v3/server/plugins \ No newline at end of file diff --git a/workbench/nitro-v3/nitro.config.ts b/workbench/nitro-v3/nitro.config.ts index e265fbe7c..0a2c61efb 100644 --- a/workbench/nitro-v3/nitro.config.ts +++ b/workbench/nitro-v3/nitro.config.ts @@ -2,17 +2,5 @@ import { defineConfig } from 'nitro'; export default defineConfig({ modules: ['workflow/nitro'], - serverDir: './', - hooks: { - // Start the Postgres World - // Needed since we test this in CI - compiled: async () => { - if (process.env.WORKFLOW_TARGET_WORLD === '@workflow/world-postgres') { - import('workflow/runtime').then(async ({ getWorld }) => { - console.log('Starting Postgres World...'); - await getWorld().start?.(); - }); - } - }, - }, + serverDir: './server', }); diff --git a/workbench/nitro-v3/server/plugins/start-pg-world.ts b/workbench/nitro-v3/server/plugins/start-pg-world.ts new file mode 100644 index 000000000..46498d0f6 --- /dev/null +++ b/workbench/nitro-v3/server/plugins/start-pg-world.ts @@ -0,0 +1,13 @@ +import { defineNitroPlugin } from 'nitro/~internal/runtime/plugin'; + +// Start the Postgres World +// Needed since we test this in CI +export default defineNitroPlugin(async () => { + console.log('HELLLLLLLLO world'); + if (process.env.WORKFLOW_TARGET_WORLD === '@workflow/world-postgres') { + import('workflow/runtime').then(async ({ getWorld }) => { + console.log('Starting Postgres World...'); + await getWorld().start?.(); + }); + } +}); diff --git a/workbench/nuxt/nuxt.config.ts b/workbench/nuxt/nuxt.config.ts index e2ef9e44a..b0ddf9a1e 100644 --- a/workbench/nuxt/nuxt.config.ts +++ b/workbench/nuxt/nuxt.config.ts @@ -1,16 +1,4 @@ export default defineNuxtConfig({ compatibilityDate: 'latest', modules: ['workflow/nuxt'], - hooks: { - // Start the Postgres World - // Needed since we test this in CI - 'nitro:init': async () => { - if (process.env.WORKFLOW_TARGET_WORLD === '@workflow/world-postgres') { - import('workflow/runtime').then(async ({ getWorld }) => { - console.log('Starting Postgres World...'); - await getWorld().start?.(); - }); - } - }, - }, }); diff --git a/workbench/nuxt/server/plugins/postgres-world.ts b/workbench/nuxt/server/plugins/postgres-world.ts new file mode 100644 index 000000000..342b2d460 --- /dev/null +++ b/workbench/nuxt/server/plugins/postgres-world.ts @@ -0,0 +1,10 @@ +// Start the Postgres World +// Needed since we test this in CI +export default defineNitroPlugin(async (nitroApp) => { + if (process.env.WORKFLOW_TARGET_WORLD === '@workflow/world-postgres') { + import('workflow/runtime').then(async ({ getWorld }) => { + console.log('Starting Postgres World...'); + await getWorld().start?.(); + }); + } +}); From 25e714addd644843fceee4d52e3428ae84f4aef8 Mon Sep 17 00:00:00 2001 From: Adrian Date: Sun, 23 Nov 2025 19:01:54 -0800 Subject: [PATCH 59/67] Update workbench/sveltekit/src/hooks.server.ts Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> --- workbench/sveltekit/src/hooks.server.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/workbench/sveltekit/src/hooks.server.ts b/workbench/sveltekit/src/hooks.server.ts index 2132449a2..16d598cf0 100644 --- a/workbench/sveltekit/src/hooks.server.ts +++ b/workbench/sveltekit/src/hooks.server.ts @@ -4,9 +4,8 @@ export const init: ServerInit = async () => { // Start the Postgres World // Needed since we test this in CI if (process.env.WORKFLOW_TARGET_WORLD === '@workflow/world-postgres') { - import('workflow/runtime').then(async ({ getWorld }) => { - console.log('Starting Postgres World...'); - await getWorld().start?.(); - }); + const { getWorld } = await import('workflow/runtime'); + console.log('Starting Postgres World...'); + await getWorld().start?.(); } }; From 3f4a23ca67dbe3a91533969c233dd7062b65e3a4 Mon Sep 17 00:00:00 2001 From: Adrian Date: Sun, 23 Nov 2025 19:02:04 -0800 Subject: [PATCH 60/67] Update workbench/vite/vite.config.ts Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> --- workbench/vite/vite.config.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/workbench/vite/vite.config.ts b/workbench/vite/vite.config.ts index 165117de7..ffc4d5d5e 100644 --- a/workbench/vite/vite.config.ts +++ b/workbench/vite/vite.config.ts @@ -11,10 +11,9 @@ export default defineConfig({ // Needed since we test this in CI compiled: async () => { if (process.env.WORKFLOW_TARGET_WORLD === '@workflow/world-postgres') { - import('workflow/runtime').then(async ({ getWorld }) => { - console.log('Starting Postgres World...'); - await getWorld().start?.(); - }); + const { getWorld } = await import('workflow/runtime'); + console.log('Starting Postgres World...'); + await getWorld().start?.(); } }, }, From d9e60ef446e95024f4733c85ab9caeabcc396b01 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Sun, 23 Nov 2025 19:08:32 -0800 Subject: [PATCH 61/67] fix nuxt plugin --- .../server/plugins/{postgres-world.ts => start-pg-world.ts} | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) rename workbench/nuxt/server/plugins/{postgres-world.ts => start-pg-world.ts} (77%) diff --git a/workbench/nuxt/server/plugins/postgres-world.ts b/workbench/nuxt/server/plugins/start-pg-world.ts similarity index 77% rename from workbench/nuxt/server/plugins/postgres-world.ts rename to workbench/nuxt/server/plugins/start-pg-world.ts index 342b2d460..001465d16 100644 --- a/workbench/nuxt/server/plugins/postgres-world.ts +++ b/workbench/nuxt/server/plugins/start-pg-world.ts @@ -1,6 +1,8 @@ +import { defineNuxtPlugin } from 'nuxt/app'; + // Start the Postgres World // Needed since we test this in CI -export default defineNitroPlugin(async (nitroApp) => { +export default defineNuxtPlugin(() => { if (process.env.WORKFLOW_TARGET_WORLD === '@workflow/world-postgres') { import('workflow/runtime').then(async ({ getWorld }) => { console.log('Starting Postgres World...'); From 19ca4e24ff73112d426e559d9f83ef138dfdc2c7 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Sun, 23 Nov 2025 19:09:19 -0800 Subject: [PATCH 62/67] revert vercel compiled hook on nitro --- packages/nitro/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nitro/src/index.ts b/packages/nitro/src/index.ts index 892461063..c9a95deb1 100644 --- a/packages/nitro/src/index.ts +++ b/packages/nitro/src/index.ts @@ -42,7 +42,7 @@ export default { // Generate functions for vercel build if (isVercelDeploy) { - nitro.hooks.hook('close', async () => { + nitro.hooks.hook('compiled', async () => { console.log('Building functions for Vercel...'); await new VercelBuilder(nitro).build(); }); From f43e1c027bffe84a9f2a447fb78d0e2deada1359 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Sun, 23 Nov 2025 19:14:19 -0800 Subject: [PATCH 63/67] . --- workbench/nuxt/server/plugins/start-pg-world.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/workbench/nuxt/server/plugins/start-pg-world.ts b/workbench/nuxt/server/plugins/start-pg-world.ts index 001465d16..622cfcd24 100644 --- a/workbench/nuxt/server/plugins/start-pg-world.ts +++ b/workbench/nuxt/server/plugins/start-pg-world.ts @@ -1,5 +1,3 @@ -import { defineNuxtPlugin } from 'nuxt/app'; - // Start the Postgres World // Needed since we test this in CI export default defineNuxtPlugin(() => { From d618959bdd3848cb925b34c375ef6d6cdd425c73 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Sun, 23 Nov 2025 19:20:49 -0800 Subject: [PATCH 64/67] revert --- workbench/nitro-v3/nitro.config.ts | 2 +- .../nitro-v3/server/plugins/start-pg-world.ts | 1 - workbench/vite/vite.config.ts | 22 +++++++++---------- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/workbench/nitro-v3/nitro.config.ts b/workbench/nitro-v3/nitro.config.ts index 0a2c61efb..5e800f3a1 100644 --- a/workbench/nitro-v3/nitro.config.ts +++ b/workbench/nitro-v3/nitro.config.ts @@ -2,5 +2,5 @@ import { defineConfig } from 'nitro'; export default defineConfig({ modules: ['workflow/nitro'], - serverDir: './server', + serverDir: './', }); diff --git a/workbench/nitro-v3/server/plugins/start-pg-world.ts b/workbench/nitro-v3/server/plugins/start-pg-world.ts index 46498d0f6..7e9cff224 100644 --- a/workbench/nitro-v3/server/plugins/start-pg-world.ts +++ b/workbench/nitro-v3/server/plugins/start-pg-world.ts @@ -3,7 +3,6 @@ import { defineNitroPlugin } from 'nitro/~internal/runtime/plugin'; // Start the Postgres World // Needed since we test this in CI export default defineNitroPlugin(async () => { - console.log('HELLLLLLLLO world'); if (process.env.WORKFLOW_TARGET_WORLD === '@workflow/world-postgres') { import('workflow/runtime').then(async ({ getWorld }) => { console.log('Starting Postgres World...'); diff --git a/workbench/vite/vite.config.ts b/workbench/vite/vite.config.ts index ffc4d5d5e..2b0f3ad5d 100644 --- a/workbench/vite/vite.config.ts +++ b/workbench/vite/vite.config.ts @@ -6,16 +6,16 @@ export default defineConfig({ plugins: [nitro(), workflow()], nitro: { serverDir: './', - hooks: { - // Start the Postgres World - // Needed since we test this in CI - compiled: async () => { - if (process.env.WORKFLOW_TARGET_WORLD === '@workflow/world-postgres') { - const { getWorld } = await import('workflow/runtime'); - console.log('Starting Postgres World...'); - await getWorld().start?.(); - } - }, - }, + // hooks: { + // // Start the Postgres World + // // Needed since we test this in CI + // compiled: async () => { + // if (process.env.WORKFLOW_TARGET_WORLD === '@workflow/world-postgres') { + // const { getWorld } = await import('workflow/runtime'); + // console.log('Starting Postgres World...'); + // await getWorld().start?.(); + // } + // }, + // }, }, }); From f4670711b2a17c537478314517148c5639c88c73 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Sun, 23 Nov 2025 19:24:20 -0800 Subject: [PATCH 65/67] update --- workbench/nuxt/server/plugins/start-pg-world.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/workbench/nuxt/server/plugins/start-pg-world.ts b/workbench/nuxt/server/plugins/start-pg-world.ts index 622cfcd24..2824d2b3e 100644 --- a/workbench/nuxt/server/plugins/start-pg-world.ts +++ b/workbench/nuxt/server/plugins/start-pg-world.ts @@ -1,6 +1,8 @@ +import { defineNitroPlugin } from '#imports'; + // Start the Postgres World // Needed since we test this in CI -export default defineNuxtPlugin(() => { +export default defineNitroPlugin(async () => { if (process.env.WORKFLOW_TARGET_WORLD === '@workflow/world-postgres') { import('workflow/runtime').then(async ({ getWorld }) => { console.log('Starting Postgres World...'); From cbd4f9c390789e2874a1ff2349fbf3ca2dfe2702 Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Sun, 23 Nov 2025 19:38:29 -0800 Subject: [PATCH 66/67] CI WILL BE GREEN --- workbench/express/plugins | 2 +- workbench/hono/plugins | 2 +- workbench/nitro-v3/nitro.config.ts | 1 + .../nitro-v3/{server => }/plugins/start-pg-world.ts | 0 workbench/vite/plugins | 1 + workbench/vite/vite.config.ts | 12 +----------- 6 files changed, 5 insertions(+), 13 deletions(-) rename workbench/nitro-v3/{server => }/plugins/start-pg-world.ts (100%) create mode 120000 workbench/vite/plugins diff --git a/workbench/express/plugins b/workbench/express/plugins index 6f4b1af53..2c8250f9c 120000 --- a/workbench/express/plugins +++ b/workbench/express/plugins @@ -1 +1 @@ -../nitro-v3/server/plugins \ No newline at end of file +../nitro-v3/plugins \ No newline at end of file diff --git a/workbench/hono/plugins b/workbench/hono/plugins index 6f4b1af53..2c8250f9c 120000 --- a/workbench/hono/plugins +++ b/workbench/hono/plugins @@ -1 +1 @@ -../nitro-v3/server/plugins \ No newline at end of file +../nitro-v3/plugins \ No newline at end of file diff --git a/workbench/nitro-v3/nitro.config.ts b/workbench/nitro-v3/nitro.config.ts index 5e800f3a1..946bbbb53 100644 --- a/workbench/nitro-v3/nitro.config.ts +++ b/workbench/nitro-v3/nitro.config.ts @@ -3,4 +3,5 @@ import { defineConfig } from 'nitro'; export default defineConfig({ modules: ['workflow/nitro'], serverDir: './', + plugins: ['plugins/start-pg-world.ts'], }); diff --git a/workbench/nitro-v3/server/plugins/start-pg-world.ts b/workbench/nitro-v3/plugins/start-pg-world.ts similarity index 100% rename from workbench/nitro-v3/server/plugins/start-pg-world.ts rename to workbench/nitro-v3/plugins/start-pg-world.ts diff --git a/workbench/vite/plugins b/workbench/vite/plugins new file mode 120000 index 000000000..2c8250f9c --- /dev/null +++ b/workbench/vite/plugins @@ -0,0 +1 @@ +../nitro-v3/plugins \ No newline at end of file diff --git a/workbench/vite/vite.config.ts b/workbench/vite/vite.config.ts index 2b0f3ad5d..3ec767479 100644 --- a/workbench/vite/vite.config.ts +++ b/workbench/vite/vite.config.ts @@ -6,16 +6,6 @@ export default defineConfig({ plugins: [nitro(), workflow()], nitro: { serverDir: './', - // hooks: { - // // Start the Postgres World - // // Needed since we test this in CI - // compiled: async () => { - // if (process.env.WORKFLOW_TARGET_WORLD === '@workflow/world-postgres') { - // const { getWorld } = await import('workflow/runtime'); - // console.log('Starting Postgres World...'); - // await getWorld().start?.(); - // } - // }, - // }, + plugins: ['plugins/start-pg-world.ts'], }, }); From ae1b490ad7264e8fe0cdfe022043a2e2f901327b Mon Sep 17 00:00:00 2001 From: Adrian Lam Date: Sun, 23 Nov 2025 19:54:46 -0800 Subject: [PATCH 67/67] remove log in nitro --- packages/nitro/src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/nitro/src/index.ts b/packages/nitro/src/index.ts index c9a95deb1..359b23e2e 100644 --- a/packages/nitro/src/index.ts +++ b/packages/nitro/src/index.ts @@ -43,7 +43,6 @@ export default { // Generate functions for vercel build if (isVercelDeploy) { nitro.hooks.hook('compiled', async () => { - console.log('Building functions for Vercel...'); await new VercelBuilder(nitro).build(); }); }