Skip to content

Commit a2c0c7e

Browse files
[ci] Fix nextjs lazy disocvery impacting e2e tests, disable experimental DurableAgent tests (#1400)
1 parent f52afe7 commit a2c0c7e

5 files changed

Lines changed: 87 additions & 6 deletions

File tree

‎.changeset/strong-dryers-begin.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@workflow/core": patch
3+
"@workflow/next": patch
4+
---
5+
6+
Seed lazy workflow file discovery in NextJS. Require workflow definitions to be in manifest for Vercel environments.

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"test": "turbo test",
3434
"clean": "turbo clean",
3535
"typecheck": "turbo typecheck",
36-
"test:e2e": "vitest run packages/core/e2e/e2e.test.ts packages/core/e2e/e2e-agent.test.ts",
36+
"test:e2e": "vitest run packages/core/e2e/e2e.test.ts",
3737
"test:e2e:nextjs-webpack:staged": "node scripts/test-staged-nextjs-webpack.mjs",
3838
"test:docs": "pnpm --filter @workflow/docs-typecheck test:docs",
3939
"bench": "vitest bench packages/core/e2e/bench.bench.ts",

‎packages/core/e2e/utils.ts‎

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -388,9 +388,23 @@ export async function getWorkflowMetadata(
388388
await sleep(manifestRetryIntervalMs);
389389
}
390390

391-
// Deferred discovery can lag behind manifest publication in staged/out-of-
392-
// monorepo tests. Fall back to the deterministic workflow ID format used by
393-
// the transform so tests can continue exercising runtime behavior.
391+
// For Vercel deployments, the workflow must be in the manifest. A missing
392+
// workflow means the deployment's build didn't include it, so a fallback ID
393+
// would just create a run that never executes and times out silently.
394+
if (!isLocalDeployment()) {
395+
const availableWorkflows = Object.entries(manifest.workflows)
396+
.flatMap(([file, fns]) => Object.keys(fns).map((fn) => `${file}:${fn}`))
397+
.join(', ');
398+
throw new Error(
399+
`Workflow "${workflowFn}" not found in manifest for "${workflowFile}" ` +
400+
`after ${manifestRetryTimeoutMs}ms. The deployment may not include this workflow. ` +
401+
`Available workflows: ${availableWorkflows || '(none)'}`
402+
);
403+
}
404+
405+
// For local development, fall back to the deterministic workflow ID format
406+
// used by the transform. Deferred discovery can lag behind manifest
407+
// publication in staged/out-of-monorepo tests.
394408
const fallbackWorkflowId = getFallbackWorkflowId(workflowFile, workflowFn);
395409
console.warn(
396410
`Workflow "${workflowFn}" not found in manifest for "${workflowFile}" after ${manifestRetryTimeoutMs}ms; ` +

‎packages/next/src/builder-deferred.ts‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,9 +661,54 @@ export async function getNextBuilderDeferred() {
661661
}
662662

663663
await this.loadWorkflowsCache();
664+
665+
// The cache only contains files discovered in previous builds. On the
666+
// first build after adding new workflow files, those files won't be in
667+
// the cache. In production builds (`watch: false`), the loader's socket
668+
// notifications arrive too late — `scheduleDeferredRebuild()` is a no-op
669+
// so newly discovered files never trigger a rebuild.
670+
//
671+
// To fix this, eagerly scan the dirs for files with workflow/step
672+
// directives and seed the discovered sets before the build runs.
673+
await this.seedDiscoveredFilesFromDirs();
674+
664675
this.cacheInitialized = true;
665676
}
666677

678+
/**
679+
* Scans the configured dirs for files containing `'use workflow'` or
680+
* `'use step'` directives and adds them to the discovered sets. This
681+
* ensures new workflow files are included in the very first production
682+
* build, without waiting for the loader's async socket notifications.
683+
*/
684+
private async seedDiscoveredFilesFromDirs(): Promise<void> {
685+
// Use the base builder's file scanning (which searches all TS/JS files
686+
// in dirs, not just entrypoints). Call the base class version directly
687+
// to bypass the deferred builder's entrypoint-only filter.
688+
const allInputFiles = await this.getAllDirFiles();
689+
690+
await Promise.all(
691+
allInputFiles.map(async (filePath) => {
692+
try {
693+
const source = await readFile(filePath, 'utf-8');
694+
const patterns = detectWorkflowPatterns(source);
695+
696+
if (patterns.hasUseWorkflow) {
697+
this.discoveredWorkflowFiles.add(filePath);
698+
}
699+
if (patterns.hasUseStep) {
700+
this.discoveredStepFiles.add(filePath);
701+
}
702+
if (patterns.hasSerde && !isWorkflowSdkFile(filePath)) {
703+
this.discoveredSerdeFiles.add(filePath);
704+
}
705+
} catch {
706+
// File may have been deleted between glob and read — skip it.
707+
}
708+
})
709+
);
710+
}
711+
667712
private getDistDir(): string {
668713
return (this.config as { distDir?: string }).distDir || '.next';
669714
}
@@ -1000,6 +1045,15 @@ export async function getNextBuilderDeferred() {
10001045
);
10011046
}
10021047

1048+
/**
1049+
* Returns ALL files from the configured dirs without filtering to
1050+
* entrypoints. Used by `seedDiscoveredFilesFromDirs` to scan for
1051+
* workflow/step directives on first build.
1052+
*/
1053+
private async getAllDirFiles(): Promise<string[]> {
1054+
return super.getInputFiles();
1055+
}
1056+
10031057
protected async getInputFiles(): Promise<string[]> {
10041058
const inputFiles = await super.getInputFiles();
10051059
return inputFiles.filter((item) => {

‎packages/next/src/index.ts‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,15 @@ export function withWorkflow(
8686
const NextBuilder = await getNextBuilder(nextVersion);
8787
return new NextBuilder({
8888
watch: shouldWatch,
89-
// discover workflows from pages/app entries
90-
dirs: ['pages', 'app', 'src/pages', 'src/app'],
89+
// discover workflows from pages/app entries and common workflow dirs
90+
dirs: [
91+
'pages',
92+
'app',
93+
'src/pages',
94+
'src/app',
95+
'workflows',
96+
'src/workflows',
97+
],
9198
workingDir: process.cwd(),
9299
distDir: nextConfig.distDir || '.next',
93100
buildTarget: 'next',

0 commit comments

Comments
 (0)