214 lines
5.2 KiB
TypeScript
214 lines
5.2 KiB
TypeScript
import { app } from "electron";
|
|
import * as path from "path";
|
|
import * as fs from "fs";
|
|
import { spawn } from "child_process";
|
|
import { getAppEnv } from "../system/dependencies";
|
|
import { APP_DATA_DIR_NAME } from "../constants";
|
|
import { isWindows } from "../system/shellEnv";
|
|
|
|
export interface AppPaths {
|
|
appData: string; // 应用数据目录
|
|
nodeModules: string; // 本地 node_modules
|
|
temp: string; // 临时目录
|
|
logs: string; // 日志目录
|
|
workspaces: string; // 工作空间
|
|
}
|
|
|
|
// Get application paths
|
|
export function getAppPaths(): AppPaths {
|
|
const appData =
|
|
app?.getPath("userData") ||
|
|
path.join(process.env.HOME || "", APP_DATA_DIR_NAME);
|
|
|
|
return {
|
|
appData,
|
|
nodeModules: path.join(appData, "node_modules"),
|
|
temp: path.join(appData, "temp"),
|
|
logs: path.join(appData, "logs"),
|
|
workspaces: path.join(appData, "workspaces"),
|
|
};
|
|
}
|
|
|
|
// Ensure directories exist
|
|
export function ensureAppDirs(): AppPaths {
|
|
const dirs = getAppPaths();
|
|
|
|
for (const [key, dir] of Object.entries(dirs)) {
|
|
if (!fs.existsSync(dir)) {
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
}
|
|
}
|
|
|
|
return dirs;
|
|
}
|
|
|
|
// Get npm/pnpm executable path (use local if available)
|
|
export function getPackageManager(): string {
|
|
// Check for pnpm first, then yarn, then npm
|
|
const appDirs = getAppPaths();
|
|
|
|
// Check local installation
|
|
const localNpm = path.join(appDirs.nodeModules, ".bin", "npm");
|
|
const localPnpm = path.join(appDirs.nodeModules, ".bin", "pnpm");
|
|
const localYarn = path.join(appDirs.nodeModules, ".bin", "yarn");
|
|
|
|
if (fs.existsSync(localPnpm)) return localPnpm;
|
|
if (fs.existsSync(localYarn)) return localYarn;
|
|
if (fs.existsSync(localNpm)) return localNpm;
|
|
|
|
// Fallback to system
|
|
return isWindows() ? "npm.cmd" : "npm";
|
|
}
|
|
|
|
// Install package locally (not globally)
|
|
export async function installPackage(
|
|
packageName: string,
|
|
options?: {
|
|
registry?: string;
|
|
cwd?: string;
|
|
},
|
|
): Promise<{ success: boolean; error?: string }> {
|
|
const dirs = ensureAppDirs();
|
|
const cwd = options?.cwd || dirs.nodeModules;
|
|
const registry = options?.registry;
|
|
|
|
// Ensure node_modules exists
|
|
if (!fs.existsSync(cwd)) {
|
|
fs.mkdirSync(cwd, { recursive: true });
|
|
}
|
|
|
|
// Build command
|
|
const npmCmd = isWindows() ? "npm.cmd" : "npm";
|
|
const args = ["install", "--save", "--no-save", packageName];
|
|
|
|
if (registry) {
|
|
args.push(`--registry=${registry}`);
|
|
}
|
|
|
|
return new Promise((resolve) => {
|
|
const proc = spawn(npmCmd, args, {
|
|
cwd,
|
|
env: {
|
|
...process.env,
|
|
...getAppEnv(),
|
|
},
|
|
stdio: "pipe",
|
|
shell: isWindows(),
|
|
});
|
|
|
|
let stdout = "";
|
|
let stderr = "";
|
|
|
|
proc.stdout?.on("data", (data) => {
|
|
stdout += data.toString();
|
|
});
|
|
|
|
proc.stderr?.on("data", (data) => {
|
|
stderr += data.toString();
|
|
});
|
|
|
|
proc.on("error", (error) => {
|
|
console.error(`[Install] Error:`, error);
|
|
resolve({ success: false, error: error.message });
|
|
});
|
|
|
|
proc.on("close", (code) => {
|
|
if (code === 0) {
|
|
resolve({ success: true });
|
|
} else {
|
|
console.error(`[Install] Failed: ${stderr}`);
|
|
resolve({ success: false, error: stderr || "Install failed" });
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Uninstall package
|
|
export async function uninstallPackage(
|
|
packageName: string,
|
|
options?: {
|
|
cwd?: string;
|
|
},
|
|
): Promise<{ success: boolean; error?: string }> {
|
|
const dirs = getAppPaths();
|
|
const cwd = options?.cwd || dirs.nodeModules;
|
|
|
|
const npmCmd = isWindows() ? "npm.cmd" : "npm";
|
|
const args = ["uninstall", packageName];
|
|
|
|
return new Promise((resolve) => {
|
|
const proc = spawn(npmCmd, args, {
|
|
cwd,
|
|
env: { ...process.env, ...getAppEnv() },
|
|
stdio: "pipe",
|
|
shell: isWindows(),
|
|
});
|
|
|
|
let stderr = "";
|
|
|
|
proc.stderr?.on("data", (data) => {
|
|
stderr += data.toString();
|
|
});
|
|
|
|
proc.on("error", (error) => {
|
|
resolve({ success: false, error: error.message });
|
|
});
|
|
|
|
proc.on("close", (code) => {
|
|
if (code === 0) {
|
|
resolve({ success: true });
|
|
} else {
|
|
resolve({ success: false, error: stderr });
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Check if package is installed locally
|
|
export function isPackageInstalled(packageName: string): boolean {
|
|
const dirs = getAppPaths();
|
|
const packagePath = path.join(dirs.nodeModules, packageName);
|
|
return fs.existsSync(packagePath);
|
|
}
|
|
|
|
// Get installed packages
|
|
export function getInstalledPackages(): string[] {
|
|
const dirs = getAppPaths();
|
|
const packageJsonPath = path.join(dirs.nodeModules, "package.json");
|
|
|
|
if (!fs.existsSync(packageJsonPath)) {
|
|
return [];
|
|
}
|
|
|
|
try {
|
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
return [
|
|
...Object.keys(packageJson.dependencies || {}),
|
|
...Object.keys(packageJson.devDependencies || {}),
|
|
];
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// Clean up temporary files
|
|
export function cleanupTemp(): void {
|
|
const dirs = getAppPaths();
|
|
|
|
if (fs.existsSync(dirs.temp)) {
|
|
fs.rmSync(dirs.temp, { recursive: true, force: true });
|
|
fs.mkdirSync(dirs.temp, { recursive: true });
|
|
}
|
|
}
|
|
|
|
export default {
|
|
getAppPaths,
|
|
ensureAppDirs,
|
|
getPackageManager,
|
|
installPackage,
|
|
uninstallPackage,
|
|
isPackageInstalled,
|
|
getInstalledPackages,
|
|
cleanupTemp,
|
|
};
|