Files
pi-agent-integrated/scripts/install-managed-packages.mjs

143 lines
4.9 KiB
JavaScript

import { spawnSync } from "node:child_process";
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { join, resolve } from "node:path";
import { configurePiMemory } from "./configure-pi-memory.mjs";
import { configurePiAiVision } from "./configure-pi-ai-vision.mjs";
import { agentDir, managedEnvironment, rootDir } from "./profile.mjs";
let settings;
try {
settings = JSON.parse(readFileSync(join(rootDir, "config", "settings.default.json"), "utf8"));
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Invalid config/settings.default.json: ${message}`);
}
const packages = Array.isArray(settings.packages) ? settings.packages : [];
const cliPath = join(rootDir, "pi", "packages", "coding-agent", "dist", "cli.js");
const env = managedEnvironment();
const npmCliPath = process.env.npm_execpath;
const localSmartFetchSource = "../../resources/packages/pi-smart-fetch";
function parseLocalSource(source) {
if (
source.startsWith("npm:") ||
source.startsWith("git:") ||
/^[a-z][a-z0-9+.-]*:\/\//i.test(source)
) {
return undefined;
}
return resolve(agentDir, source);
}
function packageDependenciesReady(packageDir) {
const packageJsonPath = join(packageDir, "package.json");
if (!existsSync(packageJsonPath)) return false;
try {
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
const dependencies = packageJson.dependencies ?? {};
return Object.keys(dependencies).every((name) =>
existsSync(join(packageDir, "node_modules", ...name.split("/"), "package.json")),
);
} catch {
return false;
}
}
function ensureLocalPackage(source) {
const packageDir = parseLocalSource(source);
if (!packageDir) return false;
if (!existsSync(join(packageDir, "package.json"))) {
throw new Error(`Local managed package is missing: ${packageDir}`);
}
if (packageDependenciesReady(packageDir)) {
console.log(`Local managed package ready: ${source}`);
return true;
}
console.log(`Installing local managed package dependencies: ${source}`);
const args = ["install", "--prefix", packageDir, "--omit=dev", "--legacy-peer-deps"];
const result = npmCliPath
? spawnSync(process.execPath, [npmCliPath, ...args], { cwd: rootDir, env, stdio: "inherit" })
: spawnSync("npm", args, { cwd: rootDir, env, stdio: "inherit" });
if (result.error) throw result.error;
if (result.status !== 0) process.exit(result.status ?? 1);
return true;
}
function ensureSmartFetchConfigured() {
const settingsPath = join(agentDir, "settings.json");
if (!existsSync(settingsPath)) return;
let settings;
try {
settings = JSON.parse(readFileSync(settingsPath, "utf8"));
} catch {
return;
}
if (!settings || typeof settings !== "object" || Array.isArray(settings)) return;
const packages = Array.isArray(settings.packages) ? settings.packages : [];
const migrated = packages.map((entry) =>
entry === "npm:pi-smart-fetch" ||
(typeof entry === "string" && entry.startsWith("npm:pi-smart-fetch@"))
? localSmartFetchSource
: entry,
);
if (!migrated.some((entry) => entry === localSmartFetchSource)) {
migrated.push(localSmartFetchSource);
}
if (JSON.stringify(packages) !== JSON.stringify(migrated)) {
writeFileSync(settingsPath, `${JSON.stringify({ ...settings, packages: migrated }, null, 2)}\n`, "utf8");
console.log(`Configured project-local smart fetch package: ${localSmartFetchSource}`);
}
}
function parseExactNpmSource(source) {
if (!source.startsWith("npm:")) return undefined;
const spec = source.slice(4);
const separator = spec.lastIndexOf("@");
if (separator <= 0) return undefined;
return { name: spec.slice(0, separator), version: spec.slice(separator + 1) };
}
function installedAtExpectedVersion(source) {
const parsed = parseExactNpmSource(source);
if (!parsed) return false;
const packageJson = join(agentDir, "npm", "node_modules", ...parsed.name.split("/"), "package.json");
if (!existsSync(packageJson)) return false;
try {
return JSON.parse(readFileSync(packageJson, "utf8")).version === parsed.version;
} catch {
return false;
}
}
ensureSmartFetchConfigured();
for (const source of packages) {
if (typeof source !== "string" || !source.trim()) continue;
if (ensureLocalPackage(source)) continue;
if (installedAtExpectedVersion(source)) {
console.log(`Managed package ready: ${source}`);
continue;
}
console.log(`Installing managed package: ${source}`);
const result = spawnSync(process.execPath, [cliPath, "install", source], {
cwd: rootDir,
env,
stdio: "inherit",
});
if (result.error) throw result.error;
if (result.status !== 0) process.exit(result.status ?? 1);
}
configurePiMemory({ agentDir });
try {
configurePiAiVision({ quiet: false });
} catch (error) {
console.warn(`[vision] pi-ai passthrough patch skipped: ${error.message}`);
}