@@ -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 ) => {
0 commit comments