Skip to content

Commit 9dbf3c1

Browse files
committed
Release v0.0.48: Fixed backend command execution PATH issues and updated title bar logo/title
1 parent a029723 commit 9dbf3c1

File tree

3 files changed

+129
-32
lines changed

3 files changed

+129
-32
lines changed

src/app/TitleBar.tsx

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -77,31 +77,39 @@ export const TitleBar = () => {
7777

7878
return (
7979
<>
80-
<div className="@container z-11 w-full h-11 bg-(--sidebar) absolute top-0 left-0 app-region-drag flex items-center">
81-
<div className={`${showWindowControls ? "pl-2" : "pl-18"}`}></div>
82-
83-
<img src={logo} alt="AliFullStack Logo" className="w-6 h-6 mr-0.5" />
84-
<Button
85-
data-testid="title-bar-app-name-button"
86-
variant="outline"
87-
size="sm"
88-
className={`hidden @2xl:block no-app-region-drag text-xs max-w-38 truncate font-medium ${
89-
selectedApp ? "cursor-pointer" : ""
90-
}`}
91-
onClick={handleAppClick}
92-
>
93-
{displayText}
94-
</Button>
95-
{isDyadPro && <DyadProButton isAliFullStackProEnabled={isAliFullStackProEnabled} />}
96-
97-
{/* Preview Header */}
98-
{location.pathname === "/chat" && (
99-
<div className="flex-1 flex justify-end">
100-
<PreviewHeader />
101-
</div>
102-
)}
103-
104-
{showWindowControls && <WindowsControls />}
80+
<div className="@container z-11 w-full h-11 bg-(--sidebar) absolute top-0 left-0 app-region-drag flex items-center justify-between">
81+
<div className="flex items-center">
82+
<div className={`${showWindowControls ? "pl-2" : "pl-18"}`}></div>
83+
<img src={logo} alt="AliFullStack Logo" className="w-[75%] h-[75%]" />
84+
<Button
85+
data-testid="title-bar-app-name-button"
86+
variant="outline"
87+
size="sm"
88+
className={`hidden @2xl:block no-app-region-drag text-xs max-w-38 truncate font-medium ${
89+
selectedApp ? "cursor-pointer" : ""
90+
}`}
91+
onClick={handleAppClick}
92+
>
93+
{displayText}
94+
</Button>
95+
</div>
96+
97+
<div className="flex-1 flex justify-center items-center">
98+
<span className="text-lg font-semibold no-app-region-drag">AliFullStack</span>
99+
</div>
100+
101+
<div className="flex items-center">
102+
{isDyadPro && <DyadProButton isAliFullStackProEnabled={isAliFullStackProEnabled} />}
103+
104+
{/* Preview Header */}
105+
{location.pathname === "/chat" && (
106+
<div className="flex justify-end">
107+
<PreviewHeader />
108+
</div>
109+
)}
110+
111+
{showWindowControls && <WindowsControls />}
112+
</div>
105113
</div>
106114

107115
<DyadProSuccessDialog

src/ipc/handlers/app_handlers.ts

Lines changed: 94 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import type {
1313
import fs from "node:fs";
1414
import path from "node:path";
1515
import { getDyadAppPath, getUserDataPath } from "../../paths/paths";
16-
import { ChildProcess, spawn } from "node:child_process";
16+
import { ChildProcess, spawn, execSync } from "node:child_process";
1717
import git from "isomorphic-git";
1818
import { promises as fsPromises } from "node:fs";
1919

@@ -30,7 +30,82 @@ import {
3030
import { getEnvVar } from "../utils/read_env";
3131
import { 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

35110
import killPort from "kill-port";
36111
import 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}`);

src/ipc/handlers/node_handlers.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { ipcMain } from "electron";
22
import { execSync } from "child_process";
33
import { platform, arch } from "os";
44
import { NodeSystemInfo } from "../ipc_types";
5-
import fixPath from "fix-path";
5+
import { getExtendedPath } from "./app_handlers";
66
import { runShellCommand } from "../utils/runShellCommand";
77
import log from "electron-log";
88

@@ -50,7 +50,7 @@ export function registerNodeHandlers() {
5050
}).trim();
5151
process.env.PATH = newPath;
5252
} else {
53-
fixPath();
53+
getExtendedPath();
5454
}
5555
logger.debug("Reloaded env path, now:", process.env.PATH);
5656
});

0 commit comments

Comments
 (0)