pidnap 0.0.0-dev.3 → 0.0.0-dev.5
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.
- package/dist/cli.mjs +157 -167
- package/dist/cli.mjs.map +1 -1
- package/dist/client.d.mts +0 -1
- package/dist/client.d.mts.map +1 -1
- package/dist/client.mjs +2 -2
- package/dist/client.mjs.map +1 -1
- package/dist/index.d.mts +263 -71
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/{logger-BF3KrCIK.mjs → logger-BU2RmetS.mjs} +231 -270
- package/dist/logger-BU2RmetS.mjs.map +1 -0
- package/package.json +4 -2
- package/src/api/client.ts +2 -2
- package/src/api/contract.ts +8 -14
- package/src/cli.ts +36 -36
- package/src/cron-process.ts +9 -18
- package/src/env-manager.ts +170 -163
- package/src/index.ts +0 -1
- package/src/lazy-process.ts +60 -80
- package/src/logger.ts +28 -36
- package/src/manager.ts +155 -212
- package/src/restarting-process.ts +16 -29
- package/src/task-list.ts +3 -5
- package/src/utils.ts +10 -0
- package/dist/logger-BF3KrCIK.mjs.map +0 -1
- package/dist/task-list-CIdbB3wM.d.mts +0 -230
- package/dist/task-list-CIdbB3wM.d.mts.map +0 -1
- package/src/port-utils.ts +0 -39
- package/src/tree-kill.ts +0 -131
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger-BU2RmetS.mjs","names":["setTimeout"],"sources":["../src/lazy-process.ts","../src/restarting-process.ts","../src/cron-process.ts","../src/task-list.ts","../src/env-manager.ts","../src/logger.ts"],"sourcesContent":["import * as v from \"valibot\";\nimport type { Logger } from \"./logger.ts\";\nimport { setTimeout } from \"node:timers/promises\";\nimport { createInterface } from \"node:readline/promises\";\nimport { spawn, type ChildProcess } from \"node:child_process\";\n\nexport const ProcessDefinition = v.object({\n command: v.string(),\n args: v.optional(v.array(v.string())),\n cwd: v.optional(v.string()),\n env: v.optional(v.record(v.string(), v.string())),\n});\nexport type ProcessDefinition = v.InferOutput<typeof ProcessDefinition>;\n\nexport const ProcessState = v.picklist([\n \"idle\",\n \"starting\",\n \"running\",\n \"stopping\",\n \"stopped\",\n \"error\",\n]);\nexport type ProcessState = v.InferOutput<typeof ProcessState>;\n\n/**\n * Kill a process group (the process and all its descendants).\n * Uses negative PID to target the entire process group.\n */\nfunction killProcessGroup(pid: number, signal: NodeJS.Signals): boolean {\n try {\n // Negative PID kills the entire process group\n process.kill(-pid, signal);\n return true;\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n // ESRCH = No such process (already dead)\n // EPERM = Permission denied (might happen if some children already exited)\n if (code === \"ESRCH\" || code === \"EPERM\") {\n return true;\n }\n return false;\n }\n}\n\nexport class LazyProcess {\n readonly name: string;\n definition: ProcessDefinition;\n private logger: Logger;\n private childProcess: ChildProcess | null = null;\n private _state: ProcessState = \"idle\";\n private processExit = Promise.withResolvers<void>();\n public exitCode: number | null = null;\n\n constructor(name: string, definition: ProcessDefinition, logger: Logger) {\n this.name = name;\n this.definition = definition;\n this.logger = logger.withPrefix(\"SYS\");\n }\n\n get state(): ProcessState {\n return this._state;\n }\n\n async start() {\n if (this._state === \"running\" || this._state === \"starting\") {\n throw new Error(`Process \"${this.name}\" is already ${this._state}`);\n }\n\n if (this._state === \"stopping\") {\n throw new Error(`Process \"${this.name}\" is currently stopping`);\n }\n\n this._state = \"starting\";\n this.processExit = Promise.withResolvers<void>();\n this.logger.debug(`Starting process: ${this.definition.command}`);\n\n try {\n const env = this.definition.env ? { ...process.env, ...this.definition.env } : process.env;\n\n this.childProcess = spawn(this.definition.command, this.definition.args ?? [], {\n cwd: this.definition.cwd,\n env,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n detached: true,\n });\n\n this._state = \"running\";\n\n if (this.childProcess.stdout) {\n const rl = createInterface({ input: this.childProcess.stdout });\n rl.on(\"line\", (line) => this.logger.withPrefix(\"OUT\").info(line));\n this.processExit.promise.then(() => rl.close());\n }\n\n if (this.childProcess.stderr) {\n const rl = createInterface({ input: this.childProcess.stderr });\n rl.on(\"line\", (line) => this.logger.withPrefix(\"ERR\").info(line));\n this.processExit.promise.then(() => rl.close());\n }\n\n this.childProcess.on(\"exit\", (code, signal) => {\n this.exitCode = code;\n\n if (this._state === \"running\") {\n if (code === 0) {\n this._state = \"stopped\";\n this.logger.info(`Process exited with code ${code}`);\n } else if (signal) {\n this._state = \"stopped\";\n this.logger.info(`Process killed with signal ${signal}`);\n } else {\n this._state = \"error\";\n this.logger.error(`Process exited with code ${code}`);\n }\n }\n\n this.processExit.resolve();\n });\n\n this.childProcess.on(\"error\", (err) => {\n if (this._state !== \"stopping\" && this._state !== \"stopped\") {\n this._state = \"error\";\n this.logger.error(`Process error:`, err);\n }\n this.processExit.resolve();\n });\n } catch (err) {\n this._state = \"error\";\n this.logger.error(`Failed to start process:`, err);\n throw err;\n }\n }\n\n async stop(timeout?: number): Promise<void> {\n if (this._state === \"idle\" || this._state === \"stopped\" || this._state === \"error\") {\n return;\n }\n\n if (this._state === \"stopping\") {\n // Already stopping, wait for completion\n await this.processExit.promise;\n return;\n }\n\n if (!this.childProcess) {\n this._state = \"stopped\";\n return;\n }\n\n this._state = \"stopping\";\n const pid = this.childProcess.pid;\n\n if (pid === undefined) {\n this._state = \"stopped\";\n this.cleanup();\n return;\n }\n\n this.logger.debug(`Stopping process group (pid: ${pid}) with SIGTERM`);\n\n const timeoutMs = timeout ?? 5000;\n const resultRace = Promise.race([\n this.processExit.promise.then(() => \"exited\" as const),\n setTimeout(timeoutMs, \"timeout\"),\n ]);\n\n killProcessGroup(pid, \"SIGTERM\");\n\n this.logger.debug(`Waiting for process exit (timeout: ${timeoutMs}ms)`);\n const result = await resultRace;\n this.logger.debug(`process exit result: ${result}`);\n\n if (result === \"timeout\") {\n this.logger.warn(`Process did not exit within ${timeoutMs}ms, sending SIGKILL (pid: ${pid})`);\n const killed = killProcessGroup(pid, \"SIGKILL\");\n this.logger.info(`SIGKILL sent to process group: ${killed ? \"success\" : \"failed\"}`);\n\n const killTimeout = setTimeout(1000, \"timeout\");\n await Promise.race([this.processExit.promise, killTimeout]);\n\n if (this.childProcess && !this.childProcess.killed) {\n this.logger.error(`Process still alive after SIGKILL (pid: ${pid})`);\n }\n }\n\n this._state = \"stopped\";\n this.cleanup();\n this.logger.info(`Process stopped`);\n }\n\n async reset(): Promise<void> {\n if (this.childProcess?.pid !== undefined) {\n // Kill the entire process group\n killProcessGroup(this.childProcess.pid, \"SIGKILL\");\n await this.processExit.promise;\n this.cleanup();\n }\n\n this._state = \"idle\";\n // Create a fresh promise for the next process lifecycle\n this.processExit = Promise.withResolvers<void>();\n this.logger.info(`Process reset to idle`);\n }\n\n updateDefinition(definition: ProcessDefinition): void {\n this.definition = definition;\n }\n\n async waitForExit(): Promise<ProcessState> {\n if (!this.childProcess) return this._state;\n await this.processExit.promise;\n return this._state;\n }\n\n private cleanup(): void {\n if (this.childProcess) {\n this.childProcess.removeAllListeners();\n this.childProcess = null;\n }\n\n this.exitCode = null;\n }\n}\n","import * as v from \"valibot\";\nimport { LazyProcess, type ProcessDefinition, type ProcessState } from \"./lazy-process.ts\";\nimport type { Logger } from \"./logger.ts\";\n\nexport const RestartPolicy = v.picklist([\n \"always\",\n \"on-failure\",\n \"never\",\n \"unless-stopped\",\n \"on-success\",\n]);\nexport type RestartPolicy = v.InferOutput<typeof RestartPolicy>;\n\nexport const BackoffStrategy = v.union([\n v.object({\n type: v.literal(\"fixed\"),\n delayMs: v.number(),\n }),\n v.object({\n type: v.literal(\"exponential\"),\n initialDelayMs: v.number(),\n maxDelayMs: v.number(),\n multiplier: v.optional(v.number()),\n }),\n]);\nexport type BackoffStrategy = v.InferOutput<typeof BackoffStrategy>;\n\nexport const CrashLoopConfig = v.object({\n maxRestarts: v.number(),\n windowMs: v.number(),\n backoffMs: v.number(),\n});\nexport type CrashLoopConfig = v.InferOutput<typeof CrashLoopConfig>;\n\nexport const RestartingProcessOptions = v.object({\n restartPolicy: RestartPolicy,\n backoff: v.optional(BackoffStrategy),\n crashLoop: v.optional(CrashLoopConfig),\n minUptimeMs: v.optional(v.number()),\n maxTotalRestarts: v.optional(v.number()),\n});\nexport type RestartingProcessOptions = v.InferOutput<typeof RestartingProcessOptions>;\n\nexport const RestartingProcessState = v.picklist([\n \"idle\",\n \"running\",\n \"restarting\",\n \"stopping\",\n \"stopped\",\n \"crash-loop-backoff\",\n \"max-restarts-reached\",\n]);\nexport type RestartingProcessState = v.InferOutput<typeof RestartingProcessState>;\n\nconst DEFAULT_BACKOFF: BackoffStrategy = { type: \"fixed\", delayMs: 1000 };\nconst DEFAULT_CRASH_LOOP: CrashLoopConfig = { maxRestarts: 5, windowMs: 60000, backoffMs: 60000 };\n\nexport class RestartingProcess {\n readonly name: string;\n readonly lazyProcess: LazyProcess;\n private options: Required<Omit<RestartingProcessOptions, \"maxTotalRestarts\">> & {\n maxTotalRestarts?: number;\n };\n private logger: Logger;\n\n // State tracking\n private _state: RestartingProcessState = \"idle\";\n private _restartCount: number = 0;\n private restartTimestamps: number[] = []; // For crash loop detection\n private consecutiveFailures: number = 0; // For exponential backoff\n private lastStartTime: number | null = null;\n private stopRequested: boolean = false;\n private pendingDelayTimeout: ReturnType<typeof setTimeout> | null = null;\n\n constructor(\n name: string,\n definition: ProcessDefinition,\n options: RestartingProcessOptions,\n logger: Logger,\n ) {\n this.name = name;\n this.logger = logger;\n this.options = {\n restartPolicy: options.restartPolicy,\n backoff: options.backoff ?? DEFAULT_BACKOFF,\n crashLoop: options.crashLoop ?? DEFAULT_CRASH_LOOP,\n minUptimeMs: options.minUptimeMs ?? 0,\n maxTotalRestarts: options.maxTotalRestarts,\n };\n this.lazyProcess = new LazyProcess(name, definition, logger);\n }\n\n get state(): RestartingProcessState {\n return this._state;\n }\n\n get restarts(): number {\n return this._restartCount;\n }\n\n start(): void {\n if (this._state === \"running\" || this._state === \"restarting\") {\n throw new Error(`Process \"${this.name}\" is already ${this._state}`);\n }\n\n if (this._state === \"stopping\") {\n throw new Error(`Process \"${this.name}\" is currently stopping`);\n }\n\n // Fresh start from terminal states - reset counters\n if (\n this._state === \"stopped\" ||\n this._state === \"idle\" ||\n this._state === \"max-restarts-reached\"\n ) {\n this.resetCounters();\n }\n\n this.stopRequested = false;\n this.startProcess();\n }\n\n async stop(timeout?: number): Promise<void> {\n this.stopRequested = true;\n\n // Clear any pending delays\n if (this.pendingDelayTimeout) {\n clearTimeout(this.pendingDelayTimeout);\n this.pendingDelayTimeout = null;\n }\n\n if (\n this._state === \"idle\" ||\n this._state === \"stopped\" ||\n this._state === \"max-restarts-reached\"\n ) {\n this._state = \"stopped\";\n return;\n }\n\n this._state = \"stopping\";\n await this.lazyProcess.stop(timeout);\n this._state = \"stopped\";\n this.logger.info(`RestartingProcess stopped`);\n }\n\n async restart(force: boolean = false): Promise<void> {\n // Fresh start from terminal states - reset counters and no delay\n if (\n this._state === \"stopped\" ||\n this._state === \"idle\" ||\n this._state === \"max-restarts-reached\"\n ) {\n this.resetCounters();\n this.stopRequested = false;\n this.startProcess();\n return;\n }\n\n // Stop the current process first\n await this.stop();\n\n this.stopRequested = false;\n\n if (force) {\n // Force restart - no delay\n this.startProcess();\n } else {\n // Follow normal delay strategy\n const delay = this.calculateDelay();\n if (delay > 0) {\n this._state = \"restarting\";\n this.logger.info(`Restarting in ${delay}ms`);\n await this.delay(delay);\n if (this.stopRequested) return;\n }\n this.startProcess();\n }\n }\n\n /**\n * Update process definition and optionally restart with new config\n */\n async reload(\n newDefinition: ProcessDefinition,\n restartImmediately: boolean = true,\n ): Promise<void> {\n this.logger.info(`Reloading process with new definition`);\n this.lazyProcess.updateDefinition(newDefinition);\n\n if (restartImmediately) {\n // Restart with force=true to apply changes immediately\n await this.restart(true);\n }\n }\n\n /**\n * Update restart options\n */\n updateOptions(newOptions: Partial<RestartingProcessOptions>): void {\n this.logger.info(`Updating restart options`);\n this.options = {\n ...this.options,\n restartPolicy: newOptions.restartPolicy ?? this.options.restartPolicy,\n backoff: newOptions.backoff ?? this.options.backoff,\n crashLoop: newOptions.crashLoop ?? this.options.crashLoop,\n minUptimeMs: newOptions.minUptimeMs ?? this.options.minUptimeMs,\n maxTotalRestarts: newOptions.maxTotalRestarts ?? this.options.maxTotalRestarts,\n };\n }\n\n private resetCounters(): void {\n this._restartCount = 0;\n this.consecutiveFailures = 0;\n this.restartTimestamps = [];\n }\n\n private startProcess(): void {\n this.lastStartTime = Date.now();\n this._state = \"running\";\n\n this.lazyProcess\n .reset()\n .then(async () => {\n if (this.stopRequested) return;\n await this.lazyProcess.start();\n return this.lazyProcess.waitForExit();\n })\n .then((exitState) => {\n if (!exitState) return;\n if (this.stopRequested && exitState === \"error\") {\n this._state = \"stopped\";\n return;\n }\n if (exitState === \"stopped\" || exitState === \"error\") {\n this.handleProcessExit(exitState);\n }\n })\n .catch((err) => {\n if (this.stopRequested) return;\n this._state = \"stopped\";\n this.logger.error(`Failed to start process:`, err);\n });\n }\n\n private handleProcessExit(exitState: ProcessState): void {\n if (this.stopRequested) {\n this._state = \"stopped\";\n return;\n }\n\n const uptime = this.lastStartTime ? Date.now() - this.lastStartTime : 0;\n const wasHealthy = uptime >= this.options.minUptimeMs;\n const exitedWithError = exitState === \"error\";\n\n // Reset consecutive failures if the process ran long enough\n if (wasHealthy) {\n this.consecutiveFailures = 0;\n } else {\n this.consecutiveFailures++;\n }\n\n // Check if policy allows restart\n if (!this.shouldRestart(exitedWithError)) {\n this._state = \"stopped\";\n this.logger.info(\n `Process exited, policy \"${this.options.restartPolicy}\" does not allow restart`,\n );\n return;\n }\n\n // Check max total restarts\n if (\n this.options.maxTotalRestarts !== undefined &&\n this._restartCount >= this.options.maxTotalRestarts\n ) {\n this._state = \"max-restarts-reached\";\n this.logger.warn(`Max total restarts (${this.options.maxTotalRestarts}) reached`);\n return;\n }\n\n // Record restart timestamp for crash loop detection\n const now = Date.now();\n this.restartTimestamps.push(now);\n\n // Check for crash loop\n if (this.isInCrashLoop()) {\n this._state = \"crash-loop-backoff\";\n this.logger.warn(\n `Crash loop detected (${this.options.crashLoop.maxRestarts} restarts in ${this.options.crashLoop.windowMs}ms), backing off for ${this.options.crashLoop.backoffMs}ms`,\n );\n this.scheduleCrashLoopRecovery();\n return;\n }\n\n // Schedule restart with delay\n this._restartCount++;\n this.scheduleRestart();\n }\n\n private shouldRestart(exitedWithError: boolean): boolean {\n switch (this.options.restartPolicy) {\n case \"always\":\n return true;\n case \"never\":\n return false;\n case \"on-failure\":\n return exitedWithError;\n case \"on-success\":\n return !exitedWithError;\n case \"unless-stopped\":\n return !this.stopRequested;\n default:\n return false;\n }\n }\n\n private isInCrashLoop(): boolean {\n const { maxRestarts, windowMs } = this.options.crashLoop;\n const now = Date.now();\n const cutoff = now - windowMs;\n\n // Clean up old timestamps\n this.restartTimestamps = this.restartTimestamps.filter((ts) => ts > cutoff);\n\n return this.restartTimestamps.length >= maxRestarts;\n }\n\n private calculateDelay(): number {\n const { backoff } = this.options;\n\n if (backoff.type === \"fixed\") {\n return backoff.delayMs;\n }\n\n // Exponential backoff\n const multiplier = backoff.multiplier ?? 2;\n const delay = backoff.initialDelayMs * Math.pow(multiplier, this.consecutiveFailures);\n return Math.min(delay, backoff.maxDelayMs);\n }\n\n private scheduleRestart(): void {\n this._state = \"restarting\";\n const delay = this.calculateDelay();\n\n this.logger.info(`Restarting in ${delay}ms (restart #${this._restartCount})`);\n\n this.pendingDelayTimeout = setTimeout(() => {\n this.pendingDelayTimeout = null;\n if (this.stopRequested) {\n this._state = \"stopped\";\n return;\n }\n this.startProcess();\n }, delay);\n }\n\n private scheduleCrashLoopRecovery(): void {\n const { backoffMs } = this.options.crashLoop;\n\n this.pendingDelayTimeout = setTimeout(() => {\n this.pendingDelayTimeout = null;\n if (this.stopRequested) {\n this._state = \"stopped\";\n return;\n }\n\n // Reset crash loop timestamps after backoff\n this.restartTimestamps = [];\n this._restartCount++;\n this.logger.info(`Crash loop backoff complete, restarting (restart #${this._restartCount})`);\n this.startProcess();\n }, backoffMs);\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => {\n this.pendingDelayTimeout = setTimeout(() => {\n this.pendingDelayTimeout = null;\n resolve();\n }, ms);\n });\n }\n}\n","import { Cron } from \"croner\";\nimport * as v from \"valibot\";\nimport { LazyProcess, type ProcessDefinition } from \"./lazy-process.ts\";\nimport type { Logger } from \"./logger.ts\";\n\nexport const RetryConfig = v.object({\n maxRetries: v.number(),\n delayMs: v.optional(v.number()),\n});\nexport type RetryConfig = v.InferOutput<typeof RetryConfig>;\n\nexport const CronProcessOptions = v.object({\n schedule: v.string(),\n retry: v.optional(RetryConfig),\n runOnStart: v.optional(v.boolean()),\n});\nexport type CronProcessOptions = v.InferOutput<typeof CronProcessOptions>;\n\nexport const CronProcessState = v.picklist([\n \"idle\",\n \"scheduled\",\n \"running\",\n \"retrying\",\n \"queued\",\n \"stopping\",\n \"stopped\",\n]);\nexport type CronProcessState = v.InferOutput<typeof CronProcessState>;\n\nconst DEFAULT_RETRY_DELAY = 1000;\n\nexport class CronProcess {\n readonly name: string;\n readonly lazyProcess: LazyProcess;\n private options: CronProcessOptions;\n private logger: Logger;\n private cronJob: Cron | null = null;\n\n private _state: CronProcessState = \"idle\";\n private _runCount: number = 0;\n private _failCount: number = 0;\n private currentRetryAttempt: number = 0;\n private queuedRun: boolean = false;\n private stopRequested: boolean = false;\n private retryTimeout: ReturnType<typeof setTimeout> | null = null;\n\n constructor(\n name: string,\n definition: ProcessDefinition,\n options: CronProcessOptions,\n logger: Logger,\n ) {\n this.name = name;\n this.options = options;\n this.logger = logger;\n this.lazyProcess = new LazyProcess(name, definition, logger);\n }\n\n get state(): CronProcessState {\n return this._state;\n }\n\n get runCount(): number {\n return this._runCount;\n }\n\n get failCount(): number {\n return this._failCount;\n }\n\n get nextRun(): Date | null {\n if (!this.cronJob) return null;\n const next = this.cronJob.nextRun();\n return next ?? null;\n }\n\n start(): void {\n if (this._state === \"scheduled\" || this._state === \"running\" || this._state === \"queued\") {\n throw new Error(`CronProcess \"${this.name}\" is already ${this._state}`);\n }\n\n if (this._state === \"stopping\") {\n throw new Error(`CronProcess \"${this.name}\" is currently stopping`);\n }\n\n this.stopRequested = false;\n this.logger.info(`Starting cron schedule: ${this.options.schedule}`);\n\n this.cronJob = new Cron(this.options.schedule, { timezone: \"UTC\" }, () => {\n this.onCronTick();\n });\n\n this._state = \"scheduled\";\n\n if (this.options.runOnStart) {\n this.executeJob();\n }\n }\n\n async stop(timeout?: number): Promise<void> {\n this.stopRequested = true;\n\n // Stop the cron job\n if (this.cronJob) {\n this.cronJob.stop();\n this.cronJob = null;\n }\n\n // Clear any pending retry timeout\n if (this.retryTimeout) {\n clearTimeout(this.retryTimeout);\n this.retryTimeout = null;\n }\n\n if (this._state === \"idle\" || this._state === \"stopped\") {\n this._state = \"stopped\";\n return;\n }\n\n // If running, stop the current job\n if (this._state === \"running\" || this._state === \"retrying\" || this._state === \"queued\") {\n this._state = \"stopping\";\n await this.lazyProcess.stop(timeout);\n }\n\n this._state = \"stopped\";\n this.queuedRun = false;\n this.logger.info(`CronProcess stopped`);\n }\n\n async trigger(): Promise<void> {\n if (this.stopRequested) {\n throw new Error(`CronProcess \"${this.name}\" is stopped`);\n }\n\n // If already queued, just return (already have a run pending)\n if (this._state === \"queued\") {\n return;\n }\n\n // If already running, queue this trigger\n if (this._state === \"running\" || this._state === \"retrying\") {\n this.queuedRun = true;\n this._state = \"queued\";\n this.logger.info(`Run queued (current job still running)`);\n return;\n }\n\n await this.executeJob();\n }\n\n private onCronTick(): void {\n if (this.stopRequested) return;\n\n // If already running, queue the next run\n if (this._state === \"running\" || this._state === \"retrying\" || this._state === \"queued\") {\n this.queuedRun = true;\n if (this._state !== \"queued\") {\n this._state = \"queued\";\n }\n this.logger.info(`Cron tick: run queued (current job still running)`);\n return;\n }\n\n this.executeJob();\n }\n\n private async executeJob(): Promise<void> {\n if (this.stopRequested) return;\n\n this._state = \"running\";\n this.currentRetryAttempt = 0;\n this.logger.info(`Executing job`);\n\n await this.runJobWithRetry();\n }\n\n private async runJobWithRetry(): Promise<void> {\n if (this.stopRequested) return;\n\n // Reset and start the process\n await this.lazyProcess.reset();\n await this.lazyProcess.start();\n\n const exitState = await this.lazyProcess.waitForExit();\n if (this.stopRequested && exitState === \"error\") {\n this._state = \"stopped\";\n return;\n }\n this.handleJobComplete(exitState === \"error\");\n }\n\n private handleJobComplete(failed: boolean): void {\n if (this.stopRequested) {\n this._state = \"stopped\";\n return;\n }\n\n if (failed) {\n const maxRetries = this.options.retry?.maxRetries ?? 0;\n\n if (this.currentRetryAttempt < maxRetries) {\n // Retry\n this.currentRetryAttempt++;\n this._state = \"retrying\";\n const delayMs = this.options.retry?.delayMs ?? DEFAULT_RETRY_DELAY;\n\n this.logger.warn(\n `Job failed, retrying in ${delayMs}ms (attempt ${this.currentRetryAttempt}/${maxRetries})`,\n );\n\n this.retryTimeout = setTimeout(() => {\n this.retryTimeout = null;\n if (this.stopRequested) {\n this._state = \"stopped\";\n return;\n }\n this.runJobWithRetry();\n }, delayMs);\n return;\n }\n\n // All retries exhausted\n this._failCount++;\n this.logger.error(`Job failed after ${this.currentRetryAttempt} retries`);\n } else {\n this._runCount++;\n this.logger.info(`Job completed successfully`);\n }\n\n // Check for queued run\n if (this.queuedRun) {\n this.queuedRun = false;\n this.logger.info(`Starting queued run`);\n this.executeJob();\n return;\n }\n\n // Back to scheduled state\n if (this.cronJob) {\n this._state = \"scheduled\";\n } else {\n this._state = \"stopped\";\n }\n }\n}\n","import * as v from \"valibot\";\nimport { LazyProcess, ProcessDefinition } from \"./lazy-process.ts\";\nimport type { Logger } from \"./logger.ts\";\n\n// Per-task state\nexport const TaskStateSchema = v.picklist([\"pending\", \"running\", \"completed\", \"failed\", \"skipped\"]);\n\nexport type TaskState = v.InferOutput<typeof TaskStateSchema>;\n\n// Schema for named process definition\nexport const NamedProcessDefinitionSchema = v.object({\n name: v.string(),\n process: ProcessDefinition,\n});\n\nexport type NamedProcessDefinition = v.InferOutput<typeof NamedProcessDefinitionSchema>;\n\n// A task entry (single or parallel processes) with its state\nexport interface TaskEntry {\n id: string; // Unique task ID\n processes: NamedProcessDefinition[]; // Array (length 1 = sequential, >1 = parallel)\n state: TaskState;\n}\n\n// Simple TaskList state (just running or not)\nexport type TaskListState = \"idle\" | \"running\" | \"stopped\";\n\nexport class TaskList {\n readonly name: string;\n private _tasks: TaskEntry[] = [];\n private _state: TaskListState = \"idle\";\n private logger: Logger;\n private logFileResolver?: (processName: string) => string | undefined;\n private taskIdCounter: number = 0;\n private runningProcesses: LazyProcess[] = [];\n private stopRequested: boolean = false;\n private runLoopPromise: Promise<void> | null = null;\n\n constructor(\n name: string,\n logger: Logger,\n initialTasks?: (NamedProcessDefinition | NamedProcessDefinition[])[],\n logFileResolver?: (processName: string) => string | undefined,\n ) {\n this.name = name;\n this.logger = logger;\n this.logFileResolver = logFileResolver;\n\n // Add initial tasks if provided\n if (initialTasks) {\n for (const task of initialTasks) {\n this.addTask(task);\n }\n }\n }\n\n get state(): TaskListState {\n return this._state;\n }\n\n get tasks(): ReadonlyArray<TaskEntry> {\n return this._tasks;\n }\n\n removeTaskByTarget(target: string | number): TaskEntry {\n const index =\n typeof target === \"number\" ? target : this._tasks.findIndex((t) => t.id === target);\n if (index < 0 || index >= this._tasks.length) {\n throw new Error(`Task not found: ${target}`);\n }\n\n const task = this._tasks[index];\n if (task.state === \"running\") {\n throw new Error(`Cannot remove running task: ${task.id}`);\n }\n\n this._tasks.splice(index, 1);\n this.logger.info(`Task \"${task.id}\" removed`);\n return task;\n }\n\n /**\n * Add a single process or parallel processes as a new task\n * @returns The unique task ID\n */\n addTask(task: NamedProcessDefinition | NamedProcessDefinition[]): string {\n const id = `task-${++this.taskIdCounter}`;\n const processes = Array.isArray(task) ? task : [task];\n\n const entry: TaskEntry = {\n id,\n processes,\n state: \"pending\",\n };\n\n this._tasks.push(entry);\n this.logger.info(`Task \"${id}\" added with ${processes.length} process(es)`);\n\n return id;\n }\n\n /**\n * Begin executing pending tasks\n */\n start(): void {\n if (this._state === \"running\") {\n throw new Error(`TaskList \"${this.name}\" is already running`);\n }\n\n this.stopRequested = false;\n this._state = \"running\";\n this.logger.info(`TaskList started`);\n\n // Start the run loop (non-blocking)\n this.runLoopPromise = this.runLoop();\n }\n\n /**\n * Wait until the TaskList becomes idle (all pending tasks completed)\n */\n async waitUntilIdle(): Promise<void> {\n if (this._state === \"idle\" || this._state === \"stopped\") {\n return;\n }\n\n // Wait for the run loop to complete\n if (this.runLoopPromise) {\n await this.runLoopPromise;\n }\n }\n\n /**\n * Stop execution and mark remaining tasks as skipped\n */\n async stop(timeout?: number): Promise<void> {\n if (this._state === \"idle\" || this._state === \"stopped\") {\n this._state = \"stopped\";\n return;\n }\n\n this.stopRequested = true;\n this.logger.info(`Stopping TaskList...`);\n\n // Stop all currently running processes\n const stopPromises = this.runningProcesses.map((p) => p.stop(timeout));\n await Promise.all(stopPromises);\n this.runningProcesses = [];\n\n // Mark all pending tasks as skipped\n for (const task of this._tasks) {\n if (task.state === \"pending\") {\n task.state = \"skipped\";\n }\n }\n\n // Wait for run loop to finish\n if (this.runLoopPromise) {\n await this.runLoopPromise;\n this.runLoopPromise = null;\n }\n\n this._state = \"stopped\";\n this.logger.info(`TaskList stopped`);\n }\n\n private async runLoop(): Promise<void> {\n while (this._state === \"running\" && !this.stopRequested) {\n // Find the next pending task\n const nextTask = this._tasks.find((t) => t.state === \"pending\");\n\n if (!nextTask) {\n // No more pending tasks, go back to idle\n this._state = \"idle\";\n this.logger.info(`All tasks completed, TaskList is idle`);\n break;\n }\n\n await this.executeTask(nextTask);\n }\n }\n\n private async executeTask(task: TaskEntry): Promise<void> {\n if (this.stopRequested) {\n task.state = \"skipped\";\n return;\n }\n\n task.state = \"running\";\n const taskNames = task.processes.map((p) => p.name).join(\", \");\n this.logger.info(`Executing task \"${task.id}\": [${taskNames}]`);\n\n // Create LazyProcess instances for each process in the task\n const lazyProcesses: LazyProcess[] = task.processes.map((p) => {\n const logFile = this.logFileResolver?.(p.name);\n const childLogger = logFile\n ? this.logger.child(p.name, { logFile })\n : this.logger.child(p.name);\n return new LazyProcess(p.name, p.process, childLogger);\n });\n\n this.runningProcesses = lazyProcesses;\n\n try {\n // Start all processes (parallel if multiple)\n await Promise.all(lazyProcesses.map((lp) => lp.start()));\n\n // Wait for all processes to complete\n const results = await Promise.all(lazyProcesses.map((lp) => this.waitForProcess(lp)));\n\n // Check if any failed\n const anyFailed = results.some((r) => r === \"error\");\n\n if (this.stopRequested) {\n task.state = \"skipped\";\n } else if (anyFailed) {\n task.state = \"failed\";\n this.logger.warn(`Task \"${task.id}\" failed`);\n } else {\n task.state = \"completed\";\n this.logger.info(`Task \"${task.id}\" completed`);\n }\n } catch (err) {\n task.state = \"failed\";\n this.logger.error(`Task \"${task.id}\" error:`, err);\n } finally {\n this.runningProcesses = [];\n }\n }\n\n private async waitForProcess(lp: LazyProcess): Promise<\"stopped\" | \"error\"> {\n const state = await lp.waitForExit();\n return state === \"error\" ? \"error\" : \"stopped\";\n }\n}\n","import { parse } from \"dotenv\";\nimport { existsSync, globSync, readFileSync } from \"node:fs\";\nimport { resolve, basename, isAbsolute } from \"node:path\";\nimport { watch } from \"chokidar\";\n\nexport type EnvChangeEvent =\n | {\n type: \"global\";\n }\n | {\n type: \"process\";\n key: string;\n };\n\nexport type EnvChangeCallback = (event: EnvChangeEvent) => void;\n\nexport interface EnvManagerConfig {\n cwd: string;\n globalEnvFile?: string;\n customEnvFiles?: Record<string, string>;\n}\n\nexport class EnvManager {\n private globalEnv: Record<string, string> = {};\n private globalEnvPath: string;\n private env: Map<string, Record<string, string>> = new Map();\n private watchers: Map<string, ReturnType<typeof watch>> = new Map();\n private fileToKey: Map<string, string> = new Map();\n private changeCallbacks: Set<EnvChangeCallback> = new Set();\n private cwdWatcher: ReturnType<typeof watch> | null = null;\n private customKeys: Set<string> = new Set();\n\n constructor(private config: EnvManagerConfig) {\n this.globalEnvPath = config.globalEnvFile\n ? isAbsolute(config.globalEnvFile)\n ? config.globalEnvFile\n : resolve(config.cwd, config.globalEnvFile)\n : resolve(config.cwd, \".env\");\n\n // Load custom env files first (before auto-discovery)\n this.loadCustomEnvFiles();\n this.loadEnvFilesFromCwd();\n this.watchCwdForNewFiles();\n }\n\n /**\n * Register a custom env file for a key.\n * Once registered, auto-discovered .env.{key} files will be ignored for this key.\n */\n public registerFile(key: string, filePath: string): void {\n const absolutePath = isAbsolute(filePath) ? filePath : resolve(this.config.cwd, filePath);\n this.customKeys.add(key);\n this.loadEnvFile(key, absolutePath);\n }\n\n /**\n * Check if a key has a custom env file registered\n */\n public hasCustomFile(key: string): boolean {\n return this.customKeys.has(key);\n }\n\n /**\n * Load custom env files from config\n */\n private loadCustomEnvFiles(): void {\n if (!this.config.customEnvFiles) return;\n\n for (const [key, filePath] of Object.entries(this.config.customEnvFiles)) {\n this.registerFile(key, filePath);\n }\n }\n\n public getEnvVars(key: string): Record<string, string> {\n const specificEnv = this.env.get(key);\n return {\n ...this.globalEnv,\n ...specificEnv,\n };\n }\n\n private loadEnvFilesFromCwd(): void {\n if (existsSync(this.globalEnvPath)) this.loadGlobalEnv(this.globalEnvPath);\n\n try {\n const envFiles = globSync(\".env.*\", { cwd: this.config.cwd });\n for (const filePath of envFiles) {\n const key = this.getEnvKeySuffix(basename(filePath));\n // Skip if key has a custom file registered\n if (key && !this.customKeys.has(key)) {\n this.loadEnvFile(key, resolve(this.config.cwd, filePath));\n }\n }\n } catch (err) {\n console.warn(\"Failed to scan env files:\", err);\n }\n }\n\n private parseEnvFile(absolutePath: string): Record<string, string> | null {\n try {\n const content = readFileSync(absolutePath, \"utf-8\");\n return parse(content) ?? {};\n } catch (err) {\n console.warn(`Failed to parse env file: ${absolutePath}`, err);\n return null;\n }\n }\n\n private getEnvKeySuffix(fileName: string): string | null {\n const match = fileName.match(/^\\.env\\.(.+)$/);\n return match ? match[1] : null;\n }\n\n private loadGlobalEnv(absolutePath: string) {\n if (!existsSync(absolutePath)) return;\n const parsed = this.parseEnvFile(absolutePath);\n if (parsed) {\n this.globalEnv = parsed;\n this.watchFile(absolutePath);\n }\n }\n\n private loadEnvFile(key: string, absolutePath: string) {\n const parsed = this.parseEnvFile(absolutePath);\n if (!parsed) return;\n\n this.env.set(key, parsed);\n this.fileToKey.set(absolutePath, key);\n this.watchFile(absolutePath);\n }\n\n private watchFile(absolutePath: string): void {\n if (this.watchers.has(absolutePath)) return;\n try {\n const watcher = watch(absolutePath, { ignoreInitial: true })\n .on(\"change\", () => {\n this.handleFileChange(absolutePath);\n })\n .on(\"unlink\", () => {\n this.handleFileDelete(absolutePath);\n });\n\n this.watchers.set(absolutePath, watcher);\n } catch (err) {\n console.warn(`Failed to watch env file: ${absolutePath}`, err);\n }\n }\n\n /**\n * Watch cwd for new .env.* files\n */\n private watchCwdForNewFiles(): void {\n try {\n this.cwdWatcher = watch(this.config.cwd, {\n ignoreInitial: true,\n depth: 0,\n }).on(\"add\", (filePath) => {\n this.handleNewFile(filePath);\n });\n } catch (err) {\n console.warn(`Failed to watch cwd for new env files:`, err);\n }\n }\n\n private handleFileChange(absolutePath: string): void {\n if (absolutePath === this.globalEnvPath) {\n this.loadGlobalEnv(absolutePath);\n this.notifyCallbacks({ type: \"global\" });\n return;\n }\n\n const key = this.fileToKey.get(absolutePath);\n if (!key) return;\n\n const parsed = this.parseEnvFile(absolutePath);\n if (!parsed) return;\n\n this.env.set(key, parsed);\n this.notifyCallbacks({ type: \"process\", key });\n }\n\n private handleFileDelete(absolutePath: string): void {\n const watcher = this.watchers.get(absolutePath);\n if (watcher) {\n watcher.close();\n this.watchers.delete(absolutePath);\n }\n\n if (absolutePath === this.globalEnvPath) {\n this.globalEnv = {};\n this.notifyCallbacks({ type: \"global\" });\n return;\n }\n\n const key = this.fileToKey.get(absolutePath);\n if (key) {\n this.env.delete(key);\n this.fileToKey.delete(absolutePath);\n this.notifyCallbacks({ type: \"process\", key });\n }\n }\n\n private handleNewFile(filePath: string): void {\n const absolutePath = isAbsolute(filePath) ? filePath : resolve(this.config.cwd, filePath);\n\n if (absolutePath === this.globalEnvPath) {\n this.loadGlobalEnv(absolutePath);\n this.notifyCallbacks({ type: \"global\" });\n return;\n }\n\n const key = this.getEnvKeySuffix(basename(filePath));\n // Skip if key has a custom file registered or already loaded\n if (key && !this.customKeys.has(key) && !this.env.has(key)) {\n this.loadEnvFile(key, absolutePath);\n this.notifyCallbacks({ type: \"process\", key });\n }\n }\n\n private notifyCallbacks(event: EnvChangeEvent): void {\n for (const callback of this.changeCallbacks) callback(event);\n }\n\n onChange(callback: EnvChangeCallback): () => void {\n this.changeCallbacks.add(callback);\n return () => {\n this.changeCallbacks.delete(callback);\n };\n }\n\n close(): void {\n for (const watcher of this.watchers.values()) {\n watcher.close();\n }\n this.watchers.clear();\n\n if (this.cwdWatcher) {\n this.cwdWatcher.close();\n this.cwdWatcher = null;\n }\n\n this.changeCallbacks.clear();\n }\n}\n","import { appendFileSync } from \"node:fs\";\nimport { format } from \"node:util\";\n\nconst colors = {\n reset: \"\\x1b[0m\",\n gray: \"\\x1b[90m\",\n white: \"\\x1b[37m\",\n green: \"\\x1b[32m\",\n yellow: \"\\x1b[33m\",\n red: \"\\x1b[31m\",\n bold: \"\\x1b[1m\",\n} as const;\n\nconst levelColors = {\n debug: colors.gray,\n info: colors.green,\n warn: colors.yellow,\n error: colors.red,\n} as const;\n\nconst formatTime = (date: Date) =>\n Intl.DateTimeFormat(\"en-US\", {\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n fractionalSecondDigits: 3,\n hourCycle: \"h23\",\n }).format(date);\n\nconst formatPrefixNoColor = (level: string, name: string, time: Date) => {\n const levelFormatted = level.toUpperCase().padStart(5);\n const timestamp = formatTime(time);\n return `[${timestamp}] ${levelFormatted} (${name})`;\n};\n\nconst formatPrefixWithColor = (level: string, name: string, time: Date) => {\n const levelFormatted = level.toUpperCase().padStart(5);\n const timestamp = formatTime(time);\n const levelTint = levelColors[level as keyof typeof levelColors] ?? \"\";\n return `${colors.gray}[${timestamp}]${colors.reset} ${levelTint}${levelFormatted}${colors.reset} (${name})`;\n};\n\ntype LoggerConfig = {\n name: string;\n stdout?: boolean;\n logFile?: string;\n prefix?: string;\n};\n\nconst formatMessagePrefix = (prefix: string | undefined, colored: boolean) => {\n if (!prefix) return \"\";\n if (colored) return `${colors.bold}${colors.white}[${prefix}]${colors.reset} `;\n return `[${prefix}]`;\n};\n\nconst logLine = (config: LoggerConfig, level: \"debug\" | \"info\" | \"warn\" | \"error\", args: any[]) => {\n const message = format(...args);\n const time = new Date();\n\n if (config.logFile) {\n const plainPrefix = formatPrefixNoColor(level, config.name, time);\n const plainMessagePrefix = formatMessagePrefix(config.prefix, false);\n const plainLine = [plainPrefix, plainMessagePrefix, message].filter(Boolean).join(\" \");\n try {\n appendFileSync(config.logFile, `${plainLine}\\n`);\n } catch {\n // Ignore errors\n }\n }\n\n if (config.stdout) {\n const coloredPrefix = formatPrefixWithColor(level, config.name, time);\n const coloredMessagePrefix = formatMessagePrefix(config.prefix, true);\n const coloredLine = [coloredPrefix, coloredMessagePrefix, message].filter(Boolean).join(\" \");\n console[level](coloredLine);\n }\n};\n\nexport const logger = (_config: LoggerConfig) => {\n const config = { stdout: true, ..._config };\n return {\n info: (...args: any[]) => logLine(config, \"info\", args),\n error: (...args: any[]) => logLine(config, \"error\", args),\n warn: (...args: any[]) => logLine(config, \"warn\", args),\n debug: (...args: any[]) => logLine(config, \"debug\", args),\n child: (suffix: string, overrides: Partial<Omit<LoggerConfig, \"name\">> = {}) =>\n logger({\n ...config,\n ...overrides,\n name: `${config.name}:${suffix}`,\n }),\n withPrefix: (prefix: string) =>\n logger({\n ...config,\n prefix,\n }),\n };\n};\n\nexport type Logger = ReturnType<typeof logger>;\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pidnap",
|
|
3
|
-
"version": "0.0.0-dev.
|
|
3
|
+
"version": "0.0.0-dev.5",
|
|
4
4
|
"bin": {
|
|
5
5
|
"pidnap": "./dist/cli.mjs"
|
|
6
6
|
},
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"@orpc/client": "^1.13.4",
|
|
24
24
|
"@orpc/contract": "^1.13.4",
|
|
25
25
|
"@orpc/server": "^1.13.4",
|
|
26
|
+
"chokidar": "^5.0.0",
|
|
26
27
|
"cli-table3": "^0.6.5",
|
|
27
28
|
"commander": "^14.0.2",
|
|
28
29
|
"croner": "^9.1.0",
|
|
@@ -45,6 +46,7 @@
|
|
|
45
46
|
"format": "oxfmt",
|
|
46
47
|
"lint": "oxlint",
|
|
47
48
|
"test": "vitest",
|
|
48
|
-
"docker:dev": "docker build -t pidnap -f Dockerfile.example . && docker run --rm pidnap"
|
|
49
|
+
"docker:dev": "docker build -t pidnap -f Dockerfile.example . && docker run --rm --name pidnap-dev pidnap",
|
|
50
|
+
"docker:shell": "docker exec -it pidnap-dev bash"
|
|
49
51
|
}
|
|
50
52
|
}
|
package/src/api/client.ts
CHANGED
|
@@ -4,9 +4,9 @@ import type { ContractRouterClient } from "@orpc/contract";
|
|
|
4
4
|
import type { api } from "./contract.ts";
|
|
5
5
|
|
|
6
6
|
export function createClient(
|
|
7
|
-
url
|
|
7
|
+
url = process.env.PIDNAP_RPC_URL ?? "http://localhost:9876/rpc",
|
|
8
8
|
): ContractRouterClient<typeof api> {
|
|
9
|
-
const authToken = process.env.
|
|
9
|
+
const authToken = process.env.PIDNAP_AUTH_TOKEN;
|
|
10
10
|
const headers = authToken ? { Authorization: `Bearer ${authToken}` } : undefined;
|
|
11
11
|
const link = new RPCLink({ url, headers });
|
|
12
12
|
return createORPCClient(link);
|
package/src/api/contract.ts
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
import { oc as ocBase } from "@orpc/contract";
|
|
2
2
|
import * as v from "valibot";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { ProcessDefinition } from "../lazy-process.ts";
|
|
4
|
+
import { RestartingProcessState } from "../restarting-process.ts";
|
|
5
|
+
import { CronProcessState } from "../cron-process.ts";
|
|
6
6
|
import { TaskStateSchema } from "../task-list.ts";
|
|
7
7
|
|
|
8
|
-
// Re-export schemas for use in other modules
|
|
9
|
-
export { ProcessDefinitionSchema } from "../lazy-process.ts";
|
|
10
|
-
export { RestartingProcessStateSchema } from "../restarting-process.ts";
|
|
11
|
-
export { CronProcessStateSchema } from "../cron-process.ts";
|
|
12
|
-
export { TaskStateSchema } from "../task-list.ts";
|
|
13
|
-
|
|
14
8
|
const oc = ocBase.$input(v.void());
|
|
15
9
|
|
|
16
10
|
// Resource target (name or index)
|
|
@@ -40,7 +34,7 @@ export type ManagerStatus = v.InferOutput<typeof ManagerStatusSchema>;
|
|
|
40
34
|
// API response schemas
|
|
41
35
|
export const RestartingProcessInfoSchema = v.object({
|
|
42
36
|
name: v.string(),
|
|
43
|
-
state:
|
|
37
|
+
state: RestartingProcessState,
|
|
44
38
|
restarts: v.number(),
|
|
45
39
|
});
|
|
46
40
|
|
|
@@ -48,7 +42,7 @@ export type RestartingProcessInfo = v.InferOutput<typeof RestartingProcessInfoSc
|
|
|
48
42
|
|
|
49
43
|
export const CronProcessInfoSchema = v.object({
|
|
50
44
|
name: v.string(),
|
|
51
|
-
state:
|
|
45
|
+
state: CronProcessState,
|
|
52
46
|
runCount: v.number(),
|
|
53
47
|
failCount: v.number(),
|
|
54
48
|
nextRun: v.nullable(v.string()), // ISO date string
|
|
@@ -73,7 +67,7 @@ export const processes = {
|
|
|
73
67
|
get: oc.input(v.object({ target: ResourceTarget })).output(RestartingProcessInfoSchema),
|
|
74
68
|
list: oc.output(v.array(RestartingProcessInfoSchema)),
|
|
75
69
|
add: oc
|
|
76
|
-
.input(v.object({ name: v.string(), definition:
|
|
70
|
+
.input(v.object({ name: v.string(), definition: ProcessDefinition }))
|
|
77
71
|
.output(RestartingProcessInfoSchema),
|
|
78
72
|
start: oc.input(v.object({ target: ResourceTarget })).output(RestartingProcessInfoSchema),
|
|
79
73
|
stop: oc.input(v.object({ target: ResourceTarget })).output(RestartingProcessInfoSchema),
|
|
@@ -84,7 +78,7 @@ export const processes = {
|
|
|
84
78
|
.input(
|
|
85
79
|
v.object({
|
|
86
80
|
target: ResourceTarget,
|
|
87
|
-
definition:
|
|
81
|
+
definition: ProcessDefinition,
|
|
88
82
|
restartImmediately: v.optional(v.boolean()),
|
|
89
83
|
}),
|
|
90
84
|
)
|
|
@@ -96,7 +90,7 @@ export const tasks = {
|
|
|
96
90
|
get: oc.input(v.object({ target: ResourceTarget })).output(TaskEntryInfoSchema),
|
|
97
91
|
list: oc.output(v.array(TaskEntryInfoSchema)),
|
|
98
92
|
add: oc
|
|
99
|
-
.input(v.object({ name: v.string(), definition:
|
|
93
|
+
.input(v.object({ name: v.string(), definition: ProcessDefinition }))
|
|
100
94
|
.output(TaskEntryInfoSchema),
|
|
101
95
|
remove: oc.input(v.object({ target: ResourceTarget })).output(TaskEntryInfoSchema),
|
|
102
96
|
};
|
package/src/cli.ts
CHANGED
|
@@ -7,25 +7,26 @@ import { RPCHandler } from "@orpc/server/node";
|
|
|
7
7
|
import { onError } from "@orpc/server";
|
|
8
8
|
import * as v from "valibot";
|
|
9
9
|
import { router } from "./api/server.ts";
|
|
10
|
-
import { Manager,
|
|
10
|
+
import { Manager, ManagerConfig } from "./manager.ts";
|
|
11
11
|
import { logger } from "./logger.ts";
|
|
12
|
-
import { tsImport } from "tsx/esm/api";
|
|
13
12
|
import Table from "cli-table3";
|
|
14
13
|
import { createClient } from "./api/client.ts";
|
|
15
|
-
import {
|
|
14
|
+
import { ProcessDefinition } from "./lazy-process.ts";
|
|
15
|
+
import { tImport } from "./utils.ts";
|
|
16
16
|
|
|
17
17
|
const program = new Command();
|
|
18
18
|
|
|
19
19
|
program
|
|
20
20
|
.name("pidnap")
|
|
21
21
|
.description("Process manager with init system capabilities")
|
|
22
|
-
.version("0.0.0-dev.
|
|
22
|
+
.version("0.0.0-dev.5");
|
|
23
23
|
|
|
24
24
|
program
|
|
25
25
|
.command("init")
|
|
26
26
|
.description("Initialize and run the process manager with config file")
|
|
27
27
|
.option("-c, --config [path]", "Path to config file", "pidnap.config.ts")
|
|
28
28
|
.action(async (options) => {
|
|
29
|
+
process.title = "pidnap";
|
|
29
30
|
const initLogger = logger({ name: "pidnap" });
|
|
30
31
|
try {
|
|
31
32
|
// Resolve config file path
|
|
@@ -33,16 +34,16 @@ program
|
|
|
33
34
|
const configUrl = pathToFileURL(configPath).href;
|
|
34
35
|
|
|
35
36
|
// Import the config file
|
|
36
|
-
const configModule = await
|
|
37
|
+
const configModule = await tImport(configUrl);
|
|
37
38
|
const rawConfig =
|
|
38
39
|
configModule.default.default || configModule.default || configModule.config || {};
|
|
39
40
|
|
|
40
41
|
// Parse and validate the config with Valibot
|
|
41
|
-
const config = v.parse(
|
|
42
|
+
const config = v.parse(ManagerConfig, rawConfig);
|
|
42
43
|
|
|
43
44
|
// Extract HTTP config with defaults
|
|
44
|
-
const host = config.http?.host ?? "
|
|
45
|
-
const port = config.http?.port ??
|
|
45
|
+
const host = config.http?.host ?? "127.0.0.1";
|
|
46
|
+
const port = config.http?.port ?? 9876;
|
|
46
47
|
const authToken = config.http?.authToken;
|
|
47
48
|
|
|
48
49
|
// Create manager with config
|
|
@@ -76,7 +77,7 @@ program
|
|
|
76
77
|
});
|
|
77
78
|
if (matched) return;
|
|
78
79
|
res.statusCode = 404;
|
|
79
|
-
res.end("Not found");
|
|
80
|
+
res.end("Not found\n");
|
|
80
81
|
});
|
|
81
82
|
|
|
82
83
|
server.listen(port, host, async () => {
|
|
@@ -108,7 +109,7 @@ program
|
|
|
108
109
|
program
|
|
109
110
|
.command("status")
|
|
110
111
|
.description("Show manager status")
|
|
111
|
-
.option("-u, --url <url>", "RPC server URL"
|
|
112
|
+
.option("-u, --url <url>", "RPC server URL")
|
|
112
113
|
.action(async (options) => {
|
|
113
114
|
try {
|
|
114
115
|
const client = createClient(options.url);
|
|
@@ -123,12 +124,12 @@ program
|
|
|
123
124
|
}
|
|
124
125
|
});
|
|
125
126
|
|
|
126
|
-
const
|
|
127
|
+
const processGroup = program.command("process").description("Manage restarting processes");
|
|
127
128
|
|
|
128
|
-
|
|
129
|
+
processGroup
|
|
129
130
|
.command("list")
|
|
130
131
|
.description("List restarting processes")
|
|
131
|
-
.option("-u, --url <url>", "RPC server URL"
|
|
132
|
+
.option("-u, --url <url>", "RPC server URL")
|
|
132
133
|
.action(async (options) => {
|
|
133
134
|
try {
|
|
134
135
|
const client = createClient(options.url);
|
|
@@ -145,11 +146,11 @@ processes
|
|
|
145
146
|
}
|
|
146
147
|
});
|
|
147
148
|
|
|
148
|
-
|
|
149
|
+
processGroup
|
|
149
150
|
.command("get")
|
|
150
151
|
.description("Get a restarting process by name or index")
|
|
151
152
|
.argument("<target>", "Process name or index")
|
|
152
|
-
.option("-u, --url <url>", "RPC server URL"
|
|
153
|
+
.option("-u, --url <url>", "RPC server URL")
|
|
153
154
|
.action(async (target, options) => {
|
|
154
155
|
try {
|
|
155
156
|
const client = createClient(options.url);
|
|
@@ -164,12 +165,12 @@ processes
|
|
|
164
165
|
}
|
|
165
166
|
});
|
|
166
167
|
|
|
167
|
-
|
|
168
|
+
processGroup
|
|
168
169
|
.command("add")
|
|
169
170
|
.description("Add a restarting process")
|
|
170
171
|
.requiredOption("-n, --name <name>", "Process name")
|
|
171
172
|
.requiredOption("-d, --definition <json>", "Process definition JSON")
|
|
172
|
-
.option("-u, --url <url>", "RPC server URL"
|
|
173
|
+
.option("-u, --url <url>", "RPC server URL")
|
|
173
174
|
.action(async (options) => {
|
|
174
175
|
try {
|
|
175
176
|
const client = createClient(options.url);
|
|
@@ -185,11 +186,11 @@ processes
|
|
|
185
186
|
}
|
|
186
187
|
});
|
|
187
188
|
|
|
188
|
-
|
|
189
|
+
processGroup
|
|
189
190
|
.command("start")
|
|
190
191
|
.description("Start a restarting process")
|
|
191
192
|
.argument("<target>", "Process name or index")
|
|
192
|
-
.option("-u, --url <url>", "RPC server URL"
|
|
193
|
+
.option("-u, --url <url>", "RPC server URL")
|
|
193
194
|
.action(async (target, options) => {
|
|
194
195
|
try {
|
|
195
196
|
const client = createClient(options.url);
|
|
@@ -204,11 +205,11 @@ processes
|
|
|
204
205
|
}
|
|
205
206
|
});
|
|
206
207
|
|
|
207
|
-
|
|
208
|
+
processGroup
|
|
208
209
|
.command("stop")
|
|
209
210
|
.description("Stop a restarting process")
|
|
210
211
|
.argument("<target>", "Process name or index")
|
|
211
|
-
.option("-u, --url <url>", "RPC server URL"
|
|
212
|
+
.option("-u, --url <url>", "RPC server URL")
|
|
212
213
|
.action(async (target, options) => {
|
|
213
214
|
try {
|
|
214
215
|
const client = createClient(options.url);
|
|
@@ -223,12 +224,12 @@ processes
|
|
|
223
224
|
}
|
|
224
225
|
});
|
|
225
226
|
|
|
226
|
-
|
|
227
|
+
processGroup
|
|
227
228
|
.command("restart")
|
|
228
229
|
.description("Restart a restarting process")
|
|
229
230
|
.argument("<target>", "Process name or index")
|
|
230
231
|
.option("-f, --force", "Force restart")
|
|
231
|
-
.option("-u, --url <url>", "RPC server URL"
|
|
232
|
+
.option("-u, --url <url>", "RPC server URL")
|
|
232
233
|
.action(async (target, options) => {
|
|
233
234
|
try {
|
|
234
235
|
const client = createClient(options.url);
|
|
@@ -246,11 +247,11 @@ processes
|
|
|
246
247
|
}
|
|
247
248
|
});
|
|
248
249
|
|
|
249
|
-
|
|
250
|
+
processGroup
|
|
250
251
|
.command("remove")
|
|
251
252
|
.description("Remove a restarting process")
|
|
252
253
|
.argument("<target>", "Process name or index")
|
|
253
|
-
.option("-u, --url <url>", "RPC server URL"
|
|
254
|
+
.option("-u, --url <url>", "RPC server URL")
|
|
254
255
|
.action(async (target, options) => {
|
|
255
256
|
try {
|
|
256
257
|
const client = createClient(options.url);
|
|
@@ -267,7 +268,7 @@ const crons = program.command("crons").description("Manage cron processes");
|
|
|
267
268
|
crons
|
|
268
269
|
.command("list")
|
|
269
270
|
.description("List cron processes")
|
|
270
|
-
.option("-u, --url <url>", "RPC server URL"
|
|
271
|
+
.option("-u, --url <url>", "RPC server URL")
|
|
271
272
|
.action(async (options) => {
|
|
272
273
|
try {
|
|
273
274
|
const client = createClient(options.url);
|
|
@@ -288,7 +289,7 @@ crons
|
|
|
288
289
|
.command("get")
|
|
289
290
|
.description("Get a cron process by name or index")
|
|
290
291
|
.argument("<target>", "Cron name or index")
|
|
291
|
-
.option("-u, --url <url>", "RPC server URL"
|
|
292
|
+
.option("-u, --url <url>", "RPC server URL")
|
|
292
293
|
.action(async (target, options) => {
|
|
293
294
|
try {
|
|
294
295
|
const client = createClient(options.url);
|
|
@@ -307,7 +308,7 @@ crons
|
|
|
307
308
|
.command("trigger")
|
|
308
309
|
.description("Trigger a cron process")
|
|
309
310
|
.argument("<target>", "Cron name or index")
|
|
310
|
-
.option("-u, --url <url>", "RPC server URL"
|
|
311
|
+
.option("-u, --url <url>", "RPC server URL")
|
|
311
312
|
.action(async (target, options) => {
|
|
312
313
|
try {
|
|
313
314
|
const client = createClient(options.url);
|
|
@@ -326,7 +327,7 @@ crons
|
|
|
326
327
|
.command("start")
|
|
327
328
|
.description("Start a cron process")
|
|
328
329
|
.argument("<target>", "Cron name or index")
|
|
329
|
-
.option("-u, --url <url>", "RPC server URL"
|
|
330
|
+
.option("-u, --url <url>", "RPC server URL")
|
|
330
331
|
.action(async (target, options) => {
|
|
331
332
|
try {
|
|
332
333
|
const client = createClient(options.url);
|
|
@@ -345,7 +346,7 @@ crons
|
|
|
345
346
|
.command("stop")
|
|
346
347
|
.description("Stop a cron process")
|
|
347
348
|
.argument("<target>", "Cron name or index")
|
|
348
|
-
.option("-u, --url <url>", "RPC server URL"
|
|
349
|
+
.option("-u, --url <url>", "RPC server URL")
|
|
349
350
|
.action(async (target, options) => {
|
|
350
351
|
try {
|
|
351
352
|
const client = createClient(options.url);
|
|
@@ -365,7 +366,7 @@ const tasks = program.command("tasks").description("Manage tasks");
|
|
|
365
366
|
tasks
|
|
366
367
|
.command("list")
|
|
367
368
|
.description("List tasks")
|
|
368
|
-
.option("-u, --url <url>", "RPC server URL"
|
|
369
|
+
.option("-u, --url <url>", "RPC server URL")
|
|
369
370
|
.action(async (options) => {
|
|
370
371
|
try {
|
|
371
372
|
const client = createClient(options.url);
|
|
@@ -386,7 +387,7 @@ tasks
|
|
|
386
387
|
.command("get")
|
|
387
388
|
.description("Get a task by id or index")
|
|
388
389
|
.argument("<target>", "Task id or index")
|
|
389
|
-
.option("-u, --url <url>", "RPC server URL"
|
|
390
|
+
.option("-u, --url <url>", "RPC server URL")
|
|
390
391
|
.action(async (target, options) => {
|
|
391
392
|
try {
|
|
392
393
|
const client = createClient(options.url);
|
|
@@ -406,7 +407,7 @@ tasks
|
|
|
406
407
|
.description("Add a task")
|
|
407
408
|
.requiredOption("-n, --name <name>", "Task name")
|
|
408
409
|
.requiredOption("-d, --definition <json>", "Process definition JSON")
|
|
409
|
-
.option("-u, --url <url>", "RPC server URL"
|
|
410
|
+
.option("-u, --url <url>", "RPC server URL")
|
|
410
411
|
.action(async (options) => {
|
|
411
412
|
try {
|
|
412
413
|
const client = createClient(options.url);
|
|
@@ -426,7 +427,7 @@ tasks
|
|
|
426
427
|
.command("remove")
|
|
427
428
|
.description("Remove a task by id or index")
|
|
428
429
|
.argument("<target>", "Task id or index")
|
|
429
|
-
.option("-u, --url <url>", "RPC server URL"
|
|
430
|
+
.option("-u, --url <url>", "RPC server URL")
|
|
430
431
|
.action(async (target, options) => {
|
|
431
432
|
try {
|
|
432
433
|
const client = createClient(options.url);
|
|
@@ -442,7 +443,6 @@ tasks
|
|
|
442
443
|
});
|
|
443
444
|
|
|
444
445
|
const TargetSchema = v.union([v.string(), v.number()]);
|
|
445
|
-
type ProcessDefinition = v.InferOutput<typeof ProcessDefinitionSchema>;
|
|
446
446
|
|
|
447
447
|
function parseTarget(value: string): string | number {
|
|
448
448
|
const asNumber = Number(value);
|
|
@@ -452,7 +452,7 @@ function parseTarget(value: string): string | number {
|
|
|
452
452
|
function parseDefinition(raw: string): ProcessDefinition {
|
|
453
453
|
try {
|
|
454
454
|
const parsed = JSON.parse(raw);
|
|
455
|
-
return v.parse(
|
|
455
|
+
return v.parse(ProcessDefinition, parsed);
|
|
456
456
|
} catch (error) {
|
|
457
457
|
console.error("Invalid --definition JSON. Expected a ProcessDefinition.");
|
|
458
458
|
throw error;
|
package/src/cron-process.ts
CHANGED
|
@@ -3,25 +3,20 @@ import * as v from "valibot";
|
|
|
3
3
|
import { LazyProcess, type ProcessDefinition } from "./lazy-process.ts";
|
|
4
4
|
import type { Logger } from "./logger.ts";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
export const RetryConfigSchema = v.object({
|
|
6
|
+
export const RetryConfig = v.object({
|
|
8
7
|
maxRetries: v.number(),
|
|
9
8
|
delayMs: v.optional(v.number()),
|
|
10
9
|
});
|
|
10
|
+
export type RetryConfig = v.InferOutput<typeof RetryConfig>;
|
|
11
11
|
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
// Cron process options schema
|
|
15
|
-
export const CronProcessOptionsSchema = v.object({
|
|
12
|
+
export const CronProcessOptions = v.object({
|
|
16
13
|
schedule: v.string(),
|
|
17
|
-
retry: v.optional(
|
|
14
|
+
retry: v.optional(RetryConfig),
|
|
18
15
|
runOnStart: v.optional(v.boolean()),
|
|
19
16
|
});
|
|
17
|
+
export type CronProcessOptions = v.InferOutput<typeof CronProcessOptions>;
|
|
20
18
|
|
|
21
|
-
export
|
|
22
|
-
|
|
23
|
-
// State
|
|
24
|
-
export const CronProcessStateSchema = v.picklist([
|
|
19
|
+
export const CronProcessState = v.picklist([
|
|
25
20
|
"idle",
|
|
26
21
|
"scheduled",
|
|
27
22
|
"running",
|
|
@@ -30,19 +25,17 @@ export const CronProcessStateSchema = v.picklist([
|
|
|
30
25
|
"stopping",
|
|
31
26
|
"stopped",
|
|
32
27
|
]);
|
|
33
|
-
|
|
34
|
-
export type CronProcessState = v.InferOutput<typeof CronProcessStateSchema>;
|
|
28
|
+
export type CronProcessState = v.InferOutput<typeof CronProcessState>;
|
|
35
29
|
|
|
36
30
|
const DEFAULT_RETRY_DELAY = 1000;
|
|
37
31
|
|
|
38
32
|
export class CronProcess {
|
|
39
33
|
readonly name: string;
|
|
40
|
-
|
|
34
|
+
readonly lazyProcess: LazyProcess;
|
|
41
35
|
private options: CronProcessOptions;
|
|
42
36
|
private logger: Logger;
|
|
43
37
|
private cronJob: Cron | null = null;
|
|
44
38
|
|
|
45
|
-
// State tracking
|
|
46
39
|
private _state: CronProcessState = "idle";
|
|
47
40
|
private _runCount: number = 0;
|
|
48
41
|
private _failCount: number = 0;
|
|
@@ -93,14 +86,12 @@ export class CronProcess {
|
|
|
93
86
|
this.stopRequested = false;
|
|
94
87
|
this.logger.info(`Starting cron schedule: ${this.options.schedule}`);
|
|
95
88
|
|
|
96
|
-
// Create cron job with UTC timezone
|
|
97
89
|
this.cronJob = new Cron(this.options.schedule, { timezone: "UTC" }, () => {
|
|
98
90
|
this.onCronTick();
|
|
99
91
|
});
|
|
100
92
|
|
|
101
93
|
this._state = "scheduled";
|
|
102
94
|
|
|
103
|
-
// Run immediately if configured
|
|
104
95
|
if (this.options.runOnStart) {
|
|
105
96
|
this.executeJob();
|
|
106
97
|
}
|
|
@@ -189,7 +180,7 @@ export class CronProcess {
|
|
|
189
180
|
|
|
190
181
|
// Reset and start the process
|
|
191
182
|
await this.lazyProcess.reset();
|
|
192
|
-
this.lazyProcess.start();
|
|
183
|
+
await this.lazyProcess.start();
|
|
193
184
|
|
|
194
185
|
const exitState = await this.lazyProcess.waitForExit();
|
|
195
186
|
if (this.stopRequested && exitState === "error") {
|