提交 f2b157b8 authored 作者: Will Chen's avatar Will Chen

Simplified setup flow: user installs node.js; pnpm is configured

上级 ffdf3e1c
import { ipcMain } from "electron"; import { ipcMain, app } from "electron";
import { spawn } from "child_process"; import { spawn } from "child_process";
import { platform } from "os"; import { platform, arch } from "os";
import { InstallNodeResult } from "../ipc_types"; import { NodeSystemInfo } from "../ipc_types";
type ShellResult =
| {
success: true;
output: string;
}
| {
success: false;
errorMessage: string;
};
function runShell(command: string): Promise<ShellResult> {
return new Promise((resolve) => {
let stdout = "";
let stderr = "";
const process = spawn(command, {
shell: true,
stdio: ["ignore", "pipe", "pipe"], // ignore stdin, pipe stdout/stderr
});
process.stdout?.on("data", (data) => {
stdout += data.toString();
});
process.stderr?.on("data", (data) => {
stderr += data.toString();
});
process.on("error", (error) => {
console.error(`Error executing command "${command}":`, error.message);
resolve({ success: false, errorMessage: error.message });
});
process.on("close", (code) => {
if (code === 0) {
resolve({ success: true, output: stdout.trim() });
} else {
const errorMessage =
stderr.trim() || `Command failed with code ${code}`;
console.error(
`Command "${command}" failed with code ${code}: ${stderr.trim()}`
);
resolve({ success: false, errorMessage });
}
});
});
}
function checkCommandExists(command: string): Promise<string | null> { function checkCommandExists(command: string): Promise<string | null> {
return new Promise((resolve) => { return new Promise((resolve) => {
let output = ""; let output = "";
const process = spawn(command, ["--version"], { const process = spawn(command, {
shell: true, shell: true,
stdio: ["ignore", "pipe", "pipe"], // ignore stdin, pipe stdout/stderr stdio: ["ignore", "pipe", "pipe"], // ignore stdin, pipe stdout/stderr
}); });
...@@ -87,64 +41,35 @@ function checkCommandExists(command: string): Promise<string | null> { ...@@ -87,64 +41,35 @@ function checkCommandExists(command: string): Promise<string | null> {
} }
export function registerNodeHandlers() { export function registerNodeHandlers() {
ipcMain.handle( ipcMain.handle("nodejs-status", async (): Promise<NodeSystemInfo> => {
"nodejs-status", // Run checks in parallel
async (): Promise<{ const [nodeVersion, pnpmVersion] = await Promise.all([
nodeVersion: string | null; checkCommandExists("node --version"),
pnpmVersion: string | null; // First, check if pnpm is installed.
}> => { // If not, try to install it using corepack.
// Run checks in parallel // If both fail, then pnpm is not available.
const [nodeVersion, pnpmVersion] = await Promise.all([ checkCommandExists(
checkCommandExists("node"), "pnpm --version || corepack enable pnpm && pnpm --version"
checkCommandExists("pnpm"), ),
]); ]);
return { nodeVersion, pnpmVersion }; // Default to mac download url.
} let nodeDownloadUrl = "https://nodejs.org/dist/v22.14.0/node-v22.14.0.pkg";
); if (platform() == "win32") {
if (arch() === "arm64" || arch() === "arm") {
ipcMain.handle("install-node", async (): Promise<InstallNodeResult> => { nodeDownloadUrl =
console.log("Installing Node.js..."); "https://nodejs.org/dist/v22.14.0/node-v22.14.0-arm64.msi";
if (platform() === "win32") { } else {
let result = await runShell("winget install Volta.Volta"); // x64 is the most common architecture for Windows so it's the
if (!result.success) { // default download url.
return { success: false, errorMessage: result.errorMessage }; nodeDownloadUrl =
} "https://nodejs.org/dist/v22.14.0/node-v22.14.0-x64.msi";
} else {
let result = await runShell("curl https://get.volta.sh | bash");
if (!result.success) {
return { success: false, errorMessage: result.errorMessage };
} }
} }
console.log("Installed Volta"); return { nodeVersion, pnpmVersion, nodeDownloadUrl };
});
process.env.PATH = ["~/.volta/bin", process.env.PATH].join(":");
console.log("Updated PATH");
let result = await runShell("volta install node");
if (!result.success) {
return { success: false, errorMessage: result.errorMessage };
}
console.log("Installed Node.js (via Volta)");
result = await runShell("node --version");
if (!result.success) {
return { success: false, errorMessage: result.errorMessage };
}
const nodeVersion = result.output.trim();
console.log("Node.js is setup with version", nodeVersion);
result = await runShell("corepack enable pnpm");
if (!result.success) {
return { success: false, errorMessage: result.errorMessage };
}
console.log("Enabled pnpm");
result = await runShell("pnpm --version");
if (!result.success) {
return { success: false, errorMessage: result.errorMessage };
}
const pnpmVersion = result.output.trim();
console.log("pnpm is setup with version", pnpmVersion);
return { success: true, nodeVersion, pnpmVersion }; ipcMain.handle("reload-dyad", async (): Promise<void> => {
app.relaunch();
app.exit(0);
}); });
} }
...@@ -15,6 +15,7 @@ import type { ...@@ -15,6 +15,7 @@ import type {
CreateAppResult, CreateAppResult,
InstallNodeResult, InstallNodeResult,
ListAppsResponse, ListAppsResponse,
NodeSystemInfo,
SandboxConfig, SandboxConfig,
Version, Version,
} from "./ipc_types"; } from "./ipc_types";
...@@ -132,6 +133,10 @@ export class IpcClient { ...@@ -132,6 +133,10 @@ export class IpcClient {
return IpcClient.instance; return IpcClient.instance;
} }
public async reloadDyad(): Promise<void> {
await this.ipcRenderer.invoke("reload-dyad");
}
// Create a new app with an initial chat // Create a new app with an initial chat
public async createApp(params: CreateAppParams): Promise<CreateAppResult> { public async createApp(params: CreateAppParams): Promise<CreateAppResult> {
try { try {
...@@ -493,10 +498,7 @@ export class IpcClient { ...@@ -493,10 +498,7 @@ export class IpcClient {
} }
// Check Node.js and npm status // Check Node.js and npm status
public async getNodejsStatus(): Promise<{ public async getNodejsStatus(): Promise<NodeSystemInfo> {
nodeVersion: string | null;
pnpmVersion: string | null;
}> {
try { try {
const result = await this.ipcRenderer.invoke("nodejs-status"); const result = await this.ipcRenderer.invoke("nodejs-status");
return result; return result;
...@@ -506,17 +508,6 @@ export class IpcClient { ...@@ -506,17 +508,6 @@ export class IpcClient {
} }
} }
// Install Node.js and npm
public async installNode(): Promise<InstallNodeResult> {
try {
const result = await this.ipcRenderer.invoke("install-node");
return result;
} catch (error) {
showError(error);
throw error;
}
}
// --- GitHub Device Flow --- // --- GitHub Device Flow ---
public startGithubDeviceFlow(appId: number | null): void { public startGithubDeviceFlow(appId: number | null): void {
this.ipcRenderer.invoke("github:start-flow", { appId }); this.ipcRenderer.invoke("github:start-flow", { appId });
......
...@@ -66,13 +66,8 @@ export interface SandboxConfig { ...@@ -66,13 +66,8 @@ export interface SandboxConfig {
entry: string; entry: string;
} }
export type InstallNodeResult = export interface NodeSystemInfo {
| { nodeVersion: string | null;
success: true; pnpmVersion: string | null;
nodeVersion: string; nodeDownloadUrl: string;
pnpmVersion: string; }
}
| {
success: false;
errorMessage: string;
};
...@@ -84,10 +84,6 @@ export default function HomePage() { ...@@ -84,10 +84,6 @@ export default function HomePage() {
// Main Home Page Content // Main Home Page Content
return ( return (
<div className="flex flex-col items-center justify-center max-w-3xl m-auto p-8"> <div className="flex flex-col items-center justify-center max-w-3xl m-auto p-8">
<h1 className="text-6xl font-bold mb-12 bg-clip-text text-transparent bg-gradient-to-r from-gray-900 to-gray-600 dark:from-gray-100 dark:to-gray-400 tracking-tight">
Build your dream app
</h1>
<SetupBanner /> <SetupBanner />
<div className="w-full"> <div className="w-full">
......
...@@ -37,6 +37,7 @@ const validInvokeChannels = [ ...@@ -37,6 +37,7 @@ const validInvokeChannels = [
"github:create-repo", "github:create-repo",
"github:push", "github:push",
"get-app-version", "get-app-version",
"reload-dyad",
] as const; ] as const;
// Add valid receive channels // Add valid receive channels
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论