@workflow/next 4.0.1-beta.50 → 4.0.1-beta.52
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/builder-deferred.d.ts +2 -0
- package/dist/builder-deferred.d.ts.map +1 -0
- package/dist/builder-deferred.js +1269 -0
- package/dist/builder-deferred.js.map +1 -0
- package/dist/builder-eager.d.ts +2 -0
- package/dist/builder-eager.d.ts.map +1 -0
- package/dist/builder-eager.js +390 -0
- package/dist/builder-eager.js.map +1 -0
- package/dist/builder.d.ts +4 -1
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +18 -381
- package/dist/builder.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +75 -27
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +7 -2
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +296 -76
- package/dist/loader.js.map +1 -1
- package/dist/socket-server.d.ts +47 -0
- package/dist/socket-server.d.ts.map +1 -0
- package/dist/socket-server.js +132 -0
- package/dist/socket-server.js.map +1 -0
- package/dist/step-copy-utils.d.ts +19 -0
- package/dist/step-copy-utils.d.ts.map +1 -0
- package/dist/step-copy-utils.js +101 -0
- package/dist/step-copy-utils.js.map +1 -0
- package/docs/api-reference/index.mdx +4 -0
- package/docs/api-reference/with-workflow.mdx +4 -0
- package/docs/next.mdx +7 -0
- package/package.json +4 -4
- package/dist/swc-cache.d.ts +0 -8
- package/dist/swc-cache.d.ts.map +0 -1
- package/dist/swc-cache.js +0 -53
- package/dist/swc-cache.js.map +0 -1
|
@@ -0,0 +1,1269 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getNextBuilderDeferred = getNextBuilderDeferred;
|
|
7
|
+
const node_crypto_1 = require("node:crypto");
|
|
8
|
+
const node_fs_1 = require("node:fs");
|
|
9
|
+
const promises_1 = require("node:fs/promises");
|
|
10
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
11
|
+
const node_path_1 = require("node:path");
|
|
12
|
+
const socket_server_js_1 = require("./socket-server.js");
|
|
13
|
+
const step_copy_utils_js_1 = require("./step-copy-utils.js");
|
|
14
|
+
const ROUTE_STUB_FILE_MARKER = 'WORKFLOW_ROUTE_STUB_FILE';
|
|
15
|
+
let CachedNextBuilderDeferred;
|
|
16
|
+
// Create the deferred Next builder dynamically by extending the ESM BaseBuilder.
|
|
17
|
+
// Exported as getNextBuilderDeferred() to allow CommonJS modules to import from
|
|
18
|
+
// the ESM @workflow/builders package via dynamic import at runtime.
|
|
19
|
+
async function getNextBuilderDeferred() {
|
|
20
|
+
if (CachedNextBuilderDeferred) {
|
|
21
|
+
return CachedNextBuilderDeferred;
|
|
22
|
+
}
|
|
23
|
+
const { BaseBuilder: BaseBuilderClass, STEP_QUEUE_TRIGGER, WORKFLOW_QUEUE_TRIGGER, applySwcTransform, detectWorkflowPatterns, getImportPath, isWorkflowSdkFile, resolveWorkflowAliasRelativePath,
|
|
24
|
+
// biome-ignore lint/security/noGlobalEval: Need to use eval here to avoid TypeScript from transpiling the import statement into `require()`
|
|
25
|
+
} = (await eval('import("@workflow/builders")'));
|
|
26
|
+
class NextDeferredBuilder extends BaseBuilderClass {
|
|
27
|
+
socketIO;
|
|
28
|
+
discoveredWorkflowFiles = new Set();
|
|
29
|
+
discoveredStepFiles = new Set();
|
|
30
|
+
discoveredSerdeFiles = new Set();
|
|
31
|
+
trackedDependencyFiles = new Set();
|
|
32
|
+
deferredBuildQueue = Promise.resolve();
|
|
33
|
+
cacheInitialized = false;
|
|
34
|
+
cacheWriteTimer = null;
|
|
35
|
+
lastDeferredBuildSignature = null;
|
|
36
|
+
async build() {
|
|
37
|
+
const outputDir = await this.findAppDirectory();
|
|
38
|
+
await this.initializeDiscoveryState();
|
|
39
|
+
await this.cleanupGeneratedArtifactsOnBoot(outputDir);
|
|
40
|
+
await this.writeStubFiles(outputDir);
|
|
41
|
+
await this.createDiscoverySocketServer();
|
|
42
|
+
}
|
|
43
|
+
async onBeforeDeferredEntries() {
|
|
44
|
+
await this.initializeDiscoveryState();
|
|
45
|
+
await this.validateDiscoveredEntryFiles();
|
|
46
|
+
const implicitStepFiles = await this.resolveImplicitStepFiles();
|
|
47
|
+
const pendingBuild = this.deferredBuildQueue.then(() => this.buildDeferredEntriesUntilStable(implicitStepFiles));
|
|
48
|
+
// Keep the queue chain alive even when the current build fails so future
|
|
49
|
+
// callbacks can enqueue another attempt without triggering unhandled
|
|
50
|
+
// rejection warnings.
|
|
51
|
+
this.deferredBuildQueue = pendingBuild.catch(() => {
|
|
52
|
+
// Error is surfaced through `pendingBuild` below.
|
|
53
|
+
});
|
|
54
|
+
await pendingBuild;
|
|
55
|
+
}
|
|
56
|
+
getCurrentInputFiles(implicitStepFiles) {
|
|
57
|
+
return Array.from(new Set([
|
|
58
|
+
...this.discoveredWorkflowFiles,
|
|
59
|
+
...this.discoveredStepFiles,
|
|
60
|
+
...this.discoveredSerdeFiles,
|
|
61
|
+
...implicitStepFiles,
|
|
62
|
+
])).sort();
|
|
63
|
+
}
|
|
64
|
+
async buildDeferredEntriesUntilStable(implicitStepFiles) {
|
|
65
|
+
// A successful build can discover additional transitive dependency files
|
|
66
|
+
// (via source maps), which changes the signature and may require one more
|
|
67
|
+
// build pass to include newly discovered serde files.
|
|
68
|
+
const maxBuildPasses = 3;
|
|
69
|
+
for (let buildPass = 0; buildPass < maxBuildPasses; buildPass++) {
|
|
70
|
+
const inputFiles = this.getCurrentInputFiles(implicitStepFiles);
|
|
71
|
+
const buildSignature = await this.createDeferredBuildSignature(inputFiles);
|
|
72
|
+
if (buildSignature === this.lastDeferredBuildSignature) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
let didBuildSucceed = false;
|
|
76
|
+
try {
|
|
77
|
+
await this.buildDiscoveredFiles(inputFiles, implicitStepFiles);
|
|
78
|
+
didBuildSucceed = true;
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
if (this.config.watch) {
|
|
82
|
+
console.warn('[workflow] Deferred entries build failed. Will retry only after inputs change.', error);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
// Record attempted signature even on failure so we don't loop on the
|
|
90
|
+
// same broken input graph.
|
|
91
|
+
this.lastDeferredBuildSignature = buildSignature;
|
|
92
|
+
}
|
|
93
|
+
if (!didBuildSucceed) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const postBuildInputFiles = this.getCurrentInputFiles(implicitStepFiles);
|
|
97
|
+
const postBuildSignature = await this.createDeferredBuildSignature(postBuildInputFiles);
|
|
98
|
+
if (postBuildSignature === buildSignature) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
console.warn('[workflow] Deferred entries build signature did not stabilize after 3 passes.');
|
|
103
|
+
}
|
|
104
|
+
async resolveImplicitStepFiles() {
|
|
105
|
+
let workflowCjsEntry;
|
|
106
|
+
try {
|
|
107
|
+
workflowCjsEntry = require.resolve('workflow', {
|
|
108
|
+
paths: [this.config.workingDir],
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
const workflowDistDir = (0, node_path_1.dirname)(workflowCjsEntry);
|
|
115
|
+
const workflowStdlibPath = this.normalizeDiscoveredFilePath((0, node_path_1.join)(workflowDistDir, 'stdlib.js'));
|
|
116
|
+
const candidatePaths = [workflowStdlibPath];
|
|
117
|
+
const existingFiles = await Promise.all(candidatePaths.map(async (filePath) => {
|
|
118
|
+
try {
|
|
119
|
+
const fileStats = await (0, promises_1.stat)(filePath);
|
|
120
|
+
return fileStats.isFile() ? filePath : null;
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}));
|
|
126
|
+
return existingFiles.filter((filePath) => Boolean(filePath));
|
|
127
|
+
}
|
|
128
|
+
areFileSetsEqual(a, b) {
|
|
129
|
+
if (a.size !== b.size) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
for (const filePath of a) {
|
|
133
|
+
if (!b.has(filePath)) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
async reconcileDiscoveredEntries({ workflowCandidates, stepCandidates, serdeCandidates, validatePatterns, }) {
|
|
140
|
+
const candidatesByFile = new Map();
|
|
141
|
+
for (const filePath of workflowCandidates) {
|
|
142
|
+
const normalizedPath = this.normalizeDiscoveredFilePath(filePath);
|
|
143
|
+
const existing = candidatesByFile.get(normalizedPath);
|
|
144
|
+
if (existing) {
|
|
145
|
+
existing.hasWorkflowCandidate = true;
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
candidatesByFile.set(normalizedPath, {
|
|
149
|
+
hasWorkflowCandidate: true,
|
|
150
|
+
hasStepCandidate: false,
|
|
151
|
+
hasSerdeCandidate: false,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
for (const filePath of stepCandidates) {
|
|
156
|
+
const normalizedPath = this.normalizeDiscoveredFilePath(filePath);
|
|
157
|
+
const existing = candidatesByFile.get(normalizedPath);
|
|
158
|
+
if (existing) {
|
|
159
|
+
existing.hasStepCandidate = true;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
candidatesByFile.set(normalizedPath, {
|
|
163
|
+
hasWorkflowCandidate: false,
|
|
164
|
+
hasStepCandidate: true,
|
|
165
|
+
hasSerdeCandidate: false,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (serdeCandidates) {
|
|
170
|
+
for (const filePath of serdeCandidates) {
|
|
171
|
+
const normalizedPath = this.normalizeDiscoveredFilePath(filePath);
|
|
172
|
+
const existing = candidatesByFile.get(normalizedPath);
|
|
173
|
+
if (existing) {
|
|
174
|
+
existing.hasSerdeCandidate = true;
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
candidatesByFile.set(normalizedPath, {
|
|
178
|
+
hasWorkflowCandidate: false,
|
|
179
|
+
hasStepCandidate: false,
|
|
180
|
+
hasSerdeCandidate: true,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const fileEntries = Array.from(candidatesByFile.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
186
|
+
const validatedEntries = await Promise.all(fileEntries.map(async ([filePath, candidates]) => {
|
|
187
|
+
try {
|
|
188
|
+
const fileStats = await (0, promises_1.stat)(filePath);
|
|
189
|
+
if (!fileStats.isFile()) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
if (!validatePatterns) {
|
|
193
|
+
const isSdkFile = isWorkflowSdkFile(filePath);
|
|
194
|
+
return {
|
|
195
|
+
filePath,
|
|
196
|
+
hasUseWorkflow: candidates.hasWorkflowCandidate,
|
|
197
|
+
hasUseStep: candidates.hasStepCandidate,
|
|
198
|
+
hasSerde: candidates.hasSerdeCandidate && !isSdkFile,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
const source = await (0, promises_1.readFile)(filePath, 'utf-8');
|
|
202
|
+
const patterns = detectWorkflowPatterns(source);
|
|
203
|
+
const isSdkFile = isWorkflowSdkFile(filePath);
|
|
204
|
+
return {
|
|
205
|
+
filePath,
|
|
206
|
+
hasUseWorkflow: patterns.hasUseWorkflow,
|
|
207
|
+
hasUseStep: patterns.hasUseStep,
|
|
208
|
+
hasSerde: patterns.hasSerde && !isSdkFile,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
}));
|
|
215
|
+
const workflowFiles = new Set();
|
|
216
|
+
const stepFiles = new Set();
|
|
217
|
+
const serdeFiles = new Set();
|
|
218
|
+
for (const entry of validatedEntries) {
|
|
219
|
+
if (!entry) {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (entry.hasUseWorkflow) {
|
|
223
|
+
workflowFiles.add(entry.filePath);
|
|
224
|
+
}
|
|
225
|
+
if (entry.hasUseStep) {
|
|
226
|
+
stepFiles.add(entry.filePath);
|
|
227
|
+
}
|
|
228
|
+
if (entry.hasSerde) {
|
|
229
|
+
serdeFiles.add(entry.filePath);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return { workflowFiles, stepFiles, serdeFiles };
|
|
233
|
+
}
|
|
234
|
+
async validateDiscoveredEntryFiles() {
|
|
235
|
+
const { workflowFiles, stepFiles, serdeFiles } = await this.reconcileDiscoveredEntries({
|
|
236
|
+
workflowCandidates: this.discoveredWorkflowFiles,
|
|
237
|
+
stepCandidates: this.discoveredStepFiles,
|
|
238
|
+
serdeCandidates: this.discoveredSerdeFiles,
|
|
239
|
+
validatePatterns: true,
|
|
240
|
+
});
|
|
241
|
+
const workflowsChanged = !this.areFileSetsEqual(this.discoveredWorkflowFiles, workflowFiles);
|
|
242
|
+
const stepsChanged = !this.areFileSetsEqual(this.discoveredStepFiles, stepFiles);
|
|
243
|
+
const serdeChanged = !this.areFileSetsEqual(this.discoveredSerdeFiles, serdeFiles);
|
|
244
|
+
if (workflowsChanged || stepsChanged || serdeChanged) {
|
|
245
|
+
this.discoveredWorkflowFiles.clear();
|
|
246
|
+
this.discoveredStepFiles.clear();
|
|
247
|
+
this.discoveredSerdeFiles.clear();
|
|
248
|
+
for (const filePath of workflowFiles) {
|
|
249
|
+
this.discoveredWorkflowFiles.add(filePath);
|
|
250
|
+
}
|
|
251
|
+
for (const filePath of stepFiles) {
|
|
252
|
+
this.discoveredStepFiles.add(filePath);
|
|
253
|
+
}
|
|
254
|
+
for (const filePath of serdeFiles) {
|
|
255
|
+
this.discoveredSerdeFiles.add(filePath);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (workflowsChanged || stepsChanged) {
|
|
259
|
+
this.scheduleWorkflowsCacheWrite();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
async buildDiscoveredFiles(inputFiles, implicitStepFiles) {
|
|
263
|
+
const outputDir = await this.findAppDirectory();
|
|
264
|
+
const workflowGeneratedDir = (0, node_path_1.join)(outputDir, '.well-known/workflow/v1');
|
|
265
|
+
const cacheDir = (0, node_path_1.join)(this.config.workingDir, this.getDistDir(), 'cache');
|
|
266
|
+
await (0, promises_1.mkdir)(cacheDir, { recursive: true });
|
|
267
|
+
const manifestBuildDir = (0, node_path_1.join)(cacheDir, 'workflow-generated-manifest');
|
|
268
|
+
const tempRouteFileName = 'route.js.temp';
|
|
269
|
+
const trackedDiscoveredEntries = await this.collectTrackedDiscoveredEntries();
|
|
270
|
+
const discoveredStepFiles = Array.from(new Set([
|
|
271
|
+
...this.discoveredStepFiles,
|
|
272
|
+
...trackedDiscoveredEntries.discoveredSteps,
|
|
273
|
+
...implicitStepFiles,
|
|
274
|
+
])).sort();
|
|
275
|
+
const discoveredWorkflowFiles = Array.from(new Set([
|
|
276
|
+
...this.discoveredWorkflowFiles,
|
|
277
|
+
...trackedDiscoveredEntries.discoveredWorkflows,
|
|
278
|
+
])).sort();
|
|
279
|
+
const discoveredSerdeFileCandidates = Array.from(new Set([
|
|
280
|
+
...this.discoveredSerdeFiles,
|
|
281
|
+
...trackedDiscoveredEntries.discoveredSerdeFiles,
|
|
282
|
+
])).sort();
|
|
283
|
+
const discoveredSerdeFiles = await this.collectTransitiveSerdeFiles({
|
|
284
|
+
entryFiles: [...discoveredStepFiles, ...discoveredWorkflowFiles],
|
|
285
|
+
serdeFiles: discoveredSerdeFileCandidates,
|
|
286
|
+
});
|
|
287
|
+
const discoveredEntries = {
|
|
288
|
+
discoveredSteps: discoveredStepFiles,
|
|
289
|
+
discoveredWorkflows: discoveredWorkflowFiles,
|
|
290
|
+
discoveredSerdeFiles,
|
|
291
|
+
};
|
|
292
|
+
const buildInputFiles = Array.from(new Set([
|
|
293
|
+
...inputFiles,
|
|
294
|
+
...discoveredStepFiles,
|
|
295
|
+
...discoveredWorkflowFiles,
|
|
296
|
+
...discoveredSerdeFiles,
|
|
297
|
+
])).sort();
|
|
298
|
+
// Ensure output directories exist
|
|
299
|
+
await (0, promises_1.mkdir)(workflowGeneratedDir, { recursive: true });
|
|
300
|
+
await this.writeFileIfChanged((0, node_path_1.join)(workflowGeneratedDir, '.gitignore'), '*');
|
|
301
|
+
const tsconfigPath = await this.findTsConfigPath();
|
|
302
|
+
const options = {
|
|
303
|
+
inputFiles: buildInputFiles,
|
|
304
|
+
workflowGeneratedDir,
|
|
305
|
+
tsconfigPath,
|
|
306
|
+
routeFileName: tempRouteFileName,
|
|
307
|
+
discoveredEntries,
|
|
308
|
+
};
|
|
309
|
+
const { manifest: stepsManifest } = await this.buildStepsFunction(options);
|
|
310
|
+
const workflowsBundle = await this.buildWorkflowsFunction(options);
|
|
311
|
+
await this.buildWebhookRoute({
|
|
312
|
+
workflowGeneratedDir,
|
|
313
|
+
routeFileName: tempRouteFileName,
|
|
314
|
+
});
|
|
315
|
+
await this.refreshTrackedDependencyFiles(workflowGeneratedDir, tempRouteFileName);
|
|
316
|
+
// Merge manifests from both bundles
|
|
317
|
+
const manifest = {
|
|
318
|
+
steps: { ...stepsManifest.steps, ...workflowsBundle?.manifest?.steps },
|
|
319
|
+
workflows: {
|
|
320
|
+
...stepsManifest.workflows,
|
|
321
|
+
...workflowsBundle?.manifest?.workflows,
|
|
322
|
+
},
|
|
323
|
+
classes: {
|
|
324
|
+
...stepsManifest.classes,
|
|
325
|
+
...workflowsBundle?.manifest?.classes,
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
const manifestFilePath = (0, node_path_1.join)(workflowGeneratedDir, 'manifest.json');
|
|
329
|
+
const manifestBuildPath = (0, node_path_1.join)(manifestBuildDir, 'manifest.json');
|
|
330
|
+
const workflowBundlePath = (0, node_path_1.join)(workflowGeneratedDir, `flow/${tempRouteFileName}`);
|
|
331
|
+
const manifestJson = await this.createManifest({
|
|
332
|
+
workflowBundlePath,
|
|
333
|
+
manifestDir: manifestBuildDir,
|
|
334
|
+
manifest,
|
|
335
|
+
});
|
|
336
|
+
if (manifestJson) {
|
|
337
|
+
await this.rewriteJsonFileWithStableKeyOrder(manifestBuildPath);
|
|
338
|
+
await this.copyFileIfChanged(manifestBuildPath, manifestFilePath);
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
await (0, promises_1.rm)(manifestBuildPath, { force: true });
|
|
342
|
+
await (0, promises_1.rm)(manifestFilePath, { force: true });
|
|
343
|
+
}
|
|
344
|
+
await this.writeFunctionsConfig(outputDir);
|
|
345
|
+
await this.copyFileIfChanged((0, node_path_1.join)(workflowGeneratedDir, `flow/${tempRouteFileName}`), (0, node_path_1.join)(workflowGeneratedDir, 'flow/route.js'));
|
|
346
|
+
await this.copyFileIfChanged((0, node_path_1.join)(workflowGeneratedDir, `step/${tempRouteFileName}`), (0, node_path_1.join)(workflowGeneratedDir, 'step/route.js'));
|
|
347
|
+
await this.copyFileIfChanged((0, node_path_1.join)(workflowGeneratedDir, `webhook/[token]/${tempRouteFileName}`), (0, node_path_1.join)(workflowGeneratedDir, 'webhook/[token]/route.js'));
|
|
348
|
+
// Expose manifest as a static file when WORKFLOW_PUBLIC_MANIFEST=1.
|
|
349
|
+
// Next.js serves files from public/ at the root URL.
|
|
350
|
+
if (this.shouldExposePublicManifest && manifestJson) {
|
|
351
|
+
const publicManifestDir = (0, node_path_1.join)(this.config.workingDir, 'public/.well-known/workflow/v1');
|
|
352
|
+
await (0, promises_1.mkdir)(publicManifestDir, { recursive: true });
|
|
353
|
+
await this.copyFileIfChanged(manifestFilePath, (0, node_path_1.join)(publicManifestDir, 'manifest.json'));
|
|
354
|
+
}
|
|
355
|
+
// Notify deferred entry loaders waiting on route.js stubs.
|
|
356
|
+
this.socketIO?.emit('build-complete');
|
|
357
|
+
}
|
|
358
|
+
async cleanupGeneratedArtifactsOnBoot(outputDir) {
|
|
359
|
+
const workflowGeneratedDir = (0, node_path_1.join)(outputDir, '.well-known/workflow/v1');
|
|
360
|
+
const flowRouteDir = (0, node_path_1.join)(workflowGeneratedDir, 'flow');
|
|
361
|
+
const stepRouteDir = (0, node_path_1.join)(workflowGeneratedDir, 'step');
|
|
362
|
+
const webhookRouteDir = (0, node_path_1.join)(workflowGeneratedDir, 'webhook/[token]');
|
|
363
|
+
const staleArtifactPaths = [
|
|
364
|
+
(0, node_path_1.join)(flowRouteDir, 'route.js.temp'),
|
|
365
|
+
(0, node_path_1.join)(flowRouteDir, 'route.js.temp.debug.json'),
|
|
366
|
+
(0, node_path_1.join)(flowRouteDir, 'route.js.debug.json'),
|
|
367
|
+
(0, node_path_1.join)(stepRouteDir, 'route.js.temp'),
|
|
368
|
+
(0, node_path_1.join)(stepRouteDir, 'route.js.temp.debug.json'),
|
|
369
|
+
(0, node_path_1.join)(stepRouteDir, 'route.js.debug.json'),
|
|
370
|
+
(0, node_path_1.join)(stepRouteDir, step_copy_utils_js_1.DEFERRED_STEP_COPY_DIR_NAME),
|
|
371
|
+
(0, node_path_1.join)(webhookRouteDir, 'route.js.temp'),
|
|
372
|
+
(0, node_path_1.join)(workflowGeneratedDir, 'manifest.json'),
|
|
373
|
+
];
|
|
374
|
+
await Promise.all(staleArtifactPaths.map((stalePath) => (0, promises_1.rm)(stalePath, { recursive: true, force: true })));
|
|
375
|
+
await Promise.all([
|
|
376
|
+
this.removeStaleDeferredTempFiles(flowRouteDir),
|
|
377
|
+
this.removeStaleDeferredTempFiles(stepRouteDir),
|
|
378
|
+
this.removeStaleDeferredTempFiles(webhookRouteDir),
|
|
379
|
+
]);
|
|
380
|
+
}
|
|
381
|
+
async removeStaleDeferredTempFiles(routeDir) {
|
|
382
|
+
const routeEntries = await (0, promises_1.readdir)(routeDir, {
|
|
383
|
+
withFileTypes: true,
|
|
384
|
+
}).catch(() => []);
|
|
385
|
+
await Promise.all(routeEntries
|
|
386
|
+
.filter((entry) => entry.isFile() &&
|
|
387
|
+
entry.name.startsWith('route.js.') &&
|
|
388
|
+
entry.name.endsWith('.tmp'))
|
|
389
|
+
.map((entry) => (0, promises_1.rm)((0, node_path_1.join)(routeDir, entry.name), {
|
|
390
|
+
force: true,
|
|
391
|
+
})));
|
|
392
|
+
}
|
|
393
|
+
async createDiscoverySocketServer() {
|
|
394
|
+
if (this.socketIO || process.env.WORKFLOW_SOCKET_PORT) {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
const config = {
|
|
398
|
+
isDevServer: Boolean(this.config.watch),
|
|
399
|
+
onFileDiscovered: (filePath, hasWorkflow, hasStep, hasSerde) => {
|
|
400
|
+
const normalizedFilePath = this.normalizeDiscoveredFilePath(filePath);
|
|
401
|
+
let hasCacheTrackingChange = false;
|
|
402
|
+
if (hasWorkflow) {
|
|
403
|
+
if (!this.discoveredWorkflowFiles.has(normalizedFilePath)) {
|
|
404
|
+
this.discoveredWorkflowFiles.add(normalizedFilePath);
|
|
405
|
+
hasCacheTrackingChange = true;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
const wasDeleted = this.discoveredWorkflowFiles.delete(normalizedFilePath);
|
|
410
|
+
hasCacheTrackingChange = wasDeleted || hasCacheTrackingChange;
|
|
411
|
+
}
|
|
412
|
+
if (hasStep) {
|
|
413
|
+
if (!this.discoveredStepFiles.has(normalizedFilePath)) {
|
|
414
|
+
this.discoveredStepFiles.add(normalizedFilePath);
|
|
415
|
+
hasCacheTrackingChange = true;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
const wasDeleted = this.discoveredStepFiles.delete(normalizedFilePath);
|
|
420
|
+
hasCacheTrackingChange = wasDeleted || hasCacheTrackingChange;
|
|
421
|
+
}
|
|
422
|
+
if (hasSerde) {
|
|
423
|
+
this.discoveredSerdeFiles.add(normalizedFilePath);
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
this.discoveredSerdeFiles.delete(normalizedFilePath);
|
|
427
|
+
}
|
|
428
|
+
if (hasCacheTrackingChange) {
|
|
429
|
+
this.scheduleWorkflowsCacheWrite();
|
|
430
|
+
}
|
|
431
|
+
},
|
|
432
|
+
onTriggerBuild: () => {
|
|
433
|
+
// Deferred builder builds via onBeforeDeferredEntries callback.
|
|
434
|
+
},
|
|
435
|
+
};
|
|
436
|
+
this.socketIO = await (0, socket_server_js_1.createSocketServer)(config);
|
|
437
|
+
}
|
|
438
|
+
async initializeDiscoveryState() {
|
|
439
|
+
if (this.cacheInitialized) {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
await this.loadWorkflowsCache();
|
|
443
|
+
this.cacheInitialized = true;
|
|
444
|
+
}
|
|
445
|
+
getDistDir() {
|
|
446
|
+
return this.config.distDir || '.next';
|
|
447
|
+
}
|
|
448
|
+
getWorkflowsCacheFilePath() {
|
|
449
|
+
return (0, node_path_1.join)(this.config.workingDir, this.getDistDir(), 'cache', 'workflows.json');
|
|
450
|
+
}
|
|
451
|
+
normalizeDiscoveredFilePath(filePath) {
|
|
452
|
+
return (0, node_path_1.isAbsolute)(filePath)
|
|
453
|
+
? filePath
|
|
454
|
+
: (0, node_path_1.resolve)(this.config.workingDir, filePath);
|
|
455
|
+
}
|
|
456
|
+
async createDeferredBuildSignature(inputFiles) {
|
|
457
|
+
const normalizedFiles = Array.from(new Set([
|
|
458
|
+
...inputFiles.map((filePath) => this.normalizeDiscoveredFilePath(filePath)),
|
|
459
|
+
...this.trackedDependencyFiles,
|
|
460
|
+
])).sort();
|
|
461
|
+
const signatureParts = await Promise.all(normalizedFiles.map(async (filePath) => {
|
|
462
|
+
try {
|
|
463
|
+
const fileStats = await (0, promises_1.stat)(filePath);
|
|
464
|
+
return `${filePath}:${fileStats.size}:${Math.trunc(fileStats.mtimeMs)}`;
|
|
465
|
+
}
|
|
466
|
+
catch {
|
|
467
|
+
return `${filePath}:missing`;
|
|
468
|
+
}
|
|
469
|
+
}));
|
|
470
|
+
const signatureHash = (0, node_crypto_1.createHash)('sha256');
|
|
471
|
+
for (const signaturePart of signatureParts) {
|
|
472
|
+
signatureHash.update(signaturePart);
|
|
473
|
+
signatureHash.update('\n');
|
|
474
|
+
}
|
|
475
|
+
return signatureHash.digest('hex');
|
|
476
|
+
}
|
|
477
|
+
async collectTrackedDiscoveredEntries() {
|
|
478
|
+
if (this.trackedDependencyFiles.size === 0) {
|
|
479
|
+
return {
|
|
480
|
+
discoveredSteps: [],
|
|
481
|
+
discoveredWorkflows: [],
|
|
482
|
+
discoveredSerdeFiles: [],
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
const { workflowFiles, stepFiles, serdeFiles } = await this.reconcileDiscoveredEntries({
|
|
486
|
+
workflowCandidates: this.trackedDependencyFiles,
|
|
487
|
+
stepCandidates: this.trackedDependencyFiles,
|
|
488
|
+
serdeCandidates: this.trackedDependencyFiles,
|
|
489
|
+
validatePatterns: true,
|
|
490
|
+
});
|
|
491
|
+
return {
|
|
492
|
+
discoveredSteps: Array.from(stepFiles).sort(),
|
|
493
|
+
discoveredWorkflows: Array.from(workflowFiles).sort(),
|
|
494
|
+
discoveredSerdeFiles: Array.from(serdeFiles).sort(),
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
async refreshTrackedDependencyFiles(workflowGeneratedDir, routeFileName) {
|
|
498
|
+
const bundleFiles = [
|
|
499
|
+
(0, node_path_1.join)(workflowGeneratedDir, `step/${routeFileName}`),
|
|
500
|
+
(0, node_path_1.join)(workflowGeneratedDir, `flow/${routeFileName}`),
|
|
501
|
+
];
|
|
502
|
+
const trackedFiles = new Set();
|
|
503
|
+
for (const bundleFile of bundleFiles) {
|
|
504
|
+
const bundleSources = await this.extractBundleSourceFiles(bundleFile);
|
|
505
|
+
for (const sourceFile of bundleSources) {
|
|
506
|
+
trackedFiles.add(sourceFile);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
if (trackedFiles.size > 0) {
|
|
510
|
+
this.trackedDependencyFiles = trackedFiles;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
async extractBundleSourceFiles(bundleFilePath) {
|
|
514
|
+
let bundleContents;
|
|
515
|
+
try {
|
|
516
|
+
bundleContents = await (0, promises_1.readFile)(bundleFilePath, 'utf-8');
|
|
517
|
+
}
|
|
518
|
+
catch {
|
|
519
|
+
return [];
|
|
520
|
+
}
|
|
521
|
+
const baseDirectory = (0, node_path_1.dirname)(bundleFilePath);
|
|
522
|
+
const localSourceFiles = new Set();
|
|
523
|
+
const sourceMapMatches = bundleContents.matchAll(/\/\/# sourceMappingURL=data:application\/json[^,]*;base64,([A-Za-z0-9+/=]+)/g);
|
|
524
|
+
for (const match of sourceMapMatches) {
|
|
525
|
+
const base64Value = match[1];
|
|
526
|
+
if (!base64Value) {
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
let sourceMap;
|
|
530
|
+
try {
|
|
531
|
+
sourceMap = JSON.parse(Buffer.from(base64Value, 'base64').toString('utf-8'));
|
|
532
|
+
}
|
|
533
|
+
catch {
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
const sourceRoot = typeof sourceMap.sourceRoot === 'string' ? sourceMap.sourceRoot : '';
|
|
537
|
+
const sources = Array.isArray(sourceMap.sources)
|
|
538
|
+
? sourceMap.sources.filter((source) => typeof source === 'string')
|
|
539
|
+
: [];
|
|
540
|
+
for (const source of sources) {
|
|
541
|
+
if (source.startsWith('webpack://') || source.startsWith('<')) {
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
let resolvedSourcePath;
|
|
545
|
+
if (source.startsWith('file://')) {
|
|
546
|
+
try {
|
|
547
|
+
resolvedSourcePath = decodeURIComponent(new URL(source).pathname);
|
|
548
|
+
}
|
|
549
|
+
catch {
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
else if ((0, node_path_1.isAbsolute)(source)) {
|
|
554
|
+
resolvedSourcePath = source;
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
resolvedSourcePath = (0, node_path_1.resolve)(baseDirectory, sourceRoot, source);
|
|
558
|
+
}
|
|
559
|
+
const normalizedSourcePath = this.normalizeDiscoveredFilePath(resolvedSourcePath);
|
|
560
|
+
const normalizedSourcePathForCheck = normalizedSourcePath.replace(/\\/g, '/');
|
|
561
|
+
if (normalizedSourcePathForCheck.includes('/.well-known/workflow/') ||
|
|
562
|
+
normalizedSourcePathForCheck.includes('/node_modules/') ||
|
|
563
|
+
normalizedSourcePathForCheck.includes('/.pnpm/') ||
|
|
564
|
+
normalizedSourcePathForCheck.includes('/.next/') ||
|
|
565
|
+
normalizedSourcePathForCheck.endsWith('/virtual-entry.js')) {
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
localSourceFiles.add(normalizedSourcePath);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
return Array.from(localSourceFiles);
|
|
572
|
+
}
|
|
573
|
+
scheduleWorkflowsCacheWrite() {
|
|
574
|
+
if (this.cacheWriteTimer) {
|
|
575
|
+
clearTimeout(this.cacheWriteTimer);
|
|
576
|
+
}
|
|
577
|
+
this.cacheWriteTimer = setTimeout(() => {
|
|
578
|
+
this.cacheWriteTimer = null;
|
|
579
|
+
void this.writeWorkflowsCache().catch((error) => {
|
|
580
|
+
console.warn('Failed to write workflow discovery cache', error);
|
|
581
|
+
});
|
|
582
|
+
}, 50);
|
|
583
|
+
}
|
|
584
|
+
async readWorkflowsCache() {
|
|
585
|
+
const cacheFilePath = this.getWorkflowsCacheFilePath();
|
|
586
|
+
try {
|
|
587
|
+
const cacheContents = await (0, promises_1.readFile)(cacheFilePath, 'utf-8');
|
|
588
|
+
const parsed = JSON.parse(cacheContents);
|
|
589
|
+
const workflowFiles = Array.isArray(parsed.workflowFiles)
|
|
590
|
+
? parsed.workflowFiles.filter((item) => typeof item === 'string')
|
|
591
|
+
: [];
|
|
592
|
+
const stepFiles = Array.isArray(parsed.stepFiles)
|
|
593
|
+
? parsed.stepFiles.filter((item) => typeof item === 'string')
|
|
594
|
+
: [];
|
|
595
|
+
return { workflowFiles, stepFiles };
|
|
596
|
+
}
|
|
597
|
+
catch {
|
|
598
|
+
return null;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
async loadWorkflowsCache() {
|
|
602
|
+
const cachedData = await this.readWorkflowsCache();
|
|
603
|
+
if (!cachedData) {
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
const { workflowFiles, stepFiles, serdeFiles } = await this.reconcileDiscoveredEntries({
|
|
607
|
+
workflowCandidates: cachedData.workflowFiles,
|
|
608
|
+
stepCandidates: cachedData.stepFiles,
|
|
609
|
+
serdeCandidates: this.discoveredSerdeFiles,
|
|
610
|
+
validatePatterns: true,
|
|
611
|
+
});
|
|
612
|
+
this.discoveredWorkflowFiles.clear();
|
|
613
|
+
this.discoveredStepFiles.clear();
|
|
614
|
+
this.discoveredSerdeFiles.clear();
|
|
615
|
+
for (const filePath of workflowFiles) {
|
|
616
|
+
this.discoveredWorkflowFiles.add(filePath);
|
|
617
|
+
}
|
|
618
|
+
for (const filePath of stepFiles) {
|
|
619
|
+
this.discoveredStepFiles.add(filePath);
|
|
620
|
+
}
|
|
621
|
+
for (const filePath of serdeFiles) {
|
|
622
|
+
this.discoveredSerdeFiles.add(filePath);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
async writeWorkflowsCache() {
|
|
626
|
+
const cacheFilePath = this.getWorkflowsCacheFilePath();
|
|
627
|
+
const cacheDir = (0, node_path_1.join)(this.config.workingDir, this.getDistDir(), 'cache');
|
|
628
|
+
await (0, promises_1.mkdir)(cacheDir, { recursive: true });
|
|
629
|
+
const cacheData = {
|
|
630
|
+
workflowFiles: Array.from(this.discoveredWorkflowFiles).sort(),
|
|
631
|
+
stepFiles: Array.from(this.discoveredStepFiles).sort(),
|
|
632
|
+
};
|
|
633
|
+
await (0, promises_1.writeFile)(cacheFilePath, JSON.stringify(cacheData, null, 2));
|
|
634
|
+
}
|
|
635
|
+
async writeStubFiles(outputDir) {
|
|
636
|
+
// Turbopack currently has a worker-concurrency limitation for pending
|
|
637
|
+
// virtual entries. Warn if parallelism is too low to reliably discover.
|
|
638
|
+
const parallelismCount = node_os_1.default.availableParallelism();
|
|
639
|
+
if (process.env.TURBOPACK && parallelismCount < 4) {
|
|
640
|
+
console.warn(`Available parallelism of ${parallelismCount} is less than needed 4. This can cause workflows/steps to fail to discover properly in turbopack`);
|
|
641
|
+
}
|
|
642
|
+
const routeStubContent = [
|
|
643
|
+
`// ${ROUTE_STUB_FILE_MARKER}`,
|
|
644
|
+
'export const __workflowRouteStub = true;',
|
|
645
|
+
].join('\n');
|
|
646
|
+
const workflowGeneratedDir = (0, node_path_1.join)(outputDir, '.well-known/workflow/v1');
|
|
647
|
+
await (0, promises_1.mkdir)((0, node_path_1.join)(workflowGeneratedDir, 'flow'), { recursive: true });
|
|
648
|
+
await (0, promises_1.mkdir)((0, node_path_1.join)(workflowGeneratedDir, 'step'), { recursive: true });
|
|
649
|
+
await (0, promises_1.mkdir)((0, node_path_1.join)(workflowGeneratedDir, 'webhook/[token]'), {
|
|
650
|
+
recursive: true,
|
|
651
|
+
});
|
|
652
|
+
await this.writeFileIfChanged((0, node_path_1.join)(workflowGeneratedDir, '.gitignore'), '*');
|
|
653
|
+
// route.js stubs are replaced by generated route.js output once discovery
|
|
654
|
+
// finishes and a deferred build completes.
|
|
655
|
+
await this.writeFileIfChanged((0, node_path_1.join)(workflowGeneratedDir, 'flow/route.js'), routeStubContent);
|
|
656
|
+
await this.writeFileIfChanged((0, node_path_1.join)(workflowGeneratedDir, 'step/route.js'), routeStubContent);
|
|
657
|
+
await this.writeFileIfChanged((0, node_path_1.join)(workflowGeneratedDir, 'webhook/[token]/route.js'), routeStubContent);
|
|
658
|
+
}
|
|
659
|
+
async getInputFiles() {
|
|
660
|
+
const inputFiles = await super.getInputFiles();
|
|
661
|
+
return inputFiles.filter((item) => {
|
|
662
|
+
// Match App Router entrypoints: route.ts, page.ts, layout.ts in app/ or src/app/ directories
|
|
663
|
+
// Matches: /app/page.ts, /app/dashboard/page.ts, /src/app/route.ts, etc.
|
|
664
|
+
if (item.match(/(^|.*[/\\])(app|src[/\\]app)([/\\](route|page|layout)\.|[/\\].*[/\\](route|page|layout)\.)/)) {
|
|
665
|
+
return true;
|
|
666
|
+
}
|
|
667
|
+
// Match Pages Router entrypoints: files in pages/ or src/pages/
|
|
668
|
+
if (item.match(/[/\\](pages|src[/\\]pages)[/\\]/)) {
|
|
669
|
+
return true;
|
|
670
|
+
}
|
|
671
|
+
return false;
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
async writeFunctionsConfig(outputDir) {
|
|
675
|
+
// we don't run this in development mode as it's not needed
|
|
676
|
+
if (process.env.NODE_ENV === 'development') {
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
const generatedConfig = {
|
|
680
|
+
version: '0',
|
|
681
|
+
steps: {
|
|
682
|
+
experimentalTriggers: [STEP_QUEUE_TRIGGER],
|
|
683
|
+
},
|
|
684
|
+
workflows: {
|
|
685
|
+
experimentalTriggers: [WORKFLOW_QUEUE_TRIGGER],
|
|
686
|
+
},
|
|
687
|
+
};
|
|
688
|
+
// We write this file to the generated directory for
|
|
689
|
+
// the Next.js builder to consume
|
|
690
|
+
await this.writeFileIfChanged((0, node_path_1.join)(outputDir, '.well-known/workflow/v1/config.json'), JSON.stringify(generatedConfig, null, 2));
|
|
691
|
+
}
|
|
692
|
+
async writeFileIfChanged(filePath, contents) {
|
|
693
|
+
const nextBuffer = Buffer.isBuffer(contents)
|
|
694
|
+
? contents
|
|
695
|
+
: Buffer.from(contents);
|
|
696
|
+
try {
|
|
697
|
+
const currentBuffer = await (0, promises_1.readFile)(filePath);
|
|
698
|
+
if (currentBuffer.equals(nextBuffer)) {
|
|
699
|
+
return false;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
catch {
|
|
703
|
+
// File does not exist yet or cannot be read; write a fresh copy.
|
|
704
|
+
}
|
|
705
|
+
await (0, promises_1.mkdir)((0, node_path_1.dirname)(filePath), { recursive: true });
|
|
706
|
+
await (0, promises_1.writeFile)(filePath, nextBuffer);
|
|
707
|
+
return true;
|
|
708
|
+
}
|
|
709
|
+
async copyFileIfChanged(sourcePath, destinationPath) {
|
|
710
|
+
const sourceContents = await (0, promises_1.readFile)(sourcePath);
|
|
711
|
+
return this.writeFileIfChanged(destinationPath, sourceContents);
|
|
712
|
+
}
|
|
713
|
+
sortJsonValue(value) {
|
|
714
|
+
if (Array.isArray(value)) {
|
|
715
|
+
return value.map((item) => this.sortJsonValue(item));
|
|
716
|
+
}
|
|
717
|
+
if (value && typeof value === 'object') {
|
|
718
|
+
const sortedEntries = Object.entries(value)
|
|
719
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
720
|
+
.map(([key, entryValue]) => [key, this.sortJsonValue(entryValue)]);
|
|
721
|
+
return Object.fromEntries(sortedEntries);
|
|
722
|
+
}
|
|
723
|
+
return value;
|
|
724
|
+
}
|
|
725
|
+
async rewriteJsonFileWithStableKeyOrder(filePath) {
|
|
726
|
+
try {
|
|
727
|
+
const contents = await (0, promises_1.readFile)(filePath, 'utf-8');
|
|
728
|
+
const parsed = JSON.parse(contents);
|
|
729
|
+
const normalized = this.sortJsonValue(parsed);
|
|
730
|
+
await this.writeFileIfChanged(filePath, `${JSON.stringify(normalized, null, 2)}\n`);
|
|
731
|
+
}
|
|
732
|
+
catch {
|
|
733
|
+
// Manifest may not exist (e.g. manifest generation failed); ignore.
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
mergeWorkflowManifest(target, source) {
|
|
737
|
+
if (source.steps) {
|
|
738
|
+
target.steps = Object.assign(target.steps || {}, source.steps);
|
|
739
|
+
}
|
|
740
|
+
if (source.workflows) {
|
|
741
|
+
target.workflows = Object.assign(target.workflows || {}, source.workflows);
|
|
742
|
+
}
|
|
743
|
+
if (source.classes) {
|
|
744
|
+
target.classes = Object.assign(target.classes || {}, source.classes);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
async getRelativeFilenameForSwc(filePath) {
|
|
748
|
+
const workingDir = this.config.workingDir;
|
|
749
|
+
const normalizedWorkingDir = workingDir
|
|
750
|
+
.replace(/\\/g, '/')
|
|
751
|
+
.replace(/\/$/, '');
|
|
752
|
+
const normalizedFilepath = filePath.replace(/\\/g, '/');
|
|
753
|
+
// Windows fix: Use case-insensitive comparison to work around drive letter casing issues.
|
|
754
|
+
const lowerWd = normalizedWorkingDir.toLowerCase();
|
|
755
|
+
const lowerPath = normalizedFilepath.toLowerCase();
|
|
756
|
+
let relativeFilename;
|
|
757
|
+
if (lowerPath.startsWith(`${lowerWd}/`)) {
|
|
758
|
+
relativeFilename = normalizedFilepath.substring(normalizedWorkingDir.length + 1);
|
|
759
|
+
}
|
|
760
|
+
else if (lowerPath === lowerWd) {
|
|
761
|
+
relativeFilename = '.';
|
|
762
|
+
}
|
|
763
|
+
else {
|
|
764
|
+
relativeFilename = (0, node_path_1.relative)(workingDir, filePath).replace(/\\/g, '/');
|
|
765
|
+
if (relativeFilename.startsWith('../')) {
|
|
766
|
+
const aliasedRelativePath = await resolveWorkflowAliasRelativePath(filePath, workingDir);
|
|
767
|
+
if (aliasedRelativePath) {
|
|
768
|
+
relativeFilename = aliasedRelativePath;
|
|
769
|
+
}
|
|
770
|
+
else {
|
|
771
|
+
relativeFilename = relativeFilename
|
|
772
|
+
.split('/')
|
|
773
|
+
.filter((part) => part !== '..')
|
|
774
|
+
.join('/');
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
if (relativeFilename.includes(':') || relativeFilename.startsWith('/')) {
|
|
779
|
+
relativeFilename = (0, node_path_1.basename)(normalizedFilepath);
|
|
780
|
+
}
|
|
781
|
+
return relativeFilename;
|
|
782
|
+
}
|
|
783
|
+
getRelativeImportSpecifier(fromFilePath, toFilePath) {
|
|
784
|
+
let relativePath = (0, node_path_1.relative)((0, node_path_1.dirname)(fromFilePath), toFilePath).replace(/\\/g, '/');
|
|
785
|
+
if (!relativePath.startsWith('.')) {
|
|
786
|
+
relativePath = `./${relativePath}`;
|
|
787
|
+
}
|
|
788
|
+
return relativePath;
|
|
789
|
+
}
|
|
790
|
+
getStepCopyFileName(filePath) {
|
|
791
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
792
|
+
const hash = (0, node_crypto_1.createHash)('sha256').update(normalizedPath).digest('hex');
|
|
793
|
+
const extension = (0, node_path_1.extname)(normalizedPath);
|
|
794
|
+
return `${hash.slice(0, 16)}${extension || '.js'}`;
|
|
795
|
+
}
|
|
796
|
+
rewriteCopiedStepImportSpecifier(specifier, sourceFilePath, copiedFilePath, copiedStepFileBySourcePath) {
|
|
797
|
+
if (!specifier.startsWith('.')) {
|
|
798
|
+
return specifier;
|
|
799
|
+
}
|
|
800
|
+
const specifierMatch = specifier.match(/^([^?#]+)(.*)$/);
|
|
801
|
+
const importPath = specifierMatch?.[1] ?? specifier;
|
|
802
|
+
const suffix = specifierMatch?.[2] ?? '';
|
|
803
|
+
const absoluteTargetPath = (0, node_path_1.resolve)((0, node_path_1.dirname)(sourceFilePath), importPath);
|
|
804
|
+
const resolvedTargetPath = this.resolveCopiedStepImportTargetPath(absoluteTargetPath);
|
|
805
|
+
const normalizedTargetPath = resolvedTargetPath.replace(/\\/g, '/');
|
|
806
|
+
const copiedTargetPath = copiedStepFileBySourcePath.get(normalizedTargetPath) ||
|
|
807
|
+
(() => {
|
|
808
|
+
try {
|
|
809
|
+
const realTargetPath = (0, node_fs_1.realpathSync)(resolvedTargetPath).replace(/\\/g, '/');
|
|
810
|
+
return copiedStepFileBySourcePath.get(realTargetPath);
|
|
811
|
+
}
|
|
812
|
+
catch {
|
|
813
|
+
return undefined;
|
|
814
|
+
}
|
|
815
|
+
})();
|
|
816
|
+
let rewrittenPath = (0, node_path_1.relative)((0, node_path_1.dirname)(copiedFilePath), copiedTargetPath || resolvedTargetPath).replace(/\\/g, '/');
|
|
817
|
+
if (!rewrittenPath.startsWith('.')) {
|
|
818
|
+
rewrittenPath = `./${rewrittenPath}`;
|
|
819
|
+
}
|
|
820
|
+
return `${rewrittenPath}${suffix}`;
|
|
821
|
+
}
|
|
822
|
+
resolveCopiedStepImportTargetPath(targetPath) {
|
|
823
|
+
if ((0, node_fs_1.existsSync)(targetPath)) {
|
|
824
|
+
return targetPath;
|
|
825
|
+
}
|
|
826
|
+
const extensionMatch = targetPath.match(/(\.[^./\\]+)$/);
|
|
827
|
+
const extension = extensionMatch?.[1]?.toLowerCase();
|
|
828
|
+
if (!extension) {
|
|
829
|
+
return targetPath;
|
|
830
|
+
}
|
|
831
|
+
const extensionFallbacks = extension === '.js'
|
|
832
|
+
? ['.ts', '.tsx', '.mts', '.cts']
|
|
833
|
+
: extension === '.mjs'
|
|
834
|
+
? ['.mts']
|
|
835
|
+
: extension === '.cjs'
|
|
836
|
+
? ['.cts']
|
|
837
|
+
: extension === '.jsx'
|
|
838
|
+
? ['.tsx']
|
|
839
|
+
: [];
|
|
840
|
+
if (extensionFallbacks.length === 0) {
|
|
841
|
+
return targetPath;
|
|
842
|
+
}
|
|
843
|
+
const targetWithoutExtension = targetPath.slice(0, -extension.length);
|
|
844
|
+
for (const fallbackExtension of extensionFallbacks) {
|
|
845
|
+
const fallbackPath = `${targetWithoutExtension}${fallbackExtension}`;
|
|
846
|
+
if ((0, node_fs_1.existsSync)(fallbackPath)) {
|
|
847
|
+
return fallbackPath;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
return targetPath;
|
|
851
|
+
}
|
|
852
|
+
rewriteRelativeImportsForCopiedStep(source, sourceFilePath, copiedFilePath, copiedStepFileBySourcePath) {
|
|
853
|
+
const rewriteSpecifier = (specifier) => this.rewriteCopiedStepImportSpecifier(specifier, sourceFilePath, copiedFilePath, copiedStepFileBySourcePath);
|
|
854
|
+
const rewritePattern = (currentSource, pattern) => currentSource.replace(pattern, (_match, prefix, specifier, suffix) => `${prefix}${rewriteSpecifier(specifier)}${suffix}`);
|
|
855
|
+
let rewrittenSource = source;
|
|
856
|
+
rewrittenSource = rewritePattern(rewrittenSource, /(from\s+['"])([^'"]+)(['"])/g);
|
|
857
|
+
rewrittenSource = rewritePattern(rewrittenSource, /(import\s+['"])([^'"]+)(['"])/g);
|
|
858
|
+
rewrittenSource = rewritePattern(rewrittenSource, /(import\(\s*['"])([^'"]+)(['"]\s*\))/g);
|
|
859
|
+
rewrittenSource = rewritePattern(rewrittenSource, /(require\(\s*['"])([^'"]+)(['"]\s*\))/g);
|
|
860
|
+
return rewrittenSource;
|
|
861
|
+
}
|
|
862
|
+
extractRelativeImportSpecifiers(source) {
|
|
863
|
+
const relativeSpecifiers = new Set();
|
|
864
|
+
const importPatterns = [
|
|
865
|
+
/from\s+['"]([^'"]+)['"]/g,
|
|
866
|
+
/import\s+['"]([^'"]+)['"]/g,
|
|
867
|
+
/import\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
868
|
+
/require\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
869
|
+
];
|
|
870
|
+
for (const importPattern of importPatterns) {
|
|
871
|
+
for (const match of source.matchAll(importPattern)) {
|
|
872
|
+
const specifier = match[1];
|
|
873
|
+
if (specifier?.startsWith('.')) {
|
|
874
|
+
relativeSpecifiers.add(specifier);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
return Array.from(relativeSpecifiers);
|
|
879
|
+
}
|
|
880
|
+
shouldSkipTransitiveStepFile(filePath) {
|
|
881
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
882
|
+
return (normalizedPath.includes('/.well-known/workflow/') ||
|
|
883
|
+
normalizedPath.includes('/.next/') ||
|
|
884
|
+
normalizedPath.includes('/node_modules/') ||
|
|
885
|
+
normalizedPath.includes('/.pnpm/'));
|
|
886
|
+
}
|
|
887
|
+
async resolveTransitiveStepImportTargetPath(sourceFilePath, specifier) {
|
|
888
|
+
const specifierMatch = specifier.match(/^([^?#]+)(.*)$/);
|
|
889
|
+
const importPath = specifierMatch?.[1] ?? specifier;
|
|
890
|
+
const absoluteTargetPath = (0, node_path_1.resolve)((0, node_path_1.dirname)(sourceFilePath), importPath);
|
|
891
|
+
const candidatePaths = new Set([
|
|
892
|
+
this.resolveCopiedStepImportTargetPath(absoluteTargetPath),
|
|
893
|
+
]);
|
|
894
|
+
if (!(0, node_path_1.extname)(absoluteTargetPath)) {
|
|
895
|
+
const extensionCandidates = [
|
|
896
|
+
'.ts',
|
|
897
|
+
'.tsx',
|
|
898
|
+
'.mts',
|
|
899
|
+
'.cts',
|
|
900
|
+
'.js',
|
|
901
|
+
'.jsx',
|
|
902
|
+
'.mjs',
|
|
903
|
+
'.cjs',
|
|
904
|
+
];
|
|
905
|
+
for (const extensionCandidate of extensionCandidates) {
|
|
906
|
+
candidatePaths.add(`${absoluteTargetPath}${extensionCandidate}`);
|
|
907
|
+
candidatePaths.add((0, node_path_1.join)(absoluteTargetPath, `index${extensionCandidate}`));
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
for (const candidatePath of candidatePaths) {
|
|
911
|
+
const resolvedPath = this.resolveCopiedStepImportTargetPath(candidatePath);
|
|
912
|
+
const normalizedResolvedPath = this.normalizeDiscoveredFilePath(resolvedPath);
|
|
913
|
+
if (this.shouldSkipTransitiveStepFile(normalizedResolvedPath)) {
|
|
914
|
+
continue;
|
|
915
|
+
}
|
|
916
|
+
try {
|
|
917
|
+
const fileStats = await (0, promises_1.stat)(normalizedResolvedPath);
|
|
918
|
+
if (fileStats.isFile()) {
|
|
919
|
+
return normalizedResolvedPath;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
catch {
|
|
923
|
+
// Try the next candidate path.
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
return null;
|
|
927
|
+
}
|
|
928
|
+
async collectTransitiveStepFiles(stepFiles) {
|
|
929
|
+
const normalizedStepFiles = Array.from(new Set(stepFiles.map((stepFile) => this.normalizeDiscoveredFilePath(stepFile)))).sort();
|
|
930
|
+
const discoveredStepFiles = new Set(normalizedStepFiles);
|
|
931
|
+
const queuedFiles = [...normalizedStepFiles];
|
|
932
|
+
const visitedFiles = new Set();
|
|
933
|
+
const sourceCache = new Map();
|
|
934
|
+
const patternCache = new Map();
|
|
935
|
+
const getSource = async (filePath) => {
|
|
936
|
+
if (sourceCache.has(filePath)) {
|
|
937
|
+
return sourceCache.get(filePath) ?? null;
|
|
938
|
+
}
|
|
939
|
+
try {
|
|
940
|
+
const source = await (0, promises_1.readFile)(filePath, 'utf-8');
|
|
941
|
+
sourceCache.set(filePath, source);
|
|
942
|
+
return source;
|
|
943
|
+
}
|
|
944
|
+
catch {
|
|
945
|
+
sourceCache.set(filePath, null);
|
|
946
|
+
return null;
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
const getPatterns = async (filePath) => {
|
|
950
|
+
if (patternCache.has(filePath)) {
|
|
951
|
+
return patternCache.get(filePath) ?? null;
|
|
952
|
+
}
|
|
953
|
+
const source = await getSource(filePath);
|
|
954
|
+
if (source === null) {
|
|
955
|
+
patternCache.set(filePath, null);
|
|
956
|
+
return null;
|
|
957
|
+
}
|
|
958
|
+
const patterns = detectWorkflowPatterns(source);
|
|
959
|
+
patternCache.set(filePath, patterns);
|
|
960
|
+
return patterns;
|
|
961
|
+
};
|
|
962
|
+
while (queuedFiles.length > 0) {
|
|
963
|
+
const currentFile = queuedFiles.pop();
|
|
964
|
+
if (!currentFile || visitedFiles.has(currentFile)) {
|
|
965
|
+
continue;
|
|
966
|
+
}
|
|
967
|
+
visitedFiles.add(currentFile);
|
|
968
|
+
const currentSource = await getSource(currentFile);
|
|
969
|
+
if (currentSource === null) {
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
972
|
+
const relativeImportSpecifiers = this.extractRelativeImportSpecifiers(currentSource);
|
|
973
|
+
for (const specifier of relativeImportSpecifiers) {
|
|
974
|
+
const resolvedImportPath = await this.resolveTransitiveStepImportTargetPath(currentFile, specifier);
|
|
975
|
+
if (!resolvedImportPath) {
|
|
976
|
+
continue;
|
|
977
|
+
}
|
|
978
|
+
if (!visitedFiles.has(resolvedImportPath)) {
|
|
979
|
+
queuedFiles.push(resolvedImportPath);
|
|
980
|
+
}
|
|
981
|
+
const importPatterns = await getPatterns(resolvedImportPath);
|
|
982
|
+
if (importPatterns?.hasUseStep) {
|
|
983
|
+
discoveredStepFiles.add(resolvedImportPath);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
return Array.from(discoveredStepFiles).sort();
|
|
988
|
+
}
|
|
989
|
+
async collectTransitiveSerdeFiles({ entryFiles, serdeFiles, }) {
|
|
990
|
+
const normalizedEntryFiles = Array.from(new Set(entryFiles.map((entryFile) => this.normalizeDiscoveredFilePath(entryFile)))).sort();
|
|
991
|
+
const discoveredSerdeFiles = new Set(serdeFiles.map((serdeFile) => this.normalizeDiscoveredFilePath(serdeFile)));
|
|
992
|
+
const queuedFiles = Array.from(new Set([...normalizedEntryFiles, ...discoveredSerdeFiles]));
|
|
993
|
+
const visitedFiles = new Set();
|
|
994
|
+
const sourceCache = new Map();
|
|
995
|
+
const patternCache = new Map();
|
|
996
|
+
const getSource = async (filePath) => {
|
|
997
|
+
if (sourceCache.has(filePath)) {
|
|
998
|
+
return sourceCache.get(filePath) ?? null;
|
|
999
|
+
}
|
|
1000
|
+
try {
|
|
1001
|
+
const source = await (0, promises_1.readFile)(filePath, 'utf-8');
|
|
1002
|
+
sourceCache.set(filePath, source);
|
|
1003
|
+
return source;
|
|
1004
|
+
}
|
|
1005
|
+
catch {
|
|
1006
|
+
sourceCache.set(filePath, null);
|
|
1007
|
+
return null;
|
|
1008
|
+
}
|
|
1009
|
+
};
|
|
1010
|
+
const getPatterns = async (filePath) => {
|
|
1011
|
+
if (patternCache.has(filePath)) {
|
|
1012
|
+
return patternCache.get(filePath) ?? null;
|
|
1013
|
+
}
|
|
1014
|
+
const source = await getSource(filePath);
|
|
1015
|
+
if (source === null) {
|
|
1016
|
+
patternCache.set(filePath, null);
|
|
1017
|
+
return null;
|
|
1018
|
+
}
|
|
1019
|
+
const patterns = detectWorkflowPatterns(source);
|
|
1020
|
+
patternCache.set(filePath, patterns);
|
|
1021
|
+
return patterns;
|
|
1022
|
+
};
|
|
1023
|
+
while (queuedFiles.length > 0) {
|
|
1024
|
+
const currentFile = queuedFiles.pop();
|
|
1025
|
+
if (!currentFile || visitedFiles.has(currentFile)) {
|
|
1026
|
+
continue;
|
|
1027
|
+
}
|
|
1028
|
+
visitedFiles.add(currentFile);
|
|
1029
|
+
const currentSource = await getSource(currentFile);
|
|
1030
|
+
if (currentSource === null) {
|
|
1031
|
+
continue;
|
|
1032
|
+
}
|
|
1033
|
+
const relativeImportSpecifiers = this.extractRelativeImportSpecifiers(currentSource);
|
|
1034
|
+
for (const specifier of relativeImportSpecifiers) {
|
|
1035
|
+
const resolvedImportPath = await this.resolveTransitiveStepImportTargetPath(currentFile, specifier);
|
|
1036
|
+
if (!resolvedImportPath) {
|
|
1037
|
+
continue;
|
|
1038
|
+
}
|
|
1039
|
+
if (!visitedFiles.has(resolvedImportPath)) {
|
|
1040
|
+
queuedFiles.push(resolvedImportPath);
|
|
1041
|
+
}
|
|
1042
|
+
const importPatterns = await getPatterns(resolvedImportPath);
|
|
1043
|
+
if (importPatterns?.hasSerde &&
|
|
1044
|
+
!isWorkflowSdkFile(resolvedImportPath)) {
|
|
1045
|
+
discoveredSerdeFiles.add(resolvedImportPath);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
return Array.from(discoveredSerdeFiles).sort();
|
|
1050
|
+
}
|
|
1051
|
+
resolveBuiltInStepFilePath() {
|
|
1052
|
+
try {
|
|
1053
|
+
return this.normalizeDiscoveredFilePath(require.resolve('workflow/internal/builtins', {
|
|
1054
|
+
paths: [this.config.workingDir],
|
|
1055
|
+
}));
|
|
1056
|
+
}
|
|
1057
|
+
catch {
|
|
1058
|
+
return null;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
async copyDiscoveredStepFiles({ stepFiles, stepsRouteDir, }) {
|
|
1062
|
+
const copiedStepsDir = (0, node_path_1.join)(stepsRouteDir, step_copy_utils_js_1.DEFERRED_STEP_COPY_DIR_NAME);
|
|
1063
|
+
await (0, promises_1.mkdir)(copiedStepsDir, { recursive: true });
|
|
1064
|
+
const normalizedStepFiles = Array.from(new Set(stepFiles.map((stepFile) => this.normalizeDiscoveredFilePath(stepFile)))).sort();
|
|
1065
|
+
const copiedStepFileBySourcePath = new Map();
|
|
1066
|
+
const expectedFileNames = new Set();
|
|
1067
|
+
const copiedStepFiles = [];
|
|
1068
|
+
for (const normalizedStepFile of normalizedStepFiles) {
|
|
1069
|
+
const copiedFileName = this.getStepCopyFileName(normalizedStepFile);
|
|
1070
|
+
const copiedFilePath = (0, node_path_1.join)(copiedStepsDir, copiedFileName);
|
|
1071
|
+
const normalizedPathKey = normalizedStepFile.replace(/\\/g, '/');
|
|
1072
|
+
copiedStepFileBySourcePath.set(normalizedPathKey, copiedFilePath);
|
|
1073
|
+
try {
|
|
1074
|
+
const realPathKey = (0, node_fs_1.realpathSync)(normalizedStepFile).replace(/\\/g, '/');
|
|
1075
|
+
copiedStepFileBySourcePath.set(realPathKey, copiedFilePath);
|
|
1076
|
+
}
|
|
1077
|
+
catch {
|
|
1078
|
+
// Keep best-effort mapping when source cannot be realpath-resolved.
|
|
1079
|
+
}
|
|
1080
|
+
expectedFileNames.add(copiedFileName);
|
|
1081
|
+
copiedStepFiles.push(copiedFilePath);
|
|
1082
|
+
}
|
|
1083
|
+
for (const normalizedStepFile of normalizedStepFiles) {
|
|
1084
|
+
const source = await (0, promises_1.readFile)(normalizedStepFile, 'utf-8');
|
|
1085
|
+
const copiedFilePath = copiedStepFileBySourcePath.get(normalizedStepFile.replace(/\\/g, '/'));
|
|
1086
|
+
if (!copiedFilePath) {
|
|
1087
|
+
continue;
|
|
1088
|
+
}
|
|
1089
|
+
const rewrittenSource = this.rewriteRelativeImportsForCopiedStep(source, normalizedStepFile, copiedFilePath, copiedStepFileBySourcePath);
|
|
1090
|
+
const metadataComment = (0, step_copy_utils_js_1.createDeferredStepSourceMetadataComment)({
|
|
1091
|
+
relativeFilename: await this.getRelativeFilenameForSwc(normalizedStepFile),
|
|
1092
|
+
absolutePath: normalizedStepFile.replace(/\\/g, '/'),
|
|
1093
|
+
});
|
|
1094
|
+
const sourceMapComment = (0, step_copy_utils_js_1.createDeferredStepCopyInlineSourceMapComment)({
|
|
1095
|
+
sourcePath: normalizedStepFile,
|
|
1096
|
+
sourceContent: source,
|
|
1097
|
+
generatedContent: rewrittenSource,
|
|
1098
|
+
});
|
|
1099
|
+
const copiedSource = `${metadataComment}\n${rewrittenSource}\n${sourceMapComment}`;
|
|
1100
|
+
await this.writeFileIfChanged(copiedFilePath, copiedSource);
|
|
1101
|
+
}
|
|
1102
|
+
const existingEntries = await (0, promises_1.readdir)(copiedStepsDir, {
|
|
1103
|
+
withFileTypes: true,
|
|
1104
|
+
}).catch(() => []);
|
|
1105
|
+
await Promise.all(existingEntries
|
|
1106
|
+
.filter((entry) => !expectedFileNames.has(entry.name))
|
|
1107
|
+
.map((entry) => (0, promises_1.rm)((0, node_path_1.join)(copiedStepsDir, entry.name), {
|
|
1108
|
+
recursive: true,
|
|
1109
|
+
force: true,
|
|
1110
|
+
})));
|
|
1111
|
+
return copiedStepFiles;
|
|
1112
|
+
}
|
|
1113
|
+
async createDeferredStepsManifest({ stepFiles, workflowFiles, serdeOnlyFiles, }) {
|
|
1114
|
+
const workflowManifest = {};
|
|
1115
|
+
const filesForStepTransform = Array.from(new Set([...stepFiles, ...serdeOnlyFiles])).sort();
|
|
1116
|
+
await Promise.all(filesForStepTransform.map(async (stepFile) => {
|
|
1117
|
+
const source = await (0, promises_1.readFile)(stepFile, 'utf-8');
|
|
1118
|
+
const relativeFilename = await this.getRelativeFilenameForSwc(stepFile);
|
|
1119
|
+
const { workflowManifest: fileManifest } = await applySwcTransform(relativeFilename, source, 'step', stepFile);
|
|
1120
|
+
this.mergeWorkflowManifest(workflowManifest, fileManifest);
|
|
1121
|
+
}));
|
|
1122
|
+
const stepFileSet = new Set(stepFiles);
|
|
1123
|
+
const workflowOnlyFiles = workflowFiles
|
|
1124
|
+
.filter((workflowFile) => !stepFileSet.has(workflowFile))
|
|
1125
|
+
.sort();
|
|
1126
|
+
await Promise.all(workflowOnlyFiles.map(async (workflowFile) => {
|
|
1127
|
+
try {
|
|
1128
|
+
const source = await (0, promises_1.readFile)(workflowFile, 'utf-8');
|
|
1129
|
+
const relativeFilename = await this.getRelativeFilenameForSwc(workflowFile);
|
|
1130
|
+
const { workflowManifest: fileManifest } = await applySwcTransform(relativeFilename, source, 'workflow', workflowFile);
|
|
1131
|
+
this.mergeWorkflowManifest(workflowManifest, {
|
|
1132
|
+
workflows: fileManifest.workflows,
|
|
1133
|
+
classes: fileManifest.classes,
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
catch (error) {
|
|
1137
|
+
console.log(`Warning: Failed to extract workflow metadata from ${workflowFile}:`, error instanceof Error ? error.message : String(error));
|
|
1138
|
+
}
|
|
1139
|
+
}));
|
|
1140
|
+
return workflowManifest;
|
|
1141
|
+
}
|
|
1142
|
+
async buildStepsFunction({ workflowGeneratedDir, routeFileName = 'route.js', discoveredEntries, }) {
|
|
1143
|
+
const stepsRouteDir = (0, node_path_1.join)(workflowGeneratedDir, 'step');
|
|
1144
|
+
await (0, promises_1.mkdir)(stepsRouteDir, { recursive: true });
|
|
1145
|
+
const discovered = discoveredEntries;
|
|
1146
|
+
const stepFiles = await this.collectTransitiveStepFiles([...discovered.discoveredSteps].sort());
|
|
1147
|
+
const workflowFiles = [...discovered.discoveredWorkflows].sort();
|
|
1148
|
+
const serdeFiles = [...discovered.discoveredSerdeFiles].sort();
|
|
1149
|
+
const stepFileSet = new Set(stepFiles);
|
|
1150
|
+
const serdeOnlyFiles = serdeFiles.filter((file) => !stepFileSet.has(file));
|
|
1151
|
+
const builtInStepFilePath = this.resolveBuiltInStepFilePath();
|
|
1152
|
+
const copiedStepSourceFiles = builtInStepFilePath
|
|
1153
|
+
? [
|
|
1154
|
+
builtInStepFilePath,
|
|
1155
|
+
...stepFiles.filter((stepFile) => stepFile !== builtInStepFilePath),
|
|
1156
|
+
]
|
|
1157
|
+
: stepFiles;
|
|
1158
|
+
const copiedStepFiles = await this.copyDiscoveredStepFiles({
|
|
1159
|
+
stepFiles: copiedStepSourceFiles,
|
|
1160
|
+
stepsRouteDir,
|
|
1161
|
+
});
|
|
1162
|
+
const stepRouteFile = (0, node_path_1.join)(stepsRouteDir, routeFileName);
|
|
1163
|
+
const copiedStepImports = copiedStepFiles
|
|
1164
|
+
.map((copiedStepFile) => {
|
|
1165
|
+
const importSpecifier = this.getRelativeImportSpecifier(stepRouteFile, copiedStepFile);
|
|
1166
|
+
return `import '${importSpecifier}';`;
|
|
1167
|
+
})
|
|
1168
|
+
.join('\n');
|
|
1169
|
+
const serdeImports = serdeOnlyFiles
|
|
1170
|
+
.map((serdeFile) => {
|
|
1171
|
+
const normalizedSerdeFile = this.normalizeDiscoveredFilePath(serdeFile);
|
|
1172
|
+
const { importPath, isPackage } = getImportPath(normalizedSerdeFile, this.config.workingDir);
|
|
1173
|
+
if (isPackage) {
|
|
1174
|
+
return `import '${importPath}';`;
|
|
1175
|
+
}
|
|
1176
|
+
const importSpecifier = this.getRelativeImportSpecifier(stepRouteFile, normalizedSerdeFile);
|
|
1177
|
+
return `import '${importSpecifier}';`;
|
|
1178
|
+
})
|
|
1179
|
+
.join('\n');
|
|
1180
|
+
const routeContents = [
|
|
1181
|
+
'// biome-ignore-all lint: generated file',
|
|
1182
|
+
'/* eslint-disable */',
|
|
1183
|
+
builtInStepFilePath ? '' : "import 'workflow/internal/builtins';",
|
|
1184
|
+
copiedStepImports,
|
|
1185
|
+
serdeImports
|
|
1186
|
+
? `// Serde files for cross-context class registration\n${serdeImports}`
|
|
1187
|
+
: '',
|
|
1188
|
+
"export { stepEntrypoint as POST } from 'workflow/runtime';",
|
|
1189
|
+
]
|
|
1190
|
+
.filter(Boolean)
|
|
1191
|
+
.join('\n');
|
|
1192
|
+
await this.writeFileIfChanged(stepRouteFile, routeContents);
|
|
1193
|
+
const manifest = await this.createDeferredStepsManifest({
|
|
1194
|
+
stepFiles: copiedStepSourceFiles,
|
|
1195
|
+
workflowFiles,
|
|
1196
|
+
serdeOnlyFiles,
|
|
1197
|
+
});
|
|
1198
|
+
return {
|
|
1199
|
+
context: undefined,
|
|
1200
|
+
manifest,
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
async buildWorkflowsFunction({ inputFiles, workflowGeneratedDir, tsconfigPath, routeFileName = 'route.js', discoveredEntries, }) {
|
|
1204
|
+
const workflowsRouteDir = (0, node_path_1.join)(workflowGeneratedDir, 'flow');
|
|
1205
|
+
await (0, promises_1.mkdir)(workflowsRouteDir, { recursive: true });
|
|
1206
|
+
return await this.createWorkflowsBundle({
|
|
1207
|
+
format: 'esm',
|
|
1208
|
+
outfile: (0, node_path_1.join)(workflowsRouteDir, routeFileName),
|
|
1209
|
+
bundleFinalOutput: false,
|
|
1210
|
+
inputFiles,
|
|
1211
|
+
tsconfigPath,
|
|
1212
|
+
discoveredEntries,
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
async buildWebhookRoute({ workflowGeneratedDir, routeFileName = 'route.js', }) {
|
|
1216
|
+
const webhookRouteFile = (0, node_path_1.join)(workflowGeneratedDir, `webhook/[token]/${routeFileName}`);
|
|
1217
|
+
await this.createWebhookBundle({
|
|
1218
|
+
outfile: webhookRouteFile,
|
|
1219
|
+
bundle: false, // Next.js doesn't need bundling
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
async findAppDirectory() {
|
|
1223
|
+
const appDir = (0, node_path_1.resolve)(this.config.workingDir, 'app');
|
|
1224
|
+
const srcAppDir = (0, node_path_1.resolve)(this.config.workingDir, 'src/app');
|
|
1225
|
+
const pagesDir = (0, node_path_1.resolve)(this.config.workingDir, 'pages');
|
|
1226
|
+
const srcPagesDir = (0, node_path_1.resolve)(this.config.workingDir, 'src/pages');
|
|
1227
|
+
// Helper to check if a path exists and is a directory
|
|
1228
|
+
const isDirectory = async (path) => {
|
|
1229
|
+
try {
|
|
1230
|
+
await (0, promises_1.access)(path, node_fs_1.constants.F_OK);
|
|
1231
|
+
const stats = await (0, promises_1.stat)(path);
|
|
1232
|
+
if (!stats.isDirectory()) {
|
|
1233
|
+
throw new Error(`Path exists but is not a directory: ${path}`);
|
|
1234
|
+
}
|
|
1235
|
+
return true;
|
|
1236
|
+
}
|
|
1237
|
+
catch (e) {
|
|
1238
|
+
if (e instanceof Error && e.message.includes('not a directory')) {
|
|
1239
|
+
throw e;
|
|
1240
|
+
}
|
|
1241
|
+
return false;
|
|
1242
|
+
}
|
|
1243
|
+
};
|
|
1244
|
+
// Check if app directory exists
|
|
1245
|
+
if (await isDirectory(appDir)) {
|
|
1246
|
+
return appDir;
|
|
1247
|
+
}
|
|
1248
|
+
// Check if src/app directory exists
|
|
1249
|
+
if (await isDirectory(srcAppDir)) {
|
|
1250
|
+
return srcAppDir;
|
|
1251
|
+
}
|
|
1252
|
+
// If no app directory exists, check for pages directory and create app next to it
|
|
1253
|
+
if (await isDirectory(pagesDir)) {
|
|
1254
|
+
// Create app directory next to pages directory
|
|
1255
|
+
await (0, promises_1.mkdir)(appDir, { recursive: true });
|
|
1256
|
+
return appDir;
|
|
1257
|
+
}
|
|
1258
|
+
if (await isDirectory(srcPagesDir)) {
|
|
1259
|
+
// Create src/app directory next to src/pages directory
|
|
1260
|
+
await (0, promises_1.mkdir)(srcAppDir, { recursive: true });
|
|
1261
|
+
return srcAppDir;
|
|
1262
|
+
}
|
|
1263
|
+
throw new Error('Could not find Next.js app or pages directory. Expected one of: "app", "src/app", "pages", or "src/pages" to exist.');
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
CachedNextBuilderDeferred = NextDeferredBuilder;
|
|
1267
|
+
return NextDeferredBuilder;
|
|
1268
|
+
}
|
|
1269
|
+
//# sourceMappingURL=builder-deferred.js.map
|