@neat.is/core 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +76 -27
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +76 -27
- package/dist/cli.js.map +1 -1
- package/dist/neatd.cjs +14 -2
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +14 -2
- package/dist/neatd.js.map +1 -1
- package/package.json +2 -2
package/dist/neatd.js
CHANGED
|
@@ -47,6 +47,9 @@ function resolveWebPackageDir() {
|
|
|
47
47
|
const pkgJsonPath = req.resolve("@neat.is/web/package.json");
|
|
48
48
|
return path.dirname(pkgJsonPath);
|
|
49
49
|
}
|
|
50
|
+
function resolveStandaloneServerEntry(webDir) {
|
|
51
|
+
return path.join(webDir, ".next/standalone/packages/web/server.js");
|
|
52
|
+
}
|
|
50
53
|
async function spawnWebUI(restPort) {
|
|
51
54
|
const portRaw = process.env.NEAT_WEB_PORT;
|
|
52
55
|
const port = portRaw && portRaw.length > 0 ? Number.parseInt(portRaw, 10) : DEFAULT_WEB_PORT;
|
|
@@ -55,13 +58,22 @@ async function spawnWebUI(restPort) {
|
|
|
55
58
|
}
|
|
56
59
|
await assertPortFree(port);
|
|
57
60
|
const cwd = resolveWebPackageDir();
|
|
61
|
+
const serverEntry = resolveStandaloneServerEntry(cwd);
|
|
62
|
+
try {
|
|
63
|
+
__require.resolve(serverEntry);
|
|
64
|
+
} catch {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`neatd: web UI standalone build missing at ${serverEntry}. The published @neat.is/web tarball should include it; if you're running from a monorepo checkout, run \`npm run build --workspace @neat.is/web\` first, or set NEAT_WEB_DISABLED=1 to skip the web UI.`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
58
69
|
const env = {
|
|
59
70
|
...process.env,
|
|
60
71
|
PORT: String(port),
|
|
72
|
+
HOSTNAME: process.env.HOSTNAME ?? "0.0.0.0",
|
|
61
73
|
NEAT_API_URL: process.env.NEAT_API_URL ?? `http://localhost:${restPort}`
|
|
62
74
|
};
|
|
63
|
-
const child = spawn(
|
|
64
|
-
cwd,
|
|
75
|
+
const child = spawn(process.execPath, [serverEntry], {
|
|
76
|
+
cwd: path.dirname(serverEntry),
|
|
65
77
|
env,
|
|
66
78
|
stdio: ["ignore", "inherit", "inherit"],
|
|
67
79
|
detached: false
|
package/dist/neatd.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/neatd.ts","../src/web-spawn.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `neatd` — distribution-layer daemon CLI (ADR-049).\n *\n * Subcommands:\n * neatd start [--foreground] boot the daemon and watch the registry\n * neatd stop signal the running daemon to shut down\n * neatd reload signal the running daemon to re-read the registry\n * neatd status print PID + per-project last-seen timestamps\n *\n * MVP runs in foreground only. Backgrounding is the supervisor's job\n * (launchd / systemd / nohup) — `neatd start` blocks until SIGINT/SIGTERM.\n *\n * v0.2.10: also brings up the web UI on port 6328 by default (ADR-059).\n */\n\nimport { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport { startDaemon } from './daemon.js'\nimport { listProjects, registryPath } from './registry.js'\nimport { spawnWebUI, DEFAULT_WEB_PORT, type WebHandle } from './web-spawn.js'\n\nfunction neatHome(): string {\n if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {\n return path.resolve(process.env.NEAT_HOME)\n }\n const home = process.env.HOME ?? process.env.USERPROFILE ?? ''\n return path.join(home, '.neat')\n}\n\nasync function readPid(): Promise<number | null> {\n try {\n const raw = await fs.readFile(path.join(neatHome(), 'neatd.pid'), 'utf8')\n const n = Number.parseInt(raw.trim(), 10)\n return Number.isFinite(n) ? n : null\n } catch {\n return null\n }\n}\n\nfunction usage(): void {\n console.log('usage: neatd <start|stop|reload|status> [--foreground]')\n}\n\nfunction restPortFromEnv(): number {\n const raw = process.env.PORT\n return raw && raw.length > 0 ? Number.parseInt(raw, 10) : 8080\n}\n\nasync function cmdStart(): Promise<void> {\n const handle = await startDaemon()\n console.log(`neatd: started, PID ${process.pid}, ${handle.slots.size} project(s)`)\n console.log(`neatd: registry at ${registryPath()}`)\n // ADR-063 — surface the bound addresses so the operator can sanity-check\n // the documented happy path without grepping startup logs.\n if (handle.restAddress) console.log(`neatd: REST → ${handle.restAddress}`)\n if (handle.otlpAddress) console.log(`neatd: OTLP → ${handle.otlpAddress}`)\n\n // ADR-059 — bring up the web UI alongside the daemon. Failure here aborts\n // start with the clear-error pattern from ADR-049 instead of silently\n // running headless.\n const skipWeb = process.env.NEAT_WEB_DISABLED === '1'\n let web: WebHandle | null = null\n if (!skipWeb) {\n try {\n web = await spawnWebUI(restPortFromEnv())\n } catch (err) {\n console.error((err as Error).message)\n await handle.stop().catch(() => {})\n process.exit(3)\n }\n } else {\n console.log('neatd: web UI disabled (NEAT_WEB_DISABLED=1)')\n }\n\n console.log('neatd: SIGHUP reloads, SIGTERM/SIGINT stops')\n\n let stopping = false\n const shutdown = (signal: NodeJS.Signals): void => {\n if (stopping) return\n stopping = true\n console.log(`neatd: ${signal} received, stopping…`)\n void Promise.allSettled([handle.stop(), web ? web.stop() : Promise.resolve()])\n .catch((err) => console.error(`neatd: shutdown error — ${(err as Error).message}`))\n .finally(() => process.exit(0))\n }\n process.on('SIGTERM', shutdown)\n process.on('SIGINT', shutdown)\n\n // Block forever — supervisors keep us in the foreground.\n await new Promise<void>(() => {})\n}\n\nasync function cmdStop(): Promise<void> {\n const pid = await readPid()\n if (pid === null) {\n console.error('neatd: no running daemon found (no PID file)')\n process.exit(1)\n }\n try {\n process.kill(pid, 'SIGTERM')\n console.log(`neatd: SIGTERM sent to PID ${pid}`)\n } catch (err) {\n console.error(`neatd: failed to signal PID ${pid} — ${(err as Error).message}`)\n process.exit(1)\n }\n}\n\nasync function cmdReload(): Promise<void> {\n const pid = await readPid()\n if (pid === null) {\n console.error('neatd: no running daemon found (no PID file)')\n process.exit(1)\n }\n try {\n process.kill(pid, 'SIGHUP')\n console.log(`neatd: SIGHUP sent to PID ${pid}`)\n } catch (err) {\n console.error(`neatd: failed to signal PID ${pid} — ${(err as Error).message}`)\n process.exit(1)\n }\n}\n\nasync function cmdStatus(): Promise<void> {\n const pid = await readPid()\n console.log(`pid: ${pid ?? '(not running)'}`)\n console.log(`registry: ${registryPath()}`)\n const webPort = process.env.NEAT_WEB_PORT\n ? Number.parseInt(process.env.NEAT_WEB_PORT, 10)\n : DEFAULT_WEB_PORT\n console.log(`web ui: http://localhost:${webPort}`)\n const projects = await listProjects().catch(() => [])\n if (projects.length === 0) {\n console.log('projects: (none)')\n return\n }\n console.log('projects:')\n for (const p of projects) {\n const seen = p.lastSeenAt ?? 'never'\n console.log(` ${p.name}\\t${p.status}\\t${p.path}\\tlast-seen=${seen}`)\n }\n}\n\nasync function main(): Promise<void> {\n const cmd = process.argv[2]\n if (!cmd || cmd === '-h' || cmd === '--help') {\n usage()\n process.exit(cmd ? 0 : 2)\n }\n\n if (cmd === 'start') return cmdStart()\n if (cmd === 'stop') return cmdStop()\n if (cmd === 'reload') return cmdReload()\n if (cmd === 'status') return cmdStatus()\n\n console.error(`neatd: unknown command \"${cmd}\"`)\n usage()\n process.exit(1)\n}\n\nconst entry = process.argv[1] ?? ''\nif (/[\\\\/]neatd\\.(?:cjs|js)$/.test(entry) || entry.endsWith('/neatd')) {\n main().catch((err) => {\n console.error(err)\n process.exit(1)\n })\n}\n","/**\n * Web UI spawn helper (ADR-059).\n *\n * `neatd start` brings up the REST API + OTel receivers, then calls\n * `spawnWebUI(restPort)` to launch the Next.js web shell as a child process.\n * Lifecycle is parent-tied: SIGTERM/SIGINT on neatd cascades to the child\n * via `stop()`.\n *\n * Default port `6328` (NEAT in T9). Override with `NEAT_WEB_PORT`.\n * Hard-fails on collision so the operator never loses track of which URL to\n * open.\n */\n\nimport { spawn, type ChildProcess } from 'node:child_process'\nimport net from 'node:net'\nimport path from 'node:path'\n\nexport const DEFAULT_WEB_PORT = 6328\n\nexport interface WebHandle {\n child: ChildProcess\n port: number\n stop: () => Promise<void>\n}\n\n/**\n * Best-effort port collision check before spawning. Binds, closes, returns.\n * Race condition between the check and the actual `next start` is acceptable\n * — Next.js will then fail loudly and the parent exits.\n */\nasync function assertPortFree(port: number): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n const tester = net.createServer()\n tester.once('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n reject(\n new Error(\n `neatd: web UI port ${port} in use; set NEAT_WEB_PORT to override or stop the conflicting process`,\n ),\n )\n } else {\n reject(err)\n }\n })\n tester.once('listening', () => {\n tester.close(() => resolve())\n })\n tester.listen(port, '127.0.0.1')\n })\n}\n\n/**\n * Resolve the web package directory. In a global install\n * (`npm install -g neat.is`) the package lives at\n * `node_modules/@neat.is/web/`; in the monorepo it's\n * `packages/web/`. `require.resolve` finds whichever one Node has on its\n * search path.\n */\nfunction resolveWebPackageDir(): string {\n const req = (typeof require !== 'undefined'\n ? require\n : // ESM fallback — daemon CJS bundle has `require`, but typecheck wants this\n (eval('require') as NodeRequire))\n const pkgJsonPath = req.resolve('@neat.is/web/package.json')\n return path.dirname(pkgJsonPath)\n}\n\nexport async function spawnWebUI(restPort: number): Promise<WebHandle> {\n const portRaw = process.env.NEAT_WEB_PORT\n const port = portRaw && portRaw.length > 0 ? Number.parseInt(portRaw, 10) : DEFAULT_WEB_PORT\n if (!Number.isFinite(port) || port <= 0 || port > 65535) {\n throw new Error(`neatd: invalid NEAT_WEB_PORT=\"${portRaw}\"`)\n }\n\n await assertPortFree(port)\n\n const cwd = resolveWebPackageDir()\n // ADR-059 #6 — child inherits NEAT_API_URL pointing at our REST server,\n // unless the operator pre-configured it.\n const env: NodeJS.ProcessEnv = {\n ...process.env,\n PORT: String(port),\n NEAT_API_URL: process.env.NEAT_API_URL ?? `http://localhost:${restPort}`,\n }\n\n // `npm exec next start` works under both monorepo and global install.\n // `detached: false` keeps the child in our process group so signals reach it.\n const child = spawn('npm', ['exec', '--', 'next', 'start', '-p', String(port)], {\n cwd,\n env,\n stdio: ['ignore', 'inherit', 'inherit'],\n detached: false,\n })\n\n child.on('error', (err) => {\n console.error(`neatd: web UI spawn error — ${err.message}`)\n })\n\n console.log(`neatd: web UI listening on http://localhost:${port}`)\n\n let stopped = false\n async function stop(): Promise<void> {\n if (stopped || !child.pid) return\n stopped = true\n try {\n child.kill('SIGTERM')\n } catch {\n /* already gone */\n }\n // Give the child up to 3s to exit gracefully, then SIGKILL.\n await new Promise<void>((resolve) => {\n const t = setTimeout(() => {\n try {\n child.kill('SIGKILL')\n } catch {\n /* gone */\n }\n resolve()\n }, 3000)\n child.once('exit', () => {\n clearTimeout(t)\n resolve()\n })\n })\n }\n\n return { child, port, stop }\n}\n"],"mappings":"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","names":["path","path"]}
|
|
1
|
+
{"version":3,"sources":["../src/neatd.ts","../src/web-spawn.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `neatd` — distribution-layer daemon CLI (ADR-049).\n *\n * Subcommands:\n * neatd start [--foreground] boot the daemon and watch the registry\n * neatd stop signal the running daemon to shut down\n * neatd reload signal the running daemon to re-read the registry\n * neatd status print PID + per-project last-seen timestamps\n *\n * MVP runs in foreground only. Backgrounding is the supervisor's job\n * (launchd / systemd / nohup) — `neatd start` blocks until SIGINT/SIGTERM.\n *\n * v0.2.10: also brings up the web UI on port 6328 by default (ADR-059).\n */\n\nimport { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport { startDaemon } from './daemon.js'\nimport { listProjects, registryPath } from './registry.js'\nimport { spawnWebUI, DEFAULT_WEB_PORT, type WebHandle } from './web-spawn.js'\n\nfunction neatHome(): string {\n if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {\n return path.resolve(process.env.NEAT_HOME)\n }\n const home = process.env.HOME ?? process.env.USERPROFILE ?? ''\n return path.join(home, '.neat')\n}\n\nasync function readPid(): Promise<number | null> {\n try {\n const raw = await fs.readFile(path.join(neatHome(), 'neatd.pid'), 'utf8')\n const n = Number.parseInt(raw.trim(), 10)\n return Number.isFinite(n) ? n : null\n } catch {\n return null\n }\n}\n\nfunction usage(): void {\n console.log('usage: neatd <start|stop|reload|status> [--foreground]')\n}\n\nfunction restPortFromEnv(): number {\n const raw = process.env.PORT\n return raw && raw.length > 0 ? Number.parseInt(raw, 10) : 8080\n}\n\nasync function cmdStart(): Promise<void> {\n const handle = await startDaemon()\n console.log(`neatd: started, PID ${process.pid}, ${handle.slots.size} project(s)`)\n console.log(`neatd: registry at ${registryPath()}`)\n // ADR-063 — surface the bound addresses so the operator can sanity-check\n // the documented happy path without grepping startup logs.\n if (handle.restAddress) console.log(`neatd: REST → ${handle.restAddress}`)\n if (handle.otlpAddress) console.log(`neatd: OTLP → ${handle.otlpAddress}`)\n\n // ADR-059 — bring up the web UI alongside the daemon. Failure here aborts\n // start with the clear-error pattern from ADR-049 instead of silently\n // running headless.\n const skipWeb = process.env.NEAT_WEB_DISABLED === '1'\n let web: WebHandle | null = null\n if (!skipWeb) {\n try {\n web = await spawnWebUI(restPortFromEnv())\n } catch (err) {\n console.error((err as Error).message)\n await handle.stop().catch(() => {})\n process.exit(3)\n }\n } else {\n console.log('neatd: web UI disabled (NEAT_WEB_DISABLED=1)')\n }\n\n console.log('neatd: SIGHUP reloads, SIGTERM/SIGINT stops')\n\n let stopping = false\n const shutdown = (signal: NodeJS.Signals): void => {\n if (stopping) return\n stopping = true\n console.log(`neatd: ${signal} received, stopping…`)\n void Promise.allSettled([handle.stop(), web ? web.stop() : Promise.resolve()])\n .catch((err) => console.error(`neatd: shutdown error — ${(err as Error).message}`))\n .finally(() => process.exit(0))\n }\n process.on('SIGTERM', shutdown)\n process.on('SIGINT', shutdown)\n\n // Block forever — supervisors keep us in the foreground.\n await new Promise<void>(() => {})\n}\n\nasync function cmdStop(): Promise<void> {\n const pid = await readPid()\n if (pid === null) {\n console.error('neatd: no running daemon found (no PID file)')\n process.exit(1)\n }\n try {\n process.kill(pid, 'SIGTERM')\n console.log(`neatd: SIGTERM sent to PID ${pid}`)\n } catch (err) {\n console.error(`neatd: failed to signal PID ${pid} — ${(err as Error).message}`)\n process.exit(1)\n }\n}\n\nasync function cmdReload(): Promise<void> {\n const pid = await readPid()\n if (pid === null) {\n console.error('neatd: no running daemon found (no PID file)')\n process.exit(1)\n }\n try {\n process.kill(pid, 'SIGHUP')\n console.log(`neatd: SIGHUP sent to PID ${pid}`)\n } catch (err) {\n console.error(`neatd: failed to signal PID ${pid} — ${(err as Error).message}`)\n process.exit(1)\n }\n}\n\nasync function cmdStatus(): Promise<void> {\n const pid = await readPid()\n console.log(`pid: ${pid ?? '(not running)'}`)\n console.log(`registry: ${registryPath()}`)\n const webPort = process.env.NEAT_WEB_PORT\n ? Number.parseInt(process.env.NEAT_WEB_PORT, 10)\n : DEFAULT_WEB_PORT\n console.log(`web ui: http://localhost:${webPort}`)\n const projects = await listProjects().catch(() => [])\n if (projects.length === 0) {\n console.log('projects: (none)')\n return\n }\n console.log('projects:')\n for (const p of projects) {\n const seen = p.lastSeenAt ?? 'never'\n console.log(` ${p.name}\\t${p.status}\\t${p.path}\\tlast-seen=${seen}`)\n }\n}\n\nasync function main(): Promise<void> {\n const cmd = process.argv[2]\n if (!cmd || cmd === '-h' || cmd === '--help') {\n usage()\n process.exit(cmd ? 0 : 2)\n }\n\n if (cmd === 'start') return cmdStart()\n if (cmd === 'stop') return cmdStop()\n if (cmd === 'reload') return cmdReload()\n if (cmd === 'status') return cmdStatus()\n\n console.error(`neatd: unknown command \"${cmd}\"`)\n usage()\n process.exit(1)\n}\n\nconst entry = process.argv[1] ?? ''\nif (/[\\\\/]neatd\\.(?:cjs|js)$/.test(entry) || entry.endsWith('/neatd')) {\n main().catch((err) => {\n console.error(err)\n process.exit(1)\n })\n}\n","/**\n * Web UI spawn helper (ADR-059).\n *\n * `neatd start` brings up the REST API + OTel receivers, then calls\n * `spawnWebUI(restPort)` to launch the Next.js web shell as a child process.\n * Lifecycle is parent-tied: SIGTERM/SIGINT on neatd cascades to the child\n * via `stop()`.\n *\n * Default port `6328` (NEAT in T9). Override with `NEAT_WEB_PORT`.\n * Hard-fails on collision so the operator never loses track of which URL to\n * open.\n */\n\nimport { spawn, type ChildProcess } from 'node:child_process'\nimport net from 'node:net'\nimport path from 'node:path'\n\nexport const DEFAULT_WEB_PORT = 6328\n\nexport interface WebHandle {\n child: ChildProcess\n port: number\n stop: () => Promise<void>\n}\n\n/**\n * Best-effort port collision check before spawning. Binds, closes, returns.\n * Race condition between the check and the actual `next start` is acceptable\n * — Next.js will then fail loudly and the parent exits.\n */\nasync function assertPortFree(port: number): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n const tester = net.createServer()\n tester.once('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n reject(\n new Error(\n `neatd: web UI port ${port} in use; set NEAT_WEB_PORT to override or stop the conflicting process`,\n ),\n )\n } else {\n reject(err)\n }\n })\n tester.once('listening', () => {\n tester.close(() => resolve())\n })\n tester.listen(port, '127.0.0.1')\n })\n}\n\n/**\n * Resolve the web package directory. In a global install\n * (`npm install -g neat.is`) the package lives at\n * `node_modules/@neat.is/web/`; in the monorepo it's\n * `packages/web/`. `require.resolve` finds whichever one Node has on its\n * search path.\n */\nfunction resolveWebPackageDir(): string {\n const req = (typeof require !== 'undefined'\n ? require\n : // ESM fallback — daemon CJS bundle has `require`, but typecheck wants this\n (eval('require') as NodeRequire))\n const pkgJsonPath = req.resolve('@neat.is/web/package.json')\n return path.dirname(pkgJsonPath)\n}\n\n/**\n * Locate the standalone server.js inside the web package. ADR-064 #2 — the\n * tarball ships `.next/standalone/packages/web/server.js` (path preserved\n * relative to the monorepo tracing root). Missing means the package was\n * published without `next build` having run, which the smoke-test gate\n * catches at publish time.\n */\nfunction resolveStandaloneServerEntry(webDir: string): string {\n return path.join(webDir, '.next/standalone/packages/web/server.js')\n}\n\nexport async function spawnWebUI(restPort: number): Promise<WebHandle> {\n const portRaw = process.env.NEAT_WEB_PORT\n const port = portRaw && portRaw.length > 0 ? Number.parseInt(portRaw, 10) : DEFAULT_WEB_PORT\n if (!Number.isFinite(port) || port <= 0 || port > 65535) {\n throw new Error(`neatd: invalid NEAT_WEB_PORT=\"${portRaw}\"`)\n }\n\n await assertPortFree(port)\n\n const cwd = resolveWebPackageDir()\n const serverEntry = resolveStandaloneServerEntry(cwd)\n // ADR-064 — fail loudly if the standalone build is missing. v0.3.0 shipped\n // without it; the symptom on the user side was `next start` aborting with\n // `Could not find a production build in the '.next' directory`. This check\n // catches it at the parent's bootstrap instead of letting the child crash\n // a few moments later with a worse error.\n try {\n require.resolve(serverEntry)\n } catch {\n throw new Error(\n `neatd: web UI standalone build missing at ${serverEntry}. ` +\n `The published @neat.is/web tarball should include it; if you're running from a ` +\n `monorepo checkout, run \\`npm run build --workspace @neat.is/web\\` first, or set ` +\n `NEAT_WEB_DISABLED=1 to skip the web UI.`,\n )\n }\n\n // ADR-059 #6 — child inherits NEAT_API_URL pointing at our REST server,\n // unless the operator pre-configured it.\n const env: NodeJS.ProcessEnv = {\n ...process.env,\n PORT: String(port),\n HOSTNAME: process.env.HOSTNAME ?? '0.0.0.0',\n NEAT_API_URL: process.env.NEAT_API_URL ?? `http://localhost:${restPort}`,\n }\n\n // The standalone bundle is self-contained — its own `node_modules` and\n // `package.json` sit alongside `server.js`. Spawn `node` against it\n // directly; no `next start` (which needs the source tree + build cache)\n // and no `npm exec` (which is monorepo-only in practice).\n // `detached: false` keeps the child in our process group so signals reach it.\n const child = spawn(process.execPath, [serverEntry], {\n cwd: path.dirname(serverEntry),\n env,\n stdio: ['ignore', 'inherit', 'inherit'],\n detached: false,\n })\n\n child.on('error', (err) => {\n console.error(`neatd: web UI spawn error — ${err.message}`)\n })\n\n console.log(`neatd: web UI listening on http://localhost:${port}`)\n\n let stopped = false\n async function stop(): Promise<void> {\n if (stopped || !child.pid) return\n stopped = true\n try {\n child.kill('SIGTERM')\n } catch {\n /* already gone */\n }\n // Give the child up to 3s to exit gracefully, then SIGKILL.\n await new Promise<void>((resolve) => {\n const t = setTimeout(() => {\n try {\n child.kill('SIGKILL')\n } catch {\n /* gone */\n }\n resolve()\n }, 3000)\n child.once('exit', () => {\n clearTimeout(t)\n resolve()\n })\n })\n }\n\n return { child, port, stop }\n}\n"],"mappings":"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","names":["path","path"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neat.is/core",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "NEAT graph engine: tree-sitter extraction, OTel ingest, REST API",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"homepage": "https://neat.is",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"@fastify/cors": "^10.0.0",
|
|
51
51
|
"@grpc/grpc-js": "^1.14.3",
|
|
52
52
|
"@grpc/proto-loader": "^0.8.0",
|
|
53
|
-
"@neat.is/types": "^0.3.
|
|
53
|
+
"@neat.is/types": "^0.3.2",
|
|
54
54
|
"chokidar": "^4.0.3",
|
|
55
55
|
"fastify": "^5.0.0",
|
|
56
56
|
"graphology": "^0.25.4",
|