From be00280fe6c27968014fa1471d567e95d9adebf3 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Mon, 29 Dec 2025 15:16:28 -0800 Subject: [PATCH 1/7] Pass `tsconfig` to esbuild for support of "paths" aliases Updates the workflow esbuild plugin to accept the `tsconfig` path so that esbuild utilizes the entire config for the features that esbuild supports natively (specifically `"paths"` config, but probably others as well). --- .changeset/soft-pets-yawn.md | 8 +++++++ lib/steps/paths-alias-test.ts | 8 +++++++ packages/astro/src/builder.ts | 21 ++++++---------- packages/builders/src/base-builder.ts | 23 +++++++++--------- packages/builders/src/standalone.ts | 21 ++++++---------- packages/builders/src/swc-esbuild-plugin.ts | 14 ++++------- .../builders/src/vercel-build-output-api.ts | 24 ++++++------------- packages/next/src/builder.ts | 21 ++++++---------- packages/sveltekit/src/builder.ts | 23 +++++++----------- workbench/nextjs-turbopack/tsconfig.json | 3 ++- 10 files changed, 69 insertions(+), 97 deletions(-) create mode 100644 .changeset/soft-pets-yawn.md create mode 100644 lib/steps/paths-alias-test.ts diff --git a/.changeset/soft-pets-yawn.md b/.changeset/soft-pets-yawn.md new file mode 100644 index 000000000..d9ced682e --- /dev/null +++ b/.changeset/soft-pets-yawn.md @@ -0,0 +1,8 @@ +--- +"@workflow/sveltekit": patch +"@workflow/builders": patch +"@workflow/astro": patch +"@workflow/next": patch +--- + +Pass `tsconfig` to esbuild for support of "paths" aliases diff --git a/lib/steps/paths-alias-test.ts b/lib/steps/paths-alias-test.ts new file mode 100644 index 000000000..e2cad9b3b --- /dev/null +++ b/lib/steps/paths-alias-test.ts @@ -0,0 +1,8 @@ +/** + * This is a test step function from outside the workbench app directory. + * It is used to test that the swc-esbuild-plugin can resolve tsconfig path aliases. + */ +export async function pathsAliasTest() { + 'use step'; + return 'pathsAliasTest'; +} diff --git a/packages/astro/src/builder.ts b/packages/astro/src/builder.ts index 523724712..b7c692181 100644 --- a/packages/astro/src/builder.ts +++ b/packages/astro/src/builder.ts @@ -55,8 +55,7 @@ export class LocalBuilder extends BaseBuilder { const options = { inputFiles, workflowGeneratedDir, - tsBaseUrl: tsConfig.baseUrl, - tsPaths: tsConfig.paths, + tsconfigPath: tsConfig.tsconfigPath, }; // Generate the three Astro route handlers @@ -68,13 +67,11 @@ export class LocalBuilder extends BaseBuilder { private async buildStepsRoute({ inputFiles, workflowGeneratedDir, - tsPaths, - tsBaseUrl, + tsconfigPath, }: { inputFiles: string[]; workflowGeneratedDir: string; - tsBaseUrl?: string; - tsPaths?: Record; + tsconfigPath?: string; }) { // Create steps route: .well-known/workflow/v1/step.js const stepsRouteFile = join(workflowGeneratedDir, 'step.js'); @@ -83,8 +80,7 @@ export class LocalBuilder extends BaseBuilder { inputFiles, outfile: stepsRouteFile, externalizeNonSteps: true, - tsBaseUrl, - tsPaths, + tsconfigPath, }); let stepsRouteContent = await readFile(stepsRouteFile, 'utf-8'); @@ -106,13 +102,11 @@ export const prerender = false;` private async buildWorkflowsRoute({ inputFiles, workflowGeneratedDir, - tsPaths, - tsBaseUrl, + tsconfigPath, }: { inputFiles: string[]; workflowGeneratedDir: string; - tsBaseUrl?: string; - tsPaths?: Record; + tsconfigPath?: string; }) { // Create workflows route: .well-known/workflow/v1/flow.js const workflowsRouteFile = join(workflowGeneratedDir, 'flow.js'); @@ -121,8 +115,7 @@ export const prerender = false;` outfile: workflowsRouteFile, bundleFinalOutput: false, inputFiles, - tsBaseUrl, - tsPaths, + tsconfigPath, }); let workflowsRouteContent = await readFile(workflowsRouteFile, 'utf-8'); diff --git a/packages/builders/src/base-builder.ts b/packages/builders/src/base-builder.ts index 043e3ff50..5092b4ddc 100644 --- a/packages/builders/src/base-builder.ts +++ b/packages/builders/src/base-builder.ts @@ -46,10 +46,12 @@ export abstract class BaseBuilder { protected async getTsConfigOptions(): Promise<{ baseUrl?: string; paths?: Record; + tsconfigPath?: string; }> { const options: { paths?: Record; baseUrl?: string; + tsconfigPath?: string; } = {}; const cwd = this.config.workingDir || process.cwd(); @@ -59,6 +61,7 @@ export abstract class BaseBuilder { }); if (tsJsConfig) { + options.tsconfigPath = tsJsConfig; try { const rawJson = await readFile(tsJsConfig, 'utf8'); const parsed: null | { @@ -288,11 +291,9 @@ export abstract class BaseBuilder { format = 'cjs', outfile, externalizeNonSteps, - tsBaseUrl, - tsPaths, + tsconfigPath, }: { - tsPaths?: Record; - tsBaseUrl?: string; + tsconfigPath?: string; inputFiles: string[]; outfile: string; format?: 'cjs' | 'esm'; @@ -394,6 +395,8 @@ export abstract class BaseBuilder { minify: false, jsx: 'preserve', logLevel: 'error', + // Use tsconfig for path alias resolution + tsconfig: tsconfigPath, resolveExtensions: [ '.ts', '.tsx', @@ -411,8 +414,6 @@ export abstract class BaseBuilder { mode: 'step', entriesToBundle: externalizeNonSteps ? combinedStepFiles : undefined, outdir: outfile ? dirname(outfile) : undefined, - tsBaseUrl, - tsPaths, workflowManifest, }), ], @@ -447,11 +448,9 @@ export abstract class BaseBuilder { format = 'cjs', outfile, bundleFinalOutput = true, - tsBaseUrl, - tsPaths, + tsconfigPath, }: { - tsPaths?: Record; - tsBaseUrl?: string; + tsconfigPath?: string; inputFiles: string[]; outfile: string; format?: 'cjs' | 'esm'; @@ -523,6 +522,8 @@ export abstract class BaseBuilder { // This intermediate bundle is executed via runInContext() in a VM, so we need // inline source maps to get meaningful stack traces instead of "evalmachine.". sourcemap: 'inline', + // Use tsconfig for path alias resolution + tsconfig: tsconfigPath, resolveExtensions: [ '.ts', '.tsx', @@ -536,8 +537,6 @@ export abstract class BaseBuilder { plugins: [ createSwcPlugin({ mode: 'workflow', - tsBaseUrl, - tsPaths, workflowManifest, }), // This plugin must run after the swc plugin to ensure dead code elimination diff --git a/packages/builders/src/standalone.ts b/packages/builders/src/standalone.ts index eb11c0454..0a156affe 100644 --- a/packages/builders/src/standalone.ts +++ b/packages/builders/src/standalone.ts @@ -15,8 +15,7 @@ export class StandaloneBuilder extends BaseBuilder { const options = { inputFiles, - tsBaseUrl: tsConfig.baseUrl, - tsPaths: tsConfig.paths, + tsconfigPath: tsConfig.tsconfigPath, }; const manifest = await this.buildStepsBundle(options); await this.buildWorkflowsBundle(options); @@ -38,12 +37,10 @@ export class StandaloneBuilder extends BaseBuilder { private async buildStepsBundle({ inputFiles, - tsPaths, - tsBaseUrl, + tsconfigPath, }: { inputFiles: string[]; - tsBaseUrl?: string; - tsPaths?: Record; + tsconfigPath?: string; }) { console.log('Creating steps bundle at', this.config.stepsBundlePath); @@ -53,8 +50,7 @@ export class StandaloneBuilder extends BaseBuilder { const { manifest } = await this.createStepsBundle({ outfile: stepsBundlePath, inputFiles, - tsBaseUrl, - tsPaths, + tsconfigPath, }); return manifest; @@ -62,12 +58,10 @@ export class StandaloneBuilder extends BaseBuilder { private async buildWorkflowsBundle({ inputFiles, - tsPaths, - tsBaseUrl, + tsconfigPath, }: { inputFiles: string[]; - tsBaseUrl?: string; - tsPaths?: Record; + tsconfigPath?: string; }): Promise { console.log( 'Creating workflows bundle at', @@ -82,8 +76,7 @@ export class StandaloneBuilder extends BaseBuilder { await this.createWorkflowsBundle({ outfile: workflowBundlePath, inputFiles, - tsBaseUrl, - tsPaths, + tsconfigPath, }); } diff --git a/packages/builders/src/swc-esbuild-plugin.ts b/packages/builders/src/swc-esbuild-plugin.ts index f19766391..81d7b4c6d 100644 --- a/packages/builders/src/swc-esbuild-plugin.ts +++ b/packages/builders/src/swc-esbuild-plugin.ts @@ -16,8 +16,6 @@ export interface SwcPluginOptions { mode: 'step' | 'workflow' | 'client'; entriesToBundle?: string[]; outdir?: string; - tsPaths?: Record; - tsBaseUrl?: string; workflowManifest?: WorkflowManifest; } @@ -209,14 +207,10 @@ export function createSwcPlugin(options: SwcPluginOptions): Plugin { await applySwcTransform( relativeFilepath, source, - options.mode, - // we need to provide the tsconfig/jsconfig - // alias via swc so that we can resolve them - // with our custom resolve logic - { - paths: options.tsPaths, - baseUrl: options.tsBaseUrl, - } + options.mode + // Note: We don't pass paths/baseUrl to SWC because: + // 1. SWC's path resolution produces relative paths that esbuild can't resolve + // 2. We handle TypeScript path aliases in our onResolve handler instead ); if (!options.workflowManifest) { diff --git a/packages/builders/src/vercel-build-output-api.ts b/packages/builders/src/vercel-build-output-api.ts index 943e7492a..094735635 100644 --- a/packages/builders/src/vercel-build-output-api.ts +++ b/packages/builders/src/vercel-build-output-api.ts @@ -17,8 +17,7 @@ export class VercelBuildOutputAPIBuilder extends BaseBuilder { const options = { inputFiles, workflowGeneratedDir, - tsBaseUrl: tsConfig.baseUrl, - tsPaths: tsConfig.paths, + tsconfigPath: tsConfig.tsconfigPath, }; const manifest = await this.buildStepsFunction(options); await this.buildWorkflowsFunction(options); @@ -39,13 +38,11 @@ export class VercelBuildOutputAPIBuilder extends BaseBuilder { private async buildStepsFunction({ inputFiles, workflowGeneratedDir, - tsPaths, - tsBaseUrl, + tsconfigPath, }: { inputFiles: string[]; workflowGeneratedDir: string; - tsBaseUrl?: string; - tsPaths?: Record; + tsconfigPath?: string; }) { console.log('Creating Vercel Build Output API steps function'); const stepsFuncDir = join(workflowGeneratedDir, 'step.func'); @@ -55,8 +52,7 @@ export class VercelBuildOutputAPIBuilder extends BaseBuilder { const { manifest } = await this.createStepsBundle({ inputFiles, outfile: join(stepsFuncDir, 'index.js'), - tsBaseUrl, - tsPaths, + tsconfigPath, }); // Create package.json and .vc-config.json for steps function @@ -72,13 +68,11 @@ export class VercelBuildOutputAPIBuilder extends BaseBuilder { private async buildWorkflowsFunction({ inputFiles, workflowGeneratedDir, - tsPaths, - tsBaseUrl, + tsconfigPath, }: { inputFiles: string[]; workflowGeneratedDir: string; - tsBaseUrl?: string; - tsPaths?: Record; + tsconfigPath?: string; }): Promise { console.log('Creating Vercel Build Output API workflows function'); const workflowsFuncDir = join(workflowGeneratedDir, 'flow.func'); @@ -87,8 +81,7 @@ export class VercelBuildOutputAPIBuilder extends BaseBuilder { await this.createWorkflowsBundle({ outfile: join(workflowsFuncDir, 'index.js'), inputFiles, - tsBaseUrl, - tsPaths, + tsconfigPath, }); // Create package.json and .vc-config.json for workflows function @@ -102,10 +95,7 @@ export class VercelBuildOutputAPIBuilder extends BaseBuilder { workflowGeneratedDir, bundle = true, }: { - inputFiles: string[]; workflowGeneratedDir: string; - tsBaseUrl?: string; - tsPaths?: Record; bundle?: boolean; }): Promise { console.log('Creating Vercel Build Output API webhook function'); diff --git a/packages/next/src/builder.ts b/packages/next/src/builder.ts index d6f55eb2b..fee16bae5 100644 --- a/packages/next/src/builder.ts +++ b/packages/next/src/builder.ts @@ -107,8 +107,7 @@ export async function getNextBuilder() { const options = { inputFiles: files, workflowGeneratedDir, - tsBaseUrl: tsConfig.baseUrl, - tsPaths: tsConfig.paths, + tsconfigPath: tsConfig.tsconfigPath, }; const { manifest } = await this.buildStepsFunction(options); @@ -167,13 +166,11 @@ export async function getNextBuilder() { private async buildStepsFunction({ inputFiles, workflowGeneratedDir, - tsPaths, - tsBaseUrl, + tsconfigPath, }: { inputFiles: string[]; workflowGeneratedDir: string; - tsBaseUrl?: string; - tsPaths?: Record; + tsconfigPath?: string; }) { // Create steps bundle const stepsRouteDir = join(workflowGeneratedDir, 'step'); @@ -188,21 +185,18 @@ export async function getNextBuilder() { inputFiles, outfile: join(stepsRouteDir, 'route.js'), externalizeNonSteps: true, - tsBaseUrl, - tsPaths, + tsconfigPath, }); } private async buildWorkflowsFunction({ inputFiles, workflowGeneratedDir, - tsPaths, - tsBaseUrl, + tsconfigPath, }: { inputFiles: string[]; workflowGeneratedDir: string; - tsBaseUrl?: string; - tsPaths?: Record; + tsconfigPath?: string; }): Promise Promise; @@ -214,8 +208,7 @@ export async function getNextBuilder() { outfile: join(workflowsRouteDir, 'route.js'), bundleFinalOutput: false, inputFiles, - tsBaseUrl, - tsPaths, + tsconfigPath, }); } diff --git a/packages/sveltekit/src/builder.ts b/packages/sveltekit/src/builder.ts index b96fc3b56..63398310e 100644 --- a/packages/sveltekit/src/builder.ts +++ b/packages/sveltekit/src/builder.ts @@ -3,8 +3,8 @@ import { access, mkdir, readFile, stat, writeFile } from 'node:fs/promises'; import { join, resolve } from 'node:path'; import { BaseBuilder, - type SvelteKitConfig, NORMALIZE_REQUEST_CODE, + type SvelteKitConfig, } from '@workflow/builders'; const SVELTEKIT_VIRTUAL_MODULES = [ @@ -50,8 +50,7 @@ export class SvelteKitBuilder extends BaseBuilder { const options = { inputFiles, workflowGeneratedDir, - tsBaseUrl: tsConfig.baseUrl, - tsPaths: tsConfig.paths, + tsconfigPath: tsConfig.tsconfigPath, }; // Generate the three SvelteKit route handlers @@ -71,13 +70,11 @@ export class SvelteKitBuilder extends BaseBuilder { private async buildStepsRoute({ inputFiles, workflowGeneratedDir, - tsPaths, - tsBaseUrl, + tsconfigPath, }: { inputFiles: string[]; workflowGeneratedDir: string; - tsBaseUrl?: string; - tsPaths?: Record; + tsconfigPath?: string; }) { // Create steps route: .well-known/workflow/v1/step/+server.js const stepsRouteDir = join(workflowGeneratedDir, 'step'); @@ -88,8 +85,7 @@ export class SvelteKitBuilder extends BaseBuilder { inputFiles, outfile: join(stepsRouteDir, '+server.js'), externalizeNonSteps: true, - tsBaseUrl, - tsPaths, + tsconfigPath, }); // Post-process the generated file to wrap with SvelteKit request converter @@ -113,13 +109,11 @@ export const POST = async ({request}) => { private async buildWorkflowsRoute({ inputFiles, workflowGeneratedDir, - tsPaths, - tsBaseUrl, + tsconfigPath, }: { inputFiles: string[]; workflowGeneratedDir: string; - tsBaseUrl?: string; - tsPaths?: Record; + tsconfigPath?: string; }) { // Create workflows route: .well-known/workflow/v1/flow/+server.js const workflowsRouteDir = join(workflowGeneratedDir, 'flow'); @@ -130,8 +124,7 @@ export const POST = async ({request}) => { outfile: join(workflowsRouteDir, '+server.js'), bundleFinalOutput: false, inputFiles, - tsBaseUrl, - tsPaths, + tsconfigPath, }); // Post-process the generated file to wrap with SvelteKit request converter diff --git a/workbench/nextjs-turbopack/tsconfig.json b/workbench/nextjs-turbopack/tsconfig.json index e60feac10..57f5d5a88 100644 --- a/workbench/nextjs-turbopack/tsconfig.json +++ b/workbench/nextjs-turbopack/tsconfig.json @@ -22,7 +22,8 @@ } ], "paths": { - "@/*": ["./*"] + "@/*": ["./*"], + "@repo/*": ["../../*"] } }, "include": [ From 94ac5e5f50efcec2af4a65204f65b08df09e7df0 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Mon, 29 Dec 2025 15:21:53 -0800 Subject: [PATCH 2/7] . --- packages/builders/src/apply-swc-transform.ts | 10 +--------- packages/builders/src/swc-esbuild-plugin.ts | 9 +-------- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/packages/builders/src/apply-swc-transform.ts b/packages/builders/src/apply-swc-transform.ts index 6d98598b9..5cd01a0e7 100644 --- a/packages/builders/src/apply-swc-transform.ts +++ b/packages/builders/src/apply-swc-transform.ts @@ -23,12 +23,7 @@ export type WorkflowManifest = { export async function applySwcTransform( filename: string, source: string, - mode: 'workflow' | 'step' | 'client' | false, - jscConfig?: { - paths?: Record; - // this must be absolute path - baseUrl?: string; - } + mode: 'workflow' | 'step' | 'client' | false ): Promise<{ code: string; workflowManifest: WorkflowManifest; @@ -62,9 +57,6 @@ export async function applySwcTransform( plugins: [[require.resolve('@workflow/swc-plugin'), { mode }]], } : undefined, - - ...jscConfig, - transform: { react: { runtime: 'preserve', diff --git a/packages/builders/src/swc-esbuild-plugin.ts b/packages/builders/src/swc-esbuild-plugin.ts index 81d7b4c6d..371517367 100644 --- a/packages/builders/src/swc-esbuild-plugin.ts +++ b/packages/builders/src/swc-esbuild-plugin.ts @@ -204,14 +204,7 @@ export function createSwcPlugin(options: SwcPluginOptions): Plugin { } const { code: transformedCode, workflowManifest } = - await applySwcTransform( - relativeFilepath, - source, - options.mode - // Note: We don't pass paths/baseUrl to SWC because: - // 1. SWC's path resolution produces relative paths that esbuild can't resolve - // 2. We handle TypeScript path aliases in our onResolve handler instead - ); + await applySwcTransform(relativeFilepath, source, options.mode); if (!options.workflowManifest) { options.workflowManifest = {}; From e8f061e2ca87ae31788d7cc7d8dd8e2fdc064ccf Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Mon, 29 Dec 2025 15:27:26 -0800 Subject: [PATCH 3/7] Remove manual tsconfig parsing --- packages/builders/package.json | 1 - packages/builders/src/base-builder.ts | 45 +++------------------------ packages/cli/package.json | 1 - pnpm-lock.yaml | 35 --------------------- 4 files changed, 4 insertions(+), 78 deletions(-) diff --git a/packages/builders/package.json b/packages/builders/package.json index 071aff37d..9c8637a5e 100644 --- a/packages/builders/package.json +++ b/packages/builders/package.json @@ -45,7 +45,6 @@ "@workflow/core": "workspace:*", "builtin-modules": "5.0.0", "chalk": "5.6.2", - "comment-json": "4.2.5", "enhanced-resolve": "5.18.2", "esbuild": "catalog:", "find-up": "7.0.0", diff --git a/packages/builders/src/base-builder.ts b/packages/builders/src/base-builder.ts index 5092b4ddc..de41860cb 100644 --- a/packages/builders/src/base-builder.ts +++ b/packages/builders/src/base-builder.ts @@ -3,7 +3,6 @@ import { mkdir, readFile, rename, writeFile } from 'node:fs/promises'; import { basename, dirname, join, relative, resolve } from 'node:path'; import { promisify } from 'node:util'; import chalk from 'chalk'; -import { parse } from 'comment-json'; import enhancedResolveOriginal from 'enhanced-resolve'; import * as esbuild from 'esbuild'; import { findUp } from 'find-up'; @@ -40,55 +39,19 @@ export abstract class BaseBuilder { abstract build(): Promise; /** - * Extracts TypeScript path mappings and baseUrl from tsconfig.json/jsconfig.json. - * Used to properly resolve module imports during bundling. + * Finds tsconfig.json/jsconfig.json for the project. + * Used by esbuild to properly resolve module imports during bundling. */ protected async getTsConfigOptions(): Promise<{ - baseUrl?: string; - paths?: Record; tsconfigPath?: string; }> { - const options: { - paths?: Record; - baseUrl?: string; - tsconfigPath?: string; - } = {}; - const cwd = this.config.workingDir || process.cwd(); - const tsJsConfig = await findUp(['tsconfig.json', 'jsconfig.json'], { + const tsconfigPath = await findUp(['tsconfig.json', 'jsconfig.json'], { cwd, }); - if (tsJsConfig) { - options.tsconfigPath = tsJsConfig; - try { - const rawJson = await readFile(tsJsConfig, 'utf8'); - const parsed: null | { - compilerOptions?: { - paths?: Record | undefined; - baseUrl?: string; - }; - } = parse(rawJson) as any; - - if (parsed) { - options.paths = parsed.compilerOptions?.paths; - - if (parsed.compilerOptions?.baseUrl) { - options.baseUrl = resolve(cwd, parsed.compilerOptions.baseUrl); - } else { - options.baseUrl = cwd; - } - } - } catch (err) { - console.error( - `Failed to parse ${tsJsConfig} aliases might not apply properly`, - err - ); - } - } - - return options; + return { tsconfigPath }; } /** diff --git a/packages/cli/package.json b/packages/cli/package.json index c48d5daa8..3f039ff42 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -56,7 +56,6 @@ "builtin-modules": "5.0.0", "chalk": "5.6.2", "chokidar": "4.0.3", - "comment-json": "4.2.5", "date-fns": "4.1.0", "easy-table": "1.2.0", "enhanced-resolve": "5.18.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4efd7ae96..8d59c2ff3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -391,9 +391,6 @@ importers: chalk: specifier: 5.6.2 version: 5.6.2 - comment-json: - specifier: 4.2.5 - version: 4.2.5 enhanced-resolve: specifier: 5.18.2 version: 5.18.2 @@ -464,9 +461,6 @@ importers: chokidar: specifier: 4.0.3 version: 4.0.3 - comment-json: - specifier: 4.2.5 - version: 4.2.5 date-fns: specifier: 4.1.0 version: 4.1.0 @@ -7007,9 +7001,6 @@ packages: array-iterate@2.0.1: resolution: {integrity: sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==} - array-timsort@1.0.3: - resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} - array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} @@ -7507,10 +7498,6 @@ packages: resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} engines: {node: '>= 12'} - comment-json@4.2.5: - resolution: {integrity: sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==} - engines: {node: '>= 6'} - common-ancestor-path@1.0.1: resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==} @@ -9021,10 +9008,6 @@ packages: resolution: {integrity: sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA==} engines: {node: '>=12'} - has-own-prop@2.0.0: - resolution: {integrity: sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==} - engines: {node: '>=8'} - has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} @@ -11571,10 +11554,6 @@ packages: remove-trailing-separator@1.1.0: resolution: {integrity: sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==} - repeat-string@1.6.1: - resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} - engines: {node: '>=0.10'} - require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -19772,8 +19751,6 @@ snapshots: array-iterate@2.0.1: {} - array-timsort@1.0.3: {} - array-union@2.1.0: {} asn1@0.2.6: @@ -20469,14 +20446,6 @@ snapshots: commander@8.3.0: {} - comment-json@4.2.5: - dependencies: - array-timsort: 1.0.3 - core-util-is: 1.0.3 - esprima: 4.0.1 - has-own-prop: 2.0.0 - repeat-string: 1.6.1 - common-ancestor-path@1.0.1: {} common-path-prefix@3.0.0: {} @@ -22133,8 +22102,6 @@ snapshots: has-flag@5.0.1: {} - has-own-prop@2.0.0: {} - has-symbols@1.1.0: {} hasown@2.0.2: @@ -25839,8 +25806,6 @@ snapshots: remove-trailing-separator@1.1.0: {} - repeat-string@1.6.1: {} - require-directory@2.1.1: {} require-from-string@2.0.2: {} From 6e5a98d6b823d50a7d91f6d9a24910d21018268f Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Mon, 29 Dec 2025 15:40:46 -0800 Subject: [PATCH 4/7] . --- lib/steps/paths-alias-test.ts | 11 ++++---- packages/astro/src/builder.ts | 4 +-- packages/builders/src/base-builder.ts | 11 ++------ packages/builders/src/standalone.ts | 4 +-- .../builders/src/vercel-build-output-api.ts | 4 +-- packages/core/e2e/e2e.test.ts | 21 ++++++++++++++++ packages/next/src/builder.ts | 4 +-- packages/sveltekit/src/builder.ts | 4 +-- workbench/example/tsconfig.json | 3 ++- workbench/example/workflows/99_e2e.ts | 25 +++++++++++++++++++ workbench/nitro-v3/tsconfig.json | 3 ++- 11 files changed, 68 insertions(+), 26 deletions(-) diff --git a/lib/steps/paths-alias-test.ts b/lib/steps/paths-alias-test.ts index e2cad9b3b..30976050c 100644 --- a/lib/steps/paths-alias-test.ts +++ b/lib/steps/paths-alias-test.ts @@ -1,8 +1,9 @@ /** - * This is a test step function from outside the workbench app directory. - * It is used to test that the swc-esbuild-plugin can resolve tsconfig path aliases. + * This is a utility function from outside the workbench app directory. + * It is used to test that esbuild can resolve tsconfig path aliases. + * Note: This is NOT a step function - it's a regular function that gets called + * from within a step to verify path alias imports work correctly. */ -export async function pathsAliasTest() { - 'use step'; - return 'pathsAliasTest'; +export function pathsAliasHelper(): string { + return 'pathsAliasHelper'; } diff --git a/packages/astro/src/builder.ts b/packages/astro/src/builder.ts index b7c692181..c60f2a2ce 100644 --- a/packages/astro/src/builder.ts +++ b/packages/astro/src/builder.ts @@ -50,12 +50,12 @@ export class LocalBuilder extends BaseBuilder { // Get workflow and step files to bundle const inputFiles = await this.getInputFiles(); - const tsConfig = await this.getTsConfigOptions(); + const tsconfigPath = await this.findTsConfigPath(); const options = { inputFiles, workflowGeneratedDir, - tsconfigPath: tsConfig.tsconfigPath, + tsconfigPath, }; // Generate the three Astro route handlers diff --git a/packages/builders/src/base-builder.ts b/packages/builders/src/base-builder.ts index de41860cb..892b2f837 100644 --- a/packages/builders/src/base-builder.ts +++ b/packages/builders/src/base-builder.ts @@ -42,16 +42,9 @@ export abstract class BaseBuilder { * Finds tsconfig.json/jsconfig.json for the project. * Used by esbuild to properly resolve module imports during bundling. */ - protected async getTsConfigOptions(): Promise<{ - tsconfigPath?: string; - }> { + protected async findTsConfigPath(): Promise { const cwd = this.config.workingDir || process.cwd(); - - const tsconfigPath = await findUp(['tsconfig.json', 'jsconfig.json'], { - cwd, - }); - - return { tsconfigPath }; + return findUp(['tsconfig.json', 'jsconfig.json'], { cwd }); } /** diff --git a/packages/builders/src/standalone.ts b/packages/builders/src/standalone.ts index 0a156affe..4c32ae4bb 100644 --- a/packages/builders/src/standalone.ts +++ b/packages/builders/src/standalone.ts @@ -11,11 +11,11 @@ export class StandaloneBuilder extends BaseBuilder { async build(): Promise { const inputFiles = await this.getInputFiles(); - const tsConfig = await this.getTsConfigOptions(); + const tsconfigPath = await this.findTsConfigPath(); const options = { inputFiles, - tsconfigPath: tsConfig.tsconfigPath, + tsconfigPath, }; const manifest = await this.buildStepsBundle(options); await this.buildWorkflowsBundle(options); diff --git a/packages/builders/src/vercel-build-output-api.ts b/packages/builders/src/vercel-build-output-api.ts index 094735635..903e0eb5c 100644 --- a/packages/builders/src/vercel-build-output-api.ts +++ b/packages/builders/src/vercel-build-output-api.ts @@ -13,11 +13,11 @@ export class VercelBuildOutputAPIBuilder extends BaseBuilder { await mkdir(workflowGeneratedDir, { recursive: true }); const inputFiles = await this.getInputFiles(); - const tsConfig = await this.getTsConfigOptions(); + const tsconfigPath = await this.findTsConfigPath(); const options = { inputFiles, workflowGeneratedDir, - tsconfigPath: tsConfig.tsconfigPath, + tsconfigPath, }; const manifest = await this.buildStepsFunction(options); await this.buildWorkflowsFunction(options); diff --git a/packages/core/e2e/e2e.test.ts b/packages/core/e2e/e2e.test.ts index e39107b1b..50bf2233f 100644 --- a/packages/core/e2e/e2e.test.ts +++ b/packages/core/e2e/e2e.test.ts @@ -952,4 +952,25 @@ describe('e2e', () => { ); } ); + + test( + 'pathsAliasWorkflow - TypeScript path aliases resolve correctly', + { timeout: 60_000 }, + async () => { + // This workflow uses a step that calls a helper function imported via @repo/* path alias + // which resolves to a file outside the workbench directory (../../lib/steps/paths-alias-test.ts) + const run = await triggerWorkflow('pathsAliasWorkflow', []); + const returnValue = await getWorkflowReturnValue(run.runId); + + // The step should return the helper's identifier string + expect(returnValue).toBe('pathsAliasHelper'); + + // Verify the run completed successfully + const { json: runData } = await cliInspectJson( + `runs ${run.runId} --withData` + ); + expect(runData.status).toBe('completed'); + expect(runData.output).toBe('pathsAliasHelper'); + } + ); }); diff --git a/packages/next/src/builder.ts b/packages/next/src/builder.ts index fee16bae5..72bb2ce28 100644 --- a/packages/next/src/builder.ts +++ b/packages/next/src/builder.ts @@ -102,12 +102,12 @@ export async function getNextBuilder() { // Use provided inputFiles or discover them const files = inputFiles || (await this.getInputFiles()); - const tsConfig = await this.getTsConfigOptions(); + const tsconfigPath = await this.findTsConfigPath(); const options = { inputFiles: files, workflowGeneratedDir, - tsconfigPath: tsConfig.tsconfigPath, + tsconfigPath, }; const { manifest } = await this.buildStepsFunction(options); diff --git a/packages/sveltekit/src/builder.ts b/packages/sveltekit/src/builder.ts index 63398310e..1277c1b8f 100644 --- a/packages/sveltekit/src/builder.ts +++ b/packages/sveltekit/src/builder.ts @@ -45,12 +45,12 @@ export class SvelteKitBuilder extends BaseBuilder { // Get workflow and step files to bundle const inputFiles = await this.getInputFiles(); - const tsConfig = await this.getTsConfigOptions(); + const tsconfigPath = await this.findTsConfigPath(); const options = { inputFiles, workflowGeneratedDir, - tsconfigPath: tsConfig.tsconfigPath, + tsconfigPath, }; // Generate the three SvelteKit route handlers diff --git a/workbench/example/tsconfig.json b/workbench/example/tsconfig.json index 6eaef8b8d..39c2f1ea6 100644 --- a/workbench/example/tsconfig.json +++ b/workbench/example/tsconfig.json @@ -14,7 +14,8 @@ "jsx": "preserve", "incremental": true, "paths": { - "@/*": ["./*"] + "@/*": ["./*"], + "@repo/*": ["../../*"] }, "plugins": [ { diff --git a/workbench/example/workflows/99_e2e.ts b/workbench/example/workflows/99_e2e.ts index 1cb227c54..8d6b3329f 100644 --- a/workbench/example/workflows/99_e2e.ts +++ b/workbench/example/workflows/99_e2e.ts @@ -1,3 +1,5 @@ +// Test path alias resolution - imports a helper from outside the workbench directory +import { pathsAliasHelper } from '@repo/lib/steps/paths-alias-test'; import { createHook, createWebhook, @@ -609,3 +611,26 @@ export async function spawnWorkflowFromStepWorkflow(inputValue: number) { childResult, }; } + +////////////////////////////////////////////////////////// + +/** + * Step that calls a helper function imported via path alias. + */ +async function callPathsAliasHelper() { + 'use step'; + // Call the helper function imported via @repo/* path alias + return pathsAliasHelper(); +} + +/** + * Test that TypeScript path aliases work correctly. + * This workflow uses a step that calls a helper function imported via the @repo/* path alias, + * which resolves to a file outside the workbench directory. + */ +export async function pathsAliasWorkflow() { + 'use workflow'; + // Call the step that uses the path alias helper + const result = await callPathsAliasHelper(); + return result; +} diff --git a/workbench/nitro-v3/tsconfig.json b/workbench/nitro-v3/tsconfig.json index 6eaef8b8d..39c2f1ea6 100644 --- a/workbench/nitro-v3/tsconfig.json +++ b/workbench/nitro-v3/tsconfig.json @@ -14,7 +14,8 @@ "jsx": "preserve", "incremental": true, "paths": { - "@/*": ["./*"] + "@/*": ["./*"], + "@repo/*": ["../../*"] }, "plugins": [ { From c029545b8dcf4973d248949cdf0c59fa4db36623 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Tue, 30 Dec 2025 10:38:44 -0800 Subject: [PATCH 5/7] . --- workbench/astro/tsconfig.json | 5 +++++ workbench/nuxt/nuxt.config.ts | 3 +++ workbench/nuxt/tsconfig.json | 19 +++++-------------- workbench/sveltekit/svelte.config.js | 3 +++ workbench/sveltekit/tsconfig.json | 10 ++++------ 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/workbench/astro/tsconfig.json b/workbench/astro/tsconfig.json index 8bf91d3bb..3bd9c20ba 100644 --- a/workbench/astro/tsconfig.json +++ b/workbench/astro/tsconfig.json @@ -1,5 +1,10 @@ { "extends": "astro/tsconfigs/strict", + "compilerOptions": { + "paths": { + "@repo/*": ["../../*"] + } + }, "include": [".astro/types.d.ts", "**/*"], "exclude": ["dist"] } diff --git a/workbench/nuxt/nuxt.config.ts b/workbench/nuxt/nuxt.config.ts index b0ddf9a1e..8c8e02325 100644 --- a/workbench/nuxt/nuxt.config.ts +++ b/workbench/nuxt/nuxt.config.ts @@ -1,4 +1,7 @@ export default defineNuxtConfig({ compatibilityDate: 'latest', modules: ['workflow/nuxt'], + alias: { + '@repo': '../../', + }, }); diff --git a/workbench/nuxt/tsconfig.json b/workbench/nuxt/tsconfig.json index 6ae5970c7..bff9c1fde 100644 --- a/workbench/nuxt/tsconfig.json +++ b/workbench/nuxt/tsconfig.json @@ -1,17 +1,8 @@ { - "files": [], - "references": [ - { - "path": "./.nuxt/tsconfig.app.json" - }, - { - "path": "./.nuxt/tsconfig.server.json" - }, - { - "path": "./.nuxt/tsconfig.shared.json" - }, - { - "path": "./.nuxt/tsconfig.node.json" + "extends": "./.nuxt/tsconfig.json", + "compilerOptions": { + "paths": { + "@repo/*": ["../../*"] } - ] + } } diff --git a/workbench/sveltekit/svelte.config.js b/workbench/sveltekit/svelte.config.js index 5d665261c..f0e887e20 100644 --- a/workbench/sveltekit/svelte.config.js +++ b/workbench/sveltekit/svelte.config.js @@ -17,6 +17,9 @@ const config = { // In production, specify only trusted origins or remove this configuration // to use SvelteKit's default CSRF protection. csrf: { trustedOrigins: ['*'] }, + alias: { + '@repo/*': '../../*', + }, }, }; diff --git a/workbench/sveltekit/tsconfig.json b/workbench/sveltekit/tsconfig.json index e3898cb22..04b8060fb 100644 --- a/workbench/sveltekit/tsconfig.json +++ b/workbench/sveltekit/tsconfig.json @@ -9,11 +9,9 @@ "skipLibCheck": true, "sourceMap": true, "strict": true, - "moduleResolution": "bundler" + "moduleResolution": "bundler", + "paths": { + "@repo/*": ["../../*"] + } } - // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias - // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files - // - // To make changes to top-level options such as include and exclude, we recommend extending - // the generated config; see https://svelte.dev/docs/kit/configuration#typescript } From 983c0694cea41d1dfd791fe0a098482a34aed556 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Tue, 30 Dec 2025 10:57:19 -0800 Subject: [PATCH 6/7] . --- workbench/express/tsconfig.json | 28 ++++++++++++++++++++++++++++ workbench/fastify/tsconfig.json | 28 ++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 workbench/express/tsconfig.json create mode 100644 workbench/fastify/tsconfig.json diff --git a/workbench/express/tsconfig.json b/workbench/express/tsconfig.json new file mode 100644 index 000000000..39c2f1ea6 --- /dev/null +++ b/workbench/express/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "es2022", + "module": "NodeNext", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "moduleResolution": "NodeNext", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "paths": { + "@/*": ["./*"], + "@repo/*": ["../../*"] + }, + "plugins": [ + { + "name": "workflow" + } + ] + }, + "include": ["**/*.ts", "**/*.tsx", "*.js"], + "exclude": ["node_modules"] +} diff --git a/workbench/fastify/tsconfig.json b/workbench/fastify/tsconfig.json new file mode 100644 index 000000000..39c2f1ea6 --- /dev/null +++ b/workbench/fastify/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "es2022", + "module": "NodeNext", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "moduleResolution": "NodeNext", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "paths": { + "@/*": ["./*"], + "@repo/*": ["../../*"] + }, + "plugins": [ + { + "name": "workflow" + } + ] + }, + "include": ["**/*.ts", "**/*.tsx", "*.js"], + "exclude": ["node_modules"] +} From b08f6d7a292b813c699de2bc5b2ffe83c7b672c2 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Tue, 30 Dec 2025 11:34:22 -0800 Subject: [PATCH 7/7] . --- workbench/example/api/test-direct-step-call.ts | 3 ++- workbench/express/src/index.ts | 3 ++- workbench/fastify/src/index.ts | 3 ++- workbench/hono/src/index.ts | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/workbench/example/api/test-direct-step-call.ts b/workbench/example/api/test-direct-step-call.ts index 3a7660c93..b62e9ce0e 100644 --- a/workbench/example/api/test-direct-step-call.ts +++ b/workbench/example/api/test-direct-step-call.ts @@ -2,7 +2,8 @@ // After the SWC compiler changes, step functions in client mode have their directive removed // and keep their original implementation, allowing them to be called as regular async functions -import { add } from '../workflows/99_e2e.js'; +// Import from 98_duplicate_case.ts to avoid path alias imports that don't work in Vercel API functions +import { add } from '../workflows/98_duplicate_case.js'; export async function POST(req: Request) { const body = await req.json(); diff --git a/workbench/express/src/index.ts b/workbench/express/src/index.ts index 76ed45078..ee9fa8083 100644 --- a/workbench/express/src/index.ts +++ b/workbench/express/src/index.ts @@ -207,7 +207,8 @@ app.post('/api/test-direct-step-call', async (req, res) => { // This route tests calling step functions directly outside of any workflow context // After the SWC compiler changes, step functions in client mode have their directive removed // and keep their original implementation, allowing them to be called as regular async functions - const { add } = await import('../workflows/99_e2e.js'); + // Import from 98_duplicate_case.ts to avoid path alias imports + const { add } = await import('../workflows/98_duplicate_case.js'); const { x, y } = req.body; diff --git a/workbench/fastify/src/index.ts b/workbench/fastify/src/index.ts index 7c354822c..32fae3297 100644 --- a/workbench/fastify/src/index.ts +++ b/workbench/fastify/src/index.ts @@ -253,7 +253,8 @@ server.post('/api/test-direct-step-call', async (req: any, reply) => { // This route tests calling step functions directly outside of any workflow context // After the SWC compiler changes, step functions in client mode have their directive removed // and keep their original implementation, allowing them to be called as regular async functions - const { add } = await import('../workflows/99_e2e.js'); + // Import from 98_duplicate_case.ts to avoid path alias imports + const { add } = await import('../workflows/98_duplicate_case.js'); const { x, y } = req.body; diff --git a/workbench/hono/src/index.ts b/workbench/hono/src/index.ts index 7554bf6d1..8fc8d1531 100644 --- a/workbench/hono/src/index.ts +++ b/workbench/hono/src/index.ts @@ -193,7 +193,8 @@ app.post('/api/test-direct-step-call', async ({ req }) => { // This route tests calling step functions directly outside of any workflow context // After the SWC compiler changes, step functions in client mode have their directive removed // and keep their original implementation, allowing them to be called as regular async functions - const { add } = await import('../workflows/99_e2e.js'); + // Import from 98_duplicate_case.ts to avoid path alias imports + const { add } = await import('../workflows/98_duplicate_case.js'); const body = await req.json(); const { x, y } = body;