Files
qiming/qimingclaw/crates/agent-electron-client/src/main/services/packages/packageManager.ts

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,
};