@@ -13,7 +13,7 @@ import type {
1313import fs from "node:fs" ;
1414import path from "node:path" ;
1515import { getDyadAppPath , getUserDataPath } from "../../paths/paths" ;
16- import { ChildProcess , spawn } from "node:child_process" ;
16+ import { ChildProcess , spawn , execSync } from "node:child_process" ;
1717import git from "isomorphic-git" ;
1818import { promises as fsPromises } from "node:fs" ;
1919
@@ -30,7 +30,82 @@ import {
3030import { getEnvVar } from "../utils/read_env" ;
3131import { readSettings } from "../../main/settings" ;
3232
33- import fixPath from "fix-path" ;
33+ // Cache for the dynamically captured PATH
34+ let cachedExtendedPath : string | undefined ;
35+ let cachedShellEnv : NodeJS . ProcessEnv | undefined ;
36+
37+ /**
38+ * Dynamically captures the PATH environment variable from a login shell.
39+ * This ensures that tools installed via pyenv, conda, or other environment managers
40+ * are accessible to child processes spawned by the Electron app.
41+ *
42+ * The result is cached to avoid repeatedly spawning a login shell.
43+ *
44+ * @returns The PATH string from the user's login shell.
45+ */
46+ export function getExtendedPath ( ) : string {
47+ if ( cachedExtendedPath ) {
48+ return cachedExtendedPath ;
49+ }
50+
51+ try {
52+ // Spawn a login shell and get its environment
53+ // For macOS, 'login -il <shell>' is used to get a login shell.
54+ // For Linux, 'bash -lc "env"' or 'zsh -lc "env"' might be used.
55+ // We'll try to detect the user's default shell.
56+ const shell = process . env . SHELL || "/bin/bash" ;
57+ let command : string ;
58+
59+ if ( process . platform === "darwin" ) {
60+ // On macOS, a login shell is crucial for tools like Homebrew, pyenv, nvm
61+ command = `login -il ${ shell } -c "env"` ;
62+ } else {
63+ // For Linux/Windows, a non-login shell might be sufficient, but -l ensures full environment
64+ command = `${ shell } -lc "env"` ;
65+ }
66+
67+ logger . info ( `Attempting to capture PATH from login shell using command: ${ command } ` ) ;
68+ const output = execSync ( command , { encoding : "utf8" , maxBuffer : 10 * 1024 * 1024 } ) ; // 10MB buffer
69+
70+ const env : NodeJS . ProcessEnv = { } ;
71+ output . split ( "\n" ) . forEach ( ( line ) => {
72+ const parts = line . split ( "=" ) ;
73+ if ( parts . length >= 2 ) {
74+ const key = parts [ 0 ] ;
75+ const value = parts . slice ( 1 ) . join ( "=" ) ;
76+ env [ key ] = value ;
77+ }
78+ } ) ;
79+
80+ cachedShellEnv = env ;
81+ cachedExtendedPath = env . PATH || process . env . PATH || "" ;
82+ logger . info ( `Successfully captured PATH from login shell: ${ cachedExtendedPath } ` ) ;
83+ return cachedExtendedPath ;
84+ } catch ( error ) {
85+ logger . error ( `Failed to capture PATH from login shell: ${ error } . Falling back to current process PATH.` ) ;
86+ cachedExtendedPath = process . env . PATH || "" ;
87+ cachedShellEnv = process . env ; // Fallback to current process env
88+ return cachedExtendedPath ;
89+ }
90+ }
91+
92+ /**
93+ * Dynamically captures the complete environment variables from a login shell.
94+ * This ensures that all environment variables (not just PATH) set by tools
95+ * like pyenv, conda, or other environment managers are accessible.
96+ *
97+ * The result is cached to avoid repeatedly spawning a login shell.
98+ *
99+ * @returns The complete environment variables from the user's login shell.
100+ */
101+ function getShellEnv ( ) : NodeJS . ProcessEnv {
102+ if ( cachedShellEnv ) {
103+ return cachedShellEnv ;
104+ }
105+ // Call getExtendedPath to populate cachedShellEnv
106+ getExtendedPath ( ) ;
107+ return cachedShellEnv || process . env ;
108+ }
34109
35110import killPort from "kill-port" ;
36111import util from "util" ;
@@ -90,7 +165,7 @@ let proxyWorker: Worker | null = null;
90165
91166// Needed, otherwise electron in MacOS/Linux will not be able
92167// to find node/pnpm.
93- fixPath ( ) ;
168+ getExtendedPath ( ) ;
94169
95170/**
96171 * Check if a port is available
@@ -475,6 +550,7 @@ async function executeAppLocalNode({
475550 cwd : strategy . cwd ,
476551 shell : true ,
477552 stdio : "pipe" ,
553+ env : getShellEnv ( ) ,
478554 } ) ;
479555
480556 let fixOutput = "" ;
@@ -606,6 +682,7 @@ async function executeAppLocalNode({
606682 shell : true ,
607683 stdio : "pipe" ,
608684 detached : false ,
685+ env : getShellEnv ( ) ,
609686 } ) ;
610687
611688 if ( backendProcess . pid ) {
@@ -672,6 +749,7 @@ async function executeAppLocalNode({
672749 shell : true ,
673750 stdio : "pipe" ,
674751 detached : false ,
752+ env : getShellEnv ( ) ,
675753 } ) ;
676754
677755 if ( frontendProcess . pid ) {
@@ -1182,7 +1260,7 @@ async function executeAppInDocker({
11821260 // First, check if Docker is available
11831261 try {
11841262 await new Promise < void > ( ( resolve , reject ) => {
1185- const checkDocker = spawn ( "docker" , [ "--version" ] , { stdio : "pipe" } ) ;
1263+ const checkDocker = spawn ( "docker" , [ "--version" ] , { stdio : "pipe" , env : getShellEnv ( ) } ) ;
11861264 checkDocker . on ( "close" , ( code ) => {
11871265 if ( code === 0 ) {
11881266 resolve ( ) ;
@@ -1205,10 +1283,12 @@ async function executeAppInDocker({
12051283 await new Promise < void > ( ( resolve ) => {
12061284 const stopContainer = spawn ( "docker" , [ "stop" , containerName ] , {
12071285 stdio : "pipe" ,
1286+ env : getShellEnv ( ) ,
12081287 } ) ;
12091288 stopContainer . on ( "close" , ( ) => {
12101289 const removeContainer = spawn ( "docker" , [ "rm" , containerName ] , {
12111290 stdio : "pipe" ,
1291+ env : getShellEnv ( ) ,
12121292 } ) ;
12131293 removeContainer . on ( "close" , ( ) => resolve ( ) ) ;
12141294 removeContainer . on ( "error" , ( ) => resolve ( ) ) ; // Container might not exist
@@ -1245,6 +1325,7 @@ RUN npm install -g pnpm
12451325 {
12461326 cwd : appPath ,
12471327 stdio : "pipe" ,
1328+ env : getShellEnv ( ) ,
12481329 } ,
12491330 ) ;
12501331
@@ -1315,6 +1396,7 @@ RUN npm install -g pnpm
13151396 {
13161397 stdio : "pipe" ,
13171398 detached : false ,
1399+ env : getShellEnv ( ) ,
13181400 } ,
13191401 ) ;
13201402
@@ -1364,6 +1446,7 @@ async function stopDockerContainersOnPort(port: number): Promise<void> {
13641446 // List container IDs that publish the given port
13651447 const list = spawn ( "docker" , [ "ps" , "--filter" , `publish=${ port } ` , "-q" ] , {
13661448 stdio : "pipe" ,
1449+ env : getShellEnv ( ) ,
13671450 } ) ;
13681451
13691452 let stdout = "" ;
@@ -1390,7 +1473,7 @@ async function stopDockerContainersOnPort(port: number): Promise<void> {
13901473 containerIds . map (
13911474 ( id ) =>
13921475 new Promise < void > ( ( resolve ) => {
1393- const stop = spawn ( "docker" , [ "stop" , id ] , { stdio : "pipe" } ) ;
1476+ const stop = spawn ( "docker" , [ "stop" , id ] , { stdio : "pipe" , env : getShellEnv ( ) } ) ;
13941477 stop . on ( "close" , ( ) => resolve ( ) ) ;
13951478 stop . on ( "error" , ( ) => resolve ( ) ) ;
13961479 } ) ,
@@ -2717,6 +2800,7 @@ async function installDependencies(projectPath: string, framework: string) {
27172800 cwd : projectPath ,
27182801 shell : true ,
27192802 stdio : "pipe" ,
2803+ env : getShellEnv ( ) ,
27202804 } ) ;
27212805
27222806 logger . info ( `Running install command: ${ installCommand } in ${ projectPath } ` ) ;
@@ -2786,6 +2870,7 @@ async function installDependenciesAuto(projectPath: string, componentType: strin
27862870 cwd : projectPath ,
27872871 shell : true ,
27882872 stdio : "pipe" ,
2873+ env : getShellEnv ( ) ,
27892874 } ) ;
27902875
27912876 logger . info ( `Installing dependencies with: ${ installCommand } in ${ projectPath } ` ) ;
@@ -2835,6 +2920,7 @@ async function installNodejsDependenciesRobust(projectPath: string, componentTyp
28352920 cwd : projectPath ,
28362921 shell : true ,
28372922 stdio : "pipe" ,
2923+ env : getShellEnv ( ) ,
28382924 } ) ;
28392925
28402926 let installOutput = "" ;
@@ -2894,6 +2980,7 @@ async function installNodejsDependenciesRobust(projectPath: string, componentTyp
28942980 cwd : projectPath ,
28952981 shell : true ,
28962982 stdio : "pipe" ,
2983+ env : getShellEnv ( ) ,
28972984 } ) ;
28982985
28992986 cleanupProcess . on ( "close" , ( code ) => {
@@ -2926,6 +3013,7 @@ async function installSpecificPackage(projectPath: string, packageName: string):
29263013 cwd : projectPath ,
29273014 shell : true ,
29283015 stdio : "pipe" ,
3016+ env : getShellEnv ( ) ,
29293017 } ) ;
29303018
29313019 logger . info ( `Installing specific package: ${ installCommand } in ${ projectPath } ` ) ;
@@ -2967,6 +3055,7 @@ async function installDependenciesAutoFallback(projectPath: string, componentTyp
29673055 cwd : projectPath ,
29683056 shell : true ,
29693057 stdio : "pipe" ,
3058+ env : getShellEnv ( ) ,
29703059 } ) ;
29713060
29723061 logger . info ( `Fallback auto-installing dependencies with: ${ installCommand } in ${ projectPath } ` ) ;
0 commit comments