@workflow/builders 4.0.1-beta.5 → 4.0.1-beta.51
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/apply-swc-transform.d.ts +14 -4
- package/dist/apply-swc-transform.d.ts.map +1 -1
- package/dist/apply-swc-transform.js +59 -9
- package/dist/apply-swc-transform.js.map +1 -1
- package/dist/base-builder.d.ts +65 -22
- package/dist/base-builder.d.ts.map +1 -1
- package/dist/base-builder.js +438 -115
- package/dist/base-builder.js.map +1 -1
- package/dist/build-queue.d.ts +18 -0
- package/dist/build-queue.d.ts.map +1 -0
- package/dist/build-queue.js +26 -0
- package/dist/build-queue.js.map +1 -0
- package/dist/config-helpers.d.ts +31 -0
- package/dist/config-helpers.d.ts.map +1 -1
- package/dist/config-helpers.js +60 -0
- package/dist/config-helpers.js.map +1 -1
- package/dist/constants.d.ts +2 -2
- package/dist/constants.js +2 -2
- package/dist/discover-entries-esbuild-plugin.d.ts +2 -3
- package/dist/discover-entries-esbuild-plugin.d.ts.map +1 -1
- package/dist/discover-entries-esbuild-plugin.js +57 -26
- package/dist/discover-entries-esbuild-plugin.js.map +1 -1
- package/dist/index.d.ts +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/module-specifier.d.ts +85 -0
- package/dist/module-specifier.d.ts.map +1 -0
- package/dist/module-specifier.js +437 -0
- package/dist/module-specifier.js.map +1 -0
- package/dist/module-specifier.test.d.ts +2 -0
- package/dist/module-specifier.test.d.ts.map +1 -0
- package/dist/module-specifier.test.js +169 -0
- package/dist/module-specifier.test.js.map +1 -0
- package/dist/node-module-esbuild-plugin.d.ts +37 -0
- package/dist/node-module-esbuild-plugin.d.ts.map +1 -1
- package/dist/node-module-esbuild-plugin.js +289 -9
- package/dist/node-module-esbuild-plugin.js.map +1 -1
- package/dist/node-module-esbuild-plugin.test.js +359 -88
- package/dist/node-module-esbuild-plugin.test.js.map +1 -1
- package/dist/pseudo-package-esbuild-plugin.d.ts +20 -0
- package/dist/pseudo-package-esbuild-plugin.d.ts.map +1 -0
- package/dist/pseudo-package-esbuild-plugin.js +47 -0
- package/dist/pseudo-package-esbuild-plugin.js.map +1 -0
- package/dist/pseudo-package-esbuild-plugin.test.d.ts +2 -0
- package/dist/pseudo-package-esbuild-plugin.test.d.ts.map +1 -0
- package/dist/pseudo-package-esbuild-plugin.test.js +315 -0
- package/dist/pseudo-package-esbuild-plugin.test.js.map +1 -0
- package/dist/request-converter.d.ts +3 -0
- package/dist/request-converter.d.ts.map +1 -0
- package/dist/request-converter.js +14 -0
- package/dist/request-converter.js.map +1 -0
- package/dist/standalone.d.ts +3 -0
- package/dist/standalone.d.ts.map +1 -1
- package/dist/standalone.js +37 -13
- package/dist/standalone.js.map +1 -1
- package/dist/swc-esbuild-plugin.d.ts +0 -2
- package/dist/swc-esbuild-plugin.d.ts.map +1 -1
- package/dist/swc-esbuild-plugin.js +33 -15
- package/dist/swc-esbuild-plugin.js.map +1 -1
- package/dist/transform-utils.d.ts +68 -0
- package/dist/transform-utils.d.ts.map +1 -0
- package/dist/transform-utils.js +97 -0
- package/dist/transform-utils.js.map +1 -0
- package/dist/transform-utils.test.d.ts +2 -0
- package/dist/transform-utils.test.d.ts.map +1 -0
- package/dist/transform-utils.test.js +266 -0
- package/dist/transform-utils.test.js.map +1 -0
- package/dist/types.d.ts +26 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -1
- package/dist/vercel-build-output-api.d.ts.map +1 -1
- package/dist/vercel-build-output-api.js +36 -14
- package/dist/vercel-build-output-api.js.map +1 -1
- package/dist/workflow-alias.d.ts +3 -0
- package/dist/workflow-alias.d.ts.map +1 -0
- package/dist/workflow-alias.js +46 -0
- package/dist/workflow-alias.js.map +1 -0
- package/dist/workflow-alias.test.d.ts +2 -0
- package/dist/workflow-alias.test.d.ts.map +1 -0
- package/dist/workflow-alias.test.js +46 -0
- package/dist/workflow-alias.test.js.map +1 -0
- package/dist/workflows-extractor.d.ts +92 -0
- package/dist/workflows-extractor.d.ts.map +1 -0
- package/dist/workflows-extractor.js +1476 -0
- package/dist/workflows-extractor.js.map +1 -0
- package/package.json +10 -9
package/dist/base-builder.js
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdir, readFile, realpath, rename, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
3
4
|
import { promisify } from 'node:util';
|
|
5
|
+
import { pluralize, usesVercelWorld } from '@workflow/utils';
|
|
4
6
|
import chalk from 'chalk';
|
|
5
|
-
import { parse } from 'comment-json';
|
|
6
7
|
import enhancedResolveOriginal from 'enhanced-resolve';
|
|
7
8
|
import * as esbuild from 'esbuild';
|
|
8
9
|
import { findUp } from 'find-up';
|
|
9
10
|
import { glob } from 'tinyglobby';
|
|
11
|
+
import { applySwcTransform, } from './apply-swc-transform.js';
|
|
10
12
|
import { createDiscoverEntriesPlugin } from './discover-entries-esbuild-plugin.js';
|
|
13
|
+
import { getImportPath } from './module-specifier.js';
|
|
11
14
|
import { createNodeModuleErrorPlugin } from './node-module-esbuild-plugin.js';
|
|
15
|
+
import { createPseudoPackagePlugin } from './pseudo-package-esbuild-plugin.js';
|
|
12
16
|
import { createSwcPlugin } from './swc-esbuild-plugin.js';
|
|
17
|
+
import { extractWorkflowGraphs } from './workflows-extractor.js';
|
|
13
18
|
const enhancedResolve = promisify(enhancedResolveOriginal);
|
|
14
19
|
const EMIT_SOURCEMAPS_FOR_DEBUGGING = process.env.WORKFLOW_EMIT_SOURCEMAPS_FOR_DEBUGGING === '1';
|
|
15
20
|
/**
|
|
@@ -24,34 +29,39 @@ export class BaseBuilder {
|
|
|
24
29
|
this.config = config;
|
|
25
30
|
}
|
|
26
31
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
32
|
+
* Whether informational BaseBuilder logs should be printed.
|
|
33
|
+
* Subclasses can override this to silence progress logs while keeping warnings/errors.
|
|
29
34
|
*/
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
console.error(`Failed to parse ${tsJsConfig} aliases might not apply properly`, err);
|
|
52
|
-
}
|
|
35
|
+
get shouldLogBaseBuilderInfo() {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
logBaseBuilderInfo(...args) {
|
|
39
|
+
if (this.shouldLogBaseBuilderInfo) {
|
|
40
|
+
console.log(...args);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
logCreateWorkflowsBundleInfo(...args) {
|
|
44
|
+
if (!this.config.suppressCreateWorkflowsBundleLogs) {
|
|
45
|
+
this.logBaseBuilderInfo(...args);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
logCreateWebhookBundleInfo(...args) {
|
|
49
|
+
if (!this.config.suppressCreateWebhookBundleLogs) {
|
|
50
|
+
this.logBaseBuilderInfo(...args);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
logCreateManifestInfo(...args) {
|
|
54
|
+
if (!this.config.suppressCreateManifestLogs) {
|
|
55
|
+
this.logBaseBuilderInfo(...args);
|
|
53
56
|
}
|
|
54
|
-
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Finds tsconfig.json/jsconfig.json for the project.
|
|
60
|
+
* Used by esbuild to properly resolve module imports during bundling.
|
|
61
|
+
*/
|
|
62
|
+
async findTsConfigPath() {
|
|
63
|
+
const cwd = this.config.workingDir || process.cwd();
|
|
64
|
+
return findUp(['tsconfig.json', 'jsconfig.json'], { cwd });
|
|
55
65
|
}
|
|
56
66
|
/**
|
|
57
67
|
* Discovers all source files in the configured directories.
|
|
@@ -73,6 +83,7 @@ export class BaseBuilder {
|
|
|
73
83
|
'**/.vercel/**',
|
|
74
84
|
'**/.workflow-data/**',
|
|
75
85
|
'**/.well-known/workflow/**',
|
|
86
|
+
'**/.svelte-kit/**',
|
|
76
87
|
],
|
|
77
88
|
absolute: true,
|
|
78
89
|
});
|
|
@@ -93,6 +104,7 @@ export class BaseBuilder {
|
|
|
93
104
|
const state = {
|
|
94
105
|
discoveredSteps: [],
|
|
95
106
|
discoveredWorkflows: [],
|
|
107
|
+
discoveredSerdeFiles: [],
|
|
96
108
|
};
|
|
97
109
|
const discoverStart = Date.now();
|
|
98
110
|
try {
|
|
@@ -104,30 +116,51 @@ export class BaseBuilder {
|
|
|
104
116
|
write: false,
|
|
105
117
|
outdir,
|
|
106
118
|
bundle: true,
|
|
107
|
-
sourcemap:
|
|
119
|
+
sourcemap: false,
|
|
108
120
|
absWorkingDir: this.config.workingDir,
|
|
109
121
|
logLevel: 'silent',
|
|
122
|
+
// External packages that should not be bundled during discovery
|
|
123
|
+
external: this.config.externalPackages || [],
|
|
110
124
|
});
|
|
111
125
|
}
|
|
112
126
|
catch (_) { }
|
|
113
|
-
|
|
127
|
+
this.logBaseBuilderInfo(`Discovering workflow directives`, `${Date.now() - discoverStart}ms`);
|
|
114
128
|
this.discoveredEntries.set(inputs, state);
|
|
115
129
|
return state;
|
|
116
130
|
}
|
|
117
131
|
/**
|
|
118
132
|
* Writes debug information to a JSON file for troubleshooting build issues.
|
|
119
|
-
*
|
|
133
|
+
* Uses atomic write (temp file + rename) to prevent race conditions when
|
|
134
|
+
* multiple builds run concurrently.
|
|
120
135
|
*/
|
|
121
136
|
async writeDebugFile(outfile, debugData, merge) {
|
|
137
|
+
const prefix = this.config.debugFilePrefix || '';
|
|
138
|
+
const targetPath = `${dirname(outfile)}/${prefix}${basename(outfile)}.debug.json`;
|
|
139
|
+
let existing = {};
|
|
122
140
|
try {
|
|
123
|
-
let existing = {};
|
|
124
141
|
if (merge) {
|
|
125
|
-
|
|
142
|
+
try {
|
|
143
|
+
const content = await readFile(targetPath, 'utf8');
|
|
144
|
+
existing = JSON.parse(content);
|
|
145
|
+
}
|
|
146
|
+
catch (e) {
|
|
147
|
+
// File doesn't exist yet or is corrupted - start fresh.
|
|
148
|
+
// Don't log error for ENOENT (file not found) as that's expected on first run.
|
|
149
|
+
if (e.code !== 'ENOENT') {
|
|
150
|
+
console.warn('Error reading debug file, starting fresh:', e);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
126
153
|
}
|
|
127
|
-
|
|
154
|
+
const mergedData = JSON.stringify({
|
|
128
155
|
...existing,
|
|
129
156
|
...debugData,
|
|
130
|
-
}, null, 2)
|
|
157
|
+
}, null, 2);
|
|
158
|
+
// Write atomically: write to temp file, then rename.
|
|
159
|
+
// rename() is atomic on POSIX systems and provides best-effort atomicity on Windows.
|
|
160
|
+
// Prevents race conditions where concurrent builds read partially-written files.
|
|
161
|
+
const tempPath = `${targetPath}.${randomUUID()}.tmp`;
|
|
162
|
+
await writeFile(tempPath, mergedData);
|
|
163
|
+
await rename(tempPath, targetPath);
|
|
131
164
|
}
|
|
132
165
|
catch (error) {
|
|
133
166
|
console.warn('Failed to write debug file:', error);
|
|
@@ -137,7 +170,7 @@ export class BaseBuilder {
|
|
|
137
170
|
* Logs and optionally throws on esbuild errors and warnings.
|
|
138
171
|
* @param throwOnError - If true, throws an error when esbuild errors are present
|
|
139
172
|
*/
|
|
140
|
-
logEsbuildMessages(result, phase, throwOnError = true) {
|
|
173
|
+
logEsbuildMessages(result, phase, throwOnError = true, options) {
|
|
141
174
|
if (result.errors && result.errors.length > 0) {
|
|
142
175
|
console.error(`❌ esbuild errors in ${phase}:`);
|
|
143
176
|
const errorMessages = [];
|
|
@@ -154,7 +187,9 @@ export class BaseBuilder {
|
|
|
154
187
|
throw new Error(`Build failed during ${phase}:\n${errorMessages.join('\n')}`);
|
|
155
188
|
}
|
|
156
189
|
}
|
|
157
|
-
if (
|
|
190
|
+
if (!options?.suppressWarnings &&
|
|
191
|
+
result.warnings &&
|
|
192
|
+
result.warnings.length > 0) {
|
|
158
193
|
console.warn(`! esbuild warnings in ${phase}:`);
|
|
159
194
|
for (const warning of result.warnings) {
|
|
160
195
|
console.warn(` ${warning.text}`);
|
|
@@ -164,18 +199,48 @@ export class BaseBuilder {
|
|
|
164
199
|
}
|
|
165
200
|
}
|
|
166
201
|
}
|
|
202
|
+
/**
|
|
203
|
+
* Converts an absolute file path to a normalized relative path for the manifest.
|
|
204
|
+
*/
|
|
205
|
+
getRelativeFilepath(absolutePath) {
|
|
206
|
+
const normalizedFile = absolutePath.replace(/\\/g, '/');
|
|
207
|
+
const normalizedWorkingDir = this.config.workingDir.replace(/\\/g, '/');
|
|
208
|
+
let relativePath = relative(normalizedWorkingDir, normalizedFile).replace(/\\/g, '/');
|
|
209
|
+
// Handle files discovered outside the working directory
|
|
210
|
+
if (relativePath.startsWith('../')) {
|
|
211
|
+
relativePath = relativePath
|
|
212
|
+
.split('/')
|
|
213
|
+
.filter((part) => part !== '..')
|
|
214
|
+
.join('/');
|
|
215
|
+
}
|
|
216
|
+
return relativePath;
|
|
217
|
+
}
|
|
167
218
|
/**
|
|
168
219
|
* Creates a bundle for workflow step functions.
|
|
169
220
|
* Steps have full Node.js runtime access and handle side effects, API calls, etc.
|
|
170
221
|
*
|
|
171
222
|
* @param externalizeNonSteps - If true, only bundles step entry points and externalizes other code
|
|
223
|
+
* @returns Build context (for watch mode) and the collected workflow manifest
|
|
172
224
|
*/
|
|
173
|
-
async createStepsBundle({ inputFiles, format = 'cjs', outfile, externalizeNonSteps,
|
|
225
|
+
async createStepsBundle({ inputFiles, format = 'cjs', outfile, externalizeNonSteps, tsconfigPath, discoveredEntries, }) {
|
|
174
226
|
// These need to handle watching for dev to scan for
|
|
175
227
|
// new entries and changes to existing ones
|
|
176
|
-
const
|
|
228
|
+
const discovered = discoveredEntries ??
|
|
229
|
+
(await this.discoverEntries(inputFiles, dirname(outfile)));
|
|
230
|
+
const stepFiles = [...discovered.discoveredSteps].sort();
|
|
231
|
+
const workflowFiles = [...discovered.discoveredWorkflows].sort();
|
|
232
|
+
const serdeFiles = [...discovered.discoveredSerdeFiles].sort();
|
|
233
|
+
// Include serde files that aren't already step files for cross-context class registration.
|
|
234
|
+
// Classes need to be registered in the step bundle so they can be deserialized
|
|
235
|
+
// when receiving data from workflows and serialized when returning data to workflows.
|
|
236
|
+
const stepFilesSet = new Set(stepFiles);
|
|
237
|
+
const serdeOnlyFiles = serdeFiles.filter((f) => !stepFilesSet.has(f));
|
|
177
238
|
// log the step files for debugging
|
|
178
|
-
await this.writeDebugFile(outfile, {
|
|
239
|
+
await this.writeDebugFile(outfile, {
|
|
240
|
+
stepFiles,
|
|
241
|
+
workflowFiles,
|
|
242
|
+
serdeOnlyFiles,
|
|
243
|
+
});
|
|
179
244
|
const stepsBundleStart = Date.now();
|
|
180
245
|
const workflowManifest = {};
|
|
181
246
|
const builtInSteps = 'workflow/internal/builtins';
|
|
@@ -187,10 +252,16 @@ export class BaseBuilder {
|
|
|
187
252
|
`Caused by: ${chalk.red(String(err))}`,
|
|
188
253
|
].join('\n'));
|
|
189
254
|
});
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
|
|
193
|
-
|
|
255
|
+
// Helper to create import statement from file path
|
|
256
|
+
// For workspace/node_modules packages, uses the package name so esbuild
|
|
257
|
+
// will resolve through package.json exports with the appropriate conditions
|
|
258
|
+
const createImport = (file) => {
|
|
259
|
+
const { importPath, isPackage } = getImportPath(file, this.config.workingDir);
|
|
260
|
+
if (isPackage) {
|
|
261
|
+
// Use package name - esbuild will resolve via package.json exports
|
|
262
|
+
return `import '${importPath}';`;
|
|
263
|
+
}
|
|
264
|
+
// Local app file - use relative path
|
|
194
265
|
// Normalize both paths to forward slashes before calling relative()
|
|
195
266
|
// This is critical on Windows where relative() can produce unexpected results with mixed path formats
|
|
196
267
|
const normalizedWorkingDir = this.config.workingDir.replace(/\\/g, '/');
|
|
@@ -202,16 +273,37 @@ export class BaseBuilder {
|
|
|
202
273
|
relativePath = `./${relativePath}`;
|
|
203
274
|
}
|
|
204
275
|
return `import '${relativePath}';`;
|
|
205
|
-
}
|
|
206
|
-
|
|
276
|
+
};
|
|
277
|
+
// Create a virtual entry that imports all files. All step definitions
|
|
278
|
+
// will get registered thanks to the swc transform.
|
|
279
|
+
const stepImports = stepFiles.map(createImport).join('\n');
|
|
280
|
+
// Include serde-only files for class registration side effects
|
|
281
|
+
const serdeImports = serdeOnlyFiles.map(createImport).join('\n');
|
|
207
282
|
const entryContent = `
|
|
208
283
|
// Built in steps
|
|
209
284
|
import '${builtInSteps}';
|
|
210
285
|
// User steps
|
|
211
|
-
${
|
|
286
|
+
${stepImports}
|
|
287
|
+
// Serde files for cross-context class registration
|
|
288
|
+
${serdeImports}
|
|
212
289
|
// API entrypoint
|
|
213
290
|
export { stepEntrypoint as POST } from 'workflow/runtime';`;
|
|
214
291
|
// Bundle with esbuild and our custom SWC plugin
|
|
292
|
+
const entriesToBundle = externalizeNonSteps
|
|
293
|
+
? [
|
|
294
|
+
...stepFiles,
|
|
295
|
+
...serdeFiles,
|
|
296
|
+
...(resolvedBuiltInSteps ? [resolvedBuiltInSteps] : []),
|
|
297
|
+
]
|
|
298
|
+
: undefined;
|
|
299
|
+
const normalizedEntriesToBundle = entriesToBundle
|
|
300
|
+
? Array.from(new Set((await Promise.all(entriesToBundle.map(async (entryToBundle) => {
|
|
301
|
+
const resolvedEntry = await realpath(entryToBundle).catch(() => undefined);
|
|
302
|
+
return resolvedEntry
|
|
303
|
+
? [entryToBundle, resolvedEntry]
|
|
304
|
+
: [entryToBundle];
|
|
305
|
+
}))).flat()))
|
|
306
|
+
: undefined;
|
|
215
307
|
const esbuildCtx = await esbuild.context({
|
|
216
308
|
banner: {
|
|
217
309
|
js: '// biome-ignore-all lint: generated file\n/* eslint-disable */\n',
|
|
@@ -233,42 +325,71 @@ export class BaseBuilder {
|
|
|
233
325
|
treeShaking: true,
|
|
234
326
|
keepNames: true,
|
|
235
327
|
minify: false,
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
328
|
+
jsx: 'preserve',
|
|
329
|
+
logLevel: 'error',
|
|
330
|
+
// Use tsconfig for path alias resolution
|
|
331
|
+
tsconfig: tsconfigPath,
|
|
332
|
+
resolveExtensions: [
|
|
333
|
+
'.ts',
|
|
334
|
+
'.tsx',
|
|
335
|
+
'.mts',
|
|
336
|
+
'.cts',
|
|
337
|
+
'.js',
|
|
338
|
+
'.jsx',
|
|
339
|
+
'.mjs',
|
|
340
|
+
'.cjs',
|
|
341
|
+
],
|
|
342
|
+
// Inline source maps for better stack traces in step execution.
|
|
343
|
+
// Steps execute in Node.js context and inline sourcemaps ensure we get
|
|
344
|
+
// meaningful stack traces with proper file names and line numbers when errors
|
|
345
|
+
// occur in deeply nested function calls across multiple files.
|
|
346
|
+
sourcemap: 'inline',
|
|
239
347
|
plugins: [
|
|
348
|
+
// Handle pseudo-packages like 'server-only' and 'client-only' by providing
|
|
349
|
+
// empty modules. Must run first to intercept these before other resolution.
|
|
350
|
+
createPseudoPackagePlugin(),
|
|
240
351
|
createSwcPlugin({
|
|
241
352
|
mode: 'step',
|
|
242
|
-
entriesToBundle:
|
|
243
|
-
? [
|
|
244
|
-
...stepFiles,
|
|
245
|
-
...(resolvedBuiltInSteps ? [resolvedBuiltInSteps] : []),
|
|
246
|
-
]
|
|
247
|
-
: undefined,
|
|
353
|
+
entriesToBundle: normalizedEntriesToBundle,
|
|
248
354
|
outdir: outfile ? dirname(outfile) : undefined,
|
|
249
|
-
tsBaseUrl,
|
|
250
|
-
tsPaths,
|
|
251
355
|
workflowManifest,
|
|
252
356
|
}),
|
|
253
357
|
],
|
|
254
358
|
// Plugin should catch most things, but this lets users hard override
|
|
255
359
|
// if the plugin misses anything that should be externalized
|
|
256
|
-
external: this.config.externalPackages || [],
|
|
360
|
+
external: ['bun', 'bun:*', ...(this.config.externalPackages || [])],
|
|
257
361
|
});
|
|
258
362
|
const stepsResult = await esbuildCtx.rebuild();
|
|
259
363
|
this.logEsbuildMessages(stepsResult, 'steps bundle creation');
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
await
|
|
364
|
+
this.logBaseBuilderInfo('Created steps bundle', `${Date.now() - stepsBundleStart}ms`);
|
|
365
|
+
// Handle workflow-only files that may have been tree-shaken from the bundle.
|
|
366
|
+
// These files have no steps, so esbuild removes them, but we still need their
|
|
367
|
+
// workflow metadata for the manifest. Transform them separately.
|
|
368
|
+
const workflowOnlyFiles = workflowFiles.filter((f) => !stepFiles.includes(f));
|
|
369
|
+
await Promise.all(workflowOnlyFiles.map(async (workflowFile) => {
|
|
370
|
+
try {
|
|
371
|
+
const source = await readFile(workflowFile, 'utf8');
|
|
372
|
+
const relativeFilepath = this.getRelativeFilepath(workflowFile);
|
|
373
|
+
const { workflowManifest: fileManifest } = await applySwcTransform(relativeFilepath, source, 'workflow');
|
|
374
|
+
if (fileManifest.workflows) {
|
|
375
|
+
workflowManifest.workflows = Object.assign(workflowManifest.workflows || {}, fileManifest.workflows);
|
|
376
|
+
}
|
|
377
|
+
if (fileManifest.classes) {
|
|
378
|
+
workflowManifest.classes = Object.assign(workflowManifest.classes || {}, fileManifest.classes);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
catch (error) {
|
|
382
|
+
// Log warning but continue - don't fail build for workflow-only file issues
|
|
383
|
+
console.warn(`Warning: Failed to extract workflow metadata from ${workflowFile}:`, error instanceof Error ? error.message : String(error));
|
|
384
|
+
}
|
|
385
|
+
}));
|
|
266
386
|
// Create .gitignore in .swc directory
|
|
267
387
|
await this.createSwcGitignore();
|
|
268
388
|
if (this.config.watch) {
|
|
269
|
-
return esbuildCtx;
|
|
389
|
+
return { context: esbuildCtx, manifest: workflowManifest };
|
|
270
390
|
}
|
|
271
391
|
await esbuildCtx.dispose();
|
|
392
|
+
return { context: undefined, manifest: workflowManifest };
|
|
272
393
|
}
|
|
273
394
|
/**
|
|
274
395
|
* Creates a bundle for workflow orchestration functions.
|
|
@@ -276,28 +397,50 @@ export class BaseBuilder {
|
|
|
276
397
|
*
|
|
277
398
|
* @param bundleFinalOutput - If false, skips the final bundling step (used by Next.js)
|
|
278
399
|
*/
|
|
279
|
-
async createWorkflowsBundle({ inputFiles, format = 'cjs', outfile, bundleFinalOutput = true,
|
|
280
|
-
const
|
|
400
|
+
async createWorkflowsBundle({ inputFiles, format = 'cjs', outfile, bundleFinalOutput = true, tsconfigPath, discoveredEntries, }) {
|
|
401
|
+
const discovered = discoveredEntries ??
|
|
402
|
+
(await this.discoverEntries(inputFiles, dirname(outfile)));
|
|
403
|
+
const workflowFiles = [...discovered.discoveredWorkflows].sort();
|
|
404
|
+
const serdeFiles = [...discovered.discoveredSerdeFiles].sort();
|
|
405
|
+
// Include serde files that aren't already workflow files for cross-context class registration.
|
|
406
|
+
// Classes need to be registered in the workflow bundle so they can be deserialized
|
|
407
|
+
// when receiving data from steps or when serializing data to send to steps.
|
|
408
|
+
const workflowFilesSet = new Set(workflowFiles);
|
|
409
|
+
const serdeOnlyFiles = serdeFiles.filter((f) => !workflowFilesSet.has(f));
|
|
281
410
|
// log the workflow files for debugging
|
|
282
|
-
await this.writeDebugFile(outfile, { workflowFiles });
|
|
283
|
-
//
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
411
|
+
await this.writeDebugFile(outfile, { workflowFiles, serdeOnlyFiles });
|
|
412
|
+
// Helper to create import statement from file path
|
|
413
|
+
// For packages, uses the package name so esbuild will resolve through
|
|
414
|
+
// package.json exports with conditions: ['workflow']
|
|
415
|
+
const createImport = (file) => {
|
|
416
|
+
const { importPath, isPackage } = getImportPath(file, this.config.workingDir);
|
|
417
|
+
if (isPackage) {
|
|
418
|
+
// Use package name - esbuild will resolve via package.json exports
|
|
419
|
+
// and apply the 'workflow' condition
|
|
420
|
+
return `import '${importPath}';`;
|
|
421
|
+
}
|
|
422
|
+
// Local app file - use relative path
|
|
423
|
+
// Normalize both paths to forward slashes before calling relative()
|
|
424
|
+
// This is critical on Windows where relative() can produce unexpected results with mixed path formats
|
|
425
|
+
const normalizedWorkingDir = this.config.workingDir.replace(/\\/g, '/');
|
|
426
|
+
const normalizedFile = file.replace(/\\/g, '/');
|
|
427
|
+
// Calculate relative path from working directory to the file
|
|
428
|
+
let relativePath = relative(normalizedWorkingDir, normalizedFile).replace(/\\/g, '/');
|
|
429
|
+
// Ensure relative paths start with ./ so esbuild resolves them correctly
|
|
430
|
+
if (!relativePath.startsWith('.')) {
|
|
431
|
+
relativePath = `./${relativePath}`;
|
|
432
|
+
}
|
|
433
|
+
return `import '${relativePath}';`;
|
|
434
|
+
};
|
|
435
|
+
// Create a virtual entry that imports all workflow files
|
|
436
|
+
// The SWC plugin in workflow mode emits `globalThis.__private_workflows.set(workflowId, fn)`
|
|
437
|
+
// calls directly, so we just need to import the files (Map is initialized via banner)
|
|
438
|
+
const workflowImports = workflowFiles.map(createImport).join('\n');
|
|
439
|
+
// Include serde-only files for class registration side effects
|
|
440
|
+
const serdeImports = serdeOnlyFiles.map(createImport).join('\n');
|
|
441
|
+
const imports = serdeImports
|
|
442
|
+
? `${workflowImports}\n// Serde files for cross-context class registration\n${serdeImports}`
|
|
443
|
+
: workflowImports;
|
|
301
444
|
const bundleStartTime = Date.now();
|
|
302
445
|
const workflowManifest = {};
|
|
303
446
|
// Bundle with esbuild and our custom SWC plugin in workflow mode.
|
|
@@ -320,28 +463,54 @@ export class BaseBuilder {
|
|
|
320
463
|
treeShaking: true,
|
|
321
464
|
keepNames: true,
|
|
322
465
|
minify: false,
|
|
323
|
-
//
|
|
324
|
-
|
|
325
|
-
|
|
466
|
+
// Initialize the workflow registry at the very top of the bundle
|
|
467
|
+
// This must be in banner (not the virtual entry) because esbuild's bundling
|
|
468
|
+
// can reorder code, and the .set() calls need the Map to exist first
|
|
469
|
+
banner: {
|
|
470
|
+
js: 'globalThis.__private_workflows = new Map();',
|
|
471
|
+
},
|
|
472
|
+
// Inline source maps for better stack traces in workflow VM execution.
|
|
473
|
+
// This intermediate bundle is executed via runInContext() in a VM, so we need
|
|
474
|
+
// inline source maps to get meaningful stack traces instead of "evalmachine.<anonymous>".
|
|
475
|
+
sourcemap: 'inline',
|
|
476
|
+
// Use tsconfig for path alias resolution
|
|
477
|
+
tsconfig: tsconfigPath,
|
|
478
|
+
resolveExtensions: [
|
|
479
|
+
'.ts',
|
|
480
|
+
'.tsx',
|
|
481
|
+
'.mts',
|
|
482
|
+
'.cts',
|
|
483
|
+
'.js',
|
|
484
|
+
'.jsx',
|
|
485
|
+
'.mjs',
|
|
486
|
+
'.cjs',
|
|
487
|
+
],
|
|
326
488
|
plugins: [
|
|
489
|
+
// Handle pseudo-packages like 'server-only' and 'client-only' by providing
|
|
490
|
+
// empty modules. Must run first to intercept these before other resolution.
|
|
491
|
+
createPseudoPackagePlugin(),
|
|
327
492
|
createSwcPlugin({
|
|
328
493
|
mode: 'workflow',
|
|
329
|
-
tsBaseUrl,
|
|
330
|
-
tsPaths,
|
|
331
494
|
workflowManifest,
|
|
332
495
|
}),
|
|
333
496
|
// This plugin must run after the swc plugin to ensure dead code elimination
|
|
334
497
|
// happens first, preventing false positives on Node.js imports in unused code paths
|
|
335
498
|
createNodeModuleErrorPlugin(),
|
|
336
499
|
],
|
|
500
|
+
// NOTE: We intentionally do NOT use the external option here for workflow bundles.
|
|
501
|
+
// When packages are marked external with format: 'cjs', esbuild generates require() calls.
|
|
502
|
+
// However, the workflow VM (vm.runInContext) does not have require() defined - it only
|
|
503
|
+
// provides module.exports and exports. External packages would fail at runtime with:
|
|
504
|
+
// ReferenceError: require is not defined
|
|
505
|
+
// Instead, we bundle everything and rely on:
|
|
506
|
+
// - createPseudoPackagePlugin() to handle server-only/client-only with empty modules
|
|
507
|
+
// - createNodeModuleErrorPlugin() to catch Node.js builtin imports at build time
|
|
337
508
|
});
|
|
338
509
|
const interimBundle = await interimBundleCtx.rebuild();
|
|
339
|
-
this.logEsbuildMessages(interimBundle, 'intermediate workflow bundle'
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
};
|
|
344
|
-
await this.writeDebugFile(join(dirname(outfile), 'manifest'), partialWorkflowManifest, true);
|
|
510
|
+
this.logEsbuildMessages(interimBundle, 'intermediate workflow bundle', true, {
|
|
511
|
+
suppressWarnings: this.config.suppressCreateWorkflowsBundleWarnings,
|
|
512
|
+
});
|
|
513
|
+
this.logCreateWorkflowsBundleInfo('Created intermediate workflow bundle', `${Date.now() - bundleStartTime}ms`);
|
|
345
514
|
if (this.config.workflowManifestPath) {
|
|
346
515
|
const resolvedPath = resolve(process.cwd(), this.config.workflowManifestPath);
|
|
347
516
|
let prefix = '';
|
|
@@ -353,7 +522,7 @@ export class BaseBuilder {
|
|
|
353
522
|
prefix = 'export default ';
|
|
354
523
|
}
|
|
355
524
|
await mkdir(dirname(resolvedPath), { recursive: true });
|
|
356
|
-
await writeFile(resolvedPath, prefix + JSON.stringify(workflowManifest
|
|
525
|
+
await writeFile(resolvedPath, prefix + JSON.stringify(workflowManifest, null, 2));
|
|
357
526
|
}
|
|
358
527
|
// Create .gitignore in .swc directory
|
|
359
528
|
await this.createSwcGitignore();
|
|
@@ -377,7 +546,11 @@ export const POST = workflowEntrypoint(workflowCode);`;
|
|
|
377
546
|
// Ensure the output directory exists
|
|
378
547
|
const outputDir = dirname(outfile);
|
|
379
548
|
await mkdir(outputDir, { recursive: true });
|
|
380
|
-
|
|
549
|
+
// Atomic write: write to temp file then rename to prevent
|
|
550
|
+
// file watchers from reading partial file during write
|
|
551
|
+
const tempPath = `${outfile}.${randomUUID()}.tmp`;
|
|
552
|
+
await writeFile(tempPath, workflowFunctionCode);
|
|
553
|
+
await rename(tempPath, outfile);
|
|
381
554
|
return;
|
|
382
555
|
}
|
|
383
556
|
const bundleStartTime = Date.now();
|
|
@@ -394,7 +567,8 @@ export const POST = workflowEntrypoint(workflowCode);`;
|
|
|
394
567
|
loader: 'js',
|
|
395
568
|
},
|
|
396
569
|
outfile,
|
|
397
|
-
//
|
|
570
|
+
// Source maps for the final workflow bundle wrapper (not important since this code
|
|
571
|
+
// doesn't run in the VM - only the intermediate bundle sourcemap is relevant)
|
|
398
572
|
sourcemap: EMIT_SOURCEMAPS_FOR_DEBUGGING,
|
|
399
573
|
absWorkingDir: this.config.workingDir,
|
|
400
574
|
bundle: true,
|
|
@@ -406,17 +580,21 @@ export const POST = workflowEntrypoint(workflowCode);`;
|
|
|
406
580
|
minify: false,
|
|
407
581
|
external: ['@aws-sdk/credential-provider-web-identity'],
|
|
408
582
|
});
|
|
409
|
-
this.logEsbuildMessages(finalWorkflowResult, 'final workflow bundle'
|
|
410
|
-
|
|
583
|
+
this.logEsbuildMessages(finalWorkflowResult, 'final workflow bundle', true, {
|
|
584
|
+
suppressWarnings: this.config.suppressCreateWorkflowsBundleWarnings,
|
|
585
|
+
});
|
|
586
|
+
this.logCreateWorkflowsBundleInfo('Created final workflow bundle', `${Date.now() - bundleStartTime}ms`);
|
|
411
587
|
};
|
|
412
588
|
await bundleFinal(interimBundle.outputFiles[0].text);
|
|
413
589
|
if (this.config.watch) {
|
|
414
590
|
return {
|
|
591
|
+
manifest: workflowManifest,
|
|
415
592
|
interimBundleCtx,
|
|
416
593
|
bundleFinal,
|
|
417
594
|
};
|
|
418
595
|
}
|
|
419
596
|
await interimBundleCtx.dispose();
|
|
597
|
+
return { manifest: workflowManifest };
|
|
420
598
|
}
|
|
421
599
|
/**
|
|
422
600
|
* Creates a client library bundle for workflow execution.
|
|
@@ -428,23 +606,45 @@ export const POST = workflowEntrypoint(workflowCode);`;
|
|
|
428
606
|
// Silently exit since no client bundle was requested
|
|
429
607
|
return;
|
|
430
608
|
}
|
|
431
|
-
|
|
432
|
-
|
|
609
|
+
this.logBaseBuilderInfo('Generating a client library at', this.config.clientBundlePath);
|
|
610
|
+
this.logBaseBuilderInfo('NOTE: The recommended way to use workflow with a framework like NextJS is using the loader/plugin with webpack/turbobpack/rollup');
|
|
433
611
|
// Ensure we have the directory for the client bundle
|
|
434
612
|
const outputDir = dirname(this.config.clientBundlePath);
|
|
435
613
|
await mkdir(outputDir, { recursive: true });
|
|
436
614
|
const inputFiles = await this.getInputFiles();
|
|
437
|
-
//
|
|
438
|
-
|
|
615
|
+
// Discover serde files from the input files' dependency tree for cross-context class registration.
|
|
616
|
+
// Classes need to be registered in the client bundle so they can be serialized
|
|
617
|
+
// when passing data to workflows via start() and deserialized when receiving workflow results.
|
|
618
|
+
const { discoveredSerdeFiles } = await this.discoverEntries(inputFiles, outputDir);
|
|
619
|
+
// Identify serde files that aren't in the inputFiles (deduplicated)
|
|
620
|
+
const inputFilesNormalized = new Set(inputFiles.map((f) => f.replace(/\\/g, '/')));
|
|
621
|
+
const serdeOnlyFiles = discoveredSerdeFiles.filter((f) => !inputFilesNormalized.has(f));
|
|
622
|
+
// Re-exports for input files (user's workflow/step definitions)
|
|
623
|
+
const reexports = inputFiles
|
|
439
624
|
.map((file) => `export * from '${file}';`)
|
|
440
625
|
.join('\n');
|
|
626
|
+
// Side-effect imports for serde files not in inputFiles (for class registration)
|
|
627
|
+
const serdeImports = serdeOnlyFiles
|
|
628
|
+
.map((file) => {
|
|
629
|
+
const normalizedWorkingDir = this.config.workingDir.replace(/\\/g, '/');
|
|
630
|
+
let relativePath = relative(normalizedWorkingDir, file).replace(/\\/g, '/');
|
|
631
|
+
if (!relativePath.startsWith('.')) {
|
|
632
|
+
relativePath = `./${relativePath}`;
|
|
633
|
+
}
|
|
634
|
+
return `import '${relativePath}';`;
|
|
635
|
+
})
|
|
636
|
+
.join('\n');
|
|
637
|
+
// Combine: serde imports (for registration side effects) + re-exports
|
|
638
|
+
const entryContent = serdeImports
|
|
639
|
+
? `// Serde files for cross-context class registration\n${serdeImports}\n${reexports}`
|
|
640
|
+
: reexports;
|
|
441
641
|
// Bundle with esbuild and our custom SWC plugin
|
|
442
642
|
const clientResult = await esbuild.build({
|
|
443
643
|
banner: {
|
|
444
644
|
js: '// biome-ignore-all lint: generated file\n/* eslint-disable */\n',
|
|
445
645
|
},
|
|
446
646
|
stdin: {
|
|
447
|
-
contents:
|
|
647
|
+
contents: entryContent,
|
|
448
648
|
resolveDir: this.config.workingDir,
|
|
449
649
|
sourcefile: 'virtual-entry.js',
|
|
450
650
|
loader: 'js',
|
|
@@ -453,11 +653,21 @@ export const POST = workflowEntrypoint(workflowCode);`;
|
|
|
453
653
|
bundle: true,
|
|
454
654
|
format: 'esm',
|
|
455
655
|
platform: 'node',
|
|
656
|
+
jsx: 'preserve',
|
|
456
657
|
target: 'es2022',
|
|
457
658
|
write: true,
|
|
458
659
|
treeShaking: true,
|
|
459
660
|
external: ['@workflow/core'],
|
|
460
|
-
resolveExtensions: [
|
|
661
|
+
resolveExtensions: [
|
|
662
|
+
'.ts',
|
|
663
|
+
'.tsx',
|
|
664
|
+
'.mts',
|
|
665
|
+
'.cts',
|
|
666
|
+
'.js',
|
|
667
|
+
'.jsx',
|
|
668
|
+
'.mjs',
|
|
669
|
+
'.cjs',
|
|
670
|
+
],
|
|
461
671
|
plugins: [createSwcPlugin({ mode: 'client' })],
|
|
462
672
|
});
|
|
463
673
|
this.logEsbuildMessages(clientResult, 'client library bundle');
|
|
@@ -470,7 +680,7 @@ export const POST = workflowEntrypoint(workflowCode);`;
|
|
|
470
680
|
* @param bundle - If true, bundles dependencies (needed for Build Output API)
|
|
471
681
|
*/
|
|
472
682
|
async createWebhookBundle({ outfile, bundle = false, }) {
|
|
473
|
-
|
|
683
|
+
this.logCreateWebhookBundleInfo('Creating webhook route');
|
|
474
684
|
await mkdir(dirname(outfile), { recursive: true });
|
|
475
685
|
// Create a static route that calls resumeWebhook
|
|
476
686
|
// This route works for both Next.js and Vercel Build Output API
|
|
@@ -512,7 +722,7 @@ export const OPTIONS = handler;`;
|
|
|
512
722
|
const webhookBundleStart = Date.now();
|
|
513
723
|
const result = await esbuild.build({
|
|
514
724
|
banner: {
|
|
515
|
-
js:
|
|
725
|
+
js: `// biome-ignore-all lint: generated file\n/* eslint-disable */`,
|
|
516
726
|
},
|
|
517
727
|
stdin: {
|
|
518
728
|
contents: routeContent,
|
|
@@ -523,6 +733,7 @@ export const OPTIONS = handler;`;
|
|
|
523
733
|
outfile,
|
|
524
734
|
absWorkingDir: this.config.workingDir,
|
|
525
735
|
bundle: true,
|
|
736
|
+
jsx: 'preserve',
|
|
526
737
|
format: 'cjs',
|
|
527
738
|
platform: 'node',
|
|
528
739
|
conditions: ['import', 'module', 'node', 'default'],
|
|
@@ -531,14 +742,23 @@ export const OPTIONS = handler;`;
|
|
|
531
742
|
treeShaking: true,
|
|
532
743
|
keepNames: true,
|
|
533
744
|
minify: false,
|
|
534
|
-
resolveExtensions: [
|
|
535
|
-
|
|
745
|
+
resolveExtensions: [
|
|
746
|
+
'.ts',
|
|
747
|
+
'.tsx',
|
|
748
|
+
'.mts',
|
|
749
|
+
'.cts',
|
|
750
|
+
'.js',
|
|
751
|
+
'.jsx',
|
|
752
|
+
'.mjs',
|
|
753
|
+
'.cjs',
|
|
754
|
+
],
|
|
755
|
+
sourcemap: EMIT_SOURCEMAPS_FOR_DEBUGGING,
|
|
536
756
|
mainFields: ['module', 'main'],
|
|
537
757
|
// Don't externalize anything - bundle everything including workflow packages
|
|
538
758
|
external: [],
|
|
539
759
|
});
|
|
540
760
|
this.logEsbuildMessages(result, 'webhook bundle creation');
|
|
541
|
-
|
|
761
|
+
this.logCreateWebhookBundleInfo('Created webhook bundle', `${Date.now() - webhookBundleStart}ms`);
|
|
542
762
|
}
|
|
543
763
|
/**
|
|
544
764
|
* Creates a package.json file with the specified module type.
|
|
@@ -586,5 +806,108 @@ export const OPTIONS = handler;`;
|
|
|
586
806
|
// We're intentionally silently ignoring this error - creating .gitignore isn't critical
|
|
587
807
|
}
|
|
588
808
|
}
|
|
809
|
+
/**
|
|
810
|
+
* Whether the manifest should be exposed as a public HTTP route.
|
|
811
|
+
* Controlled by the `WORKFLOW_PUBLIC_MANIFEST` environment variable.
|
|
812
|
+
*/
|
|
813
|
+
get shouldExposePublicManifest() {
|
|
814
|
+
return process.env.WORKFLOW_PUBLIC_MANIFEST === '1';
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Whether diagnostics artifacts should be emitted to Vercel output.
|
|
818
|
+
* This is enabled when the resolved world target is Vercel.
|
|
819
|
+
*/
|
|
820
|
+
get shouldEmitVercelDiagnostics() {
|
|
821
|
+
return (usesVercelWorld() || this.config.buildTarget === 'vercel-build-output-api');
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Creates a manifest JSON file containing step/workflow/class metadata
|
|
825
|
+
* and graph data for visualization.
|
|
826
|
+
*
|
|
827
|
+
* @returns The manifest JSON string, or undefined if manifest creation failed.
|
|
828
|
+
*/
|
|
829
|
+
async createManifest({ workflowBundlePath, manifestDir, manifest, }) {
|
|
830
|
+
const buildStart = Date.now();
|
|
831
|
+
this.logCreateManifestInfo('Creating manifest...');
|
|
832
|
+
try {
|
|
833
|
+
const workflowGraphs = await extractWorkflowGraphs(workflowBundlePath);
|
|
834
|
+
const steps = this.convertStepsManifest(manifest.steps);
|
|
835
|
+
const workflows = this.convertWorkflowsManifest(manifest.workflows, workflowGraphs);
|
|
836
|
+
const classes = this.convertClassesManifest(manifest.classes);
|
|
837
|
+
const output = { version: '1.0.0', steps, workflows, classes };
|
|
838
|
+
const manifestJson = JSON.stringify(output, null, 2);
|
|
839
|
+
await mkdir(manifestDir, { recursive: true });
|
|
840
|
+
await writeFile(join(manifestDir, 'manifest.json'), manifestJson);
|
|
841
|
+
if (this.shouldEmitVercelDiagnostics) {
|
|
842
|
+
const diagnosticsManifestPath = this.resolvePath('.vercel/output/diagnostics/workflows-manifest.json');
|
|
843
|
+
await this.ensureDirectory(diagnosticsManifestPath);
|
|
844
|
+
await writeFile(diagnosticsManifestPath, manifestJson);
|
|
845
|
+
}
|
|
846
|
+
const stepCount = Object.values(steps).reduce((acc, s) => acc + Object.keys(s).length, 0);
|
|
847
|
+
const workflowCount = Object.values(workflows).reduce((acc, w) => acc + Object.keys(w).length, 0);
|
|
848
|
+
const classCount = Object.values(classes).reduce((acc, c) => acc + Object.keys(c).length, 0);
|
|
849
|
+
this.logCreateManifestInfo(`Created manifest with ${stepCount} ${pluralize('step', 'steps', stepCount)}, ${workflowCount} ${pluralize('workflow', 'workflows', workflowCount)}, and ${classCount} ${pluralize('class', 'classes', classCount)}`, `${Date.now() - buildStart}ms`);
|
|
850
|
+
return manifestJson;
|
|
851
|
+
}
|
|
852
|
+
catch (error) {
|
|
853
|
+
console.warn('Failed to create manifest:', error instanceof Error ? error.message : String(error));
|
|
854
|
+
return undefined;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
convertStepsManifest(steps) {
|
|
858
|
+
const result = {};
|
|
859
|
+
if (!steps)
|
|
860
|
+
return result;
|
|
861
|
+
for (const [filePath, entries] of Object.entries(steps)) {
|
|
862
|
+
result[filePath] = {};
|
|
863
|
+
for (const [name, data] of Object.entries(entries)) {
|
|
864
|
+
result[filePath][name] = { stepId: data.stepId };
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
return result;
|
|
868
|
+
}
|
|
869
|
+
convertWorkflowsManifest(workflows, graphs) {
|
|
870
|
+
const result = {};
|
|
871
|
+
if (!workflows)
|
|
872
|
+
return result;
|
|
873
|
+
// Build a normalized lookup for graphs since the graph extractor uses
|
|
874
|
+
// paths from workflowId (e.g. "./workflows/hello-agent") while the
|
|
875
|
+
// manifest uses source file paths (e.g. "workflows/hello-agent.ts").
|
|
876
|
+
// Normalize by stripping leading "./" and file extensions.
|
|
877
|
+
const normalizedGraphs = new Map();
|
|
878
|
+
for (const [graphPath, graphEntries] of Object.entries(graphs)) {
|
|
879
|
+
const normalized = graphPath
|
|
880
|
+
.replace(/^\.\//, '')
|
|
881
|
+
.replace(/\.[^/.]+$/, '');
|
|
882
|
+
normalizedGraphs.set(normalized, graphEntries);
|
|
883
|
+
}
|
|
884
|
+
for (const [filePath, entries] of Object.entries(workflows)) {
|
|
885
|
+
result[filePath] = {};
|
|
886
|
+
// Normalize the manifest file path for lookup
|
|
887
|
+
const normalizedFilePath = filePath
|
|
888
|
+
.replace(/^\.\//, '')
|
|
889
|
+
.replace(/\.[^/.]+$/, '');
|
|
890
|
+
const graphEntries = graphs[filePath] || normalizedGraphs.get(normalizedFilePath);
|
|
891
|
+
for (const [name, data] of Object.entries(entries)) {
|
|
892
|
+
result[filePath][name] = {
|
|
893
|
+
workflowId: data.workflowId,
|
|
894
|
+
graph: graphEntries?.[name]?.graph || { nodes: [], edges: [] },
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
return result;
|
|
899
|
+
}
|
|
900
|
+
convertClassesManifest(classes) {
|
|
901
|
+
const result = {};
|
|
902
|
+
if (!classes)
|
|
903
|
+
return result;
|
|
904
|
+
for (const [filePath, entries] of Object.entries(classes)) {
|
|
905
|
+
result[filePath] = {};
|
|
906
|
+
for (const [name, data] of Object.entries(entries)) {
|
|
907
|
+
result[filePath][name] = { classId: data.classId };
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
return result;
|
|
911
|
+
}
|
|
589
912
|
}
|
|
590
913
|
//# sourceMappingURL=base-builder.js.map
|