diff --git a/.changeset/nine-books-heal.md b/.changeset/nine-books-heal.md new file mode 100644 index 000000000..3f6b8049b --- /dev/null +++ b/.changeset/nine-books-heal.md @@ -0,0 +1,5 @@ +--- +"@workflow/sveltekit": patch +--- + +Use hotUpdate for HMR in sveltekit diff --git a/.changeset/tiny-coins-sip.md b/.changeset/tiny-coins-sip.md new file mode 100644 index 000000000..722531070 --- /dev/null +++ b/.changeset/tiny-coins-sip.md @@ -0,0 +1,5 @@ +--- +"@workflow/nitro": patch +--- + +Add HMR to nitro integration diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2d9a62098..1fe6080c0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -178,35 +178,18 @@ jobs: - name: Run Initial Build run: pnpm turbo run build --filter='!./workbench/*' - - name: Resolve symlinks for next dev to work - if: matrix.app.name == 'nextjs-webpack' - run: cd workbench/${{ matrix.app.name }} && ./resolve-symlinks.sh - - - name: Run E2E Tests (Next.js) - if: matrix.app.name != 'nitro' && matrix.app.name != 'vite' && matrix.app.name != 'sveltekit' - run: cd workbench/${{ matrix.app.name }} && pnpm dev & echo "starting tests in 10 seconds" && sleep 10 && pnpm vitest run packages/core/e2e/next-dev.test.ts && pnpm run test:e2e - env: - APP_NAME: ${{ matrix.app.name }} - DEPLOYMENT_URL: "http://localhost:3000" + - name: Resolve symlinks + run: | + if [ -f "workbench/${{ matrix.app.name }}/resolve-symlinks.sh" ]; then + cd workbench/${{ matrix.app.name }} && ./resolve-symlinks.sh + fi - - name: Run E2E Tests (Nitro) - if: matrix.app.name == 'nitro' - run: cd workbench/${{ matrix.app.name }} && pnpm dev & echo "starting tests in 10 seconds" && sleep 10 && pnpm vitest run packages/core/e2e/e2e.test.ts - env: - APP_NAME: ${{ matrix.app.name }} - DEPLOYMENT_URL: "http://localhost:3000" - - name: Run E2E Tests (Vite) - if: matrix.app.name == 'vite' - run: cd workbench/${{ matrix.app.name }} && pnpm dev & echo "starting tests in 10 seconds" && sleep 10 && pnpm vitest run packages/core/e2e/e2e.test.ts - env: - APP_NAME: ${{ matrix.app.name }} - DEPLOYMENT_URL: "http://localhost:3000" - - name: Run E2E Tests (SvelteKit) - if: matrix.app.name == 'sveltekit' - run: cd workbench/${{ matrix.app.name }} && pnpm dev & echo "starting tests in 10 seconds" && sleep 10 && pnpm vitest run packages/core/e2e/e2e.test.ts + - 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 env: APP_NAME: ${{ matrix.app.name }} - DEPLOYMENT_URL: "http://localhost:3000" + DEPLOYMENT_URL: "http://localhost:${{ matrix.app.port }}" + DEV_TEST_CONFIG: ${{ toJSON(matrix.app) }} e2e-local-prod: name: E2E Local Prod Tests (${{ matrix.app.name }} - ${{ matrix.app.canary && 'canary' || 'stable' }}) @@ -299,10 +282,11 @@ jobs: $job = Start-Job -ScriptBlock { Set-Location $using:PWD; pnpm dev } Start-Sleep -Seconds 15 cd ../.. - pnpm vitest run packages/core/e2e/next-dev.test.ts + pnpm vitest run packages/core/e2e/dev.test.ts pnpm run test:e2e Stop-Job $job shell: powershell env: APP_NAME: "nextjs-turbopack" DEPLOYMENT_URL: "http://localhost:3000" + DEV_TEST_CONFIG: '{"generatedStepPath":"app/.well-known/workflow/v1/step/route.js","generatedWorkflowPath":"app/.well-known/workflow/v1/flow/route.js","apiFilePath":"app/api/chat/route.ts","apiFileImportPath":"../../..","port":3000}' diff --git a/packages/core/e2e/dev.test.ts b/packages/core/e2e/dev.test.ts new file mode 100644 index 000000000..578a01288 --- /dev/null +++ b/packages/core/e2e/dev.test.ts @@ -0,0 +1,159 @@ +import fs from 'fs/promises'; +import path from 'path'; +import { afterEach, describe, expect, test } from 'vitest'; +import { getWorkbenchAppPath } from './utils'; + +export interface DevTestConfig { + generatedStepPath: string; + generatedWorkflowPath: string; + apiFilePath: string; + apiFileImportPath: string; + /** The workflow file to modify for testing HMR. Defaults to '3_streams.ts' */ + testWorkflowFile?: string; +} + +function getConfigFromEnv(): DevTestConfig | null { + const envConfig = process.env.DEV_TEST_CONFIG; + if (envConfig) { + try { + return JSON.parse(envConfig); + } catch (e) { + console.error('Failed to parse DEV_TEST_CONFIG:', e); + } + } + return null; +} + +export function createDevTests(config?: DevTestConfig) { + const finalConfig = config || getConfigFromEnv(); + if (!finalConfig) { + throw new Error( + 'No dev test config provided via parameter or DEV_TEST_CONFIG env var' + ); + } + describe('dev e2e', () => { + const appPath = getWorkbenchAppPath(); + const generatedStep = path.join(appPath, finalConfig.generatedStepPath); + const generatedWorkflow = path.join( + appPath, + finalConfig.generatedWorkflowPath + ); + const testWorkflowFile = finalConfig.testWorkflowFile ?? '3_streams.ts'; + const restoreFiles: Array<{ path: string; content: string }> = []; + + afterEach(async () => { + await Promise.all( + restoreFiles.map(async (item) => { + if (item.content === '') { + await fs.unlink(item.path); + } else { + await fs.writeFile(item.path, item.content); + } + }) + ); + restoreFiles.length = 0; + }); + + test('should rebuild on workflow change', { timeout: 10_000 }, async () => { + const workflowFile = path.join(appPath, 'workflows', testWorkflowFile); + + const content = await fs.readFile(workflowFile, 'utf8'); + + await fs.writeFile( + workflowFile, + `${content} + +export async function myNewWorkflow() { + 'use workflow' + return 'hello world' +} +` + ); + restoreFiles.push({ path: workflowFile, content }); + + while (true) { + try { + const workflowContent = await fs.readFile(generatedWorkflow, 'utf8'); + expect(workflowContent).toContain('myNewWorkflow'); + break; + } catch (_) { + await new Promise((res) => setTimeout(res, 1_000)); + } + } + }); + + test('should rebuild on step change', { timeout: 10_000 }, async () => { + const stepFile = path.join(appPath, 'workflows', testWorkflowFile); + + const content = await fs.readFile(stepFile, 'utf8'); + + await fs.writeFile( + stepFile, + `${content} + +export async function myNewStep() { + 'use step' + return 'hello world' +} +` + ); + restoreFiles.push({ path: stepFile, content }); + + while (true) { + try { + const workflowContent = await fs.readFile(generatedStep, 'utf8'); + expect(workflowContent).toContain('myNewStep'); + break; + } catch (_) { + await new Promise((res) => setTimeout(res, 1_000)); + } + } + }); + + test( + 'should rebuild on adding workflow file', + { timeout: 10_000 }, + async () => { + const workflowFile = path.join(appPath, 'workflows', 'new-workflow.ts'); + + await fs.writeFile( + workflowFile, + `export async function newWorkflowFile() { + 'use workflow' + return 'hello world' +} +` + ); + restoreFiles.push({ path: workflowFile, content: '' }); + const apiFile = path.join(appPath, finalConfig.apiFilePath); + + const apiFileContent = await fs.readFile(apiFile, 'utf8'); + restoreFiles.push({ path: apiFile, content: apiFileContent }); + + await fs.writeFile( + apiFile, + `import '${finalConfig.apiFileImportPath}/workflows/new-workflow'; +${apiFileContent}` + ); + + while (true) { + try { + const workflowContent = await fs.readFile( + generatedWorkflow, + 'utf8' + ); + expect(workflowContent).toContain('newWorkflowFile'); + break; + } catch (_) { + await new Promise((res) => setTimeout(res, 1_000)); + } + } + } + ); + }); +} + +// Run tests with environment-based config if this file is executed directly +if (process.env.DEV_TEST_CONFIG) { + createDevTests(); +} diff --git a/packages/core/e2e/next-dev.test.ts b/packages/core/e2e/next-dev.test.ts deleted file mode 100644 index 0c2d190f5..000000000 --- a/packages/core/e2e/next-dev.test.ts +++ /dev/null @@ -1,134 +0,0 @@ -import fs from 'fs/promises'; -import path from 'path'; -import { afterEach, describe, expect, test } from 'vitest'; -import { getWorkbenchAppPath } from './utils'; - -describe('dev e2e', () => { - const appPath = getWorkbenchAppPath(); - const generatedStep = path.join( - appPath, - 'app', - '.well-known/workflow/v1/step', - 'route.js' - ); - const generatedWorkflow = path.join( - appPath, - 'app', - '.well-known/workflow/v1/flow', - 'route.js' - ); - - const restoreFiles: Array<{ path: string; content: string }> = []; - - afterEach(async () => { - await Promise.all( - restoreFiles.map(async (item) => { - if (item.content === '') { - await fs.unlink(item.path); - } else { - await fs.writeFile(item.path, item.content); - } - }) - ); - restoreFiles.length = 0; - }); - - test('should rebuild on workflow change', { timeout: 10_000 }, async () => { - const workflowFile = path.join(appPath, 'workflows', 'streams.ts'); - - const content = await fs.readFile(workflowFile, 'utf8'); - - await fs.writeFile( - workflowFile, - ` - ${content} - - export async function myNewWorkflow() { - 'use workflow' - return 'hello world' - } - ` - ); - restoreFiles.push({ path: workflowFile, content }); - - while (true) { - try { - const workflowContent = await fs.readFile(generatedWorkflow, 'utf8'); - expect(workflowContent).toContain('myNewWorkflow'); - break; - } catch (_) { - await new Promise((res) => setTimeout(res, 1_000)); - } - } - }); - - test('should rebuild on step change', { timeout: 10_000 }, async () => { - const stepFile = path.join(appPath, 'workflows', 'streams.ts'); - - const content = await fs.readFile(stepFile, 'utf8'); - - await fs.writeFile( - stepFile, - ` - ${content} - - export async function myNewStep() { - 'use step' - return 'hello world' - } - ` - ); - restoreFiles.push({ path: stepFile, content }); - - while (true) { - try { - const workflowContent = await fs.readFile(generatedStep, 'utf8'); - expect(workflowContent).toContain('myNewStep'); - break; - } catch (_) { - await new Promise((res) => setTimeout(res, 1_000)); - } - } - }); - - test( - 'should rebuild on adding workflow file', - { timeout: 10_000 }, - async () => { - const workflowFile = path.join(appPath, 'workflows', 'new-workflow.ts'); - - await fs.writeFile( - workflowFile, - ` - export async function newWorkflowFile() { - 'use workflow' - return 'hello world' - } - ` - ); - restoreFiles.push({ path: workflowFile, content: '' }); - const chatApiFile = path.join(appPath, 'app', 'api', 'chat', 'route.ts'); - - const chatApiFileContent = await fs.readFile(chatApiFile, 'utf8'); - restoreFiles.push({ path: chatApiFile, content: chatApiFileContent }); - - await fs.writeFile( - chatApiFile, - ` - import '../../../workflows/new-workflow'; - ${chatApiFileContent} - ` - ); - - while (true) { - try { - const workflowContent = await fs.readFile(generatedWorkflow, 'utf8'); - expect(workflowContent).toContain('newWorkflowFile'); - break; - } catch (_) { - await new Promise((res) => setTimeout(res, 1_000)); - } - } - } - ); -}); diff --git a/packages/nitro/src/index.ts b/packages/nitro/src/index.ts index 43535543c..acb33b9a3 100644 --- a/packages/nitro/src/index.ts +++ b/packages/nitro/src/index.ts @@ -31,10 +31,18 @@ export default { // Generate local bundles for dev and local prod if (!isVercelDeploy) { + const builder = new LocalBuilder(nitro); nitro.hooks.hook('build:before', async () => { - await new LocalBuilder(nitro).build(); + await builder.build(); }); + // Allows for HMR + if (nitro.options.dev) { + nitro.hooks.hook('dev:reload', async () => { + await builder.build(); + }); + } + addVirtualHandler( nitro, '/.well-known/workflow/v1/webhook/:token', diff --git a/packages/sveltekit/src/plugin.ts b/packages/sveltekit/src/plugin.ts index f4c529af1..308d75d37 100644 --- a/packages/sveltekit/src/plugin.ts +++ b/packages/sveltekit/src/plugin.ts @@ -1,7 +1,7 @@ import { relative } from 'node:path'; import { transform } from '@swc/core'; import { resolveModulePath } from 'exsolve'; -import type { HmrContext, Plugin } from 'vite'; +import type { HotUpdateOptions, Plugin } from 'vite'; import { SvelteKitBuilder } from './builder.js'; export function workflowPlugin(): Plugin { @@ -92,13 +92,9 @@ export function workflowPlugin(): Plugin { builder = new SvelteKitBuilder(); }, - async buildStart() { - await builder.build(); - }, - // TODO: Move this to @workflow/vite or something since this is vite specific - async handleHotUpdate(ctx: HmrContext) { - const { file, server, read } = ctx; + async hotUpdate(options: HotUpdateOptions) { + const { file, server, read } = options; // Check if this is a TS/JS file that might contain workflow directives const jsTsRegex = /\.(ts|tsx|js|jsx|mjs|cjs)$/; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 01614c57d..7b8674456 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1199,6 +1199,10 @@ importers: version: 5.9.3 workbench/nitro: + dependencies: + '@node-rs/xxhash': + specifier: 1.7.6 + version: 1.7.6 devDependencies: ai: specifier: 'catalog:' @@ -1247,6 +1251,10 @@ importers: version: 4.1.11 workbench/nitro-v3: + dependencies: + '@node-rs/xxhash': + specifier: 1.7.6 + version: 1.7.6 devDependencies: ai: specifier: 'catalog:' @@ -1362,6 +1370,10 @@ importers: version: 1.0.0(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.0)) workbench/vite: + dependencies: + '@node-rs/xxhash': + specifier: 1.7.6 + version: 1.7.6 devDependencies: ai: specifier: 'catalog:' diff --git a/scripts/create-test-matrix.mjs b/scripts/create-test-matrix.mjs index 007fd7598..7489c685e 100644 --- a/scripts/create-test-matrix.mjs +++ b/scripts/create-test-matrix.mjs @@ -1,12 +1,53 @@ +// Framework-specific dev test configurations +const DEV_TEST_CONFIGS = { + 'nextjs-turbopack': { + generatedStepPath: 'app/.well-known/workflow/v1/step/route.js', + generatedWorkflowPath: 'app/.well-known/workflow/v1/flow/route.js', + apiFilePath: 'app/api/chat/route.ts', + apiFileImportPath: '../../..', + port: 3000, + }, + 'nextjs-webpack': { + generatedStepPath: 'app/.well-known/workflow/v1/step/route.js', + generatedWorkflowPath: 'app/.well-known/workflow/v1/flow/route.js', + apiFilePath: 'app/api/chat/route.ts', + apiFileImportPath: '../../..', + port: 3000, + }, + nitro: { + generatedStepPath: '.nitro/workflow/steps.mjs', + generatedWorkflowPath: '.nitro/workflow/workflows.mjs', + apiFilePath: 'routes/api/chat.post.ts', + apiFileImportPath: '../..', + port: 3000, + }, + sveltekit: { + generatedStepPath: 'src/routes/.well-known/workflow/v1/step/+server.js', + generatedWorkflowPath: 'src/routes/.well-known/workflow/v1/flow/+server.js', + apiFilePath: 'src/routes/api/chat/+server.ts', + apiFileImportPath: '../../../..', + port: 3000, + }, + vite: { + generatedStepPath: 'dist/workflow/steps.mjs', + generatedWorkflowPath: 'dist/workflow/workflows.mjs', + apiFilePath: 'src/main.ts', + apiFileImportPath: '..', + port: 3000, + }, +}; + const matrix = { app: [ { name: 'nextjs-turbopack', project: 'example-nextjs-workflow-turbopack', + ...DEV_TEST_CONFIGS['nextjs-turbopack'], }, { name: 'nextjs-webpack', project: 'example-nextjs-workflow-webpack', + ...DEV_TEST_CONFIGS['nextjs-webpack'], }, ], }; @@ -24,11 +65,13 @@ if (process.env.GITHUB_REF === 'refs/heads/main') { matrix.app.push({ name: 'nitro', project: 'workbench-nitro-workflow', + ...DEV_TEST_CONFIGS.nitro, }); matrix.app.push({ name: 'sveltekit', project: 'workbench-sveltekit-workflow', + ...DEV_TEST_CONFIGS.sveltekit, }); console.log(JSON.stringify(matrix)); diff --git a/workbench/nextjs-turbopack/app/api/chat/route.ts b/workbench/nextjs-turbopack/app/api/chat/route.ts index a857b6963..da18db04d 100644 --- a/workbench/nextjs-turbopack/app/api/chat/route.ts +++ b/workbench/nextjs-turbopack/app/api/chat/route.ts @@ -1,6 +1,6 @@ // THIS FILE IS JUST FOR TESTING HMR AS AN ENTRY NEEDS // TO IMPORT THE WORKFLOWS TO DISCOVER THEM AND WATCH -import * as workflows from '@/workflows/streams'; +import * as workflows from '@/workflows/3_streams'; export async function POST(_req: Request) { console.log(workflows); diff --git a/workbench/nextjs-turbopack/resolve-symlinks.sh b/workbench/nextjs-turbopack/resolve-symlinks.sh new file mode 100755 index 000000000..425bd7296 --- /dev/null +++ b/workbench/nextjs-turbopack/resolve-symlinks.sh @@ -0,0 +1,43 @@ +#!/bin/bash +set -e + +# Script to recursively resolve all symlinks in the app directory +# This is needed for CI where Next.js dev mode doesn't work well with symlinks + +# Only run in CI +if [ -z "$CI" ]; then + echo "Error: This script should only be run in CI environments" + echo "If you need to resolve symlinks locally, run it manually with CI=true" + exit 1 +fi + +echo "Resolving all symlinks in current directory..." + +# Find all symlinks in current directory (including nested ones), excluding gitignored files +git ls-files -z --cached --others --exclude-standard | xargs -0 -I {} sh -c 'test -L "{}" && echo "{}"' | while read -r symlink; do + # Get the target of the symlink + target=$(readlink "$symlink") + + # Check if target is absolute or relative + if [[ "$target" = /* ]]; then + resolved_target="$target" + else + # Resolve relative symlink path + symlink_dir=$(dirname "$symlink") + resolved_target="$symlink_dir/$target" + fi + + echo "Resolving: $symlink -> $resolved_target" + + # Remove the symlink + rm "$symlink" + + # Copy the target to the symlink location + if [ -d "$resolved_target" ]; then + cp -r "$resolved_target" "$symlink" + else + cp "$resolved_target" "$symlink" + fi +done + +echo "All symlinks resolved successfully!" diff --git a/workbench/nextjs-turbopack/workflows/3_streams.ts b/workbench/nextjs-turbopack/workflows/3_streams.ts new file mode 120000 index 000000000..6b28c945e --- /dev/null +++ b/workbench/nextjs-turbopack/workflows/3_streams.ts @@ -0,0 +1 @@ +../../example/workflows/3_streams.ts \ No newline at end of file diff --git a/workbench/nextjs-turbopack/workflows/streams.ts b/workbench/nextjs-turbopack/workflows/streams.ts deleted file mode 100644 index 15c1123a4..000000000 --- a/workbench/nextjs-turbopack/workflows/streams.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { xxh32 } from '@node-rs/xxhash'; - -// TODO: HMR should work if you have a step only file and -// add a workflow to it -export async function stremWorkflow() { - 'use workflow'; - return 'hi'; -} - -/** - * Durable version of the add function - */ -export async function pumpData(writable: WritableStream) { - 'use step'; - console.log('pumpData', writable); - - const hash = xxh32('hello', 1); - console.log('native hash', hash); - - const writer = writable.getWriter(); - await writer.write('first'); - - await new Promise((resolve) => setTimeout(resolve, 1_000)); - await writer.write('second'); - - return true; -} - -export async function write( - writable: WritableStream, - data: string, - eof = false -) { - 'use step'; - const writer = writable.getWriter(); - await writer.write(new TextEncoder().encode(data)); - if (eof) { - writer.close(); - } -} diff --git a/workbench/nextjs-webpack/resolve-symlinks.sh b/workbench/nextjs-webpack/resolve-symlinks.sh index 425bd7296..6c8a7acbb 100755 --- a/workbench/nextjs-webpack/resolve-symlinks.sh +++ b/workbench/nextjs-webpack/resolve-symlinks.sh @@ -11,10 +11,54 @@ if [ -z "$CI" ]; then exit 1 fi -echo "Resolving all symlinks in current directory..." +echo "Resolving symlinked files in workflows directory..." -# Find all symlinks in current directory (including nested ones), excluding gitignored files +# Special handling for workflows directory if it's a symlink +if [ -L "workflows" ]; then + workflows_target=$(readlink "workflows") + # Resolve relative path + if [[ "$workflows_target" != /* ]]; then + workflows_target="$PWD/$workflows_target" + fi + + echo "Workflows directory is a symlink to: $workflows_target" + + # Remove the workflows symlink + rm "workflows" + + # Create workflows as a real directory + mkdir -p "workflows" + + # Copy all files from the target, resolving any symlinks in the process + if [ -d "$workflows_target" ]; then + for file in "$workflows_target"/*; do + filename=$(basename "$file") + if [ -L "$file" ]; then + # If it's a symlink, resolve it + file_target=$(readlink "$file") + if [[ "$file_target" != /* ]]; then + file_target="$(dirname "$file")/$file_target" + fi + echo " Copying and resolving: $filename -> $file_target" + cp "$file_target" "workflows/$filename" + else + # If it's a regular file, just copy it + echo " Copying: $filename" + cp "$file" "workflows/$filename" + fi + done + fi +fi + +echo "Resolving other symlinks..." + +# Find all other symlinks (excluding workflows which we already handled) git ls-files -z --cached --others --exclude-standard | xargs -0 -I {} sh -c 'test -L "{}" && echo "{}"' | while read -r symlink; do + # Skip workflows directory as we already handled it + if [ "$symlink" = "workflows" ]; then + continue + fi + # Get the target of the symlink target=$(readlink "$symlink") diff --git a/workbench/nitro-v3/package.json b/workbench/nitro-v3/package.json index f6b1e6365..bf388b6df 100644 --- a/workbench/nitro-v3/package.json +++ b/workbench/nitro-v3/package.json @@ -16,5 +16,8 @@ "openai": "^6.1.0", "workflow": "workspace:*", "zod": "catalog:" + }, + "dependencies": { + "@node-rs/xxhash": "1.7.6" } } diff --git a/workbench/nitro-v3/resolve-symlinks.sh b/workbench/nitro-v3/resolve-symlinks.sh new file mode 100755 index 000000000..425bd7296 --- /dev/null +++ b/workbench/nitro-v3/resolve-symlinks.sh @@ -0,0 +1,43 @@ +#!/bin/bash +set -e + +# Script to recursively resolve all symlinks in the app directory +# This is needed for CI where Next.js dev mode doesn't work well with symlinks + +# Only run in CI +if [ -z "$CI" ]; then + echo "Error: This script should only be run in CI environments" + echo "If you need to resolve symlinks locally, run it manually with CI=true" + exit 1 +fi + +echo "Resolving all symlinks in current directory..." + +# Find all symlinks in current directory (including nested ones), excluding gitignored files +git ls-files -z --cached --others --exclude-standard | xargs -0 -I {} sh -c 'test -L "{}" && echo "{}"' | while read -r symlink; do + # Get the target of the symlink + target=$(readlink "$symlink") + + # Check if target is absolute or relative + if [[ "$target" = /* ]]; then + resolved_target="$target" + else + # Resolve relative symlink path + symlink_dir=$(dirname "$symlink") + resolved_target="$symlink_dir/$target" + fi + + echo "Resolving: $symlink -> $resolved_target" + + # Remove the symlink + rm "$symlink" + + # Copy the target to the symlink location + if [ -d "$resolved_target" ]; then + cp -r "$resolved_target" "$symlink" + else + cp "$resolved_target" "$symlink" + fi +done + +echo "All symlinks resolved successfully!" diff --git a/workbench/nitro-v3/routes/api/chat.post.ts b/workbench/nitro-v3/routes/api/chat.post.ts new file mode 100644 index 000000000..c534d8d4b --- /dev/null +++ b/workbench/nitro-v3/routes/api/chat.post.ts @@ -0,0 +1,9 @@ +// THIS FILE IS JUST FOR TESTING HMR AS AN ENTRY NEEDS +// TO IMPORT THE WORKFLOWS TO DISCOVER THEM AND WATCH + +import * as workflows from '../../workflows/3_streams.js'; + +export default async ({ req }: { req: Request }) => { + console.log(workflows); + return Response.json('hello world'); +}; diff --git a/workbench/sveltekit/resolve-symlinks.sh b/workbench/sveltekit/resolve-symlinks.sh new file mode 100755 index 000000000..425bd7296 --- /dev/null +++ b/workbench/sveltekit/resolve-symlinks.sh @@ -0,0 +1,43 @@ +#!/bin/bash +set -e + +# Script to recursively resolve all symlinks in the app directory +# This is needed for CI where Next.js dev mode doesn't work well with symlinks + +# Only run in CI +if [ -z "$CI" ]; then + echo "Error: This script should only be run in CI environments" + echo "If you need to resolve symlinks locally, run it manually with CI=true" + exit 1 +fi + +echo "Resolving all symlinks in current directory..." + +# Find all symlinks in current directory (including nested ones), excluding gitignored files +git ls-files -z --cached --others --exclude-standard | xargs -0 -I {} sh -c 'test -L "{}" && echo "{}"' | while read -r symlink; do + # Get the target of the symlink + target=$(readlink "$symlink") + + # Check if target is absolute or relative + if [[ "$target" = /* ]]; then + resolved_target="$target" + else + # Resolve relative symlink path + symlink_dir=$(dirname "$symlink") + resolved_target="$symlink_dir/$target" + fi + + echo "Resolving: $symlink -> $resolved_target" + + # Remove the symlink + rm "$symlink" + + # Copy the target to the symlink location + if [ -d "$resolved_target" ]; then + cp -r "$resolved_target" "$symlink" + else + cp "$resolved_target" "$symlink" + fi +done + +echo "All symlinks resolved successfully!" diff --git a/workbench/sveltekit/src/routes/api/chat/+server.ts b/workbench/sveltekit/src/routes/api/chat/+server.ts new file mode 100644 index 000000000..3e2b41d90 --- /dev/null +++ b/workbench/sveltekit/src/routes/api/chat/+server.ts @@ -0,0 +1,14 @@ +// THIS FILE IS JUST FOR TESTING HMR AS AN ENTRY NEEDS +// TO IMPORT THE WORKFLOWS TO DISCOVER THEM AND WATCH + +import { json, type RequestHandler } from '@sveltejs/kit'; +import * as workflows from '../../../../workflows/3_streams'; + +export const POST: RequestHandler = async ({ + request, +}: { + request: Request; +}) => { + console.log(workflows); + return json('hello world'); +}; diff --git a/workbench/sveltekit/workflows/3_streams.ts b/workbench/sveltekit/workflows/3_streams.ts new file mode 120000 index 000000000..6b28c945e --- /dev/null +++ b/workbench/sveltekit/workflows/3_streams.ts @@ -0,0 +1 @@ +../../example/workflows/3_streams.ts \ No newline at end of file diff --git a/workbench/vite/package.json b/workbench/vite/package.json index d4669d022..e7828db22 100644 --- a/workbench/vite/package.json +++ b/workbench/vite/package.json @@ -16,5 +16,8 @@ "vite": "^7.1.12", "workflow": "workspace:*", "zod": "catalog:" + }, + "dependencies": { + "@node-rs/xxhash": "1.7.6" } }