@procedures/node
v0.1.0
Published
Node.js process procedures: run scripts, spawn and manage long-running processes, with output caps and process-tree cleanup.
Maintainers
Readme
@procedures/node
Node.js process management procedures for the client ecosystem. Provides procedures for running scripts, spawning long-running processes, and managing process lifecycles.
Architecture Overview
┌─────────────────────────────────────────────────────────────────────────────┐
│ Client Application │
│ │
│ await client.exec(["node", "run"], { script: "build.js" }) │
│ await client.exec(["node", "spawn"], { script: "server.js" }) │
│ await client.exec(["node", "kill"], { processId: "abc123" }) │
│ await client.exec(["node", "status"], {}) │
│ │
└─────────────────────────────────┬───────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Procedure Registry │
│ │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ ┌─────────────┐ │
│ │ node.run │ │ node.spawn │ │ node.kill │ │ node.status │ │
│ │ │ │ │ │ │ │ │ │
│ │ Run to │ │ Start │ │ Kill by │ │ List all │ │
│ │ completion │ │ detached │ │ process ID │ │ processes │ │
│ └───────┬───────┘ └───────┬───────┘ └───────┬───────┘ └──────┬──────┘ │
│ │ │ │ │ │
└───────────┼──────────────────┼──────────────────┼─────────────────┼────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ ProcessManager (Singleton) │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Active Processes Map │ │
│ │ │ │
│ │ processId: "abc123" ──► { script, pid, startedAt, status } │ │
│ │ processId: "def456" ──► { script, pid, startedAt, status } │ │
│ │ processId: "ghi789" ──► { script, pid, startedAt, status } │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Node.js child_process │
│ │
│ spawn("node", ["script.js"], { detached: true, stdio: "pipe" }) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘Installation
pnpm add @procedures/nodeProcedures
node.run
Run a Node.js script to completion and return the output.
import { Client } from "@procedures/client";
const result = await client.exec<{
stdout: string;
stderr: string;
exitCode: number;
}>(["node", "run"], {
script: "./scripts/build.js",
args: ["--production"],
cwd: "/path/to/project",
env: { NODE_ENV: "production" },
timeout: 60000 // 1 minute timeout
});
console.log("Exit code:", result.exitCode);
console.log("Output:", result.stdout);Input Schema:
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| script | string | Yes | Path to script file |
| args | string[] | No | Command line arguments |
| cwd | string | No | Working directory |
| env | Record<string, string> | No | Environment variables |
| timeout | number | No | Timeout in milliseconds. At the timeout, the script and its child processes are killed, and the call throws. |
| maxOutputBytes | number | No | The most bytes kept of each output stream (default: 64 MiB) |
The script runs with the Node.js of the host (process.execPath). The signal of the call also kills the script and its child processes.
Output Schema:
| Field | Type | Description |
|-------|------|-------------|
| stdout | string | Standard output |
| stderr | string | Standard error |
| exitCode | number | Process exit code |
| truncated | boolean | Present when an output stream was longer than maxOutputBytes |
node.spawn
Start a long-running Node.js process. The process is a record of the process registry of client-shell: node.status shows it, node.kill ends it, and it ends with the host.
const result = await client.exec<{
processId: string;
pid: number;
}>(["node", "spawn"], {
script: "./server.js",
args: ["--port", "3000"],
cwd: "/path/to/project",
env: { NODE_ENV: "production" },
ready: { pattern: "Server listening on", timeout: 30000 } // Wait for this output
});
console.log("Process ID:", result.processId);
console.log("PID:", result.pid);Input Schema:
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| script | string | Yes | Path to script file |
| args | string[] | No | Command line arguments |
| cwd | string | No | Working directory |
| env | Record<string, string> | No | Environment variables |
| ready | { pattern: string; timeout?: number } | No | Wait until the output matches pattern (a regular expression). When the process does not get ready, the procedure stops it and throws. |
Output Schema:
| Field | Type | Description |
|-------|------|-------------|
| processId | string | Internal process identifier |
| pid | number | System process ID |
node.kill
Kill a spawned process by its process ID, with the processes that it started, and wait for its end.
const result = await client.exec<{
success: boolean;
}>(["node", "kill"], {
processId: "abc123"
});
if (result.success) {
console.log("Process killed successfully");
}Input Schema:
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| processId | string | Yes | Process ID from spawn |
Output Schema:
| Field | Type | Description |
|-------|------|-------------|
| success | boolean | Whether kill succeeded |
node.status
Get status of all running processes or a specific one.
// Get all processes
const result = await client.exec<{
processes: ProcessInfo[];
}>(["node", "status"], {});
// Get specific process
const result = await client.exec<{
processes: ProcessInfo[];
}>(["node", "status"], {
processId: "abc123"
});
for (const proc of result.processes) {
console.log(`${proc.processId}: ${proc.status} (PID: ${proc.pid})`);
}Input Schema:
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| processId | string | No | Specific process ID to query |
| output | boolean | No | Also give the end of the output of each process (the last 64 KiB) |
Output Schema:
| Field | Type | Description |
|-------|------|-------------|
| processes | ProcessInfo[] | Array of process information |
ProcessInfo:
| Field | Type | Description |
|-------|------|-------------|
| processId | string | Internal process identifier |
| script | string | Script path |
| pid | number | System process ID |
| status | "running" \| "stopped" | Process status |
| startedAt | string | ISO timestamp when started |
Process Lifecycle
┌─────────────┐ spawn() ┌─────────────┐
│ │ ───────────────► │ │
│ (none) │ │ running │
│ │ │ │
└─────────────┘ └──────┬──────┘
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
kill() called process exits crash/error
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ stopped │ │ stopped │ │ stopped │
│ (killed) │ │ (exited) │ │ (crashed) │
└─────────────┘ └─────────────┘ └─────────────┘Ready Pattern Detection
To wait until a server is ready, give ready.pattern (and optionally ready.timeout in milliseconds):
// The spawn resolves when "Server listening on port" appears in stdout
const result = await client.exec(["node", "spawn"], {
script: "./server.js",
ready: { pattern: "Server listening on port", timeout: 30000 },
});Timeline:
spawn() called
│
▼
┌─────────────────────────────────────────────┐
│ Process Starting │
│ │
│ stdout: "Loading configuration..." │
│ stdout: "Connecting to database..." │
│ stdout: "Server listening on port 3000" ◄──┼── Pattern matched!
│ │
└─────────────────────────────────────────────┘
│
▼
Promise resolves with { processId, pid }Integration Example
import { Client, LocalTransport, PROCEDURE_REGISTRY } from "@procedures/client";
import "@procedures/node"; // registers its procedures
const client = new Client(new LocalTransport({ registry: PROCEDURE_REGISTRY }));
// Start a dev server
const server = await client.exec(["node", "spawn"], {
script: "./server.js",
args: ["--port", "3000"],
ready: { pattern: "listening on" },
env: { NODE_ENV: "development" }
});
console.log(`Server started with PID ${server.pid}`);
// Check status
const status = await client.exec(["node", "status"], {
processId: server.processId
});
console.log(`Status: ${status.processes[0].status}`);
// Cleanup
await client.exec(["node", "kill"], {
processId: server.processId
});Error Handling
try {
const result = await client.exec(["node", "run"], {
script: "./nonexistent.js"
});
} catch (error) {
if (error.code === "ENOENT") {
console.error("Script not found");
} else if (error.code === "TIMEOUT") {
console.error("Script timed out");
}
}Auto-Registration
Import the register module to auto-register all procedures:
import "@procedures/node"; // registers its proceduresOr register manually:
import { registerNodeProcedures } from "@procedures/node";
registerNodeProcedures();Related Packages
@procedures/client- Core client framework@procedures/shell- Shell command execution@procedures/vite- Vite dev server management
License
MIT
