
Research
Shai-Hulud Descends to Hades: Miasma Worm Campaign Spreads with New PyPI Wave
Socket found 37 malicious PyPI wheels that abuse Python startup hooks to launch a Bun-powered credential stealer tied to Mini Shai-Hulud/Miasma.
@codeyam/codeyam-cli
Advanced tools
A comprehensive, test-driven process management system for CodeYam that provides:
✅ 57 tests passing across 3 test suites:
ProcessManager.test.ts - 32 tests for core functionalityGlobalProcessManager.test.ts - 8 tests for singleton and signal handlingmanagedExecAsync.test.ts - 17 tests for the managed spawn wrapperProcessManager (ProcessManager.ts)
GlobalProcessManager (GlobalProcessManager.ts)
managedExecAsync (managedExecAsync.ts)
spawn() with automatic lifecycle managementAt 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:
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
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!
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();
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
}
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
}
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 });
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}`);
});
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(''));
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
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}`);
});
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
}
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
}
The system automatically cleans up ALL managed processes when:
For each process, the system:
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 });
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
});
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!
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
If processes aren't being cleaned up:
installSignalHandlers() is called at the top of your CLI entry pointmanagedExecAsync instead of raw spawn()If you find orphaned processes:
# Find all CodeYam processes
ps aux | grep CODEYAM
# Kill all CodeYam processes
pkill -f CODEYAM_PROCESS_NAME
If tests are interfering with each other:
afterEach(async () => {
const manager = getGlobalProcessManager();
await manager.shutdownAll();
manager.cleanupCompleted({ retentionMs: 0 });
});
Potential improvements:
See the inline documentation in:
ProcessManager.ts - Core process managementGlobalProcessManager.ts - Singleton and signal handlingmanagedExecAsync.ts - Managed spawn wrapperindex.ts - Exported APIFAQs
Local development CLI for CodeYam analysis
We found that @codeyam/codeyam-cli demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

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.

Research
Socket found 37 malicious PyPI wheels that abuse Python startup hooks to launch a Bun-powered credential stealer tied to Mini Shai-Hulud/Miasma.

Security News
RubyGems and Bundler 4.0.13 introduced an opt-in cooldown feature that delays newly published gems during dependency resolution.

Security News
pnpm 11.5 now recognizes npm staged publish approvals in release metadata, preventing those releases from being mistaken for lower-trust package publishes.