New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details
Socket
Book a DemoSign in
Socket

@codeyam/codeyam-cli

Package Overview
Dependencies
Maintainers
1
Versions
136
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@codeyam/codeyam-cli

Local development CLI for CodeYam analysis

npmnpm
Version
0.1.0-staging.8aea589
Version published
Weekly downloads
1.2K
-39.27%
Maintainers
1
Weekly downloads
 
Created
Source

Process Management System

A comprehensive, test-driven process management system for CodeYam that provides:

  • Automatic tracking and registration of all spawned processes
  • Graceful shutdown with signal escalation (SIGINT → SIGTERM → SIGKILL)
  • Parent-child process relationships
  • Process metadata and querying capabilities
  • Global cleanup on exit/error/signal

Test Coverage

✅ 57 tests passing across 3 test suites:

  • ProcessManager.test.ts - 32 tests for core functionality
  • GlobalProcessManager.test.ts - 8 tests for singleton and signal handling
  • managedExecAsync.test.ts - 17 tests for the managed spawn wrapper

Architecture

Core Components

  • ProcessManager (ProcessManager.ts)

    • Central registry for tracking process lifecycle
    • Manages state transitions (running → completed/failed/killed)
    • Supports parent-child relationships
    • Event emitter for process lifecycle events
  • GlobalProcessManager (GlobalProcessManager.ts)

    • Singleton wrapper around ProcessManager
    • Installs signal handlers for graceful shutdown
    • Ensures cleanup on SIGINT, SIGTERM, uncaughtException, unhandledRejection
  • managedExecAsync (managedExecAsync.ts)

    • Drop-in replacement for spawn() with automatic lifecycle management
    • Registers processes with the global manager
    • Supports all spawn options plus additional metadata

Quick Start

1. Add Signal Handlers to CLI Entry Points

At the top of each CLI entry point (e.g., runLocally.ts, orchestrate.ts):

import { installSignalHandlers } from './lib/process';

// Install once at the top of the file
installSignalHandlers();

// Rest of your code...

This ensures ALL processes are cleaned up when:

  • User presses Ctrl+C (SIGINT)
  • Process receives SIGTERM
  • Uncaught exception occurs
  • Unhandled promise rejection occurs

2. Use managedExecAsync Instead of spawn

Before:

import { spawn } from 'child_process';

const process = spawn('node', ['dist/analyzer/start.js'], {
  detached: true,
  env: { ...process.env, CODEYAM_PROCESS_NAME: 'analyzer' },
});

// No automatic cleanup, must manually track

After:

import { managedExecAsync, ProcessType } from './lib/process';

const exitCode = await managedExecAsync({
  command: 'node',
  args: ['dist/analyzer/start.js'],
  processType: ProcessType.Analyzer,
  processName: 'main-analyzer',
  metadata: { projectId: 'abc123' },
});

// Automatically tracked and cleaned up on exit!

3. Manual Process Control

For cases where you need more control:

import { getGlobalProcessManager, ProcessType } from './lib/process';

const manager = getGlobalProcessManager();

// List all running processes
const allProcesses = manager.listAll();
console.log(`Running ${allProcesses.length} processes`);

// Find specific processes
const servers = manager.listByType(ProcessType.Server);
const analyzersByName = manager.findByName('main-analyzer');

// Shutdown specific processes
await manager.shutdown(processId);

// Shutdown all processes of a type
await manager.shutdownByType(ProcessType.Capture);

// Shutdown everything
await manager.shutdownAll();

Process Types

enum ProcessType {
  Server = 'server', // Web servers (Next.js, Remix, etc.)
  Analyzer = 'analyzer', // Analysis workers
  Capture = 'capture', // Playwright capture processes
  Controller = 'controller', // Controller servers
  Worker = 'worker', // Worker threads
  Project = 'project', // Project-specific processes
  Other = 'other', // Miscellaneous
}

Process States

enum ProcessState {
  Running = 'running', // Process is currently running
  Completed = 'completed', // Process exited normally (exit code 0)
  Failed = 'failed', // Process exited with non-zero code
  Killed = 'killed', // Process was killed by signal
}

Advanced Usage

Parent-Child Relationships

Track hierarchical process relationships:

// Start parent
const { processId: parentId } = managedExecAsync({
  command: 'node',
  args: ['server.js'],
  processType: ProcessType.Server,
  processName: 'main-server',
  returnProcessId: true,
});

// Start child with parent reference
const { processId: childId } = managedExecAsync({
  command: 'node',
  args: ['worker.js'],
  processType: ProcessType.Worker,
  processName: 'worker-1',
  parentId,
  returnProcessId: true,
});

// Shutdown parent and all children
await manager.shutdown(parentId, { shutdownChildren: true });

Process Metadata

Store arbitrary metadata with processes:

await managedExecAsync({
  command: 'node',
  args: ['analyze.js'],
  processType: ProcessType.Analyzer,
  processName: 'project-analyzer',
  metadata: {
    projectId: 'project-123',
    commitSha: 'abc123def',
    analysisType: 'full',
    startedBy: 'user@example.com',
  },
});

// Query later
const analyzers = manager.listByType(ProcessType.Analyzer);
analyzers.forEach((proc) => {
  console.log(`Analyzing ${proc.metadata?.projectId}`);
});

Stream Output

Capture stdout/stderr:

const output: string[] = [];
const errors: string[] = [];

await managedExecAsync({
  command: 'npm',
  args: ['run', 'build'],
  processType: ProcessType.Other,
  processName: 'build',
  onStdout: (data) => output.push(data.toString()),
  onStderr: (data) => errors.push(data.toString()),
});

console.log('Build output:', output.join(''));

Abort Signals

Cancel processes programmatically:

const abortController = new AbortController();

const buildPromise = managedExecAsync({
  command: 'npm',
  args: ['run', 'build'],
  processType: ProcessType.Other,
  signal: abortController.signal,
});

// Cancel after 30 seconds
setTimeout(() => abortController.abort(), 30000);

await buildPromise; // Will be killed if timeout reached

Event Listeners

React to process lifecycle events:

const manager = getGlobalProcessManager();

manager.on('processStarted', (info) => {
  console.log(`Started ${info.name} (PID: ${info.pid})`);
});

manager.on('processExited', (info) => {
  console.log(`Exited ${info.name} with code ${info.exitCode}`);
});

Integration Examples

Example 1: Update runLocally.ts

import {
  installSignalHandlers,
  managedExecAsync,
  ProcessType,
} from './lib/process';

installSignalHandlers();

async function runLocally(args: RunLocallyArgs) {
  // Start analyzer
  const analyzerExitCode = await managedExecAsync({
    command: 'node',
    args: ['dist/project/start.js', ...buildArgs(args)],
    processType: ProcessType.Analyzer,
    processName: 'local-analyzer',
    metadata: { projectSlug: args.projectSlug },
    env: {
      ...process.env,
      PROJECT_SLUG: args.projectSlug,
    },
  });

  if (analyzerExitCode !== 0) {
    throw new Error(`Analyzer failed with code ${analyzerExitCode}`);
  }

  // All processes automatically cleaned up on exit
}

Example 2: Update orchestrate.ts

import {
  installSignalHandlers,
  managedExecAsync,
  getGlobalProcessManager,
  ProcessType,
} from './lib/process';

installSignalHandlers();

async function runOrchestration(projectSlug: string) {
  const manager = getGlobalProcessManager();

  // Start server
  const { processId: serverId } = managedExecAsync({
    command: 'pnpm',
    args: ['runLocally:no-build', projectSlug],
    processType: ProcessType.Server,
    processName: 'project-server',
    metadata: { projectSlug },
    returnProcessId: true,
  });

  // Start capture with parent relationship
  const { processId: captureId } = managedExecAsync({
    command: 'pnpm',
    args: ['captureStatic:no-build', projectSlug],
    processType: ProcessType.Capture,
    processName: 'screenshot-capture',
    parentId: serverId,
    metadata: { projectSlug },
    returnProcessId: true,
  });

  // Monitor progress
  const checkProgress = setInterval(() => {
    const capture = manager.getInfo(captureId);
    if (capture?.state !== 'running') {
      clearInterval(checkProgress);
    }
  }, 1000);

  // Cleanup happens automatically via signal handlers
}

Cleanup Behavior

Automatic Cleanup Triggers

The system automatically cleans up ALL managed processes when:

  • Normal Exit: Process exits normally
  • SIGINT: User presses Ctrl+C
  • SIGTERM: System sends termination signal
  • Uncaught Exception: Unhandled error occurs
  • Unhandled Rejection: Promise rejection not caught

Cleanup Strategy

For each process, the system:

  • Sends SIGINT and waits up to 5 seconds
  • If still running, sends SIGTERM and waits up to 5 seconds
  • If still running, sends SIGKILL and waits up to 2 seconds
  • Recursively kills all child processes first (bottom-up)

Manual Cleanup

You can also manually clean up:

const manager = getGlobalProcessManager();

// Cleanup completed processes older than 1 minute
manager.cleanupCompleted({ retentionMs: 60000 });

// Shutdown all and cleanup immediately
await manager.shutdownAll();
manager.cleanupCompleted({ retentionMs: 0 });

Migration Guide

Migrating from execAsync

The old execAsync function can be gradually replaced:

// Old way
import execAsync from './lib/virtualized/common/execAsync';

await execAsync({
  command: 'node',
  args: ['script.js'],
  env: { CODEYAM_PROCESS_NAME: 'my-script' },
  // ... other options
});

// New way
import { managedExecAsync, ProcessType } from './lib/process';

await managedExecAsync({
  command: 'node',
  args: ['script.js'],
  processType: ProcessType.Other,
  processName: 'my-script',
  env: { CUSTOM_VAR: 'value' },
  // ... other options
});

Migrating Process Tracking

Old manual tracking code:

const processMap: ProcessMap = { server: undefined, capture: undefined };

const serverProcess = spawn('node', ['server.js']);
processMap.server = serverProcess;

// ... later ...
if (processMap.server) {
  await killProcess(processMap.server.pid);
}

New automatic tracking:

const { processId } = managedExecAsync({
  command: 'node',
  args: ['server.js'],
  processType: ProcessType.Server,
  processName: 'server',
  returnProcessId: true,
});

// ... later ...
const manager = getGlobalProcessManager();
await manager.shutdown(processId);

// Or just let signal handlers clean up automatically!

Testing

Run the test suite:

# Run all process management tests
npx jest background/src/lib/process/__tests__/

# Run specific test file
npx jest background/src/lib/process/__tests__/ProcessManager.test.ts

# Run with coverage
npx jest background/src/lib/process/__tests__/ --coverage

Troubleshooting

Processes Not Cleaning Up

If processes aren't being cleaned up:

  • Ensure installSignalHandlers() is called at the top of your CLI entry point
  • Check that you're using managedExecAsync instead of raw spawn()
  • Verify the global process manager is being used

Orphaned Processes

If you find orphaned processes:

# Find all CodeYam processes
ps aux | grep CODEYAM

# Kill all CodeYam processes
pkill -f CODEYAM_PROCESS_NAME

Test Pollution

If tests are interfering with each other:

afterEach(async () => {
  const manager = getGlobalProcessManager();
  await manager.shutdownAll();
  manager.cleanupCompleted({ retentionMs: 0 });
});

Future Enhancements

Potential improvements:

  • Persistent process registry (SQLite) for cross-restart tracking
  • Process resource monitoring (CPU, memory)
  • Automatic restart on failure
  • Process pooling and reuse
  • Integration with Docker container lifecycle
  • Web dashboard for process monitoring
  • Structured logging integration
  • Metrics/telemetry export

API Reference

See the inline documentation in:

  • ProcessManager.ts - Core process management
  • GlobalProcessManager.ts - Singleton and signal handling
  • managedExecAsync.ts - Managed spawn wrapper
  • index.ts - Exported API

Keywords

codeyam

FAQs

Package last updated on 09 Dec 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts