@@ -44,9 +44,22 @@ export function createDevTests(config?: DevTestConfig) {
4444 ) ;
4545 const testWorkflowFile = finalConfig . testWorkflowFile ?? '3_streams.ts' ;
4646 const workflowsDir = finalConfig . workflowsDir ?? 'workflows' ;
47- const supportsDeferredStepCopies = generatedStep . includes (
47+ const usesDeferredBuilder = generatedStep . includes (
4848 path . join ( '.well-known' , 'workflow' , 'v1' , 'step' , 'route.js' )
4949 ) ;
50+ const workflowManifestPath = path . join (
51+ appPath ,
52+ 'app/.well-known/workflow/v1/manifest.json'
53+ ) ;
54+ const readManifestStepFunctionNames = async ( ) : Promise < string [ ] > => {
55+ const manifestJson = await fs . readFile ( workflowManifestPath , 'utf8' ) ;
56+ const manifest = JSON . parse ( manifestJson ) as {
57+ steps ?: Record < string , Record < string , unknown > > ;
58+ } ;
59+ return Object . values ( manifest . steps || { } ) . flatMap ( ( entry ) =>
60+ Object . keys ( entry )
61+ ) ;
62+ } ;
5063 const restoreFiles : Array < { path : string ; content : string } > = [ ] ;
5164
5265 const fetchWithTimeout = ( pathname : string ) => {
@@ -192,10 +205,6 @@ export async function myNewStep() {
192205`
193206 ) ;
194207 restoreFiles . push ( { path : stepFile , content } ) ;
195- const copiedStepDir = path . join (
196- path . dirname ( generatedStep ) ,
197- '__workflow_step_files__'
198- ) ;
199208
200209 await pollUntil ( {
201210 description : 'generated step outputs to include myNewStep' ,
@@ -205,28 +214,20 @@ export async function myNewStep() {
205214 return ;
206215 }
207216
208- const copiedStepFileNames = await fs . readdir ( copiedStepDir ) ;
209- const copiedStepContents = await Promise . all (
210- copiedStepFileNames . map ( async ( copiedStepFileName ) => {
211- const copiedStepFilePath = path . join (
212- copiedStepDir ,
213- copiedStepFileName
214- ) ;
215- const copiedStepStats = await fs . stat ( copiedStepFilePath ) ;
216- if ( ! copiedStepStats . isFile ( ) ) {
217- return '' ;
218- }
219- return await fs . readFile ( copiedStepFilePath , 'utf8' ) ;
220- } )
221- ) ;
222- expect (
223- copiedStepContents . some ( ( content ) => content . includes ( 'myNewStep' ) )
224- ) . toBe ( true ) ;
217+ // The deferred builder regenerates manifest.json on every rebuild.
218+ // Check the manifest for the new step function name.
219+ if ( usesDeferredBuilder ) {
220+ const manifestFunctionNames = await readManifestStepFunctionNames ( ) ;
221+ expect ( manifestFunctionNames ) . toContain ( 'myNewStep' ) ;
222+ return ;
223+ }
224+
225+ throw new Error ( 'myNewStep not found in generated step outputs' ) ;
225226 } ,
226227 } ) ;
227228 } ) ;
228229
229- test . skipIf ( ! supportsDeferredStepCopies ) (
230+ test . skipIf ( ! usesDeferredBuilder ) (
230231 'should rebuild on imported step dependency change' ,
231232 { timeout : 60_000 } ,
232233 async ( ) => {
@@ -250,36 +251,14 @@ export async function ${marker}() {
250251 ) ;
251252 restoreFiles . push ( { path : importedStepFile , content } ) ;
252253
253- const copiedStepDir = path . join (
254- path . dirname ( generatedStep ) ,
255- '__workflow_step_files__'
256- ) ;
257-
258254 await pollUntil ( {
259255 description :
260- 'copied deferred step files to include imported step hot-reload marker' ,
256+ 'manifest.json to include imported step hot-reload marker' ,
261257 timeoutMs : 50_000 ,
262258 check : async ( ) => {
263259 await triggerWorkflowRun ( 'importedStepOnlyWorkflow' ) ;
264- const copiedStepFileNames = await fs . readdir ( copiedStepDir ) ;
265- const copiedStepContents = await Promise . all (
266- copiedStepFileNames . map ( async ( copiedStepFileName ) => {
267- const copiedStepFilePath = path . join (
268- copiedStepDir ,
269- copiedStepFileName
270- ) ;
271- const copiedStepStats = await fs . stat ( copiedStepFilePath ) ;
272- if ( ! copiedStepStats . isFile ( ) ) {
273- return '' ;
274- }
275- return await fs . readFile ( copiedStepFilePath , 'utf8' ) ;
276- } )
277- ) ;
278- expect (
279- copiedStepContents . some ( ( copiedStepContent ) =>
280- copiedStepContent . includes ( marker )
281- )
282- ) . toBe ( true ) ;
260+ const manifestFunctionNames = await readManifestStepFunctionNames ( ) ;
261+ expect ( manifestFunctionNames ) . toContain ( marker ) ;
283262 } ,
284263 } ) ;
285264 }
@@ -330,7 +309,7 @@ ${apiFileContent}`
330309 }
331310 ) ;
332311
333- test . skipIf ( ! supportsDeferredStepCopies ) (
312+ test . skipIf ( ! usesDeferredBuilder ) (
334313 'should include steps discovered from workflow imports' ,
335314 { timeout : 30_000 } ,
336315 async ( ) => {
@@ -378,57 +357,28 @@ export async function discoveredViaWorkflowStep() {
378357${ apiFileContent } `
379358 ) ;
380359
381- const copiedStepDir = path . join (
382- path . dirname ( generatedStep ) ,
383- '__workflow_step_files__'
384- ) ;
385-
386360 await pollUntil ( {
387361 description :
388- 'copied deferred step files to include discoveredViaWorkflowStep' ,
362+ 'manifest.json to include discoveredViaWorkflowStep after discovery ' ,
389363 timeoutMs : 25_000 ,
390364 check : async ( ) => {
391365 await fetchWithTimeout ( '/api/chat' ) ;
392- const copiedStepFileNames = await fs . readdir ( copiedStepDir ) ;
393- const copiedStepContents = await Promise . all (
394- copiedStepFileNames . map ( async ( copiedStepFileName ) => {
395- const copiedStepFilePath = path . join (
396- copiedStepDir ,
397- copiedStepFileName
398- ) ;
399- const copiedStepStats = await fs . stat ( copiedStepFilePath ) ;
400- if ( ! copiedStepStats . isFile ( ) ) {
401- return '' ;
402- }
403- return await fs . readFile ( copiedStepFilePath , 'utf8' ) ;
404- } )
366+ const manifestFunctionNames = await readManifestStepFunctionNames ( ) ;
367+ expect ( manifestFunctionNames ) . toContain (
368+ 'discoveredViaWorkflowStep'
405369 ) ;
406- expect (
407- copiedStepContents . some ( ( content ) =>
408- content . includes ( 'discoveredViaWorkflowStep' )
409- )
410- ) . toBe ( true ) ;
411370 } ,
412371 } ) ;
413372 }
414373 ) ;
415374
416- test . skipIf ( ! supportsDeferredStepCopies ) (
417- 'should copy package step sources discovered via manifest entries' ,
375+ test . skipIf ( ! usesDeferredBuilder ) (
376+ 'should reference package step sources discovered via manifest entries' ,
418377 { timeout : 30_000 } ,
419378 async ( ) => {
420- const workflowManifestPath = path . join (
421- appPath ,
422- 'app/.well-known/workflow/v1/manifest.json'
423- ) ;
424- const copiedStepDir = path . join (
425- path . dirname ( generatedStep ) ,
426- '__workflow_step_files__'
427- ) ;
428-
429379 await pollUntil ( {
430380 description :
431- 'copied deferred step files to include @workflow/ai package steps' ,
381+ 'generated step route to reference @workflow/ai package steps' ,
432382 timeoutMs : 25_000 ,
433383 check : async ( ) => {
434384 await fetchWithTimeout ( '/api/chat' ) ;
@@ -446,24 +396,13 @@ ${apiFileContent}`
446396 )
447397 ) . toBe ( true ) ;
448398
449- const copiedStepFileNames = await fs . readdir ( copiedStepDir ) ;
450- const copiedStepContents = await Promise . all (
451- copiedStepFileNames . map ( async ( copiedStepFileName ) => {
452- const copiedStepFilePath = path . join (
453- copiedStepDir ,
454- copiedStepFileName
455- ) ;
456- const copiedStepStats = await fs . stat ( copiedStepFilePath ) ;
457- if ( ! copiedStepStats . isFile ( ) ) {
458- return '' ;
459- }
460- return await fs . readFile ( copiedStepFilePath , 'utf8' ) ;
461- } )
462- ) ;
399+ // Package step sources are imported directly (not copied). Verify
400+ // the generated step route imports the @workflow /ai package or
401+ // otherwise references `durable-agent` via its resolved path.
402+ const stepRouteContent = await fs . readFile ( generatedStep , 'utf8' ) ;
463403 expect (
464- copiedStepContents . some ( ( content ) =>
465- content . includes ( 'async function closeStream' )
466- )
404+ stepRouteContent . includes ( '@workflow/ai' ) ||
405+ stepRouteContent . includes ( 'durable-agent' )
467406 ) . toBe ( true ) ;
468407 } ,
469408 } ) ;
0 commit comments