src/service.js
Rama main · réplica de lectura del repositorio original.
import { homedir } from 'node:os';
import { join, dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
const BIN_PATH = join(dirname(fileURLToPath(import.meta.url)), '..', 'bin', 'weave.js');
export function serviceOptions(flags = {}, { home = homedir(), cwd = process.cwd(), nodePath = process.execPath } = {}) {
const port = Number(flags.port ?? 4400);
const label = flags.label && flags.label !== true ? String(flags.label) : `ai.grunion.weave.${port}`;
const dataPath = flags.data && flags.data !== true
? resolve(cwd, String(flags.data))
: join(home, '.weave', 'workspace.json');
return {
port,
label,
dataPath,
nodePath,
binPath: BIN_PATH,
plistPath: join(home, 'Library', 'LaunchAgents', `${label}.plist`),
logPath: join(home, 'Library', 'Logs', 'weave', `${label}.log`),
};
}
const escapeXml = (s) => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
export function buildPlist({ label, port, dataPath, nodePath, binPath, logPath }) {
const args = [nodePath, binPath, 'serve', '--port', String(port), '--data', dataPath];
return `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>${escapeXml(label)}</string>
<key>ProgramArguments</key>
<array>
${args.map((a) => ` <string>${escapeXml(a)}</string>`).join('\n')}
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>${escapeXml(logPath)}</string>
<key>StandardErrorPath</key>
<string>${escapeXml(logPath)}</string>
</dict>
</plist>
`;
}
export function parseLaunchctlPrint(text) {
if (!text || !String(text).trim()) return { loaded: false };
const s = String(text);
const grab = (re) => s.match(re)?.[1] ?? null;
const state = grab(/^\s*state = (.+)$/m);
const pid = grab(/^\s*pid = (\d+)$/m);
const exit = grab(/^\s*last exit code = (\d+)$/m);
return {
loaded: true,
state,
pid: pid == null ? null : Number(pid),
lastExitCode: exit == null ? null : Number(exit),
};
}
export async function probeHealth(port, { fetchImpl = fetch, timeoutMs = 2000 } = {}) {
try {
const res = await fetchImpl(`http://127.0.0.1:${port}/api/health`, { signal: AbortSignal.timeout(timeoutMs) });
const payload = await res.json();
return { reachable: true, ...payload };
} catch {
return { reachable: false };
}
}
export function buildStatus({ label, port, plistPath, plistInstalled, launchctl, health, localVersion }) {
const reachable = health?.reachable === true && health?.ok !== false;
const versionStale = reachable && localVersion != null && health.version != null && health.version !== localVersion;
const buildStale = reachable && health.stale === true;
const stale = versionStale || buildStale;
let summary;
if (reachable) {
if (versionStale) summary = `running (stale: server v${health.version}, local v${localVersion} — restart to update)`;
else if (buildStale) summary = `running (stale: server booted at ${health.sha}, the checkout it serves is at ${health.diskSha} — restart to update)`;
else if (!plistInstalled) summary = 'running (unmanaged — run `weave service install` to auto-start on login)';
else summary = 'running';
} else if (plistInstalled && launchctl?.loaded) {
summary = `installed but not responding on port ${port}`;
} else if (plistInstalled) {
summary = 'installed but not loaded (launchctl)';
} else {
summary = 'not installed';
}
return {
label,
port,
summary,
plist: { installed: Boolean(plistInstalled), path: plistPath },
launchctl: launchctl ?? { loaded: false },
server: {
reachable: Boolean(health?.reachable),
ok: health?.ok ?? null,
version: health?.version ?? null,
workspace: health?.workspace ?? null,
startedAt: health?.startedAt ?? null,
uptime: health?.uptime ?? null,
sha: health?.sha ?? null,
diskSha: health?.diskSha ?? null,
stale,
},
};
}