Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 640f170de8 | |||
| 119b1f3722 | |||
| 96ea4b46ff | |||
| 57881533a8 | |||
| a62a993a82 |
@@ -56,9 +56,10 @@ pnpm -r run build
|
||||
|
||||
```bash
|
||||
# Only publish packages that have version bumps
|
||||
cd packages/core && npm publish --access public
|
||||
cd packages/daemon && npm publish --access public
|
||||
cd packages/cli && npm publish --access public
|
||||
# MUST use pnpm publish (not npm) — pnpm converts workspace:* to real versions
|
||||
cd packages/core && pnpm publish --access public --no-git-checks
|
||||
cd packages/daemon && pnpm publish --access public --no-git-checks
|
||||
cd packages/cli && pnpm publish --access public --no-git-checks
|
||||
```
|
||||
|
||||
### 5. Commit & tag
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@uncaged/nerve-cli",
|
||||
"version": "0.1.5",
|
||||
"version": "0.1.7",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"nerve": "dist/cli.js"
|
||||
@@ -14,6 +14,7 @@
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"prepublishOnly": "bash ../../scripts/prepublish-check.sh",
|
||||
"build": "tsup",
|
||||
"test": "vitest run"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { daemonCommand } from "../commands/daemon.js";
|
||||
import { devCommand } from "../commands/dev.js";
|
||||
import { daemonStartCommand } from "../commands/start.js";
|
||||
|
||||
describe("nerve daemon command group", () => {
|
||||
it("exposes start, stop, status, restart, and logs subcommands", () => {
|
||||
const subs = daemonCommand.subCommands;
|
||||
expect(subs).toBeDefined();
|
||||
if (!subs) {
|
||||
throw new Error("expected daemonCommand.subCommands");
|
||||
}
|
||||
expect(Object.keys(subs).sort()).toEqual(["logs", "restart", "start", "status", "stop"]);
|
||||
});
|
||||
|
||||
it("shares the same start command object as top-level nerve start alias", () => {
|
||||
const subs = daemonCommand.subCommands;
|
||||
expect(subs?.start).toBe(daemonStartCommand);
|
||||
});
|
||||
});
|
||||
|
||||
describe("nerve dev", () => {
|
||||
it("is a foreground dev command", () => {
|
||||
expect(devCommand.meta?.name).toBe("dev");
|
||||
expect(devCommand.meta?.description).toMatch(/foreground/i);
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,11 @@
|
||||
import { defineCommand, runMain } from "citty";
|
||||
|
||||
import { daemonCommand } from "./commands/daemon.js";
|
||||
import { devCommand } from "./commands/dev.js";
|
||||
import { initCommand } from "./commands/init.js";
|
||||
import { logsCommand } from "./commands/logs.js";
|
||||
import { senseCommand } from "./commands/sense.js";
|
||||
import { startCommand } from "./commands/start.js";
|
||||
import { daemonStartCommand } from "./commands/start.js";
|
||||
import { statusCommand } from "./commands/status.js";
|
||||
import { stopCommand } from "./commands/stop.js";
|
||||
import { storeCommand } from "./commands/store.js";
|
||||
@@ -17,7 +19,9 @@ const main = defineCommand({
|
||||
},
|
||||
subCommands: {
|
||||
init: initCommand,
|
||||
start: startCommand,
|
||||
daemon: daemonCommand,
|
||||
dev: devCommand,
|
||||
start: daemonStartCommand,
|
||||
stop: stopCommand,
|
||||
status: statusCommand,
|
||||
logs: logsCommand,
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { defineCommand } from "citty";
|
||||
|
||||
import { logsCommand } from "./logs.js";
|
||||
import { daemonStartCommand, runDaemonStartCommand } from "./start.js";
|
||||
import { statusCommand } from "./status.js";
|
||||
import { runStopCommand, stopCommand } from "./stop.js";
|
||||
|
||||
const daemonRestartCommand = defineCommand({
|
||||
meta: {
|
||||
name: "restart",
|
||||
description: "Stop then start the nerve daemon",
|
||||
},
|
||||
async run() {
|
||||
await runStopCommand();
|
||||
await runDaemonStartCommand();
|
||||
},
|
||||
});
|
||||
|
||||
export const daemonCommand = defineCommand({
|
||||
meta: {
|
||||
name: "daemon",
|
||||
description: "Manage the nerve background daemon",
|
||||
},
|
||||
subCommands: {
|
||||
start: daemonStartCommand,
|
||||
stop: stopCommand,
|
||||
status: statusCommand,
|
||||
restart: daemonRestartCommand,
|
||||
logs: logsCommand,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { defineCommand } from "citty";
|
||||
|
||||
import { runForegroundKernelSession } from "../run-foreground-kernel.js";
|
||||
import { loadDaemonModule } from "../workspace-daemon.js";
|
||||
import { getNerveRoot } from "../workspace.js";
|
||||
|
||||
export const devCommand = defineCommand({
|
||||
meta: {
|
||||
name: "dev",
|
||||
description: "Run the nerve kernel in the foreground (development mode)",
|
||||
},
|
||||
async run() {
|
||||
const nerveRoot = getNerveRoot();
|
||||
const { createKernel } = await loadDaemonModule(nerveRoot);
|
||||
await runForegroundKernelSession(nerveRoot, createKernel);
|
||||
},
|
||||
});
|
||||
@@ -5,8 +5,6 @@ import { fileURLToPath } from "node:url";
|
||||
|
||||
import { defineCommand } from "citty";
|
||||
|
||||
import { runForegroundKernelSession } from "../run-foreground-kernel.js";
|
||||
import { loadDaemonModule } from "../workspace-daemon.js";
|
||||
import {
|
||||
getLogPath,
|
||||
getNerveRoot,
|
||||
@@ -52,15 +50,10 @@ function daemonBootstrapScript(): string {
|
||||
return bootstrapJs;
|
||||
}
|
||||
throw new Error(
|
||||
`daemon-bootstrap.js not found next to CLI at ${bootstrapJs}. Build the CLI package (e.g. \`pnpm --filter @uncaged/nerve-cli build\`) before using background mode (\`nerve start -d\`).`,
|
||||
`daemon-bootstrap.js not found next to CLI at ${bootstrapJs}. Build the CLI package (e.g. \`pnpm --filter @uncaged/nerve-cli build\`) before using \`nerve daemon start\`.`,
|
||||
);
|
||||
}
|
||||
|
||||
async function runForeground(nerveRoot: string): Promise<void> {
|
||||
const { createKernel } = await loadDaemonModule(nerveRoot);
|
||||
await runForegroundKernelSession(nerveRoot, createKernel);
|
||||
}
|
||||
|
||||
async function runDaemon(nerveRoot: string): Promise<void> {
|
||||
if (isRunning()) {
|
||||
const pid = readPidFile();
|
||||
@@ -110,29 +103,20 @@ async function runDaemon(nerveRoot: string): Promise<void> {
|
||||
|
||||
process.stdout.write(`✅ Nerve daemon started (pid ${pid}).\n`);
|
||||
process.stdout.write(` Logs: ${logPath}\n`);
|
||||
process.stdout.write(" Run `nerve stop` to stop.\n");
|
||||
process.stdout.write(" Run `nerve daemon stop` (or `nerve stop`) to stop.\n");
|
||||
}
|
||||
|
||||
export const startCommand = defineCommand({
|
||||
/** Background daemon only — use `nerve dev` for foreground mode. */
|
||||
export async function runDaemonStartCommand(): Promise<void> {
|
||||
await runDaemon(getNerveRoot());
|
||||
}
|
||||
|
||||
export const daemonStartCommand = defineCommand({
|
||||
meta: {
|
||||
name: "start",
|
||||
description: "Start the nerve daemon",
|
||||
description: "Start the nerve daemon in the background",
|
||||
},
|
||||
args: {
|
||||
daemon: {
|
||||
type: "boolean",
|
||||
alias: "d",
|
||||
description: "Run as background daemon",
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
async run({ args }) {
|
||||
const nerveRoot = getNerveRoot();
|
||||
|
||||
if (args.daemon) {
|
||||
await runDaemon(nerveRoot);
|
||||
} else {
|
||||
await runForeground(nerveRoot);
|
||||
}
|
||||
async run() {
|
||||
await runDaemonStartCommand();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -15,44 +15,49 @@ async function waitForExit(pid: number, timeoutMs: number): Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Core stop logic — also used by `nerve daemon restart`. */
|
||||
export async function runStopCommand(): Promise<void> {
|
||||
const pid = readPidFile();
|
||||
if (pid === null) {
|
||||
process.stdout.write("⚠️ No PID file found — daemon may not be running.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isRunning()) {
|
||||
process.stdout.write("⚠️ Daemon is not running (stale PID file). Cleaning up.\n");
|
||||
removePidFile();
|
||||
return;
|
||||
}
|
||||
|
||||
process.stdout.write(`Stopping nerve daemon (pid ${pid})…\n`);
|
||||
try {
|
||||
process.kill(pid, "SIGTERM");
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : String(e);
|
||||
process.stderr.write(`❌ Failed to send SIGTERM: ${msg}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const graceful = await waitForExit(pid, 10_000);
|
||||
if (!graceful) {
|
||||
process.stdout.write("⚠️ Daemon did not exit in 10s — sending SIGKILL.\n");
|
||||
try {
|
||||
process.kill(pid, "SIGKILL");
|
||||
} catch {
|
||||
// already dead
|
||||
}
|
||||
}
|
||||
|
||||
removePidFile();
|
||||
process.stdout.write("✅ Daemon stopped.\n");
|
||||
}
|
||||
|
||||
export const stopCommand = defineCommand({
|
||||
meta: {
|
||||
name: "stop",
|
||||
description: "Stop the nerve daemon",
|
||||
},
|
||||
async run() {
|
||||
const pid = readPidFile();
|
||||
if (pid === null) {
|
||||
process.stdout.write("⚠️ No PID file found — daemon may not be running.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isRunning()) {
|
||||
process.stdout.write("⚠️ Daemon is not running (stale PID file). Cleaning up.\n");
|
||||
removePidFile();
|
||||
return;
|
||||
}
|
||||
|
||||
process.stdout.write(`Stopping nerve daemon (pid ${pid})…\n`);
|
||||
try {
|
||||
process.kill(pid, "SIGTERM");
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : String(e);
|
||||
process.stderr.write(`❌ Failed to send SIGTERM: ${msg}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const graceful = await waitForExit(pid, 10_000);
|
||||
if (!graceful) {
|
||||
process.stdout.write("⚠️ Daemon did not exit in 10s — sending SIGKILL.\n");
|
||||
try {
|
||||
process.kill(pid, "SIGKILL");
|
||||
} catch {
|
||||
// already dead
|
||||
}
|
||||
}
|
||||
|
||||
removePidFile();
|
||||
process.stdout.write("✅ Daemon stopped.\n");
|
||||
await runStopCommand();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { runForegroundKernelSession } from "./run-foreground-kernel.js";
|
||||
import { loadDaemonModule } from "./workspace-daemon.js";
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"name": "@uncaged/nerve-core",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.4",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"prepublishOnly": "bash ../../scripts/prepublish-check.sh",
|
||||
"build": "tsup",
|
||||
"test": "vitest run"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@uncaged/nerve-daemon",
|
||||
"version": "0.1.4",
|
||||
"version": "0.1.5",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
@@ -11,6 +11,7 @@
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"prepublishOnly": "bash ../../scripts/prepublish-check.sh",
|
||||
"build": "tsup",
|
||||
"test": "vitest run"
|
||||
},
|
||||
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
# All packages must use pnpm publish. Block npm publish unconditionally.
|
||||
|
||||
if [ -z "$npm_execpath" ] || [[ "$npm_execpath" != *pnpm* ]]; then
|
||||
echo "❌ Use 'pnpm publish' instead of 'npm publish'."
|
||||
echo " pnpm auto-converts workspace:* dependencies to real versions."
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user