Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 640f170de8 | |||
| 119b1f3722 | |||
| 96ea4b46ff | |||
| 57881533a8 | |||
| a62a993a82 |
@@ -56,9 +56,10 @@ pnpm -r run build
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Only publish packages that have version bumps
|
# Only publish packages that have version bumps
|
||||||
cd packages/core && npm publish --access public
|
# MUST use pnpm publish (not npm) — pnpm converts workspace:* to real versions
|
||||||
cd packages/daemon && npm publish --access public
|
cd packages/core && pnpm publish --access public --no-git-checks
|
||||||
cd packages/cli && npm publish --access public
|
cd packages/daemon && pnpm publish --access public --no-git-checks
|
||||||
|
cd packages/cli && pnpm publish --access public --no-git-checks
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5. Commit & tag
|
### 5. Commit & tag
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@uncaged/nerve-cli",
|
"name": "@uncaged/nerve-cli",
|
||||||
"version": "0.1.5",
|
"version": "0.1.7",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
"nerve": "dist/cli.js"
|
"nerve": "dist/cli.js"
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"prepublishOnly": "bash ../../scripts/prepublish-check.sh",
|
||||||
"build": "tsup",
|
"build": "tsup",
|
||||||
"test": "vitest run"
|
"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 { defineCommand, runMain } from "citty";
|
||||||
|
|
||||||
|
import { daemonCommand } from "./commands/daemon.js";
|
||||||
|
import { devCommand } from "./commands/dev.js";
|
||||||
import { initCommand } from "./commands/init.js";
|
import { initCommand } from "./commands/init.js";
|
||||||
import { logsCommand } from "./commands/logs.js";
|
import { logsCommand } from "./commands/logs.js";
|
||||||
import { senseCommand } from "./commands/sense.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 { statusCommand } from "./commands/status.js";
|
||||||
import { stopCommand } from "./commands/stop.js";
|
import { stopCommand } from "./commands/stop.js";
|
||||||
import { storeCommand } from "./commands/store.js";
|
import { storeCommand } from "./commands/store.js";
|
||||||
@@ -17,7 +19,9 @@ const main = defineCommand({
|
|||||||
},
|
},
|
||||||
subCommands: {
|
subCommands: {
|
||||||
init: initCommand,
|
init: initCommand,
|
||||||
start: startCommand,
|
daemon: daemonCommand,
|
||||||
|
dev: devCommand,
|
||||||
|
start: daemonStartCommand,
|
||||||
stop: stopCommand,
|
stop: stopCommand,
|
||||||
status: statusCommand,
|
status: statusCommand,
|
||||||
logs: logsCommand,
|
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 { defineCommand } from "citty";
|
||||||
|
|
||||||
import { runForegroundKernelSession } from "../run-foreground-kernel.js";
|
|
||||||
import { loadDaemonModule } from "../workspace-daemon.js";
|
|
||||||
import {
|
import {
|
||||||
getLogPath,
|
getLogPath,
|
||||||
getNerveRoot,
|
getNerveRoot,
|
||||||
@@ -52,15 +50,10 @@ function daemonBootstrapScript(): string {
|
|||||||
return bootstrapJs;
|
return bootstrapJs;
|
||||||
}
|
}
|
||||||
throw new Error(
|
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> {
|
async function runDaemon(nerveRoot: string): Promise<void> {
|
||||||
if (isRunning()) {
|
if (isRunning()) {
|
||||||
const pid = readPidFile();
|
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(`✅ Nerve daemon started (pid ${pid}).\n`);
|
||||||
process.stdout.write(` Logs: ${logPath}\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: {
|
meta: {
|
||||||
name: "start",
|
name: "start",
|
||||||
description: "Start the nerve daemon",
|
description: "Start the nerve daemon in the background",
|
||||||
},
|
},
|
||||||
args: {
|
async run() {
|
||||||
daemon: {
|
await runDaemonStartCommand();
|
||||||
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);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,44 +15,49 @@ async function waitForExit(pid: number, timeoutMs: number): Promise<boolean> {
|
|||||||
return false;
|
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({
|
export const stopCommand = defineCommand({
|
||||||
meta: {
|
meta: {
|
||||||
name: "stop",
|
name: "stop",
|
||||||
description: "Stop the nerve daemon",
|
description: "Stop the nerve daemon",
|
||||||
},
|
},
|
||||||
async run() {
|
async run() {
|
||||||
const pid = readPidFile();
|
await runStopCommand();
|
||||||
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");
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
import { runForegroundKernelSession } from "./run-foreground-kernel.js";
|
import { runForegroundKernelSession } from "./run-foreground-kernel.js";
|
||||||
import { loadDaemonModule } from "./workspace-daemon.js";
|
import { loadDaemonModule } from "./workspace-daemon.js";
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "@uncaged/nerve-core",
|
"name": "@uncaged/nerve-core",
|
||||||
"version": "0.1.3",
|
"version": "0.1.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"prepublishOnly": "bash ../../scripts/prepublish-check.sh",
|
||||||
"build": "tsup",
|
"build": "tsup",
|
||||||
"test": "vitest run"
|
"test": "vitest run"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@uncaged/nerve-daemon",
|
"name": "@uncaged/nerve-daemon",
|
||||||
"version": "0.1.4",
|
"version": "0.1.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"prepublishOnly": "bash ../../scripts/prepublish-check.sh",
|
||||||
"build": "tsup",
|
"build": "tsup",
|
||||||
"test": "vitest run"
|
"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