unitup 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,35 @@
1
+ # Unitup Workspace Guidelines for AI Agents
2
+
3
+ Welcome to **Unitup** — a lightweight, zero-dependency, cross-platform process manager and zero-downtime deployment orchestration engine.
4
+
5
+ ---
6
+
7
+ ## Core Invariants
8
+
9
+ 1. **Zero External Dependencies**:
10
+ - Production code in `src/` must NEVER import anything outside Node.js built-ins (`node:*`).
11
+ - `package.json` must keep `"dependencies": {}`.
12
+ 2. **Zero-Downtime Architectural Boundary**:
13
+ - Zero-downtime runtime primitives belong to `src/core/` (ProcessManager, Router, GenerationManager, ReadinessChecker, DrainManager, Supervisor, IPC).
14
+ - Deployment sequencing belongs to `src/deploy/` (DeploymentManager, RollbackManager).
15
+ - CLI (`src/cli.js`) is only a thin translation layer.
16
+ 3. **Multi-OS Native Daemon Support**:
17
+ - Linux: systemd user/system services & timers.
18
+ - macOS: launchd LaunchAgents / LaunchDaemons `.plist`.
19
+ - Windows: Windows Services via `sc.exe` and `WindowsServiceHost`.
20
+ 4. **Validation Triad**:
21
+ - Every code modification must pass:
22
+ ```bash
23
+ npm test && npx tsc --noEmit && npx biome check src/ test/
24
+ ```
25
+
26
+ ---
27
+
28
+ ## Workspace Skills Available
29
+
30
+ For deep-dive runbooks and procedures, activate the specialized skills in `.agents/skills/`:
31
+
32
+ - **[zero-downtime-deployment](file:///Users/ahmet/projects/unitup/.agents/skills/zero-downtime-deployment/SKILL.md)**: State machine, canary weighted routing, rollback, connection draining, and streaming protocol handling.
33
+ - **[platform-service-adapters](file:///Users/ahmet/projects/unitup/.agents/skills/platform-service-adapters/SKILL.md)**: Systemd, launchd, and Windows service integration, escaping, user lingering, and command runner mocking.
34
+ - **[multi-runtime-management](file:///Users/ahmet/projects/unitup/.agents/skills/multi-runtime-management/SKILL.md)**: Auto-detection and arguments for Node, Python, Bun, Deno, Go, Ruby, PHP, and generic binaries.
35
+ - **[unitup-development-and-qa](file:///Users/ahmet/projects/unitup/.agents/skills/unitup-development-and-qa/SKILL.md)**: Testing standards with `node:test`, TypeScript declaration sync, and Biome linting.
@@ -0,0 +1,80 @@
1
+ ---
2
+ name: multi-runtime-management
3
+ description: >-
4
+ Manage, extend, and test multi-runtime support in Unitup: Node.js, Python, Bun, Deno, Go, Ruby,
5
+ PHP, Elixir, and generic executables. Use when modifying runtime detection, shebang parsing,
6
+ argument building, environment variable normalization, or memory limits.
7
+ ---
8
+
9
+ # Multi-Runtime Management in Unitup
10
+
11
+ This skill details how Unitup automatically detects, configures, and executes services across different programming runtimes without requiring external dependencies or containers.
12
+
13
+ ---
14
+
15
+ ## 1. Runtime Architecture
16
+
17
+ Runtime logic resides in:
18
+ ```text
19
+ src/runtimes/
20
+ ├── base.js # BaseRuntime contract: detect(), getExecutable(), getArgs()
21
+ ├── node.js # NodeRuntime (.js, .mjs, .cjs)
22
+ ├── python.js # PythonRuntime (.py, python shebang, unbuffered -u flag)
23
+ ├── bun.js # BunRuntime (.js, .ts with bun)
24
+ ├── deno.js # DenoRuntime (.ts with deno, auto permission flags)
25
+ ├── go.js # GoRuntime (.go -> go run, or compiled binaries)
26
+ ├── ruby.js # RubyRuntime (.rb)
27
+ ├── php.js # PHPRuntime (.php -> built-in web server or CLI)
28
+ ├── shell.js # ShellRuntime (.sh, bash shebang)
29
+ ├── elixir.js # ElixirRuntime (.ex, .exs)
30
+ ├── generic.js # GenericExecutableRuntime (arbitrary command line)
31
+ └── index.js # detectRuntime(), resolveRuntimeConfig(), listSupportedRuntimes()
32
+ ```
33
+
34
+ ---
35
+
36
+ ## 2. Detection & Resolution Rules
37
+
38
+ ### 1. File Extension Detection
39
+ - `.js`, `.mjs`, `.cjs` -> Node.js (or Bun/Deno if explicitly specified via `--runtime`)
40
+ - `.py` -> Python (`python3` or `python` with `-u` for unbuffered logs)
41
+ - `.rb` -> Ruby (`ruby`)
42
+ - `.php` -> PHP (`php`)
43
+ - `.sh` -> Shell (`bash` or `sh`)
44
+ - `.go` -> Go (`go run`)
45
+ - `.ex`, `.exs` -> Elixir (`elixir`)
46
+
47
+ ### 2. TypeScript Ambiguity Rule
48
+ - `.ts` files **cannot** be auto-resolved to a single engine because both Bun and Deno execute TypeScript natively.
49
+ - An error is thrown requiring the user to specify `--runtime bun` or `--runtime deno`.
50
+
51
+ ### 3. Shebang Header Detection
52
+ If a file has no extension or is executable:
53
+ - Reads the first 256 bytes of the file.
54
+ - Inspects `#!/usr/bin/env ...` or `#!/bin/...` to detect runtime (e.g. `python3`, `ruby`, `node`, `bash`).
55
+
56
+ ### 4. Generic Command (`--command`)
57
+ - When `--command "my-binary --flag"` is provided:
58
+ - Bypasses runtime detection.
59
+ - Resolves executable using `resolveExecutable()`.
60
+ - Splits and preserves argument tokens safely without shell injection vulnerabilities.
61
+
62
+ ---
63
+
64
+ ## 3. Normalization & App Metadata
65
+
66
+ [normalizeServiceConfig](file:///Users/ahmet/projects/unitup/src/service/normalize.js) converts raw CLI/API flags into a canonical service descriptor:
67
+
68
+ - Resolves absolute `cwd` (Working directory).
69
+ - Resolves executable path or validates binary exists in `$PATH`.
70
+ - Normalizes memory limits: parses `512M`, `1G`, `256MB` into bytes and native OS config strings.
71
+ - Reads and saves metadata in `~/.config/unitup/apps/<name>.json`.
72
+
73
+ ---
74
+
75
+ ## 4. Testing Multi-Runtime Capabilities
76
+
77
+ ```bash
78
+ # Run multi-runtime detection and argument builder tests
79
+ node --test test/multi-runtime.test.js
80
+ ```
@@ -0,0 +1,108 @@
1
+ ---
2
+ name: platform-service-adapters
3
+ description: >-
4
+ Implement, test, and debug cross-platform OS service adapters in Unitup: systemd (Linux),
5
+ launchd (macOS), and Windows Services (Windows). Use when working with service definition generators,
6
+ system commands (systemctl, launchctl, sc.exe), user vs. system scopes, linger, or timer units.
7
+ ---
8
+
9
+ # Cross-Platform Service Adapters in Unitup
10
+
11
+ This skill covers the implementation patterns, escaping rules, permission boundaries, and testing practices for Unitup's native OS service adapters.
12
+
13
+ ---
14
+
15
+ ## 1. Adapter Architecture
16
+
17
+ Unitup implements a unified interface across operating systems via [ServiceAdapter](file:///Users/ahmet/projects/unitup/src/platform/base.js):
18
+
19
+ ```text
20
+ src/platform/
21
+ ├── base.js # Base ServiceAdapter abstract class with standard contract
22
+ ├── linux.js # LinuxAdapter: systemd user & system units via systemctl
23
+ ├── macos.js # MacOSAdapter: launchd launchd.plist via launchctl
24
+ ├── windows.js # WindowsAdapter: Windows Service via sc.exe
25
+ ├── windows-host.js # WindowsServiceHost child process manager for Windows
26
+ └── index.js # getAdapter(platform), getPlatformCapabilities()
27
+ ```
28
+
29
+ ### Standard Adapter Methods:
30
+ Each adapter implements:
31
+ - `getCapabilities()`: `{ systemd, launchd, windowsService, userUnits, systemUnits, timers, memoryLimits }`
32
+ - `generateService(config)`: Returns formatted service definition string
33
+ - `install(config, opts)`: Writes service file and registers with OS daemon
34
+ - `uninstall(name, opts)`: Unregisters service and deletes service file
35
+ - `start(name, opts)`, `stop(name, opts)`, `restart(name, opts)`
36
+ - `status(name, opts)`: Queries native service state (running, stopped, PID, uptime)
37
+ - `enable(name, opts)`, `disable(name, opts)`
38
+
39
+ ---
40
+
41
+ ## 2. Platform Specifics & Service Formats
42
+
43
+ ### A. Linux (systemd)
44
+ - **User scope (default)**: `~/.config/systemd/user/unitup-<name>.service`
45
+ - Requires user lingering for persistence across logout: `loginctl enable-linger <user>` (verified via `checkUserLinger()`).
46
+ - Controlled with `systemctl --user`.
47
+ - **System scope**: `/etc/systemd/system/unitup-<name>.service`
48
+ - Requires root privileges; throws `PermissionRequiredError` if unprivileged.
49
+ - **Unit Content Structure**:
50
+ ```ini
51
+ [Unit]
52
+ Description=Unitup Service - <name>
53
+ After=network.target
54
+
55
+ [Service]
56
+ Type=simple
57
+ WorkingDirectory=/path/to/app
58
+ ExecStart=/path/to/executable ...
59
+ Restart=always
60
+ RestartSec=3
61
+ MemoryMax=512M
62
+
63
+ [Install]
64
+ WantedBy=default.target
65
+ ```
66
+ - **Escaping**: Always use `formatSystemdEnv()` and `escapeExecArg()` from [src/utils.js](file:///Users/ahmet/projects/unitup/src/utils.js).
67
+
68
+ ### B. macOS (launchd)
69
+ - **User scope (default)**: `~/Library/LaunchAgents/com.unitup.<name>.plist`
70
+ - Domain target: `gui/<uid>` (for logged-in user) or `user/<uid>`.
71
+ - Bootstrapping: `launchctl bootstrap gui/<uid> <plistPath>`
72
+ - Kickstarting: `launchctl kickstart -k gui/<uid>/com.unitup.<name>`
73
+ - **System scope**: `/Library/LaunchDaemons/com.unitup.<name>.plist`
74
+ - Domain target: `system` (requires root).
75
+ - **XML Plist Generation**: Generated cleanly with standard XML elements (`ProgramArguments`, `WorkingDirectory`, `KeepAlive`, `EnvironmentVariables`, `StandardOutPath`, `StandardErrorPath`).
76
+
77
+ ### C. Windows (Windows Services)
78
+ - **Tooling**: Uses `sc.exe create <name> binPath= ... start= auto`.
79
+ - **WindowsServiceHost**: Because standard node scripts cannot run directly as Windows Services without a service wrapper, Unitup uses [WindowsServiceHost](file:///Users/ahmet/projects/unitup/src/platform/windows-host.js) to bridge process signals, log redirection, and Windows shutdown events.
80
+
81
+ ---
82
+
83
+ ## 3. Testing Platform Adapters Safely
84
+
85
+ Native system commands (`systemctl`, `launchctl`, `sc.exe`) cannot and should not be executed directly in unit tests.
86
+
87
+ Unitup provides runner injection via [src/utils.js](file:///Users/ahmet/projects/unitup/src/utils.js):
88
+
89
+ ```javascript
90
+ import { setCommandRunner, resetCommandRunner } from '../src/utils.js';
91
+
92
+ // Mock commands in tests:
93
+ setCommandRunner(async (cmd, args) => {
94
+ if (cmd === 'systemctl' && args.includes('is-active')) {
95
+ return { stdout: 'active\n', stderr: '', code: 0 };
96
+ }
97
+ return { stdout: '', stderr: '', code: 0 };
98
+ });
99
+
100
+ // Always reset runner in afterEach / finally:
101
+ resetCommandRunner();
102
+ ```
103
+
104
+ ### Verification Commands:
105
+ ```bash
106
+ # Test all platform adapters
107
+ node --test test/platform.test.js test/macos.test.js test/windows.test.js test/unit.test.js
108
+ ```
@@ -0,0 +1,88 @@
1
+ ---
2
+ name: unitup-development-and-qa
3
+ description: >-
4
+ Development standards, testing procedures, TypeScript type verification, Biome linting,
5
+ and zero-external-dependency constraints for the Unitup codebase. Use when adding new features,
6
+ running tests, updating index.d.ts, or validating pull requests.
7
+ ---
8
+
9
+ # Unitup Development, QA & Quality Standards
10
+
11
+ This skill outlines the core engineering invariants, code quality checks, and testing workflows required for any contribution to Unitup.
12
+
13
+ ---
14
+
15
+ ## 1. Golden Constraint: Zero External Dependencies
16
+
17
+ **Unitup has strictly ZERO production external dependencies (`dependencies: {}` in package.json).**
18
+
19
+ - Only Node.js built-in standard modules are allowed (`node:fs`, `node:path`, `node:os`, `node:child_process`, `node:http`, `node:net`, `node:events`, `node:assert`, `node:test`).
20
+ - Never add npm packages to `dependencies`.
21
+ - Development dependencies (`devDependencies`) are strictly limited to developer tooling: `biome` for linting/formatting and `typescript` for `.d.ts` type-checking.
22
+
23
+ ---
24
+
25
+ ## 2. Testing Principles & Node.js Test Runner
26
+
27
+ Unitup uses the native Node.js test runner (`node:test` and `node:assert`). No Jest, Mocha, or Vitest.
28
+
29
+ ### Test Authoring Rules:
30
+ 1. **Never use hardcoded network ports**:
31
+ - Always use `port: 0` for ephemeral port allocation or `findFreePort()`.
32
+ - Hardcoding ports like `3000` or `8080` breaks developer machines running local web servers.
33
+ 2. **Deterministic Cleanup**:
34
+ - Every test that opens an HTTP server, router, socket, or temporary file must clean it up in an `afterEach` or `finally` block.
35
+ 3. **Mock Process & System Calls**:
36
+ - Do not call real `systemctl`, `launchctl`, or `sc.exe` in unit tests. Use `setCommandRunner()`.
37
+ - Use simulated process managers when testing deployment state machines to keep tests blazing fast (< 100ms per suite).
38
+
39
+ ### Common Test Commands:
40
+ ```bash
41
+ # Run a specific test suite
42
+ node --test test/canary.test.js
43
+ node --test test/router.test.js
44
+ node --test test/deployment-manager.test.js
45
+
46
+ # Run the complete test suite across all platforms
47
+ npm test
48
+ ```
49
+
50
+ ---
51
+
52
+ ## 3. TypeScript Type Synchronization (`index.d.ts`)
53
+
54
+ Unitup is written in standard ES Modules (`"type": "module"`), but provides high-grade TypeScript declarations in [index.d.ts](file:///Users/ahmet/projects/unitup/index.d.ts).
55
+
56
+ Whenever a public function, class, option, or result object is added or modified in `src/`:
57
+ 1. Update `index.d.ts` with matching types.
58
+ 2. Verify with the TypeScript compiler:
59
+ ```bash
60
+ npx tsc --noEmit
61
+ ```
62
+ 3. Ensure `npx tsc --noEmit` exits with **0 errors**.
63
+
64
+ ---
65
+
66
+ ## 4. Linting & Formatting with Biome
67
+
68
+ Unitup uses Biome for sub-second formatting and static analysis:
69
+
70
+ ```bash
71
+ # Check code style, formatting, and lint rules
72
+ npx biome check src/ test/
73
+
74
+ # Automatically apply safe fixes
75
+ npx biome check --write src/ test/
76
+ ```
77
+
78
+ Always ensure `npx biome check src/ test/` exits cleanly with zero errors before concluding tasks.
79
+
80
+ ---
81
+
82
+ ## 5. Pre-Commit / Pre-Push Checklist
83
+
84
+ Before submitting any task, run the full validation chain:
85
+
86
+ ```bash
87
+ npm test && npx tsc --noEmit && npx biome check src/ test/
88
+ ```
@@ -0,0 +1,135 @@
1
+ ---
2
+ name: zero-downtime-deployment
3
+ description: >-
4
+ Develop, test, and debug zero-downtime deployments, canary releases, rollback sequencing,
5
+ generation management, router reverse proxying, connection draining, and readiness probes in Unitup.
6
+ Use when modifying core routing, process lifecycle, readiness checks, drain manager, or deploy orchestration.
7
+ ---
8
+
9
+ # Zero-Downtime Deployment & Traffic Routing in Unitup
10
+
11
+ This skill provides architectural guidelines, lifecycle specifications, and testing procedures for Unitup's zero-downtime deployment subsystem.
12
+
13
+ ---
14
+
15
+ ## 1. Architectural Invariant: Core vs. Deploy Separation
16
+
17
+ Unitup strictly decouples low-level process and routing primitives from deployment sequencing:
18
+
19
+ ```text
20
+ unitup
21
+ ├── core/ # Zero-downtime runtime primitives (No deployment sequencing)
22
+ │ ├── process-manager.js # Child process lifecycle (start, stop, isAlive, signal, waitForExit)
23
+ │ ├── generation-manager.js # Generation records, ephemeral internal ports, disk persistence
24
+ │ ├── router.js # HTTP & WebSocket reverse proxy on stable public port, atomic switch, canary weights
25
+ │ ├── readiness.js # TCP connect & HTTP 2xx health probes, fast startup failure detection
26
+ │ ├── drain-manager.js # In-flight connection tracking, graceful drain, process retirement
27
+ │ ├── lock.js # Cross-process deployment concurrency lock
28
+ │ ├── supervisor.js # Daemon supervisor (unitupd) managing generations and IPC
29
+ │ └── ipc.js # Domain socket / Named Pipe IPC server and client
30
+ │
31
+ └── deploy/ # Orchestration layer (Composes core primitives)
32
+ ├── deployment-manager.js # State machine for full deployments and canary rollouts
33
+ └── rollback-manager.js # Rollback sequencing and canary abortion
34
+ ```
35
+
36
+ ### Golden Rules:
37
+ 1. **Never put deployment sequencing into `core/`**: Router, ProcessManager, GenerationManager, and DrainManager must have no knowledge of "steps" or deployment decisions.
38
+ 2. **Never put networking or OS primitives into `deploy/`**: DeploymentManager and RollbackManager must only compose core primitives.
39
+ 3. **Never build zero-downtime inside CLI commands**: `src/cli.js` must remain a thin adapter that calls `deploy`, `promote`, or `rollback`.
40
+ 4. **Never create per-generation OS service units**: Only one supervisor service unit exists per application.
41
+
42
+ ---
43
+
44
+ ## 2. Deployment State Machine
45
+
46
+ DeploymentManager executes a strict, observable state machine:
47
+
48
+ ```text
49
+ IDLE
50
+ └── ACQUIRING_LOCK
51
+ └── CREATING_GENERATION
52
+ └── STARTING
53
+ └── WAITING_READY
54
+ ├── (If canary): SETTING_CANARY -> COMPLETE
55
+ └── (If full deploy):
56
+ SWITCHING
57
+ └── STABILIZING
58
+ └── DRAINING
59
+ └── STOPPING_PREVIOUS
60
+ └── COMPLETE
61
+ ```
62
+
63
+ ### Critical Safety Invariants:
64
+ - **Previous Generation Safety**: The previous generation is NEVER stopped or drained until the new generation has passed readiness and traffic has switched.
65
+ - **Readiness Failure Preservation**: If the new generation fails readiness or crashes on startup, it is immediately stopped and removed. The router remains pointed at the previous generation, and the deployment fails cleanly.
66
+ - **Stabilization Window**: During `stabilizationWindowMs` (default 500ms), if the new generation crashes, traffic is automatically reverted to the previous generation before an error is thrown.
67
+ - **Port Mismatch & EADDRINUSE Diagnosis**: If user code hardcodes its listening port instead of reading `process.env.PORT`, `ReadinessChecker` diagnoses this immediately (raising `PortMismatchError` or diagnosing `EADDRINUSE` from captured process stderr) rather than hanging for 15 seconds.
68
+
69
+ ---
70
+
71
+ ## 3. Canary & Weighted Routing Mechanics
72
+
73
+ ### Router Primitives (`src/core/router.js`):
74
+ - `setCanary(serviceName, generation, weight)`: Accepts decimal (`0.15`) or percentage (`"15%"` or `15`), normalized to `[0.0, 1.0]`.
75
+ - `resolveBackend(serviceName, req)`:
76
+ 1. **Header Override**:
77
+ - `x-canary: true`, `1`, `always` -> Routes to canary generation deterministically.
78
+ - `x-canary: false`, `0`, `never` -> Routes to primary active generation deterministically.
79
+ 2. **Probabilistic Splitting**: If `Math.random() < canary.weight`, routes to canary.
80
+ 3. **Fallback**: Routes to primary active generation.
81
+ - `promoteCanary(serviceName)`: Atomically shifts primary backend to canary, clears canary map, returns promoted generation.
82
+ - `clearCanary(serviceName)`: Removes canary backend without touching primary active generation.
83
+
84
+ ### Canary Deployment Flow:
85
+ ```bash
86
+ # 1. Deploy canary with 10% traffic (keeps existing active generation intact)
87
+ unitup deploy <app> --canary --weight 10%
88
+
89
+ # 2. Inspect status (shows both active and canary generations with port & weight)
90
+ unitup status <app>
91
+
92
+ # 3A. Promote canary to 100% active (drains and stops old active generation)
93
+ unitup promote <app>
94
+
95
+ # 3B. OR abort canary (stops and drains canary, keeps old active generation intact)
96
+ unitup rollback <app>
97
+ ```
98
+
99
+ ---
100
+
101
+ ## 4. Connection Draining & Streaming Protocols
102
+
103
+ [DrainManager](file:///Users/ahmet/projects/unitup/src/core/drain-manager.js) ensures graceful retirement:
104
+ 1. `inFlightRequests` map tracks active HTTP requests and WebSocket connections per generation ID.
105
+ 2. In `_handleHttpRequest`: increments in-flight counter, decrements on response `'finish'` or `'close'`.
106
+ 3. In `_handleWebSocketUpgrade`: increments in-flight counter, decrements on socket `'close'`, `'end'`, or `'error'`.
107
+ 4. `DrainManager.drain(generation, { timeout, pollInterval, stopProcess })`:
108
+ - Polls `router.getInFlightCount(generation.id)`.
109
+ - Waits for active in-flight requests, long-lived WebSockets, Server-Sent Events (SSE), and chunked uploads to complete up to `timeout`.
110
+ - Stops the process gracefully (`SIGTERM` -> timeout -> `SIGKILL`).
111
+
112
+ ---
113
+
114
+ ## 5. Testing & Verification Runbook
115
+
116
+ When modifying zero-downtime or routing code, run:
117
+
118
+ ```bash
119
+ # 1. Core routing and architectural boundary tests
120
+ node --test test/router.test.js test/deployment-manager.test.js test/rollback-manager.test.js test/canary.test.js
121
+
122
+ # 2. Connection draining & streaming protocols test
123
+ node --test test/live-connections.test.js test/drain-manager.test.js
124
+
125
+ # 3. Continuous zero-downtime traffic integration test (v1 -> v2 -> v1 -> v2 under load)
126
+ node --test test/zero-downtime-integration.test.js
127
+
128
+ # 4. Full test suite and type check
129
+ npm test
130
+ npx tsc --noEmit
131
+ ```
132
+
133
+ ### Test Construction Rules:
134
+ - **Never bind hardcoded ports** like `3000` or `8080`. Always use `port: 0` (ephemeral port allocation) or `findFreePort()` to avoid collisions with running user services.
135
+ - **Always clean up servers and sockets** in `afterEach` or `finally` blocks.
package/index.d.ts CHANGED
@@ -337,6 +337,20 @@ export class InvalidServiceConfigError extends UnitupError {
337
337
  export class ExecutableNotFoundError extends UnitupError {
338
338
  executable: string;
339
339
  }
340
+ export class ReadinessTimeoutError extends Error {
341
+ generation: GenerationRecord;
342
+ }
343
+ export class ProcessStartupError extends Error {
344
+ generation: GenerationRecord;
345
+ }
346
+ export class PortMismatchError extends Error {
347
+ generation: GenerationRecord;
348
+ detectedPort: number;
349
+ }
350
+ export class DeploymentLockError extends Error {
351
+ service: string;
352
+ lockedPid?: number;
353
+ }
340
354
 
341
355
  // Adapters
342
356
  export abstract class ServiceAdapter {
@@ -443,6 +457,7 @@ export function formatTable(
443
457
  columns: Array<{ key: string; label: string }>
444
458
  ): string;
445
459
  export function formatMemoryBytes(bytes: number | string): string;
460
+ export function formatDuration(ms: number): string;
446
461
  export function validateMemorySize(val: string | number, paramName?: string): string;
447
462
  export function validateDuration(val: string | number, paramName?: string): string;
448
463
 
@@ -515,4 +530,125 @@ export function readScheduleMetadata(name: string): Record<string, unknown> | nu
515
530
  export function getScheduleMetadataPath(name: string): string;
516
531
  export function getSchedulesDir(): string;
517
532
 
533
+ // Zero-Downtime Deployment Primitives & Orchestration
534
+ export interface GenerationRecord {
535
+ id: number;
536
+ service: string;
537
+ pid: number;
538
+ internalPort: number;
539
+ status: 'starting' | 'active' | 'previous' | 'draining' | 'stopped' | 'canary' | string;
540
+ createdAt: string;
541
+ activatedAt?: string;
542
+ drainingAt?: string;
543
+ stoppedAt?: string;
544
+ canaryWeight?: number;
545
+ }
546
+
547
+ export interface DeploymentResult {
548
+ service: string;
549
+ previousGeneration: number | null;
550
+ currentGeneration: number;
551
+ downtimeMs: number;
552
+ status: string;
553
+ canaryWeight?: number;
554
+ }
555
+
556
+ export interface PromoteResult {
557
+ service: string;
558
+ promotedGeneration: number;
559
+ previousGeneration: number | null;
560
+ downtimeMs: number;
561
+ status: string;
562
+ }
563
+
564
+ export interface RollbackResult {
565
+ service: string;
566
+ rolledBackFrom: number | null;
567
+ activeGeneration: number;
568
+ status: string;
569
+ }
570
+
571
+ export function deploy(name: string, options?: Record<string, unknown>): Promise<DeploymentResult>;
572
+ export function promote(name: string, options?: Record<string, unknown>): Promise<PromoteResult>;
573
+ export function rollback(name: string, options?: Record<string, unknown>): Promise<RollbackResult>;
574
+ export function generations(name: string, options?: Record<string, unknown>): GenerationRecord[];
575
+
576
+ export class ProcessManager {
577
+ constructor(options?: Record<string, unknown>);
578
+ start(command: string, args?: string[], options?: Record<string, unknown>): { pid: number; child: any; exitPromise: Promise<any> };
579
+ stop(processOrPid: number | object, options?: { timeout?: number; signal?: string }): Promise<boolean>;
580
+ signal(processOrPid: number | object, signal?: string): boolean;
581
+ isAlive(processOrPid: number | object): boolean;
582
+ waitForExit(processOrPid: number | object, timeout?: number): Promise<{ exited: boolean; code?: number | null; signal?: string | null }>;
583
+ }
584
+
585
+ export class GenerationManager {
586
+ constructor(options?: Record<string, unknown>);
587
+ create(serviceName: string, config?: Record<string, unknown>, options?: Record<string, unknown>): Promise<GenerationRecord>;
588
+ getActive(serviceName: string): GenerationRecord | null;
589
+ getPrevious(serviceName: string): GenerationRecord | null;
590
+ getCanary(serviceName: string): GenerationRecord | null;
591
+ getGeneration(serviceName: string, id: number | string): GenerationRecord | null;
592
+ markActive(generation: GenerationRecord): void;
593
+ markCanary(generation: GenerationRecord, weight?: number | string): void;
594
+ markDraining(generation: GenerationRecord): void;
595
+ remove(generation: GenerationRecord, purge?: boolean): void;
596
+ cleanupStale(serviceName: string): number;
597
+ listGenerations(serviceName: string): GenerationRecord[];
598
+ }
599
+
600
+ export class Router {
601
+ constructor(options?: Record<string, unknown>);
602
+ listen(publicPort: number, host?: string): Promise<{ port: number; host: string }>;
603
+ switchBackend(serviceName: string, generation: { id: number; internalPort: number }): void;
604
+ getActiveBackend(serviceName: string): GenerationRecord | null;
605
+ setCanary(serviceName: string, generation: { id: number; internalPort: number }, weight?: number | string): void;
606
+ clearCanary(serviceName: string): void;
607
+ promoteCanary(serviceName: string): void;
608
+ getCanary(serviceName: string): { generation: { id: number; internalPort: number }; weight: number } | null;
609
+ resolveBackend(serviceName: string, req?: any): { id: number; internalPort: number } | null;
610
+ getInFlightCount(generationId: number | string): number;
611
+ close(): Promise<void>;
612
+ }
613
+
614
+ export class ReadinessChecker {
615
+ constructor(options?: Record<string, unknown>);
616
+ checkTcp(port: number, host?: string, timeout?: number): Promise<boolean>;
617
+ checkHttp(port: number, path?: string, timeout?: number): Promise<boolean>;
618
+ ensure(generation: GenerationRecord, options?: { path?: string }): Promise<boolean>;
619
+ wait(generation: GenerationRecord, options?: { timeout?: number; interval?: number; path?: string }): Promise<boolean>;
620
+ }
621
+
622
+ export class DrainManager {
623
+ constructor(options?: Record<string, unknown>);
624
+ drain(generation: GenerationRecord, options?: { timeout?: number; pollInterval?: number; stopProcess?: boolean }): Promise<{ drained: boolean; inFlightRemaining: number; timedOut: boolean }>;
625
+ }
626
+
627
+ export class DeploymentLock {
628
+ constructor(options?: Record<string, unknown>);
629
+ acquire(serviceName: string): Promise<boolean>;
630
+ release(serviceName: string): Promise<boolean>;
631
+ }
632
+
633
+ export class DeploymentManager {
634
+ constructor(options?: Record<string, unknown>);
635
+ deploy(serviceName: string, serviceConfig?: Record<string, unknown>, options?: Record<string, unknown>): Promise<DeploymentResult>;
636
+ promote(serviceName: string, serviceConfig?: Record<string, unknown>, options?: Record<string, unknown>): Promise<PromoteResult>;
637
+ }
638
+
639
+ export class RollbackManager {
640
+ constructor(options?: Record<string, unknown>);
641
+ rollback(serviceName: string, serviceConfig?: Record<string, unknown>, options?: Record<string, unknown>): Promise<RollbackResult>;
642
+ }
643
+
644
+ export class Supervisor {
645
+ constructor(serviceName: string, serviceConfig?: Record<string, unknown>, options?: Record<string, unknown>);
646
+ start(): Promise<Supervisor>;
647
+ stop(): Promise<void>;
648
+ static run(serviceName: string): Promise<void>;
649
+ }
650
+
651
+ export const defaultDeploymentManager: DeploymentManager;
652
+ export const defaultRollbackManager: RollbackManager;
653
+
518
654
  export default defaultManager;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unitup",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Minimal, zero-dependency cross-platform native service manager (systemd, launchd, Windows Services) for any runtime, script, or executable",
5
5
  "main": "src/index.js",
6
6
  "types": "./index.d.ts",