@@ -12,6 +12,7 @@ import type {
1212} from "../ipc_types" ;
1313import fs from "node:fs" ;
1414import path from "node:path" ;
15+ import os from "node:os" ;
1516import { getDyadAppPath , getUserDataPath } from "../../paths/paths" ;
1617import { ChildProcess , spawn , execSync } from "node:child_process" ;
1718import git from "isomorphic-git" ;
@@ -398,6 +399,88 @@ python app.py
398399 }
399400}
400401
402+ /**
403+ * Execute a command, using a temporary script file for complex commands with shell operators.
404+ * This allows handling of multi-line commands, pipes, conditionals, and other shell features.
405+ */
406+ async function executeComplexCommand (
407+ command : string ,
408+ workingDir : string ,
409+ env : NodeJS . ProcessEnv
410+ ) : Promise < ChildProcess > {
411+ // Check if the command contains shell operators that require script execution
412+ const hasShellOperators = / ( & & | \| \| | s o u r c e | \| | ; | \$ \( | ` .* ` ) / . test ( command ) ;
413+
414+ if ( ! hasShellOperators ) {
415+ // For simple commands, use spawn directly
416+ logger . debug ( `Using spawn for simple command: ${ command } ` ) ;
417+ return spawn ( command , [ ] , {
418+ cwd : workingDir ,
419+ shell : true ,
420+ stdio : "pipe" ,
421+ detached : false ,
422+ env,
423+ } ) ;
424+ }
425+
426+ // For complex commands, create a temporary script file
427+ logger . debug ( `Using script file for complex command: ${ command } ` ) ;
428+
429+ try {
430+ // Create temporary script file
431+ const tempDir = os . tmpdir ( ) ;
432+ const scriptName = `dyad-script-${ Date . now ( ) } -${ Math . random ( ) . toString ( 36 ) . substr ( 2 , 9 ) } .sh` ;
433+ const scriptPath = path . join ( tempDir , scriptName ) ;
434+
435+ // Write the command to the script file
436+ const scriptContent = `#!/bin/bash
437+ # Temporary script generated by AliFullStack
438+ cd "${ workingDir } "
439+ ${ command }
440+ ` ;
441+
442+ await fsPromises . writeFile ( scriptPath , scriptContent , 'utf-8' ) ;
443+
444+ // Make the script executable
445+ await fsPromises . chmod ( scriptPath , 0o755 ) ;
446+
447+ logger . debug ( `Created temporary script: ${ scriptPath } ` ) ;
448+
449+ // Execute the script
450+ const process = spawn ( scriptPath , [ ] , {
451+ cwd : workingDir ,
452+ shell : true ,
453+ stdio : "pipe" ,
454+ detached : false ,
455+ env,
456+ } ) ;
457+
458+ // Clean up the script file after the process exits
459+ process . on ( 'exit' , async ( code , signal ) => {
460+ try {
461+ await fsPromises . unlink ( scriptPath ) ;
462+ logger . debug ( `Cleaned up temporary script: ${ scriptPath } ` ) ;
463+ } catch ( cleanupError ) {
464+ logger . warn ( `Failed to clean up temporary script ${ scriptPath } :` , cleanupError ) ;
465+ }
466+ } ) ;
467+
468+ process . on ( 'error' , async ( error ) => {
469+ try {
470+ await fsPromises . unlink ( scriptPath ) ;
471+ logger . debug ( `Cleaned up temporary script after error: ${ scriptPath } ` ) ;
472+ } catch ( cleanupError ) {
473+ logger . warn ( `Failed to clean up temporary script ${ scriptPath } :` , cleanupError ) ;
474+ }
475+ } ) ;
476+
477+ return process ;
478+ } catch ( error ) {
479+ logger . error ( `Failed to create temporary script for complex command:` , error ) ;
480+ throw error ;
481+ }
482+ }
483+
401484async function executeAppLocalNode ( {
402485 appPath,
403486 appId,
@@ -677,13 +760,17 @@ async function executeAppLocalNode({
677760
678761 logger . info ( `Final backend command: ${ backendCommand } ` ) ;
679762
680- const backendProcess = spawn ( backendCommand , [ ] , {
681- cwd : backendPath ,
682- shell : true ,
683- stdio : "pipe" ,
684- detached : false ,
685- env : getShellEnv ( ) ,
686- } ) ;
763+ // Check if the command contains shell operators that require script execution
764+ const hasShellOperators = / ( & & | \| \| | s o u r c e | \| | ; | \$ \( | ` .* ` ) / . test ( backendCommand ) ;
765+ const backendProcess = hasShellOperators
766+ ? await executeComplexCommand ( backendCommand , backendPath , getShellEnv ( ) )
767+ : spawn ( backendCommand , [ ] , {
768+ cwd : backendPath ,
769+ shell : true ,
770+ stdio : "pipe" ,
771+ detached : false ,
772+ env : getShellEnv ( ) ,
773+ } ) ;
687774
688775 if ( backendProcess . pid ) {
689776 const backendProcessId = processCounter . increment ( ) ;
@@ -710,7 +797,7 @@ async function executeAppLocalNode({
710797 } ) ;
711798
712799 // Also send backend startup logs to system messages for visibility
713- backendProcess . stdout ?. on ( "data" , ( data ) => {
800+ backendProcess . stdout ?. on ( "data" , ( data : Buffer ) => {
714801 const message = util . stripVTControlCharacters ( data . toString ( ) ) ;
715802 safeSend ( event . sender , "app:output" , {
716803 type : "stdout" ,
@@ -719,7 +806,7 @@ async function executeAppLocalNode({
719806 } ) ;
720807 } ) ;
721808
722- backendProcess . stderr ?. on ( "data" , ( data ) => {
809+ backendProcess . stderr ?. on ( "data" , ( data : Buffer ) => {
723810 const message = util . stripVTControlCharacters ( data . toString ( ) ) ;
724811 safeSend ( event . sender , "app:output" , {
725812 type : "stderr" ,
@@ -778,7 +865,7 @@ async function executeAppLocalNode({
778865 } ) ;
779866
780867 // Also send frontend startup logs to system messages for visibility
781- frontendProcess . stdout ?. on ( "data" , ( data ) => {
868+ frontendProcess . stdout ?. on ( "data" , ( data : Buffer ) => {
782869 const message = util . stripVTControlCharacters ( data . toString ( ) ) ;
783870 safeSend ( event . sender , "app:output" , {
784871 type : "stdout" ,
@@ -787,7 +874,7 @@ async function executeAppLocalNode({
787874 } ) ;
788875 } ) ;
789876
790- frontendProcess . stderr ?. on ( "data" , ( data ) => {
877+ frontendProcess . stderr ?. on ( "data" , ( data : Buffer ) => {
791878 const message = util . stripVTControlCharacters ( data . toString ( ) ) ;
792879 safeSend ( event . sender , "app:output" , {
793880 type : "stderr" ,
@@ -926,18 +1013,13 @@ async function executeAppLocalNode({
9261013 }
9271014 }
9281015
929- const spawnedProcess = spawn ( command , [ ] , {
930- cwd : workingDir ,
931- shell : true ,
932- stdio : "pipe" , // Ensure stdio is piped so we can capture output/errors and detect close
933- detached : false , // Ensure child process is attached to the main process lifecycle unless explicitly backgrounded
934- } ) ;
1016+ const spawnedProcess = await executeComplexCommand ( command , workingDir , getShellEnv ( ) ) ;
9351017
9361018 // Check if process spawned correctly
9371019 if ( ! spawnedProcess . pid ) {
9381020 // Attempt to capture any immediate errors if possible
9391021 let errorOutput = "" ;
940- spawnedProcess . stderr ?. on ( "data" , ( data ) => ( errorOutput += data ) ) ;
1022+ spawnedProcess . stderr ?. on ( "data" , ( data : Buffer ) => ( errorOutput += data ) ) ;
9411023 await new Promise ( ( resolve ) => spawnedProcess . on ( "error" , resolve ) ) ; // Wait for error event
9421024 throw new Error (
9431025 `Failed to spawn process for app ${ appId } . Error: ${
@@ -1005,7 +1087,7 @@ function listenToProcess({
10051087 } , 15000 ) ; // 15 seconds
10061088
10071089 // Log output
1008- spawnedProcess . stdout ?. on ( "data" , async ( data ) => {
1090+ spawnedProcess . stdout ?. on ( "data" , async ( data : Buffer ) => {
10091091 const rawMessage = util . stripVTControlCharacters ( data . toString ( ) ) ;
10101092 const message = rawMessage ; // Remove prefix since addTerminalOutput handles it
10111093
@@ -1630,22 +1712,24 @@ export function registerAppHandlers() {
16301712 logger . warn ( `App ${ app . id } created without Git repository` ) ;
16311713 }
16321714
1633- // Start autonomous development process
1634- try {
1635- logger . info ( `Starting autonomous development for app ${ app . id } ` ) ;
1636- const requirements : string [ ] = [ ] ; // Requirements will be gathered during development
1637- developmentOrchestrator . startAutonomousDevelopment (
1638- app . id ,
1639- "react" , // default frontend framework
1640- params . selectedBackendFramework || undefined ,
1641- requirements
1642- ) ;
1643- logger . info ( `Autonomous development started for app ${ app . id } ` ) ;
1644- } catch ( devError ) {
1645- logger . error ( `Failed to start autonomous development for app ${ app . id } :` , devError ) ;
1646- // Don't fail app creation if autonomous development fails to start
1647- logger . warn ( `App ${ app . id } created but autonomous development failed to start` ) ;
1648- }
1715+ // Start autonomous development process asynchronously to prevent UI blocking
1716+ setTimeout ( ( ) => {
1717+ try {
1718+ logger . info ( `Starting autonomous development for app ${ app . id } ` ) ;
1719+ const requirements : string [ ] = [ ] ; // Requirements will be gathered during development
1720+ developmentOrchestrator . startAutonomousDevelopment (
1721+ app . id ,
1722+ "react" , // default frontend framework
1723+ params . selectedBackendFramework || undefined ,
1724+ requirements
1725+ ) ;
1726+ logger . info ( `Autonomous development started for app ${ app . id } ` ) ;
1727+ } catch ( devError ) {
1728+ logger . error ( `Failed to start autonomous development for app ${ app . id } :` , devError ) ;
1729+ // Don't fail app creation if autonomous development fails to start
1730+ logger . warn ( `App ${ app . id } created but autonomous development failed to start` ) ;
1731+ }
1732+ } , 0 ) ;
16491733
16501734 return { app, chatId : chat . id } ;
16511735 } ,
0 commit comments