@workflow/builders 5.0.0-beta.4 → 5.0.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/base-builder.d.ts +69 -2
- package/dist/base-builder.d.ts.map +1 -1
- package/dist/base-builder.js +368 -19
- package/dist/base-builder.js.map +1 -1
- package/dist/config-helpers.d.ts +2 -1
- package/dist/config-helpers.d.ts.map +1 -1
- package/dist/config-helpers.js +1 -0
- package/dist/config-helpers.js.map +1 -1
- package/dist/constants.d.ts +3 -13
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +3 -13
- package/dist/constants.js.map +1 -1
- package/dist/discover-entries-esbuild-plugin.d.ts.map +1 -1
- package/dist/discover-entries-esbuild-plugin.js +16 -1
- package/dist/discover-entries-esbuild-plugin.js.map +1 -1
- package/dist/external-package-warning.test.d.ts +2 -0
- package/dist/external-package-warning.test.d.ts.map +1 -0
- package/dist/external-package-warning.test.js +219 -0
- package/dist/external-package-warning.test.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/module-specifier.d.ts.map +1 -1
- package/dist/module-specifier.js +47 -9
- package/dist/module-specifier.js.map +1 -1
- package/dist/module-specifier.test.js +20 -0
- package/dist/module-specifier.test.js.map +1 -1
- package/dist/node-module-esbuild-plugin.d.ts.map +1 -1
- package/dist/node-module-esbuild-plugin.js +72 -22
- package/dist/node-module-esbuild-plugin.js.map +1 -1
- package/dist/node-module-esbuild-plugin.test.js +96 -0
- package/dist/node-module-esbuild-plugin.test.js.map +1 -1
- package/dist/resolve-sourcemap.test.d.ts +2 -0
- package/dist/resolve-sourcemap.test.d.ts.map +1 -0
- package/dist/resolve-sourcemap.test.js +126 -0
- package/dist/resolve-sourcemap.test.js.map +1 -0
- package/dist/standalone.d.ts +0 -3
- package/dist/standalone.d.ts.map +1 -1
- package/dist/standalone.js +10 -39
- package/dist/standalone.js.map +1 -1
- package/dist/swc-esbuild-plugin.d.ts +9 -0
- package/dist/swc-esbuild-plugin.d.ts.map +1 -1
- package/dist/swc-esbuild-plugin.js +63 -8
- package/dist/swc-esbuild-plugin.js.map +1 -1
- package/dist/swc-esbuild-plugin.test.js +279 -3
- package/dist/swc-esbuild-plugin.test.js.map +1 -1
- package/dist/types.d.ts +29 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/vercel-build-output-api.d.ts +0 -2
- package/dist/vercel-build-output-api.d.ts.map +1 -1
- package/dist/vercel-build-output-api.js +24 -55
- package/dist/vercel-build-output-api.js.map +1 -1
- package/package.json +5 -5
package/dist/base-builder.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { mkdir, readFile, realpath, rename, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
3
4
|
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
4
5
|
import { promisify } from 'node:util';
|
|
6
|
+
import { WorkflowBuildError } from '@workflow/errors';
|
|
5
7
|
import { pluralize } from '@workflow/utils';
|
|
6
8
|
import chalk from 'chalk';
|
|
7
9
|
import enhancedResolveOriginal from 'enhanced-resolve';
|
|
@@ -15,9 +17,47 @@ import { getImportPath } from './module-specifier.js';
|
|
|
15
17
|
import { createNodeModuleErrorPlugin } from './node-module-esbuild-plugin.js';
|
|
16
18
|
import { createPseudoPackagePlugin } from './pseudo-package-esbuild-plugin.js';
|
|
17
19
|
import { createSwcPlugin } from './swc-esbuild-plugin.js';
|
|
20
|
+
import { detectWorkflowPatterns } from './transform-utils.js';
|
|
18
21
|
import { extractWorkflowGraphs } from './workflows-extractor.js';
|
|
19
22
|
const enhancedResolve = promisify(enhancedResolveOriginal);
|
|
23
|
+
const require = createRequire(import.meta.url);
|
|
24
|
+
/**
|
|
25
|
+
* Legacy opt-in for source maps on the final workflow wrapper + webhook
|
|
26
|
+
* bundles (which default to off, unlike the step/interim workflow bundles
|
|
27
|
+
* that default to inline). Superseded by the `sourcemap` config option and
|
|
28
|
+
* the `WORKFLOW_SOURCEMAP` environment variable; kept for back-compat.
|
|
29
|
+
*/
|
|
20
30
|
const EMIT_SOURCEMAPS_FOR_DEBUGGING = process.env.WORKFLOW_EMIT_SOURCEMAPS_FOR_DEBUGGING === '1';
|
|
31
|
+
const VALID_SOURCEMAP_STRINGS = new Set([
|
|
32
|
+
'inline',
|
|
33
|
+
'linked',
|
|
34
|
+
'external',
|
|
35
|
+
'both',
|
|
36
|
+
]);
|
|
37
|
+
/**
|
|
38
|
+
* Parse the value of the `WORKFLOW_SOURCEMAP` environment variable into a
|
|
39
|
+
* `SourcemapMode`. Returns `undefined` if the env var is unset, empty, or
|
|
40
|
+
* unrecognized (a warning is emitted for unrecognized values).
|
|
41
|
+
*/
|
|
42
|
+
function parseSourcemapEnv(value) {
|
|
43
|
+
if (value === undefined || value === '')
|
|
44
|
+
return undefined;
|
|
45
|
+
switch (value) {
|
|
46
|
+
case '0':
|
|
47
|
+
case 'false':
|
|
48
|
+
return false;
|
|
49
|
+
case '1':
|
|
50
|
+
case 'true':
|
|
51
|
+
return true;
|
|
52
|
+
default:
|
|
53
|
+
if (VALID_SOURCEMAP_STRINGS.has(value)) {
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
console.warn(`Ignoring unrecognized WORKFLOW_SOURCEMAP=${value}. ` +
|
|
57
|
+
`Expected one of: true, false, 0, 1, inline, linked, external, both.`);
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
21
61
|
/**
|
|
22
62
|
* Normalize an array of file paths by appending the `realpath()` of each entry
|
|
23
63
|
* (to handle symlinks, e.g. pnpm/workspace layouts) and deduplicating.
|
|
@@ -36,6 +76,11 @@ async function withRealpaths(entries) {
|
|
|
36
76
|
*/
|
|
37
77
|
export class BaseBuilder {
|
|
38
78
|
config;
|
|
79
|
+
/**
|
|
80
|
+
* Tracks which external packages have already been warned about
|
|
81
|
+
* to avoid duplicate warnings across multiple discoverEntries() calls.
|
|
82
|
+
*/
|
|
83
|
+
warnedExternalPackages = new Set();
|
|
39
84
|
constructor(config) {
|
|
40
85
|
this.config = config;
|
|
41
86
|
}
|
|
@@ -152,6 +197,105 @@ export class BaseBuilder {
|
|
|
152
197
|
* (e.g., when files are added/removed during watch mode).
|
|
153
198
|
*/
|
|
154
199
|
discoveredEntries = new WeakMap();
|
|
200
|
+
/**
|
|
201
|
+
* Pseudo-packages that should not be checked for workflow patterns.
|
|
202
|
+
*/
|
|
203
|
+
static PSEUDO_PACKAGES = new Set([
|
|
204
|
+
'server-only',
|
|
205
|
+
'client-only',
|
|
206
|
+
]);
|
|
207
|
+
/**
|
|
208
|
+
* Checks each package in externalPackages for workflow patterns and emits
|
|
209
|
+
* warnings if any contain "use step", "use workflow" directives, or
|
|
210
|
+
* serialization classes. These patterns will not be transformed by the
|
|
211
|
+
* workflow compiler when the package is externalized.
|
|
212
|
+
*/
|
|
213
|
+
async warnAboutExternalWorkflowPackages() {
|
|
214
|
+
const externalPackages = this.config.externalPackages;
|
|
215
|
+
if (!externalPackages?.length)
|
|
216
|
+
return;
|
|
217
|
+
for (const pkg of externalPackages) {
|
|
218
|
+
if (BaseBuilder.PSEUDO_PACKAGES.has(pkg))
|
|
219
|
+
continue;
|
|
220
|
+
if (this.warnedExternalPackages.has(pkg))
|
|
221
|
+
continue;
|
|
222
|
+
if (pkg.startsWith('.') ||
|
|
223
|
+
pkg.startsWith('/') ||
|
|
224
|
+
pkg.startsWith('$') ||
|
|
225
|
+
pkg.includes('*') ||
|
|
226
|
+
pkg.includes(':')) {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
try {
|
|
230
|
+
// Check package.json dependencies for @workflow/serde (fast path)
|
|
231
|
+
let hasWorkflowSerdeDep = false;
|
|
232
|
+
try {
|
|
233
|
+
const pkgJsonPath = require.resolve(`${pkg}/package.json`, {
|
|
234
|
+
paths: [this.config.workingDir],
|
|
235
|
+
});
|
|
236
|
+
const pkgJsonSource = await readFile(pkgJsonPath, 'utf-8');
|
|
237
|
+
const pkgJson = JSON.parse(pkgJsonSource);
|
|
238
|
+
const dependencies = typeof pkgJson.dependencies === 'object' &&
|
|
239
|
+
pkgJson.dependencies !== null &&
|
|
240
|
+
!Array.isArray(pkgJson.dependencies)
|
|
241
|
+
? pkgJson.dependencies
|
|
242
|
+
: {};
|
|
243
|
+
const peerDependencies = typeof pkgJson.peerDependencies === 'object' &&
|
|
244
|
+
pkgJson.peerDependencies !== null &&
|
|
245
|
+
!Array.isArray(pkgJson.peerDependencies)
|
|
246
|
+
? pkgJson.peerDependencies
|
|
247
|
+
: {};
|
|
248
|
+
hasWorkflowSerdeDep =
|
|
249
|
+
Object.hasOwn(dependencies, '@workflow/serde') ||
|
|
250
|
+
Object.hasOwn(peerDependencies, '@workflow/serde');
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
// package.json not resolvable - continue to source check
|
|
254
|
+
}
|
|
255
|
+
// Check source patterns (thorough path).
|
|
256
|
+
// Note: require.resolve only inspects the package's main entry point.
|
|
257
|
+
// If workflow constructs live in sub-paths (e.g. `my-pkg/workflows`),
|
|
258
|
+
// they won't be detected here. The @workflow/serde dep check above
|
|
259
|
+
// partially covers serde cases. This is acceptable as a best-effort
|
|
260
|
+
// heuristic — the primary fix is auto-removal in withWorkflow().
|
|
261
|
+
let hasUseStep = false;
|
|
262
|
+
let hasUseWorkflow = false;
|
|
263
|
+
let hasSerde = hasWorkflowSerdeDep;
|
|
264
|
+
try {
|
|
265
|
+
const entryPath = require.resolve(pkg, {
|
|
266
|
+
paths: [this.config.workingDir],
|
|
267
|
+
});
|
|
268
|
+
const source = await readFile(entryPath, 'utf-8');
|
|
269
|
+
const patterns = detectWorkflowPatterns(source);
|
|
270
|
+
hasUseStep = patterns.hasUseStep;
|
|
271
|
+
hasUseWorkflow = patterns.hasUseWorkflow;
|
|
272
|
+
if (!hasSerde) {
|
|
273
|
+
hasSerde = patterns.hasSerde;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
// Entry file not resolvable or not readable - use what we have
|
|
278
|
+
}
|
|
279
|
+
if (!hasUseStep && !hasUseWorkflow && !hasSerde)
|
|
280
|
+
continue;
|
|
281
|
+
// Build a specific description of what was found
|
|
282
|
+
const issues = [];
|
|
283
|
+
if (hasUseWorkflow)
|
|
284
|
+
issues.push('"use workflow" functions');
|
|
285
|
+
if (hasUseStep)
|
|
286
|
+
issues.push('"use step" functions');
|
|
287
|
+
if (hasSerde)
|
|
288
|
+
issues.push('serialization classes');
|
|
289
|
+
this.warnedExternalPackages.add(pkg);
|
|
290
|
+
console.warn(`\n${chalk.yellow('⚠')} Warning: ${chalk.bold(`"${pkg}"`)} is listed in ${chalk.bold('externalPackages')} (${chalk.bold('serverExternalPackages')} in Next.js) but contains workflow code (${issues.join(', ')}).` +
|
|
291
|
+
`\n This code will ${chalk.bold('not')} be transformed by the workflow compiler, which can cause runtime failures.` +
|
|
292
|
+
`\n Remove ${chalk.bold(`"${pkg}"`)} from ${chalk.bold('externalPackages')} (${chalk.bold('serverExternalPackages')} in Next.js) to fix this.\n`);
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
// Best-effort: if anything goes wrong, skip this package silently
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
155
299
|
async discoverEntries(inputs, outdir, tsconfigPath) {
|
|
156
300
|
const previousResult = this.discoveredEntries.get(inputs);
|
|
157
301
|
if (previousResult) {
|
|
@@ -198,6 +342,8 @@ export class BaseBuilder {
|
|
|
198
342
|
}
|
|
199
343
|
catch (_) { }
|
|
200
344
|
this.logBaseBuilderInfo(`Discovering workflow directives`, `${Date.now() - discoverStart}ms`);
|
|
345
|
+
// Warn about external packages that contain workflow code
|
|
346
|
+
await this.warnAboutExternalWorkflowPackages();
|
|
201
347
|
this.discoveredEntries.set(inputs, state);
|
|
202
348
|
return state;
|
|
203
349
|
}
|
|
@@ -257,7 +403,9 @@ export class BaseBuilder {
|
|
|
257
403
|
}
|
|
258
404
|
}
|
|
259
405
|
if (throwOnError) {
|
|
260
|
-
throw new
|
|
406
|
+
throw new WorkflowBuildError(`Build failed during ${phase}:\n${errorMessages.join('\n')}`, {
|
|
407
|
+
hint: `Review the esbuild errors above — they come from the ${phase} bundle. Fix the offending source files and re-run the build.`,
|
|
408
|
+
});
|
|
261
409
|
}
|
|
262
410
|
}
|
|
263
411
|
if (!options?.suppressWarnings &&
|
|
@@ -293,19 +441,18 @@ export class BaseBuilder {
|
|
|
293
441
|
* Steps have full Node.js runtime access and handle side effects, API calls, etc.
|
|
294
442
|
*
|
|
295
443
|
* @param externalizeNonSteps - If true, only bundles step entry points and externalizes other code
|
|
444
|
+
* @param bundleTransitiveLocalStepDependencies - If true, also bundles project-local files imported by step entries for direct runtime loading
|
|
296
445
|
* @returns Build context (for watch mode) and the collected workflow manifest
|
|
297
446
|
*/
|
|
298
|
-
async createStepsBundle({ inputFiles, format = 'esm', outfile, externalizeNonSteps, rewriteTsExtensions, tsconfigPath, discoveredEntries, }) {
|
|
447
|
+
async createStepsBundle({ inputFiles, format = 'esm', outfile, externalizeNonSteps, bundleTransitiveLocalStepDependencies, rewriteTsExtensions, tsconfigPath, discoveredEntries, skipEsmRequireBanner = false, }) {
|
|
299
448
|
const stepsBundleStart = Date.now();
|
|
300
449
|
const workflowManifest = {};
|
|
301
450
|
const builtInSteps = 'workflow/internal/builtins';
|
|
302
451
|
const resolvedBuiltInSteps = await enhancedResolve(dirname(outfile), 'workflow/internal/builtins').catch((err) => {
|
|
303
|
-
throw new
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
`Caused by: ${chalk.red(String(err))}`,
|
|
308
|
-
].join('\n'));
|
|
452
|
+
throw new WorkflowBuildError(`Failed to resolve built-in steps sources.\n\nCaused by: ${String(err)}`, {
|
|
453
|
+
hint: 'run `pnpm install workflow` to resolve this issue.',
|
|
454
|
+
cause: err,
|
|
455
|
+
});
|
|
309
456
|
});
|
|
310
457
|
// Discovery of workflow/step/serde entries. The SDK runtime entry point
|
|
311
458
|
// (workflow/runtime) is resolved inside discoverEntries() itself so that
|
|
@@ -331,6 +478,28 @@ export class BaseBuilder {
|
|
|
331
478
|
// For workspace/node_modules packages, uses the package name so esbuild
|
|
332
479
|
// will resolve through package.json exports with the appropriate conditions
|
|
333
480
|
const createImport = (file) => {
|
|
481
|
+
const normalizedWorkspaceRoot = this.config.workingDir
|
|
482
|
+
.replace(/\\/g, '/')
|
|
483
|
+
.replace(/\/$/, '');
|
|
484
|
+
const normalizedWorkspaceFile = file.replace(/\\/g, '/');
|
|
485
|
+
// Only use relative source paths for workspace symlinks (files
|
|
486
|
+
// outside node_modules in a packages/*/src/ directory). For tarball-
|
|
487
|
+
// installed packages (files inside node_modules/), fall through to
|
|
488
|
+
// getImportPath which returns package specifiers — this allows the
|
|
489
|
+
// SWC plugin's externalizeNonSteps to work correctly.
|
|
490
|
+
const isWorkspaceSourceBackedPackageFile = normalizedWorkspaceFile.includes('/packages/') &&
|
|
491
|
+
normalizedWorkspaceFile.includes('/src/') &&
|
|
492
|
+
!normalizedWorkspaceFile.includes('/node_modules/') &&
|
|
493
|
+
!(normalizedWorkspaceFile === normalizedWorkspaceRoot ||
|
|
494
|
+
normalizedWorkspaceFile.startsWith(`${normalizedWorkspaceRoot}/`));
|
|
495
|
+
const isSourceBackedPackageFile = isWorkspaceSourceBackedPackageFile;
|
|
496
|
+
if (isSourceBackedPackageFile) {
|
|
497
|
+
let relativePath = relative(normalizedWorkspaceRoot, normalizedWorkspaceFile).replace(/\\/g, '/');
|
|
498
|
+
if (!relativePath.startsWith('./') && !relativePath.startsWith('../')) {
|
|
499
|
+
relativePath = `./${relativePath}`;
|
|
500
|
+
}
|
|
501
|
+
return `import '${relativePath}';`;
|
|
502
|
+
}
|
|
334
503
|
const { importPath, isPackage } = getImportPath(file, this.config.workingDir);
|
|
335
504
|
if (isPackage) {
|
|
336
505
|
// Use package name - esbuild will resolve via package.json exports
|
|
@@ -363,8 +532,9 @@ export class BaseBuilder {
|
|
|
363
532
|
${stepImports}
|
|
364
533
|
// Serde files for cross-context class registration
|
|
365
534
|
${serdeImports}
|
|
366
|
-
//
|
|
367
|
-
|
|
535
|
+
// Sentinel export so bundlers (rollup) don't tree-shake this module
|
|
536
|
+
// when it's imported as a side-effect-only dependency.
|
|
537
|
+
export const __steps_registered = true;`;
|
|
368
538
|
// Bundle with esbuild and our custom SWC plugin
|
|
369
539
|
const entriesToBundle = externalizeNonSteps
|
|
370
540
|
? [
|
|
@@ -383,7 +553,9 @@ export class BaseBuilder {
|
|
|
383
553
|
]);
|
|
384
554
|
const esbuildTsconfigOptions = await getEsbuildTsconfigOptions(tsconfigPath);
|
|
385
555
|
const { banner: importMetaBanner, define: importMetaDefine } = this.getCjsImportMetaPolyfill(format);
|
|
386
|
-
const esmRequireBanner =
|
|
556
|
+
const esmRequireBanner = skipEsmRequireBanner
|
|
557
|
+
? ''
|
|
558
|
+
: this.getEsmRequireBanner(format);
|
|
387
559
|
const esbuildCtx = await esbuild.context({
|
|
388
560
|
banner: {
|
|
389
561
|
js: `// biome-ignore-all lint: generated file\n/* eslint-disable */\n${importMetaBanner}${esmRequireBanner}`,
|
|
@@ -425,7 +597,7 @@ export class BaseBuilder {
|
|
|
425
597
|
// Steps execute in Node.js context and inline sourcemaps ensure we get
|
|
426
598
|
// meaningful stack traces with proper file names and line numbers when errors
|
|
427
599
|
// occur in deeply nested function calls across multiple files.
|
|
428
|
-
sourcemap: 'inline',
|
|
600
|
+
sourcemap: this.resolveSourcemap('inline'),
|
|
429
601
|
plugins: [
|
|
430
602
|
// Handle pseudo-packages like 'server-only' and 'client-only' by providing
|
|
431
603
|
// empty modules. Must run first to intercept these before other resolution.
|
|
@@ -436,6 +608,7 @@ export class BaseBuilder {
|
|
|
436
608
|
outdir: outfile ? dirname(outfile) : undefined,
|
|
437
609
|
projectRoot: this.transformProjectRoot,
|
|
438
610
|
workflowManifest,
|
|
611
|
+
bundleTransitiveLocalStepDependencies,
|
|
439
612
|
rewriteTsExtensions,
|
|
440
613
|
sideEffectEntries: normalizedSideEffectEntries,
|
|
441
614
|
}),
|
|
@@ -564,7 +737,7 @@ export class BaseBuilder {
|
|
|
564
737
|
// Inline source maps for better stack traces in workflow VM execution.
|
|
565
738
|
// This intermediate bundle is executed via runInContext() in a VM, so we need
|
|
566
739
|
// inline source maps to get meaningful stack traces instead of "evalmachine.<anonymous>".
|
|
567
|
-
sourcemap: 'inline',
|
|
740
|
+
sourcemap: this.resolveSourcemap('inline'),
|
|
568
741
|
// Use tsconfig for path alias resolution.
|
|
569
742
|
// For symlinked configs this uses tsconfigRaw to preserve cwd-relative aliases.
|
|
570
743
|
...esbuildTsconfigOptions,
|
|
@@ -625,7 +798,9 @@ export class BaseBuilder {
|
|
|
625
798
|
await this.createSwcGitignore();
|
|
626
799
|
if (!interimBundle.outputFiles ||
|
|
627
800
|
interimBundle.outputFiles.length === 0) {
|
|
628
|
-
throw new
|
|
801
|
+
throw new WorkflowBuildError('No output files generated from esbuild', {
|
|
802
|
+
hint: 'This usually indicates a misconfigured entry point or an empty workflow directory. Check that your workflow files contain a `"use workflow"` or `"use step"` directive.',
|
|
803
|
+
});
|
|
629
804
|
}
|
|
630
805
|
// Serde compliance warnings: check if workflow bundle has Node.js imports
|
|
631
806
|
// alongside serde-registered classes (these will fail at runtime in the sandbox)
|
|
@@ -701,7 +876,7 @@ export const POST = workflowEntrypoint(workflowCode);`;
|
|
|
701
876
|
outfile,
|
|
702
877
|
// Source maps for the final workflow bundle wrapper (not important since this code
|
|
703
878
|
// doesn't run in the VM - only the intermediate bundle sourcemap is relevant)
|
|
704
|
-
sourcemap: EMIT_SOURCEMAPS_FOR_DEBUGGING,
|
|
879
|
+
sourcemap: this.resolveSourcemap(EMIT_SOURCEMAPS_FOR_DEBUGGING),
|
|
705
880
|
absWorkingDir: this.config.workingDir,
|
|
706
881
|
bundle: true,
|
|
707
882
|
format,
|
|
@@ -717,16 +892,18 @@ export const POST = workflowEntrypoint(workflowCode);`;
|
|
|
717
892
|
});
|
|
718
893
|
this.logCreateWorkflowsBundleInfo('Created final workflow bundle', `${Date.now() - bundleStartTime}ms`);
|
|
719
894
|
};
|
|
720
|
-
|
|
895
|
+
const interimBundleText = interimBundle.outputFiles[0].text;
|
|
896
|
+
await bundleFinal(interimBundleText);
|
|
721
897
|
if (keepInterimBundleContext) {
|
|
722
898
|
shouldDisposeInterimBundleCtx = false;
|
|
723
899
|
return {
|
|
724
900
|
manifest: workflowManifest,
|
|
725
901
|
interimBundleCtx,
|
|
726
902
|
bundleFinal,
|
|
903
|
+
interimBundleText,
|
|
727
904
|
};
|
|
728
905
|
}
|
|
729
|
-
return { manifest: workflowManifest };
|
|
906
|
+
return { manifest: workflowManifest, interimBundleText };
|
|
730
907
|
}
|
|
731
908
|
catch (error) {
|
|
732
909
|
shouldDisposeInterimBundleCtx = true;
|
|
@@ -743,6 +920,153 @@ export const POST = workflowEntrypoint(workflowCode);`;
|
|
|
743
920
|
}
|
|
744
921
|
}
|
|
745
922
|
}
|
|
923
|
+
/**
|
|
924
|
+
* V2: Creates a combined bundle that includes both step registrations and
|
|
925
|
+
* workflow orchestration in a single route. The combined entrypoint executes
|
|
926
|
+
* steps inline when possible, reducing function invocations and queue overhead.
|
|
927
|
+
*
|
|
928
|
+
* This method reuses createStepsBundle (for step registrations) and
|
|
929
|
+
* createWorkflowsBundle (for workflow VM code), then combines them into
|
|
930
|
+
* a single route file using workflowEntrypoint().
|
|
931
|
+
*/
|
|
932
|
+
async createCombinedBundle({ inputFiles, stepsOutfile, flowOutfile, format = 'esm', bundleFinalOutput = true, tsconfigPath, externalizeNonSteps, bundleTransitiveLocalStepDependencies, discoveredEntries, }) {
|
|
933
|
+
// 1. Build step registrations bundle (used as separate file for
|
|
934
|
+
// bundleFinalOutput: false, or read back for inline content when true)
|
|
935
|
+
const { context: stepsContext, manifest: stepsManifest } = await this.createStepsBundle({
|
|
936
|
+
inputFiles,
|
|
937
|
+
outfile: stepsOutfile,
|
|
938
|
+
// When bundleFinalOutput is true, use ESM for the steps bundle
|
|
939
|
+
// regardless of the final output format. The final esbuild pass
|
|
940
|
+
// converts everything to the target format. Using CJS here causes
|
|
941
|
+
// a module.exports collision: the steps bundle's top-level
|
|
942
|
+
// module.exports overwrites the combined route's module.exports
|
|
943
|
+
// when esbuild inlines the steps without a __commonJS wrapper.
|
|
944
|
+
format: bundleFinalOutput ? 'esm' : format,
|
|
945
|
+
externalizeNonSteps,
|
|
946
|
+
bundleTransitiveLocalStepDependencies,
|
|
947
|
+
tsconfigPath,
|
|
948
|
+
discoveredEntries,
|
|
949
|
+
// Skip the createRequire banner here — when bundleFinalOutput is true
|
|
950
|
+
// the outer esbuild pass will inline this bundle and add its own
|
|
951
|
+
// banner. Emitting it twice declares __createRequire twice.
|
|
952
|
+
skipEsmRequireBanner: bundleFinalOutput,
|
|
953
|
+
});
|
|
954
|
+
// 2. Build workflow VM code
|
|
955
|
+
const tempWorkflowOutfile = `${flowOutfile}.__wf_tmp.js`;
|
|
956
|
+
const workflowsResult = await this.createWorkflowsBundle({
|
|
957
|
+
inputFiles,
|
|
958
|
+
outfile: tempWorkflowOutfile,
|
|
959
|
+
format,
|
|
960
|
+
bundleFinalOutput: false,
|
|
961
|
+
tsconfigPath,
|
|
962
|
+
discoveredEntries,
|
|
963
|
+
});
|
|
964
|
+
const workflowVMCode = workflowsResult.interimBundleText;
|
|
965
|
+
if (!workflowVMCode) {
|
|
966
|
+
throw new Error('createWorkflowsBundle did not return interimBundleText');
|
|
967
|
+
}
|
|
968
|
+
// Clean up the wrapper file
|
|
969
|
+
try {
|
|
970
|
+
const { unlink } = await import('node:fs/promises');
|
|
971
|
+
await unlink(tempWorkflowOutfile);
|
|
972
|
+
}
|
|
973
|
+
catch {
|
|
974
|
+
// Ignore cleanup errors
|
|
975
|
+
}
|
|
976
|
+
// 3. Generate combined route file
|
|
977
|
+
const stepsRelativePath = './' + basename(stepsOutfile).replace(/\\/g, '/');
|
|
978
|
+
const escapedVMCode = workflowVMCode.replace(/[\\`$]/g, '\\$&');
|
|
979
|
+
const combinedFunctionCode = `// biome-ignore-all lint: generated file
|
|
980
|
+
/* eslint-disable */
|
|
981
|
+
import { __steps_registered } from '${stepsRelativePath}';
|
|
982
|
+
import { workflowEntrypoint } from 'workflow/runtime';
|
|
983
|
+
|
|
984
|
+
// Prevent rollup from tree-shaking the steps side-effect import
|
|
985
|
+
void __steps_registered;
|
|
986
|
+
|
|
987
|
+
const workflowCode = \`${escapedVMCode}\`;
|
|
988
|
+
|
|
989
|
+
export const POST = workflowEntrypoint(workflowCode);`;
|
|
990
|
+
if (!bundleFinalOutput) {
|
|
991
|
+
// Write directly (Next.js will bundle)
|
|
992
|
+
const tempPath = `${flowOutfile}.${randomUUID()}.tmp`;
|
|
993
|
+
await writeFile(tempPath, combinedFunctionCode);
|
|
994
|
+
await rename(tempPath, flowOutfile);
|
|
995
|
+
}
|
|
996
|
+
else {
|
|
997
|
+
// Bundle the combined code for standalone use
|
|
998
|
+
const bundleStartTime = Date.now();
|
|
999
|
+
const { banner: importMetaBanner, define: importMetaDefine } = this.getCjsImportMetaPolyfill(format);
|
|
1000
|
+
// ESM banner provides `require` via createRequire(import.meta.url) so
|
|
1001
|
+
// CJS dependencies that call require() for Node.js builtins keep working
|
|
1002
|
+
// in the ESM output produced by bundleFinalOutput: true.
|
|
1003
|
+
const finalEsmRequireBanner = this.getEsmRequireBanner(format);
|
|
1004
|
+
const finalResult = await esbuild.build({
|
|
1005
|
+
banner: {
|
|
1006
|
+
js: `// biome-ignore-all lint: generated file\n/* eslint-disable */\n${importMetaBanner}${finalEsmRequireBanner}`,
|
|
1007
|
+
},
|
|
1008
|
+
stdin: {
|
|
1009
|
+
contents: combinedFunctionCode,
|
|
1010
|
+
resolveDir: dirname(flowOutfile),
|
|
1011
|
+
sourcefile: 'virtual-entry.js',
|
|
1012
|
+
loader: 'js',
|
|
1013
|
+
},
|
|
1014
|
+
outfile: flowOutfile,
|
|
1015
|
+
absWorkingDir: this.config.workingDir,
|
|
1016
|
+
bundle: true,
|
|
1017
|
+
format,
|
|
1018
|
+
platform: 'node',
|
|
1019
|
+
target: 'es2022',
|
|
1020
|
+
write: true,
|
|
1021
|
+
keepNames: true,
|
|
1022
|
+
minify: false,
|
|
1023
|
+
define: importMetaDefine,
|
|
1024
|
+
external: ['@aws-sdk/credential-provider-web-identity'],
|
|
1025
|
+
});
|
|
1026
|
+
this.logEsbuildMessages(finalResult, 'combined bundle', true);
|
|
1027
|
+
this.logBaseBuilderInfo('Created combined bundle', `${Date.now() - bundleStartTime}ms`);
|
|
1028
|
+
}
|
|
1029
|
+
// Merge manifests
|
|
1030
|
+
const manifest = {
|
|
1031
|
+
...stepsManifest,
|
|
1032
|
+
workflows: {
|
|
1033
|
+
...stepsManifest.workflows,
|
|
1034
|
+
...workflowsResult.manifest.workflows,
|
|
1035
|
+
},
|
|
1036
|
+
classes: {
|
|
1037
|
+
...stepsManifest.classes,
|
|
1038
|
+
...workflowsResult.manifest.classes,
|
|
1039
|
+
},
|
|
1040
|
+
};
|
|
1041
|
+
// Create a custom bundleFinal for watch mode that uses workflowEntrypoint
|
|
1042
|
+
const combinedBundleFinal = async (interimBundleText) => {
|
|
1043
|
+
const escaped = interimBundleText.replace(/[\\`$]/g, '\\$&');
|
|
1044
|
+
const code = `// biome-ignore-all lint: generated file
|
|
1045
|
+
/* eslint-disable */
|
|
1046
|
+
import { __steps_registered } from '${stepsRelativePath}';
|
|
1047
|
+
import { workflowEntrypoint } from 'workflow/runtime';
|
|
1048
|
+
|
|
1049
|
+
void __steps_registered;
|
|
1050
|
+
|
|
1051
|
+
const workflowCode = \`${escaped}\`;
|
|
1052
|
+
|
|
1053
|
+
export const POST = workflowEntrypoint(workflowCode);`;
|
|
1054
|
+
const outputDir = dirname(flowOutfile);
|
|
1055
|
+
await mkdir(outputDir, { recursive: true });
|
|
1056
|
+
const tempPath = `${flowOutfile}.${randomUUID()}.tmp`;
|
|
1057
|
+
await writeFile(tempPath, code);
|
|
1058
|
+
await rename(tempPath, flowOutfile);
|
|
1059
|
+
};
|
|
1060
|
+
if (this.config.watch) {
|
|
1061
|
+
return {
|
|
1062
|
+
manifest,
|
|
1063
|
+
stepsContext,
|
|
1064
|
+
interimBundleCtx: workflowsResult.interimBundleCtx,
|
|
1065
|
+
bundleFinal: combinedBundleFinal,
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
return { manifest };
|
|
1069
|
+
}
|
|
746
1070
|
/**
|
|
747
1071
|
* Creates a client library bundle for workflow execution.
|
|
748
1072
|
* The client library allows importing and calling workflows from application code.
|
|
@@ -850,7 +1174,10 @@ export const POST = workflowEntrypoint(workflowCode);`;
|
|
|
850
1174
|
await mkdir(dirname(outfile), { recursive: true });
|
|
851
1175
|
// Create a static route that calls resumeWebhook
|
|
852
1176
|
// This route works for both Next.js and Vercel Build Output API
|
|
853
|
-
|
|
1177
|
+
// Bundled Build Output API webhook functions need world.ts statically
|
|
1178
|
+
// present so getWorldLazy() can use the global getWorld registration
|
|
1179
|
+
// instead of falling back to a missing sibling import("./world.js").
|
|
1180
|
+
const routeContent = `${bundle ? "import 'workflow/runtime';\n" : ''}import { resumeWebhook } from 'workflow/api';
|
|
854
1181
|
|
|
855
1182
|
async function handler(request) {
|
|
856
1183
|
const url = new URL(request.url);
|
|
@@ -919,7 +1246,7 @@ export const OPTIONS = handler;`;
|
|
|
919
1246
|
'.mjs',
|
|
920
1247
|
'.cjs',
|
|
921
1248
|
],
|
|
922
|
-
sourcemap: EMIT_SOURCEMAPS_FOR_DEBUGGING,
|
|
1249
|
+
sourcemap: this.resolveSourcemap(EMIT_SOURCEMAPS_FOR_DEBUGGING),
|
|
923
1250
|
mainFields: ['module', 'main'],
|
|
924
1251
|
// Don't externalize anything - bundle everything including workflow packages
|
|
925
1252
|
external: [],
|
|
@@ -995,6 +1322,28 @@ export const OPTIONS = handler;`;
|
|
|
995
1322
|
return this.resolvePath('.vercel/output/diagnostics/workflows-manifest.json');
|
|
996
1323
|
}
|
|
997
1324
|
}
|
|
1325
|
+
/**
|
|
1326
|
+
* Resolve the effective source map mode for a given call site. Precedence:
|
|
1327
|
+
* explicit `sourcemap` config > `WORKFLOW_SOURCEMAP` env var > the call
|
|
1328
|
+
* site's default. Returned value is passed directly to esbuild's
|
|
1329
|
+
* `sourcemap` option.
|
|
1330
|
+
*/
|
|
1331
|
+
resolveSourcemap(defaultMode) {
|
|
1332
|
+
if (this.config.sourcemap !== undefined)
|
|
1333
|
+
return this.config.sourcemap;
|
|
1334
|
+
const envMode = parseSourcemapEnv(process.env.WORKFLOW_SOURCEMAP);
|
|
1335
|
+
if (envMode !== undefined)
|
|
1336
|
+
return envMode;
|
|
1337
|
+
return defaultMode;
|
|
1338
|
+
}
|
|
1339
|
+
/**
|
|
1340
|
+
* Whether the resolved source map mode emits any source maps at all.
|
|
1341
|
+
* Used by consumers like the Vercel builder to decide whether to include
|
|
1342
|
+
* the source-map-support runtime shim in generated functions.
|
|
1343
|
+
*/
|
|
1344
|
+
get sourcemapsEnabled() {
|
|
1345
|
+
return this.resolveSourcemap(true) !== false;
|
|
1346
|
+
}
|
|
998
1347
|
/**
|
|
999
1348
|
* Creates a manifest JSON file containing step/workflow/class metadata
|
|
1000
1349
|
* and graph data for visualization.
|