92 lines
3.2 KiB
TypeScript
92 lines
3.2 KiB
TypeScript
import { execSync } from "node:child_process";
|
|
import { existsSync } from "node:fs";
|
|
import { type } from "arktype";
|
|
import { log } from "../utils/cli.ts";
|
|
import type { ToolContext } from "./server.ts";
|
|
import { execute, tool } from "./shared.ts";
|
|
|
|
const EmptyParams = type({});
|
|
|
|
async function runInstallation(): Promise<unknown[]> {
|
|
if (!existsSync("package.json")) {
|
|
return [];
|
|
}
|
|
try {
|
|
log.info("» installing Node.js dependencies...");
|
|
execSync("npm install --silent", { stdio: "pipe" });
|
|
log.info("» Node.js dependencies installed");
|
|
return [{ language: "node", installed: true }];
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
log.warning(`» dependency installation failed: ${msg}`);
|
|
return [{ language: "node", installed: false, error: msg }];
|
|
}
|
|
}
|
|
|
|
export function startInstallation(ctx: ToolContext): void {
|
|
if (ctx.toolState.dependencyInstallation) return;
|
|
|
|
const promise = runInstallation();
|
|
ctx.toolState.dependencyInstallation = {
|
|
status: "in_progress",
|
|
promise,
|
|
results: undefined,
|
|
};
|
|
|
|
promise.then(
|
|
(results) => {
|
|
if (ctx.toolState.dependencyInstallation) {
|
|
ctx.toolState.dependencyInstallation.status = "completed";
|
|
ctx.toolState.dependencyInstallation.results = results;
|
|
}
|
|
},
|
|
() => {
|
|
if (ctx.toolState.dependencyInstallation) {
|
|
ctx.toolState.dependencyInstallation.status = "failed";
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
export function StartDependencyInstallationTool(ctx: ToolContext) {
|
|
return tool({
|
|
name: "start_dependency_installation",
|
|
description:
|
|
"Start installing project dependencies in the background. Non-blocking, returns immediately. Call early after branch checkout.",
|
|
parameters: EmptyParams,
|
|
execute: execute(async () => {
|
|
const state = ctx.toolState.dependencyInstallation;
|
|
if (state?.status === "completed" || state?.status === "failed") {
|
|
return { status: state.status, message: "Dependency installation already completed." };
|
|
}
|
|
if (state?.status === "in_progress") {
|
|
return { status: "in_progress", message: "Dependency installation is already in progress." };
|
|
}
|
|
startInstallation(ctx);
|
|
return { status: "started", message: "Dependency installation started in background." };
|
|
}),
|
|
});
|
|
}
|
|
|
|
export function AwaitDependencyInstallationTool(ctx: ToolContext) {
|
|
return tool({
|
|
name: "await_dependency_installation",
|
|
description:
|
|
"Wait for dependency installation to complete. Auto-starts if not yet started.",
|
|
parameters: EmptyParams,
|
|
execute: execute(async () => {
|
|
if (!ctx.toolState.dependencyInstallation) {
|
|
startInstallation(ctx);
|
|
}
|
|
const state = ctx.toolState.dependencyInstallation;
|
|
if (!state) throw new Error("failed to initialize dependency installation state");
|
|
if (state.status === "completed" || state.status === "failed") {
|
|
return { status: state.status, message: "Dependency installation complete." };
|
|
}
|
|
if (!state.promise) throw new Error("dependency installation state corrupted");
|
|
await state.promise;
|
|
return { status: state.status, message: "Dependency installation complete." };
|
|
}),
|
|
});
|
|
}
|