@workflow/next 5.0.0-beta.0 → 5.0.0-beta.2
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/README.md +1 -1
- package/dist/builder-deferred.d.ts.map +1 -1
- package/dist/builder-deferred.js +261 -85
- package/dist/builder-deferred.js.map +1 -1
- package/dist/builder-eager.js.map +1 -1
- package/dist/builder.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -6
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +171 -21
- package/dist/loader.js.map +1 -1
- package/dist/runtime.js.map +1 -1
- package/dist/socket-server.js.map +1 -1
- package/dist/step-copy-utils.js.map +1 -1
- package/docs/next.mdx +24 -0
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builder-deferred.d.ts","sourceRoot":"","sources":["../src/builder-deferred.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"builder-deferred.d.ts","sourceRoot":"","sources":["../src/builder-deferred.ts"],"names":[],"mappings":"AAkDA,wBAAsB,sBAAsB,iBAosE3C"}
|
package/dist/builder-deferred.js
CHANGED
|
@@ -9,9 +9,11 @@ const node_fs_1 = require("node:fs");
|
|
|
9
9
|
const promises_1 = require("node:fs/promises");
|
|
10
10
|
const node_os_1 = __importDefault(require("node:os"));
|
|
11
11
|
const node_path_1 = require("node:path");
|
|
12
|
+
const enhanced_resolve_1 = __importDefault(require("enhanced-resolve"));
|
|
12
13
|
const socket_server_js_1 = require("./socket-server.js");
|
|
13
14
|
const step_copy_utils_js_1 = require("./step-copy-utils.js");
|
|
14
15
|
const ROUTE_STUB_FILE_MARKER = 'WORKFLOW_ROUTE_STUB_FILE';
|
|
16
|
+
const ROUTE_STUB_MARKER_SCAN_BYTES = 4 * 1024;
|
|
15
17
|
let CachedNextBuilderDeferred;
|
|
16
18
|
// Create the deferred Next builder dynamically by extending the ESM BaseBuilder.
|
|
17
19
|
// Exported as getNextBuilderDeferred() to allow CommonJS modules to import from
|
|
@@ -20,9 +22,46 @@ async function getNextBuilderDeferred() {
|
|
|
20
22
|
if (CachedNextBuilderDeferred) {
|
|
21
23
|
return CachedNextBuilderDeferred;
|
|
22
24
|
}
|
|
23
|
-
const { BaseBuilder: BaseBuilderClass, STEP_QUEUE_TRIGGER, WORKFLOW_QUEUE_TRIGGER, applySwcTransform, detectWorkflowPatterns, getImportPath,
|
|
25
|
+
const { BaseBuilder: BaseBuilderClass, STEP_QUEUE_TRIGGER, WORKFLOW_QUEUE_TRIGGER, applySwcTransform, detectWorkflowPatterns, getImportPath, resolveWorkflowAliasRelativePath,
|
|
24
26
|
// biome-ignore lint/security/noGlobalEval: Need to use eval here to avoid TypeScript from transpiling the import statement into `require()`
|
|
25
27
|
} = (await eval('import("@workflow/builders")'));
|
|
28
|
+
// Shared resolve options matching the configuration used by the SWC
|
|
29
|
+
// esbuild plugin (swc-esbuild-plugin.ts) for consistent resolution
|
|
30
|
+
// semantics across the toolchain.
|
|
31
|
+
const NODE_RESOLVE_OPTIONS = {
|
|
32
|
+
dependencyType: 'commonjs',
|
|
33
|
+
modules: ['node_modules'],
|
|
34
|
+
exportsFields: ['exports'],
|
|
35
|
+
importsFields: ['imports'],
|
|
36
|
+
conditionNames: ['node', 'require'],
|
|
37
|
+
descriptionFiles: ['package.json'],
|
|
38
|
+
extensions: [
|
|
39
|
+
'.ts',
|
|
40
|
+
'.tsx',
|
|
41
|
+
'.mts',
|
|
42
|
+
'.cts',
|
|
43
|
+
'.cjs',
|
|
44
|
+
'.mjs',
|
|
45
|
+
'.js',
|
|
46
|
+
'.jsx',
|
|
47
|
+
'.json',
|
|
48
|
+
'.node',
|
|
49
|
+
],
|
|
50
|
+
enforceExtensions: false,
|
|
51
|
+
symlinks: true,
|
|
52
|
+
mainFields: ['main'],
|
|
53
|
+
mainFiles: ['index'],
|
|
54
|
+
roots: [],
|
|
55
|
+
fullySpecified: false,
|
|
56
|
+
preferRelative: false,
|
|
57
|
+
preferAbsolute: false,
|
|
58
|
+
restrictions: [],
|
|
59
|
+
};
|
|
60
|
+
const NODE_ESM_RESOLVE_OPTIONS = {
|
|
61
|
+
...NODE_RESOLVE_OPTIONS,
|
|
62
|
+
dependencyType: 'esm',
|
|
63
|
+
conditionNames: ['node', 'import'],
|
|
64
|
+
};
|
|
26
65
|
class NextDeferredBuilder extends BaseBuilderClass {
|
|
27
66
|
socketIO;
|
|
28
67
|
discoveredWorkflowFiles = new Set();
|
|
@@ -34,6 +73,11 @@ async function getNextBuilderDeferred() {
|
|
|
34
73
|
cacheWriteTimer = null;
|
|
35
74
|
deferredRebuildTimer = null;
|
|
36
75
|
lastDeferredBuildSignature = null;
|
|
76
|
+
// Lazily initialized resolvers for bare specifier rewriting.
|
|
77
|
+
// Cached to avoid re-creating on every import rewrite.
|
|
78
|
+
esmSyncResolver;
|
|
79
|
+
cjsSyncResolver;
|
|
80
|
+
manifestStepResolveBaseDirs = null;
|
|
37
81
|
async build() {
|
|
38
82
|
const outputDir = await this.findAppDirectory();
|
|
39
83
|
await this.initializeDiscoveryState();
|
|
@@ -70,7 +114,9 @@ async function getNextBuilderDeferred() {
|
|
|
70
114
|
for (let buildPass = 0; buildPass < maxBuildPasses; buildPass++) {
|
|
71
115
|
const inputFiles = this.getCurrentInputFiles(implicitStepFiles);
|
|
72
116
|
const buildSignature = await this.createDeferredBuildSignature(inputFiles);
|
|
73
|
-
|
|
117
|
+
const shouldForceBuildForGeneratedRoutes = await this.shouldForceBuildForGeneratedRoutes();
|
|
118
|
+
if (buildSignature === this.lastDeferredBuildSignature &&
|
|
119
|
+
!shouldForceBuildForGeneratedRoutes) {
|
|
74
120
|
return;
|
|
75
121
|
}
|
|
76
122
|
try {
|
|
@@ -107,6 +153,53 @@ async function getNextBuilderDeferred() {
|
|
|
107
153
|
const workflowStdlibPath = this.resolveWorkflowStdlibStepFilePath();
|
|
108
154
|
return workflowStdlibPath ? [workflowStdlibPath] : [];
|
|
109
155
|
}
|
|
156
|
+
async shouldForceBuildForGeneratedRoutes() {
|
|
157
|
+
const outputDir = await this.findAppDirectory();
|
|
158
|
+
const generatedRouteFiles = [
|
|
159
|
+
(0, node_path_1.join)(outputDir, '.well-known/workflow/v1/flow/route.js'),
|
|
160
|
+
(0, node_path_1.join)(outputDir, '.well-known/workflow/v1/step/route.js'),
|
|
161
|
+
(0, node_path_1.join)(outputDir, '.well-known/workflow/v1/webhook/[token]/route.js'),
|
|
162
|
+
];
|
|
163
|
+
for (const routeFilePath of generatedRouteFiles) {
|
|
164
|
+
const routeState = await this.getGeneratedRouteState(routeFilePath);
|
|
165
|
+
if (routeState === 'missing') {
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
if (routeState === 'stub') {
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
async getGeneratedRouteState(routeFilePath) {
|
|
175
|
+
let routeStats;
|
|
176
|
+
try {
|
|
177
|
+
routeStats = await (0, promises_1.stat)(routeFilePath);
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return 'missing';
|
|
181
|
+
}
|
|
182
|
+
if (!routeStats.isFile()) {
|
|
183
|
+
return 'missing';
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
const routeFileHandle = await (0, promises_1.open)(routeFilePath, 'r');
|
|
187
|
+
try {
|
|
188
|
+
const markerScanBuffer = Buffer.alloc(ROUTE_STUB_MARKER_SCAN_BYTES);
|
|
189
|
+
const { bytesRead } = await routeFileHandle.read(markerScanBuffer, 0, ROUTE_STUB_MARKER_SCAN_BYTES, 0);
|
|
190
|
+
const markerScanSource = markerScanBuffer.toString('utf8', 0, bytesRead);
|
|
191
|
+
return markerScanSource.includes(ROUTE_STUB_FILE_MARKER)
|
|
192
|
+
? 'stub'
|
|
193
|
+
: 'generated';
|
|
194
|
+
}
|
|
195
|
+
finally {
|
|
196
|
+
await routeFileHandle.close();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return 'missing';
|
|
201
|
+
}
|
|
202
|
+
}
|
|
110
203
|
resolveWorkflowStdlibStepFilePath() {
|
|
111
204
|
let workflowCjsEntry;
|
|
112
205
|
try {
|
|
@@ -175,22 +268,20 @@ async function getNextBuilderDeferred() {
|
|
|
175
268
|
return null;
|
|
176
269
|
}
|
|
177
270
|
if (!validatePatterns) {
|
|
178
|
-
const isSdkFile = isWorkflowSdkFile(filePath);
|
|
179
271
|
return {
|
|
180
272
|
filePath,
|
|
181
273
|
hasUseWorkflow: candidates.hasWorkflowCandidate,
|
|
182
274
|
hasUseStep: candidates.hasStepCandidate,
|
|
183
|
-
hasSerde: candidates.hasSerdeCandidate
|
|
275
|
+
hasSerde: candidates.hasSerdeCandidate,
|
|
184
276
|
};
|
|
185
277
|
}
|
|
186
278
|
const source = await (0, promises_1.readFile)(filePath, 'utf-8');
|
|
187
279
|
const patterns = detectWorkflowPatterns(source);
|
|
188
|
-
const isSdkFile = isWorkflowSdkFile(filePath);
|
|
189
280
|
return {
|
|
190
281
|
filePath,
|
|
191
282
|
hasUseWorkflow: patterns.hasUseWorkflow,
|
|
192
283
|
hasUseStep: patterns.hasUseStep,
|
|
193
|
-
hasSerde: patterns.hasSerde
|
|
284
|
+
hasSerde: patterns.hasSerde,
|
|
194
285
|
};
|
|
195
286
|
}
|
|
196
287
|
catch {
|
|
@@ -322,8 +413,11 @@ async function getNextBuilderDeferred() {
|
|
|
322
413
|
routeFileName: tempRouteFileName,
|
|
323
414
|
discoveredEntries,
|
|
324
415
|
};
|
|
325
|
-
const { manifest: stepsManifest } = await this.buildStepsFunction(options);
|
|
326
416
|
const workflowsBundle = await this.buildWorkflowsFunction(options);
|
|
417
|
+
const { manifest: stepsManifest } = await this.buildStepsFunction({
|
|
418
|
+
...options,
|
|
419
|
+
additionalStepSourceManifest: workflowsBundle?.manifest,
|
|
420
|
+
});
|
|
327
421
|
await this.buildWebhookRoute({
|
|
328
422
|
workflowGeneratedDir,
|
|
329
423
|
routeFileName: tempRouteFileName,
|
|
@@ -451,11 +545,7 @@ async function getNextBuilderDeferred() {
|
|
|
451
545
|
if (hasCacheTrackingChange) {
|
|
452
546
|
this.scheduleWorkflowsCacheWrite();
|
|
453
547
|
}
|
|
454
|
-
if (
|
|
455
|
-
hasStep ||
|
|
456
|
-
hasSerde ||
|
|
457
|
-
hasCacheTrackingChange ||
|
|
458
|
-
wasTrackedDependency) {
|
|
548
|
+
if (hasCacheTrackingChange || wasTrackedDependency) {
|
|
459
549
|
this.scheduleDeferredRebuild();
|
|
460
550
|
}
|
|
461
551
|
},
|
|
@@ -470,7 +560,8 @@ async function getNextBuilderDeferred() {
|
|
|
470
560
|
return;
|
|
471
561
|
}
|
|
472
562
|
await this.loadWorkflowsCache();
|
|
473
|
-
|
|
563
|
+
// Deferred mode must not run eager input-graph discovery; entries are
|
|
564
|
+
// discovered via loader->socket notifications during Next's build.
|
|
474
565
|
this.cacheInitialized = true;
|
|
475
566
|
}
|
|
476
567
|
getDistDir() {
|
|
@@ -483,9 +574,38 @@ async function getNextBuilderDeferred() {
|
|
|
483
574
|
return (0, node_path_1.join)(this.config.workingDir, this.getDistDir(), 'cache', 'workflow-socket.json');
|
|
484
575
|
}
|
|
485
576
|
normalizeDiscoveredFilePath(filePath) {
|
|
486
|
-
|
|
577
|
+
const resolvedPath = (0, node_path_1.isAbsolute)(filePath)
|
|
487
578
|
? filePath
|
|
488
579
|
: (0, node_path_1.resolve)(this.config.workingDir, filePath);
|
|
580
|
+
try {
|
|
581
|
+
return (0, node_fs_1.realpathSync)(resolvedPath);
|
|
582
|
+
}
|
|
583
|
+
catch {
|
|
584
|
+
return resolvedPath;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
getManifestStepResolveBaseDirs() {
|
|
588
|
+
if (this.manifestStepResolveBaseDirs) {
|
|
589
|
+
return this.manifestStepResolveBaseDirs;
|
|
590
|
+
}
|
|
591
|
+
const resolveBaseDirs = new Set();
|
|
592
|
+
const addResolveBaseDir = (baseDir) => {
|
|
593
|
+
resolveBaseDirs.add(this.normalizeDiscoveredFilePath(baseDir));
|
|
594
|
+
};
|
|
595
|
+
if (this.config.projectRoot) {
|
|
596
|
+
addResolveBaseDir(this.config.projectRoot);
|
|
597
|
+
}
|
|
598
|
+
let currentResolveDir = this.config.workingDir;
|
|
599
|
+
while (currentResolveDir) {
|
|
600
|
+
addResolveBaseDir(currentResolveDir);
|
|
601
|
+
const parentResolveDir = (0, node_path_1.dirname)(currentResolveDir);
|
|
602
|
+
if (parentResolveDir === currentResolveDir) {
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
currentResolveDir = parentResolveDir;
|
|
606
|
+
}
|
|
607
|
+
this.manifestStepResolveBaseDirs = Array.from(resolveBaseDirs);
|
|
608
|
+
return this.manifestStepResolveBaseDirs;
|
|
489
609
|
}
|
|
490
610
|
async filterExistingFiles(filePaths) {
|
|
491
611
|
const normalizedFilePaths = Array.from(new Set(filePaths.map((filePath) => this.normalizeDiscoveredFilePath(filePath)))).sort();
|
|
@@ -683,41 +803,6 @@ async function getNextBuilderDeferred() {
|
|
|
683
803
|
this.discoveredSerdeFiles.add(filePath);
|
|
684
804
|
}
|
|
685
805
|
}
|
|
686
|
-
async loadDiscoveredEntriesFromInputGraph() {
|
|
687
|
-
const inputFiles = await this.getInputFiles();
|
|
688
|
-
if (inputFiles.length === 0) {
|
|
689
|
-
return;
|
|
690
|
-
}
|
|
691
|
-
const { discoveredWorkflows, discoveredSteps, discoveredSerdeFiles } = await this.discoverEntries(inputFiles, this.config.workingDir);
|
|
692
|
-
const { workflowFiles, stepFiles, serdeFiles } = await this.reconcileDiscoveredEntries({
|
|
693
|
-
workflowCandidates: discoveredWorkflows,
|
|
694
|
-
stepCandidates: discoveredSteps,
|
|
695
|
-
serdeCandidates: discoveredSerdeFiles,
|
|
696
|
-
validatePatterns: true,
|
|
697
|
-
});
|
|
698
|
-
let hasChanges = false;
|
|
699
|
-
for (const filePath of workflowFiles) {
|
|
700
|
-
if (!this.discoveredWorkflowFiles.has(filePath)) {
|
|
701
|
-
this.discoveredWorkflowFiles.add(filePath);
|
|
702
|
-
hasChanges = true;
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
for (const filePath of stepFiles) {
|
|
706
|
-
if (!this.discoveredStepFiles.has(filePath)) {
|
|
707
|
-
this.discoveredStepFiles.add(filePath);
|
|
708
|
-
hasChanges = true;
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
for (const filePath of serdeFiles) {
|
|
712
|
-
if (!this.discoveredSerdeFiles.has(filePath)) {
|
|
713
|
-
this.discoveredSerdeFiles.add(filePath);
|
|
714
|
-
hasChanges = true;
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
if (hasChanges) {
|
|
718
|
-
this.scheduleWorkflowsCacheWrite();
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
806
|
async writeWorkflowsCache() {
|
|
722
807
|
const cacheFilePath = this.getWorkflowsCacheFilePath();
|
|
723
808
|
const cacheDir = (0, node_path_1.join)(this.config.workingDir, this.getDistDir(), 'cache');
|
|
@@ -752,21 +837,6 @@ async function getNextBuilderDeferred() {
|
|
|
752
837
|
await this.writeFileIfChanged((0, node_path_1.join)(workflowGeneratedDir, 'step/route.js'), routeStubContent);
|
|
753
838
|
await this.writeFileIfChanged((0, node_path_1.join)(workflowGeneratedDir, 'webhook/[token]/route.js'), routeStubContent);
|
|
754
839
|
}
|
|
755
|
-
async getInputFiles() {
|
|
756
|
-
const inputFiles = await super.getInputFiles();
|
|
757
|
-
return inputFiles.filter((item) => {
|
|
758
|
-
// Match App Router entrypoints: route.ts, page.ts, layout.ts in app/ or src/app/ directories
|
|
759
|
-
// Matches: /app/page.ts, /app/dashboard/page.ts, /src/app/route.ts, etc.
|
|
760
|
-
if (item.match(/(^|.*[/\\])(app|src[/\\]app)([/\\](route|page|layout)\.|[/\\].*[/\\](route|page|layout)\.)/)) {
|
|
761
|
-
return true;
|
|
762
|
-
}
|
|
763
|
-
// Match Pages Router entrypoints: files in pages/ or src/pages/
|
|
764
|
-
if (item.match(/[/\\](pages|src[/\\]pages)[/\\]/)) {
|
|
765
|
-
return true;
|
|
766
|
-
}
|
|
767
|
-
return false;
|
|
768
|
-
});
|
|
769
|
-
}
|
|
770
840
|
async writeFunctionsConfig(outputDir) {
|
|
771
841
|
// we don't run this in development mode as it's not needed
|
|
772
842
|
if (process.env.NODE_ENV === 'development') {
|
|
@@ -893,6 +963,25 @@ async function getNextBuilderDeferred() {
|
|
|
893
963
|
}
|
|
894
964
|
rewriteCopiedStepImportSpecifier(specifier, sourceFilePath, copiedFilePath, copiedStepFileBySourcePath) {
|
|
895
965
|
if (!specifier.startsWith('.')) {
|
|
966
|
+
// Bare specifiers (e.g. '@workflow/serde') that are transitive
|
|
967
|
+
// dependencies of SDK packages can't be resolved by the bundler
|
|
968
|
+
// from the copied file's location (__workflow_step_files__/ inside
|
|
969
|
+
// the app dir) because the app doesn't directly depend on them.
|
|
970
|
+
//
|
|
971
|
+
// Only rewrite when the specifier can't be resolved from the app
|
|
972
|
+
// directory. If the package is a direct dependency of the app,
|
|
973
|
+
// the bare specifier will resolve normally and should be left as-is.
|
|
974
|
+
const appResolvable = this.resolveBareCopiedStepSpecifier(specifier, copiedFilePath);
|
|
975
|
+
if (!appResolvable) {
|
|
976
|
+
const resolved = this.resolveBareCopiedStepSpecifier(specifier, sourceFilePath);
|
|
977
|
+
if (!resolved)
|
|
978
|
+
return specifier;
|
|
979
|
+
let rewrittenPath = (0, node_path_1.relative)((0, node_path_1.dirname)(copiedFilePath), resolved).replace(/\\/g, '/');
|
|
980
|
+
if (!rewrittenPath.startsWith('.')) {
|
|
981
|
+
rewrittenPath = `./${rewrittenPath}`;
|
|
982
|
+
}
|
|
983
|
+
return rewrittenPath;
|
|
984
|
+
}
|
|
896
985
|
return specifier;
|
|
897
986
|
}
|
|
898
987
|
const specifierMatch = specifier.match(/^([^?#]+)(.*)$/);
|
|
@@ -917,6 +1006,39 @@ async function getNextBuilderDeferred() {
|
|
|
917
1006
|
}
|
|
918
1007
|
return `${rewrittenPath}${suffix}`;
|
|
919
1008
|
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Resolves a bare specifier (e.g. '@workflow/serde', 'workflow') to an
|
|
1011
|
+
* absolute file path using ESM-compatible resolution semantics via
|
|
1012
|
+
* `enhanced-resolve`. Tries ESM conditions first (`node`, `import`),
|
|
1013
|
+
* falling back to CJS resolution if ESM fails.
|
|
1014
|
+
*/
|
|
1015
|
+
resolveBareCopiedStepSpecifier(specifier, sourceFilePath) {
|
|
1016
|
+
if (!this.esmSyncResolver) {
|
|
1017
|
+
this.esmSyncResolver = enhanced_resolve_1.default.create.sync(NODE_ESM_RESOLVE_OPTIONS);
|
|
1018
|
+
}
|
|
1019
|
+
if (!this.cjsSyncResolver) {
|
|
1020
|
+
this.cjsSyncResolver =
|
|
1021
|
+
enhanced_resolve_1.default.create.sync(NODE_RESOLVE_OPTIONS);
|
|
1022
|
+
}
|
|
1023
|
+
const context = (0, node_path_1.dirname)(sourceFilePath);
|
|
1024
|
+
try {
|
|
1025
|
+
const resolved = this.esmSyncResolver(context, specifier);
|
|
1026
|
+
if (resolved)
|
|
1027
|
+
return resolved;
|
|
1028
|
+
}
|
|
1029
|
+
catch {
|
|
1030
|
+
// ESM resolution failed, try CJS
|
|
1031
|
+
}
|
|
1032
|
+
try {
|
|
1033
|
+
const resolved = this.cjsSyncResolver(context, specifier);
|
|
1034
|
+
if (resolved)
|
|
1035
|
+
return resolved;
|
|
1036
|
+
}
|
|
1037
|
+
catch {
|
|
1038
|
+
// CJS resolution also failed
|
|
1039
|
+
}
|
|
1040
|
+
return undefined;
|
|
1041
|
+
}
|
|
920
1042
|
resolveCopiedStepImportTargetPath(targetPath) {
|
|
921
1043
|
if ((0, node_fs_1.existsSync)(targetPath)) {
|
|
922
1044
|
return targetPath;
|
|
@@ -975,10 +1097,14 @@ async function getNextBuilderDeferred() {
|
|
|
975
1097
|
}
|
|
976
1098
|
return Array.from(relativeSpecifiers);
|
|
977
1099
|
}
|
|
978
|
-
|
|
1100
|
+
isGeneratedWorkflowArtifact(filePath) {
|
|
979
1101
|
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
980
1102
|
return (normalizedPath.includes('/.well-known/workflow/') ||
|
|
981
|
-
normalizedPath.includes('/.next/')
|
|
1103
|
+
normalizedPath.includes('/.next/'));
|
|
1104
|
+
}
|
|
1105
|
+
shouldSkipTransitiveStepFile(filePath) {
|
|
1106
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
1107
|
+
return (this.isGeneratedWorkflowArtifact(normalizedPath) ||
|
|
982
1108
|
normalizedPath.includes('/node_modules/') ||
|
|
983
1109
|
normalizedPath.includes('/.pnpm/'));
|
|
984
1110
|
}
|
|
@@ -1094,9 +1220,9 @@ async function getNextBuilderDeferred() {
|
|
|
1094
1220
|
async collectTransitiveSerdeFiles({ entryFiles, serdeFiles, }) {
|
|
1095
1221
|
const normalizedEntryFiles = Array.from(new Set(entryFiles.map((entryFile) => this.normalizeDiscoveredFilePath(entryFile)))).sort();
|
|
1096
1222
|
const normalizedSerdeSeedFiles = Array.from(new Set(serdeFiles.map((serdeFile) => this.normalizeDiscoveredFilePath(serdeFile)))).sort();
|
|
1097
|
-
// Intentionally re-validate serde seeds against source
|
|
1223
|
+
// Intentionally re-validate serde seeds against source patterns.
|
|
1098
1224
|
// This keeps previously discovered/manual seed entries from sticking when
|
|
1099
|
-
// files no longer match serde patterns
|
|
1225
|
+
// files no longer match serde patterns.
|
|
1100
1226
|
const discoveredSerdeFiles = new Set();
|
|
1101
1227
|
const queuedFiles = Array.from(new Set([...normalizedEntryFiles, ...normalizedSerdeSeedFiles]));
|
|
1102
1228
|
const visitedFiles = new Set();
|
|
@@ -1131,7 +1257,7 @@ async function getNextBuilderDeferred() {
|
|
|
1131
1257
|
};
|
|
1132
1258
|
for (const serdeSeedFile of normalizedSerdeSeedFiles) {
|
|
1133
1259
|
const seedPatterns = await getPatterns(serdeSeedFile);
|
|
1134
|
-
if (seedPatterns?.hasSerde
|
|
1260
|
+
if (seedPatterns?.hasSerde) {
|
|
1135
1261
|
discoveredSerdeFiles.add(serdeSeedFile);
|
|
1136
1262
|
}
|
|
1137
1263
|
}
|
|
@@ -1155,13 +1281,37 @@ async function getNextBuilderDeferred() {
|
|
|
1155
1281
|
queuedFiles.push(resolvedImportPath);
|
|
1156
1282
|
}
|
|
1157
1283
|
const importPatterns = await getPatterns(resolvedImportPath);
|
|
1158
|
-
if (importPatterns?.hasSerde
|
|
1159
|
-
!isWorkflowSdkFile(resolvedImportPath)) {
|
|
1284
|
+
if (importPatterns?.hasSerde) {
|
|
1160
1285
|
discoveredSerdeFiles.add(resolvedImportPath);
|
|
1161
1286
|
}
|
|
1162
1287
|
}
|
|
1163
1288
|
}
|
|
1164
|
-
|
|
1289
|
+
// AST-level verification: run SWC detect mode on regex-matched candidates
|
|
1290
|
+
// to confirm they actually define serde classes. This prevents SDK internal
|
|
1291
|
+
// files (which match serde regex patterns but define no classes) from being
|
|
1292
|
+
// bundled into the workflow sandbox.
|
|
1293
|
+
const projectRoot = this.config.projectRoot || this.config.workingDir;
|
|
1294
|
+
const verifiedSerdeFiles = [];
|
|
1295
|
+
await Promise.all(Array.from(discoveredSerdeFiles).map(async (filePath) => {
|
|
1296
|
+
const source = await getSource(filePath);
|
|
1297
|
+
if (!source)
|
|
1298
|
+
return;
|
|
1299
|
+
try {
|
|
1300
|
+
const relativeFilename = await this.getRelativeFilenameForSwc(filePath);
|
|
1301
|
+
const { workflowManifest } = await applySwcTransform(relativeFilename, source, 'detect', filePath, projectRoot);
|
|
1302
|
+
// Only include files that actually define serde classes
|
|
1303
|
+
const hasClasses = workflowManifest.classes &&
|
|
1304
|
+
Object.values(workflowManifest.classes).some((entries) => Object.keys(entries).length > 0);
|
|
1305
|
+
if (hasClasses) {
|
|
1306
|
+
verifiedSerdeFiles.push(filePath);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
catch {
|
|
1310
|
+
// If detect fails, include the file to be safe
|
|
1311
|
+
verifiedSerdeFiles.push(filePath);
|
|
1312
|
+
}
|
|
1313
|
+
}));
|
|
1314
|
+
return verifiedSerdeFiles.sort();
|
|
1165
1315
|
}
|
|
1166
1316
|
async createResponseBuiltinsStepFile({ stepsRouteDir, }) {
|
|
1167
1317
|
const copiedStepsDir = (0, node_path_1.join)(stepsRouteDir, step_copy_utils_js_1.DEFERRED_STEP_COPY_DIR_NAME);
|
|
@@ -1190,12 +1340,12 @@ async function getNextBuilderDeferred() {
|
|
|
1190
1340
|
await this.writeFileIfChanged(responseBuiltinsFilePath, `${source}\n${sourceMapComment}\n`);
|
|
1191
1341
|
return responseBuiltinsFilePath;
|
|
1192
1342
|
}
|
|
1193
|
-
async copyDiscoveredStepFiles({ stepFiles, stepsRouteDir, }) {
|
|
1343
|
+
async copyDiscoveredStepFiles({ stepFiles, stepsRouteDir, preserveFileNames = [], }) {
|
|
1194
1344
|
const copiedStepsDir = (0, node_path_1.join)(stepsRouteDir, step_copy_utils_js_1.DEFERRED_STEP_COPY_DIR_NAME);
|
|
1195
1345
|
await (0, promises_1.mkdir)(copiedStepsDir, { recursive: true });
|
|
1196
1346
|
const normalizedStepFiles = Array.from(new Set(stepFiles.map((stepFile) => this.normalizeDiscoveredFilePath(stepFile)))).sort();
|
|
1197
1347
|
const copiedStepFileBySourcePath = new Map();
|
|
1198
|
-
const expectedFileNames = new Set();
|
|
1348
|
+
const expectedFileNames = new Set(preserveFileNames);
|
|
1199
1349
|
const copiedStepFiles = [];
|
|
1200
1350
|
for (const normalizedStepFile of normalizedStepFiles) {
|
|
1201
1351
|
const copiedFileName = this.getStepCopyFileName(normalizedStepFile);
|
|
@@ -1271,7 +1421,24 @@ async function getNextBuilderDeferred() {
|
|
|
1271
1421
|
}));
|
|
1272
1422
|
return workflowManifest;
|
|
1273
1423
|
}
|
|
1274
|
-
async
|
|
1424
|
+
async collectManifestStepSourceFiles(manifest) {
|
|
1425
|
+
const manifestStepEntries = Object.keys(manifest.steps || {});
|
|
1426
|
+
if (manifestStepEntries.length === 0) {
|
|
1427
|
+
return [];
|
|
1428
|
+
}
|
|
1429
|
+
const resolveBaseDirs = this.getManifestStepResolveBaseDirs();
|
|
1430
|
+
const candidateFiles = manifestStepEntries
|
|
1431
|
+
.flatMap((stepEntry) => {
|
|
1432
|
+
if ((0, node_path_1.isAbsolute)(stepEntry)) {
|
|
1433
|
+
return [this.normalizeDiscoveredFilePath(stepEntry)];
|
|
1434
|
+
}
|
|
1435
|
+
return resolveBaseDirs.map((baseDir) => this.normalizeDiscoveredFilePath((0, node_path_1.resolve)(baseDir, stepEntry)));
|
|
1436
|
+
})
|
|
1437
|
+
.filter((candidateFile) => !this.isGeneratedWorkflowArtifact(candidateFile));
|
|
1438
|
+
const existingCandidates = await this.filterExistingFiles(candidateFiles);
|
|
1439
|
+
return Array.from(new Set(existingCandidates)).sort();
|
|
1440
|
+
}
|
|
1441
|
+
async buildStepsFunction({ workflowGeneratedDir, routeFileName = 'route.js', discoveredEntries, additionalStepSourceManifest, }) {
|
|
1275
1442
|
const stepsRouteDir = (0, node_path_1.join)(workflowGeneratedDir, 'step');
|
|
1276
1443
|
await (0, promises_1.mkdir)(stepsRouteDir, { recursive: true });
|
|
1277
1444
|
const discovered = discoveredEntries;
|
|
@@ -1285,22 +1452,36 @@ async function getNextBuilderDeferred() {
|
|
|
1285
1452
|
const serdeFiles = [...discovered.discoveredSerdeFiles].sort();
|
|
1286
1453
|
const stepFileSet = new Set(stepFiles);
|
|
1287
1454
|
const serdeOnlyFiles = serdeFiles.filter((file) => !stepFileSet.has(file));
|
|
1455
|
+
const additionalManifestStepFiles = additionalStepSourceManifest
|
|
1456
|
+
? await this.collectManifestStepSourceFiles(additionalStepSourceManifest)
|
|
1457
|
+
: [];
|
|
1458
|
+
const stepFilesWithManifestSources = Array.from(new Set([...stepFiles, ...additionalManifestStepFiles])).sort();
|
|
1459
|
+
const responseBuiltinsStepFilePath = await this.createResponseBuiltinsStepFile({
|
|
1460
|
+
stepsRouteDir,
|
|
1461
|
+
});
|
|
1462
|
+
const manifestStepFiles = Array.from(new Set([...stepFilesWithManifestSources, responseBuiltinsStepFilePath])).sort();
|
|
1463
|
+
const manifest = await this.createDeferredStepsManifest({
|
|
1464
|
+
stepFiles: manifestStepFiles,
|
|
1465
|
+
workflowFiles,
|
|
1466
|
+
serdeOnlyFiles,
|
|
1467
|
+
});
|
|
1468
|
+
const manifestDiscoveredStepFiles = await this.collectManifestStepSourceFiles(manifest);
|
|
1288
1469
|
// Copy all discovered step sources so they are transformed in step mode.
|
|
1289
1470
|
// Importing raw node_modules files directly can bypass loader transforms,
|
|
1290
1471
|
// which prevents step registrars from being emitted.
|
|
1291
|
-
const copiedStepSourceFiles =
|
|
1472
|
+
const copiedStepSourceFiles = Array.from(new Set([
|
|
1473
|
+
...stepFilesWithManifestSources,
|
|
1474
|
+
...manifestDiscoveredStepFiles,
|
|
1475
|
+
])).sort();
|
|
1292
1476
|
const copiedDiscoveredStepFiles = await this.copyDiscoveredStepFiles({
|
|
1293
1477
|
stepFiles: copiedStepSourceFiles,
|
|
1294
1478
|
stepsRouteDir,
|
|
1295
|
-
|
|
1296
|
-
const responseBuiltinsStepFilePath = await this.createResponseBuiltinsStepFile({
|
|
1297
|
-
stepsRouteDir,
|
|
1479
|
+
preserveFileNames: [(0, node_path_1.basename)(responseBuiltinsStepFilePath)],
|
|
1298
1480
|
});
|
|
1299
1481
|
const copiedStepFiles = [
|
|
1300
1482
|
responseBuiltinsStepFilePath,
|
|
1301
1483
|
...copiedDiscoveredStepFiles,
|
|
1302
1484
|
];
|
|
1303
|
-
const manifestStepFiles = Array.from(new Set([...stepFiles, responseBuiltinsStepFilePath])).sort();
|
|
1304
1485
|
const stepRouteFile = (0, node_path_1.join)(stepsRouteDir, routeFileName);
|
|
1305
1486
|
const copiedStepImports = copiedStepFiles
|
|
1306
1487
|
.map((copiedStepFile) => {
|
|
@@ -1331,11 +1512,6 @@ async function getNextBuilderDeferred() {
|
|
|
1331
1512
|
.filter(Boolean)
|
|
1332
1513
|
.join('\n');
|
|
1333
1514
|
await this.writeFileIfChanged(stepRouteFile, routeContents);
|
|
1334
|
-
const manifest = await this.createDeferredStepsManifest({
|
|
1335
|
-
stepFiles: manifestStepFiles,
|
|
1336
|
-
workflowFiles,
|
|
1337
|
-
serdeOnlyFiles,
|
|
1338
|
-
});
|
|
1339
1515
|
return {
|
|
1340
1516
|
context: undefined,
|
|
1341
1517
|
manifest,
|