64 lines
2.6 KiB
TypeScript
64 lines
2.6 KiB
TypeScript
import { tool } from '@opencode-ai/plugin';
|
|
|
|
export default tool({
|
|
description:
|
|
"Start, stop, or health-check the Next.js dev server for the generated clone project. Use action='health' to check if a server is already responding at url; action='start' to spawn `bun run dev` in project_dir; action='stop' to kill the pid from a prior start.",
|
|
args: {
|
|
action: tool.schema.enum(['health', 'start', 'stop']).describe('Which lifecycle action to perform'),
|
|
project_dir: tool.schema.string().nullable().describe('Path to the generated Next.js project (required for start)'),
|
|
url: tool.schema.string().nullable().describe('Base URL to health-check. Default http://localhost:3000'),
|
|
port: tool.schema.number().nullable().describe('Port for `bun run dev` when starting. Default 3000.'),
|
|
pid: tool.schema.number().nullable().describe('Pid to kill when action=stop'),
|
|
},
|
|
async execute(args, context) {
|
|
const { spawn } = await import('node:child_process');
|
|
const { resolve } = await import('node:path');
|
|
const url = args.url ?? 'http://localhost:3000';
|
|
|
|
if (args.action === 'health') {
|
|
try {
|
|
const res = await fetch(url, { method: 'HEAD' });
|
|
return JSON.stringify({ healthy: res.ok || res.status === 404, status: res.status, url });
|
|
} catch {
|
|
return JSON.stringify({ healthy: false, status: 0, url });
|
|
}
|
|
}
|
|
|
|
if (args.action === 'start') {
|
|
if (!args.project_dir) throw new Error('project_dir is required for action=start');
|
|
const cwd = resolve(context.worktree, args.project_dir);
|
|
const port = args.port ?? 3000;
|
|
const child = spawn('bun', ['run', 'dev', '--port', String(port)], {
|
|
cwd,
|
|
detached: true,
|
|
stdio: 'ignore',
|
|
});
|
|
child.unref();
|
|
|
|
const deadline = Date.now() + 30_000;
|
|
while (Date.now() < deadline) {
|
|
try {
|
|
const res = await fetch(url, { method: 'HEAD' });
|
|
if (res.ok || res.status === 404) {
|
|
return JSON.stringify({ status: 'started', url, pid: child.pid });
|
|
}
|
|
} catch {}
|
|
await new Promise((r) => setTimeout(r, 1000));
|
|
}
|
|
return JSON.stringify({ status: 'timeout', url, pid: child.pid });
|
|
}
|
|
|
|
if (args.action === 'stop') {
|
|
if (args.pid == null) throw new Error('pid is required for action=stop');
|
|
try {
|
|
process.kill(args.pid, 'SIGTERM');
|
|
return JSON.stringify({ status: 'stopped', pid: args.pid });
|
|
} catch (err) {
|
|
return JSON.stringify({ status: 'error', pid: args.pid, error: String(err) });
|
|
}
|
|
}
|
|
|
|
throw new Error(`unknown action ${args.action}`);
|
|
},
|
|
});
|