Simplified setup flow: user installs node.js; pnpm is configured
This commit is contained in:
@@ -20,29 +20,26 @@ import {
|
|||||||
} from "@/components/ui/accordion";
|
} from "@/components/ui/accordion";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { showError } from "@/lib/toast";
|
import { NodeSystemInfo } from "@/ipc/ipc_types";
|
||||||
|
|
||||||
export function SetupBanner() {
|
export function SetupBanner() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { isAnyProviderSetup } = useSettings();
|
const { isAnyProviderSetup, loading } = useSettings();
|
||||||
const [nodeVersion, setNodeVersion] = useState<string | null>(null);
|
const [nodeSystemInfo, setNodeSystemInfo] = useState<NodeSystemInfo | null>(
|
||||||
const [pnpmVersion, setPnpmVersion] = useState<string | null>(null);
|
null
|
||||||
|
);
|
||||||
const [nodeCheckError, setNodeCheckError] = useState<boolean>(false);
|
const [nodeCheckError, setNodeCheckError] = useState<boolean>(false);
|
||||||
const [nodeInstallError, setNodeInstallError] = useState<string | null>(null);
|
|
||||||
const [nodeInstallLoading, setNodeInstallLoading] = useState<boolean>(false);
|
const [nodeInstallLoading, setNodeInstallLoading] = useState<boolean>(false);
|
||||||
const checkNode = useCallback(async () => {
|
const checkNode = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
setNodeCheckError(false);
|
setNodeCheckError(false);
|
||||||
const status = await IpcClient.getInstance().getNodejsStatus();
|
const status = await IpcClient.getInstance().getNodejsStatus();
|
||||||
setNodeVersion(status.nodeVersion);
|
setNodeSystemInfo(status);
|
||||||
setPnpmVersion(status.pnpmVersion);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to check Node.js status:", error);
|
console.error("Failed to check Node.js status:", error);
|
||||||
setNodeVersion(null);
|
setNodeSystemInfo(null);
|
||||||
setPnpmVersion(null);
|
|
||||||
setNodeCheckError(true);
|
setNodeCheckError(true);
|
||||||
}
|
}
|
||||||
}, [setNodeVersion, setNodeCheckError]);
|
}, [setNodeSystemInfo, setNodeCheckError]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
checkNode();
|
checkNode();
|
||||||
@@ -57,39 +54,35 @@ export function SetupBanner() {
|
|||||||
|
|
||||||
const handleNodeInstallClick = async () => {
|
const handleNodeInstallClick = async () => {
|
||||||
setNodeInstallLoading(true);
|
setNodeInstallLoading(true);
|
||||||
try {
|
IpcClient.getInstance().openExternalUrl(nodeSystemInfo!.nodeDownloadUrl);
|
||||||
const result = await IpcClient.getInstance().installNode();
|
|
||||||
if (!result.success) {
|
|
||||||
showError(result.errorMessage);
|
|
||||||
setNodeInstallError(result.errorMessage || "Unknown error");
|
|
||||||
} else {
|
|
||||||
setNodeVersion(result.nodeVersion);
|
|
||||||
setPnpmVersion(result.pnpmVersion);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
showError("Failed to install Node.js. " + (error as Error).message);
|
|
||||||
setNodeInstallError(
|
|
||||||
"Failed to install Node.js. " + (error as Error).message
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
setNodeInstallLoading(false);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const isNodeSetupComplete = !!nodeVersion && !!pnpmVersion;
|
const finishNodeInstallAndRestart = () => {
|
||||||
const isAiProviderSetup = isAnyProviderSetup();
|
IpcClient.getInstance().reloadDyad();
|
||||||
|
};
|
||||||
|
|
||||||
|
const isNodeSetupComplete = Boolean(
|
||||||
|
nodeSystemInfo?.nodeVersion && nodeSystemInfo?.pnpmVersion
|
||||||
|
);
|
||||||
|
|
||||||
const itemsNeedAction: string[] = [];
|
const itemsNeedAction: string[] = [];
|
||||||
if (!isNodeSetupComplete) itemsNeedAction.push("node-setup");
|
if (!isNodeSetupComplete && nodeSystemInfo) {
|
||||||
if (isNodeSetupComplete && !isAiProviderSetup)
|
itemsNeedAction.push("node-setup");
|
||||||
|
}
|
||||||
|
if (!isAnyProviderSetup() && !loading) {
|
||||||
itemsNeedAction.push("ai-setup");
|
itemsNeedAction.push("ai-setup");
|
||||||
|
}
|
||||||
|
|
||||||
if (itemsNeedAction.length === 0) {
|
if (itemsNeedAction.length === 0) {
|
||||||
return null;
|
return (
|
||||||
|
<h1 className="text-6xl font-bold mb-8 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>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const bannerClasses = cn(
|
const bannerClasses = cn(
|
||||||
"w-full mb-8 border rounded-xl shadow-sm overflow-hidden",
|
"w-full mb-6 border rounded-xl shadow-sm overflow-hidden",
|
||||||
"border-zinc-200 dark:border-zinc-700"
|
"border-zinc-200 dark:border-zinc-700"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -105,131 +98,131 @@ export function SetupBanner() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={bannerClasses}>
|
<>
|
||||||
<Accordion
|
<p className="text-xl text-zinc-700 dark:text-zinc-300 p-4">
|
||||||
type="multiple"
|
Follow these steps and you'll be ready to start building with Dyad...
|
||||||
className="w-full"
|
</p>
|
||||||
defaultValue={itemsNeedAction}
|
<div className={bannerClasses}>
|
||||||
>
|
<Accordion
|
||||||
<AccordionItem
|
type="multiple"
|
||||||
value="node-setup"
|
className="w-full"
|
||||||
className={cn(
|
defaultValue={itemsNeedAction}
|
||||||
nodeCheckError
|
|
||||||
? "bg-red-50 dark:bg-red-900/30"
|
|
||||||
: isNodeSetupComplete
|
|
||||||
? "bg-green-50 dark:bg-green-900/30"
|
|
||||||
: "bg-yellow-50 dark:bg-yellow-900/30"
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<AccordionTrigger className="px-4 py-3 transition-colors w-full hover:no-underline">
|
<AccordionItem
|
||||||
<div className="flex items-center justify-between w-full">
|
value="node-setup"
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
{getStatusIcon(isNodeSetupComplete, nodeCheckError)}
|
|
||||||
<span className="font-medium text-sm">
|
|
||||||
1. Check Node.js Runtime
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</AccordionTrigger>
|
|
||||||
<AccordionContent className="px-4 pt-2 pb-4 bg-white dark:bg-zinc-900 border-t border-inherit">
|
|
||||||
{nodeInstallError && (
|
|
||||||
<p className="text-sm text-red-600 dark:text-red-400">
|
|
||||||
{nodeInstallError}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{nodeCheckError ? (
|
|
||||||
<p className="text-sm text-red-600 dark:text-red-400">
|
|
||||||
Error checking Node.js status. Please ensure node.js are
|
|
||||||
installed correctly and accessible in your system's PATH.
|
|
||||||
</p>
|
|
||||||
) : isNodeSetupComplete ? (
|
|
||||||
<p className="text-sm">Node.js ({nodeVersion}) installed.</p>
|
|
||||||
) : (
|
|
||||||
<div>
|
|
||||||
<p className="text-sm mb-3">
|
|
||||||
Node.js is required to run apps locally. We also use pnpm as
|
|
||||||
our package manager as it's faster and more efficient than
|
|
||||||
npm.
|
|
||||||
</p>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
onClick={handleNodeInstallClick}
|
|
||||||
disabled={nodeInstallLoading}
|
|
||||||
>
|
|
||||||
{nodeInstallLoading ? (
|
|
||||||
<>
|
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
||||||
Installing...
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
"Install Node.js Runtime"
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
|
|
||||||
<AccordionItem
|
|
||||||
value="ai-setup"
|
|
||||||
disabled={!isNodeSetupComplete}
|
|
||||||
className={cn(
|
|
||||||
isAiProviderSetup
|
|
||||||
? "bg-green-50 dark:bg-green-900/30"
|
|
||||||
: "bg-yellow-50 dark:bg-yellow-900/30",
|
|
||||||
!isNodeSetupComplete ? "opacity-60" : ""
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<AccordionTrigger
|
|
||||||
className={cn(
|
className={cn(
|
||||||
"px-4 py-3 transition-colors w-full hover:no-underline",
|
nodeCheckError
|
||||||
!isNodeSetupComplete ? "cursor-not-allowed" : ""
|
? "bg-red-50 dark:bg-red-900/30"
|
||||||
|
: isNodeSetupComplete
|
||||||
|
? "bg-green-50 dark:bg-green-900/30"
|
||||||
|
: "bg-yellow-50 dark:bg-yellow-900/30"
|
||||||
)}
|
)}
|
||||||
onClick={(e) => !isNodeSetupComplete && e.preventDefault()}
|
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between w-full">
|
<AccordionTrigger className="px-4 py-3 transition-colors w-full hover:no-underline">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center justify-between w-full">
|
||||||
{getStatusIcon(isAiProviderSetup)}
|
<div className="flex items-center gap-3">
|
||||||
<span className="font-medium text-sm">
|
{getStatusIcon(isNodeSetupComplete, nodeCheckError)}
|
||||||
2. Setup AI Model Access
|
<span className="font-medium text-sm">
|
||||||
</span>
|
1. Install Node.js
|
||||||
</div>
|
</span>
|
||||||
</div>
|
|
||||||
</AccordionTrigger>
|
|
||||||
<AccordionContent className="px-4 pt-2 pb-4 bg-white dark:bg-zinc-900 border-t border-inherit">
|
|
||||||
<p className="text-sm mb-3">
|
|
||||||
Connect your preferred AI provider to start generating code.
|
|
||||||
</p>
|
|
||||||
<div
|
|
||||||
className="p-3 bg-blue-50 dark:bg-blue-900/50 border border-blue-200 dark:border-blue-700 rounded-lg cursor-pointer hover:bg-blue-100 dark:hover:bg-blue-900/70 transition-colors"
|
|
||||||
onClick={handleAiSetupClick}
|
|
||||||
role="button"
|
|
||||||
tabIndex={isNodeSetupComplete ? 0 : -1}
|
|
||||||
onKeyDown={(e) =>
|
|
||||||
isNodeSetupComplete && e.key === "Enter" && handleAiSetupClick()
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between gap-3">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="bg-blue-100 dark:bg-blue-800 p-1.5 rounded-full">
|
|
||||||
<Sparkles className="w-4 h-4 text-blue-600 dark:text-blue-400" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4 className="font-medium text-sm text-blue-800 dark:text-blue-300">
|
|
||||||
Setup Google Gemini API Key
|
|
||||||
</h4>
|
|
||||||
<p className="text-xs text-blue-600 dark:text-blue-400 flex items-center gap-1">
|
|
||||||
<GiftIcon className="w-3 h-3" />
|
|
||||||
Use Google Gemini for free
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<ChevronRight className="w-4 h-4 text-blue-600 dark:text-blue-400" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</AccordionTrigger>
|
||||||
</AccordionContent>
|
<AccordionContent className="px-4 pt-2 pb-4 bg-white dark:bg-zinc-900 border-t border-inherit">
|
||||||
</AccordionItem>
|
{nodeCheckError && (
|
||||||
</Accordion>
|
<p className="text-sm text-red-600 dark:text-red-400">
|
||||||
</div>
|
Error checking Node.js status. Try installing Node.js.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{isNodeSetupComplete ? (
|
||||||
|
<p className="text-sm">
|
||||||
|
Node.js ({nodeSystemInfo!.nodeVersion}) installed.{" "}
|
||||||
|
{nodeSystemInfo!.pnpmVersion && (
|
||||||
|
<span className="text-xs text-gray-500">
|
||||||
|
pnpm ({nodeSystemInfo!.pnpmVersion}) installed.
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<div className="text-sm">
|
||||||
|
<p>Node.js is required to run apps locally.</p>
|
||||||
|
<p className="mb-3">
|
||||||
|
After you have installed node.js, click "Finish setup" to
|
||||||
|
restart Dyad.
|
||||||
|
</p>
|
||||||
|
{nodeInstallLoading ? (
|
||||||
|
<Button onClick={finishNodeInstallAndRestart}>
|
||||||
|
Finish setup and restart Dyad
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button onClick={handleNodeInstallClick}>
|
||||||
|
Install Node.js Runtime
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
|
||||||
|
<AccordionItem
|
||||||
|
value="ai-setup"
|
||||||
|
className={cn(
|
||||||
|
isAnyProviderSetup()
|
||||||
|
? "bg-green-50 dark:bg-green-900/30"
|
||||||
|
: "bg-yellow-50 dark:bg-yellow-900/30"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<AccordionTrigger
|
||||||
|
className={cn(
|
||||||
|
"px-4 py-3 transition-colors w-full hover:no-underline"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between w-full">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
{getStatusIcon(isAnyProviderSetup())}
|
||||||
|
<span className="font-medium text-sm">
|
||||||
|
2. Setup AI Model Access
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className="px-4 pt-2 pb-4 bg-white dark:bg-zinc-900 border-t border-inherit">
|
||||||
|
<p className="text-sm mb-3">
|
||||||
|
Connect your preferred AI provider to start generating code.
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
className="p-3 bg-blue-50 dark:bg-blue-900/50 border border-blue-200 dark:border-blue-700 rounded-lg cursor-pointer hover:bg-blue-100 dark:hover:bg-blue-900/70 transition-colors"
|
||||||
|
onClick={handleAiSetupClick}
|
||||||
|
role="button"
|
||||||
|
tabIndex={isNodeSetupComplete ? 0 : -1}
|
||||||
|
onKeyDown={(e) =>
|
||||||
|
isNodeSetupComplete &&
|
||||||
|
e.key === "Enter" &&
|
||||||
|
handleAiSetupClick()
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between gap-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="bg-blue-100 dark:bg-blue-800 p-1.5 rounded-full">
|
||||||
|
<Sparkles className="w-4 h-4 text-blue-600 dark:text-blue-400" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="font-medium text-sm text-blue-800 dark:text-blue-300">
|
||||||
|
Setup Google Gemini API Key
|
||||||
|
</h4>
|
||||||
|
<p className="text-xs text-blue-600 dark:text-blue-400 flex items-center gap-1">
|
||||||
|
<GiftIcon className="w-3 h-3" />
|
||||||
|
Use Google Gemini for free
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ChevronRight className="w-4 h-4 text-blue-600 dark:text-blue-400" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,58 +1,12 @@
|
|||||||
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> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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(":");
|
ipcMain.handle("reload-dyad", async (): Promise<void> => {
|
||||||
console.log("Updated PATH");
|
app.relaunch();
|
||||||
let result = await runShell("volta install node");
|
app.exit(0);
|
||||||
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 };
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
|||||||
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() {
|
|||||||
// 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 = [
|
|||||||
"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
|
||||||
|
|||||||
Reference in New Issue
Block a user