From 5e0be42b89ed7679394510f85f9110086eff7438 Mon Sep 17 00:00:00 2001 From: Hexagon Date: Sun, 12 Mar 2023 12:27:02 +0100 Subject: [PATCH] Prepare 1.0.0-alpha-4 Change process option 'name' to 'id'. Add option 'overrun'. Add option 'timeout'. Refactor process status tracking. --- README.md | 8 +- examples/max-restarts/pup.jsonc | 2 +- examples/minimal/server.js | 2 +- lib/common/utils.ts | 4 +- lib/core/configuration.ts | 18 ++- lib/core/logger.ts | 2 +- lib/core/process.ts | 209 ++++++++++++++++++++++++++++++++ lib/core/pup.ts | 117 +++++++++--------- lib/core/runner.ts | 69 +++++++++++ lib/core/status.ts | 81 +------------ lib/core/subprocess.ts | 82 ------------- pup.ts | 21 ++-- 12 files changed, 382 insertions(+), 233 deletions(-) create mode 100644 lib/core/process.ts create mode 100644 lib/core/runner.ts delete mode 100644 lib/core/subprocess.ts diff --git a/README.md b/README.md index 3b4bdc6..2805492 100644 --- a/README.md +++ b/README.md @@ -76,11 +76,11 @@ Here's an example of a `pup.jsonc` with all possible options defined: "TZ": "Europe/Olso" }, "autostart": true, // default undefined, process will not autostart by default + "overrun": false, // allow overrun, default false // "cron": "*/5 * * * * *", // default undefined "restart": "always", // default undefined, possible values ["always" | "error" | undefined] "maxRestarts": 10, // default undefined - restart infinitely' - "restartDelayMs": 10000 // default 10000 - + "restartDelayMs": 10000, // default 10000 // Only needed if you want to overrides the global logger // Note: "colors" is not configurable per process "logger": { @@ -94,8 +94,8 @@ Here's an example of a `pup.jsonc` with all possible options defined: } ``` -In this example, we define a process called `server-task`. We specify the command to start the process using an array of strings. We set it to start immediately with, and to restart after 10 seconds after -quitting for whatever reason. +In this example, we define a process called `server-task`. We specify the command to start the process using an array of strings. We set it to start immediately with, and to restart after 10 seconds +after quitting for whatever reason. If you use the line `cron: ""` instead of `autostart: true` it would be triggered periodically. diff --git a/examples/max-restarts/pup.jsonc b/examples/max-restarts/pup.jsonc index 2ebc1e6..6b49ee4 100644 --- a/examples/max-restarts/pup.jsonc +++ b/examples/max-restarts/pup.jsonc @@ -6,7 +6,7 @@ "autostart": true, "restart": "always", "maxRestarts": 3, - "restartDelayMs": 1000 + "restartDelayMs": 3000 } ] } diff --git a/examples/minimal/server.js b/examples/minimal/server.js index 6b98954..a905c8e 100644 --- a/examples/minimal/server.js +++ b/examples/minimal/server.js @@ -1,2 +1,2 @@ console.log("I will restart forever") -console.log("My process id is ", Deno.env.get("PUP_PROCESS_ID")); \ No newline at end of file +console.log("My process id is ", Deno.env.get("PUP_PROCESS_ID")) diff --git a/lib/common/utils.ts b/lib/common/utils.ts index 08c6c2f..2a8af5e 100644 --- a/lib/common/utils.ts +++ b/lib/common/utils.ts @@ -15,14 +15,14 @@ async function fileExists(filePath: string) { } } -function isRunning(pid: number, heartbeat: number, thresholdMs: number): string { +function isRunning(pid: number, heartbeat: Date, thresholdMs: number): string { try { Deno.kill(pid, "SIGURG") return "Running" } catch (e) { if (e.name === "TypeError" || e.name === "PermissionDenied") { if (heartbeat) { - return (new Date().getTime() - heartbeat) < thresholdMs ? "Running" : "Unknown" + return (new Date().getTime() - heartbeat.getTime()) < thresholdMs ? "Running" : "Unknown" } else { return "Not running" } diff --git a/lib/core/configuration.ts b/lib/core/configuration.ts index 4b014bf..9dfb9b0 100644 --- a/lib/core/configuration.ts +++ b/lib/core/configuration.ts @@ -3,8 +3,14 @@ import { z } from "../../deps.ts" interface Configuration { logger?: GlobalLoggerConfiguration processes: ProcessConfiguration[] + /* plugins?: PluginEntry[] */ } +/*interface PluginEntry { + url: string + options?: unknown +}*/ + interface _BaseLoggerConfiguration { console?: boolean stdout?: string @@ -28,11 +34,13 @@ interface ProcessConfiguration { env?: Record cwd?: string autostart?: boolean + overrun?: boolean cron?: string maxRestarts?: number restart?: string restartDelayMs?: number logger?: ProcessLoggerConfiguration + timeout?: number } const ConfigurationSchema = z.object({ @@ -46,6 +54,12 @@ const ConfigurationSchema = z.object({ decorate: z.optional(z.boolean()), }).strict(), ), + /*plugins: z.optional( + z.object({ + url: z.string(), + options: z.optional(z.object({})) + }).strict() + ),*/ processes: z.array( z.object({ id: z.string().min(1).max(64).regex(/^[a-z0-9@._\-]+$/i, "Process ID can only contain characters a-Z 0-9 . _ - or @"), @@ -55,8 +69,10 @@ const ConfigurationSchema = z.object({ autostart: z.optional(z.boolean()), cron: z.optional(z.string().min(9).max(256)), restart: z.optional(z.enum(["always", "error"])), - restartDelayMs: z.optional(z.number().min(0).max(24*60*60*1000*1)), // Max one day + restartDelayMs: z.optional(z.number().min(0).max(24 * 60 * 60 * 1000 * 1)), // Max one day + overrun: z.optional(z.boolean()), maxRestarts: z.optional(z.number().min(0)), + timeout: z.optional(z.number().min(1)), logger: z.optional( z.object({ console: z.optional(z.boolean()), diff --git a/lib/core/logger.ts b/lib/core/logger.ts index 46a5c7d..e499528 100644 --- a/lib/core/logger.ts +++ b/lib/core/logger.ts @@ -30,7 +30,7 @@ class Logger { if (blockedByAttachedLogger) return // Default initiator to - const initiator = process?.name || "core" + const initiator = process?.id || "core" // Log to console const logToConsoleProcess = (process?.logger?.console ?? true) === false diff --git a/lib/core/process.ts b/lib/core/process.ts new file mode 100644 index 0000000..4493c51 --- /dev/null +++ b/lib/core/process.ts @@ -0,0 +1,209 @@ +import { Pup } from "./pup.ts" +import { Cron } from "../../deps.ts" +import { Runner } from "./runner.ts" +import { ProcessConfiguration } from "./configuration.ts" + +enum ProcessStatus { + CREATED = 0, + STARTING = 100, + RUNNING = 200, + STOPPING = 250, + FINISHED = 300, + ERRORED = 400, + EXHAUSTED = 450, + BLOCKED = 500, +} + +interface ProcessInformation { + id: string + status: ProcessStatus + code?: number + signal?: number + pid?: number + started?: Date + exited?: Date + blocked?: boolean + restarts?: number + updated: Date +} + +interface ProcessInformationParsed { + id: string + status: ProcessStatus + code?: number + signal?: number + pid?: number + started?: string + exited?: string + blocked?: boolean + restarts?: number + updated: string +} + +class Process { + private readonly config: ProcessConfiguration + private readonly pup: Pup + + // Subprocess runner + private runner?: Runner + + // Allow manual block + private blocked = false + + // Status + private status: ProcessStatus = ProcessStatus.CREATED + private pid?: number + private code?: number + private signal?: number + private started?: Date + private exited?: Date + private restarts = 0 + private updated: Date = new Date() + + constructor(pup: Pup, config: ProcessConfiguration) { + this.config = config + this.pup = pup + } + + private setStatus(s: ProcessStatus) { + this.status = s + this.updated = new Date() + } + + public getStatus(): ProcessInformation { + return { + id: this.config.id, + status: this.status, + pid: this.pid, + code: this.code, + signal: this.signal, + started: this.started, + exited: this.exited, + blocked: this.blocked, + restarts: this.restarts, + updated: this.updated, + } + } + + public getConfig() { + return this.config + } + + public init = () => { + // Start using cron pattern + if (this.config.cron) this.setupCron() + } + + public start = async (reason?: string, restart?: boolean) => { + const logger = this.pup.logger + + // Do not start if blocked + if (this.blocked) { + logger.log("blocked", `Process blocked, refusing to start`, this.config) + return + } + + // Do not start if running and overrun isn't enabled + if (this.status === ProcessStatus.RUNNING && !this.config.overrun) { + logger.log("blocked", `Process still running, refusing to start`, this.config) + return + } + + // Do not restart if maximum number of restarts are exhausted + if (this.restarts >= (this.config.maxRestarts ?? Infinity)) { + logger.log("exhausted", `Maximum number of starts exhausted, refusing to start`, this.config) + this.setStatus(ProcessStatus.EXHAUSTED) + return + } + + logger.log("starting", `Process starting, reason: ${reason}`, this.config) + + // Update status + this.setStatus(ProcessStatus.STARTING) + this.pid = undefined + this.code = undefined + this.signal = undefined + this.exited = undefined + this.started = undefined + + // Start process (await for it to exit) + this.runner = new Runner(this.pup, this.config) + + // Update restart counter, this is reset on successful exit, or manual .stop() + if (restart) { + this.restarts = this.restarts + 1 + } + + // Try to start + try { + const result = await this.runner.run((pid: number) => { + // Process started + this.setStatus(ProcessStatus.RUNNING) + this.pid = pid + this.started = new Date() + }) + + this.code = result.code + this.signal = result.signal + + // Exited - Update status + if (result.code === 0) { + // Reset restarts on successful exit + this.setStatus(ProcessStatus.FINISHED) + logger.log("finished", `Process finished with code ${result.code}`, this.config) + } else { + this.setStatus(ProcessStatus.ERRORED) + logger.log("errored", `Process exited with code ${result.code}`, this.config) + } + } catch (e) { + this.code = undefined + this.signal = undefined + this.setStatus(ProcessStatus.ERRORED) + logger.log("errored", `Process could not start, error: ${e}`, this.config) + } + + this.exited = new Date() + this.pid = undefined + this.runner = undefined + } + + public stop = (reason: string): boolean => { + if (this.runner) { + try { + this.pup.logger.log("starting", `Killing process, reason: ${reason}`, this.config) + this.runner?.kill("SIGINT") + this.restarts = 0 + return true + } catch (_e) { + return false + } + } + return false + } + + public block = () => { + this.blocked = true + } + + public unblock = () => { + this.blocked = false + } + + private setupCron = () => { + try { + // ToDo: Take care of env TZ? + const cronJob = new Cron(this.config.cron as string, () => { + this.start("Cron pattern") + this.pup.logger.log("scheduler", `${this.config.id} is scheduled to run at '${this.config.cron} (${cronJob.nextRun()?.toLocaleString()})'`) + }) + + // Initial next run time + this.pup.logger.log("scheduler", `${this.config.id} is scheduled to run at '${this.config.cron} (${cronJob.nextRun()?.toLocaleString()})'`) + } catch (e) { + this.pup.logger.error("scheduled", `Fatal error setup up the cron job for '${this.config.id}', process will not autostart. Error: ${e}`) + } + } +} + +export { Process, ProcessStatus } +export type { ProcessInformation, ProcessInformationParsed } diff --git a/lib/core/pup.ts b/lib/core/pup.ts index 9fc01ac..0d4d660 100644 --- a/lib/core/pup.ts +++ b/lib/core/pup.ts @@ -1,7 +1,6 @@ -import { SubProcess } from "./subprocess.ts" -import { Cron } from "../../deps.ts" -import { Logger } from "./logger.ts" import { Configuration, GlobalLoggerConfiguration, ProcessConfiguration, validateConfiguration } from "./configuration.ts" +import { Logger } from "./logger.ts" +import { Process, ProcessStatus } from "./process.ts" import { Status } from "./status.ts" class Pup { @@ -9,6 +8,8 @@ class Pup { public logger: Logger public status: Status + public processes: Process[] = [] + constructor(unvalidatedConfiguration: unknown, statusFile?: string) { // Throw on invalid configuration this.configuration = validateConfiguration(unvalidatedConfiguration) @@ -16,70 +17,76 @@ class Pup { // Initialise core logger this.logger = new Logger(this.configuration.logger ?? {}) - // Initialize status tracker + // Initialise status tracker this.status = new Status(statusFile) - } - public start = () => { - // Start processses + // Create processes if (this.configuration.processes) { for (const process of this.configuration.processes) { - // Start using cron pattern - if (process.cron) this.startCronSubprocess(process) - // Start instantly - if (process.autostart) this.autostartSubprocess(process) + const newProcess = new Process(this, process) + this.processes.push(newProcess) } } - - // Start heartbeat - this.heartbeat() - } - - private heartbeat = () => { - this.status.updateHeartBeat() - - const heartBeatTimer = setTimeout(() => { - this.heartbeat() - }, 5000) - - // Do not block main process - Deno.unrefTimer(heartBeatTimer) } - private startCronSubprocess = (processConfig: ProcessConfiguration) => { - const cronJob = new Cron(processConfig.cron as string, async () => { - // We await this so that croner can keep track of overruns - await (new SubProcess(this, processConfig)).run("Cron trigger") - - // And so that we can write next run time after the process finishes - this.logger.log("scheduler", `${processConfig.name} is scheduled to run at '${processConfig.cron} (${cronJob.nextRun()?.toLocaleString()})'`) - }) - - // Initial next run time - this.logger.log("scheduler", `${processConfig.name} is scheduled to run at '${processConfig.cron} (${cronJob.nextRun()?.toLocaleString()})'`) + public start = () => { + for (const process of this.processes) { + process.init() + } + this.watchdog() } - private autostartSubprocess = async (processConfig: ProcessConfiguration, restart?: number) => { - // Evaluate restarts - - // Run subprocess and await result - const result = await (new SubProcess(this, processConfig)).run(restart ? "Autostart" : "Restart") - - // Check conditions to restart - if (processConfig.restart === "always" || (result.code > 0 && processConfig.restart === "error")) { - const delay = processConfig.restartDelayMs || 10000 - const maxRestarts = processConfig.maxRestarts ?? Infinity - const currentRestarts = (restart ?? 0) + 1 - const restartText = (processConfig.maxRestarts !== undefined) ? `, restart ${currentRestarts} of ${maxRestarts}` : "" - - // Go ahead restarting - if (currentRestarts <= maxRestarts) { - this.logger.log("scheduler", `${processConfig.id} is scheduled to restart in ${delay} ms${restartText}`) - setTimeout(() => this.autostartSubprocess(processConfig, currentRestarts), delay) - } else { - this.logger.log("scheduler", `${processConfig.id} has exceeded the maximum number of restarts (${maxRestarts}) and will exit`) + private watchdog = () => { + // Wrap watchdog operation in a catch to prevent it from ever stopping + try { + // Loop through all processes, checking if some actions are needed + for (const process of this.processes) { + const status = process.getStatus() + const config = process.getConfig() + + // Handle initial starts + if (config.autostart && status.status === ProcessStatus.CREATED) { + process.start("Autostart") + } + + // Handle restarts + if (status.status === ProcessStatus.FINISHED || status.status === ProcessStatus.ERRORED) { + // Handle max restart policy + const msSinceExited = status.exited ? (new Date().getTime() - status.exited?.getTime()) : Infinity + const restartDelay = config.restartDelayMs ?? 10000 + const restartPolicy = config.restart ?? "always" + if (restartPolicy === "always" && msSinceExited > restartDelay) { + process.start("restart", true) + } else if (restartPolicy === "error" && ProcessStatus.ERRORED) { + process.start("restart", true) + } + } + + // Handle timeouts + if (status.status === ProcessStatus.RUNNING && config.timeout && status.started) { + const secondsSinceStart = (new Date().getTime() - status.started.getTime()) / 1000 + if (secondsSinceStart > config.timeout) { + process.stop("Timeout") + } + } } + } catch (e) { + this.logger.error("watchdog", "Watchdog error: ", e) } + + // Update process status + try { + this.status.updateHeartBeat() + this.status.writeToDisk(this.processes) + } catch (e) { + this.logger.error("watchdog", `Heartbeat update failed: ${e}`) + } + + // Reschedule watchdog + // ToDo: Exit if all processes are exhausted? + setTimeout(() => { + this.watchdog() + }, 1000) } } diff --git a/lib/core/runner.ts b/lib/core/runner.ts new file mode 100644 index 0000000..59b052c --- /dev/null +++ b/lib/core/runner.ts @@ -0,0 +1,69 @@ +import { ProcessConfiguration, Pup } from "./pup.ts" +import { readLines } from "../../deps.ts" + +type RunnerCallback = (pid: number) => void + +class Runner { + private readonly processConfig: ProcessConfiguration + private readonly pup: Pup + + private process?: Deno.Process + + constructor(pup: Pup, processConfig: ProcessConfiguration) { + this.processConfig = processConfig + this.pup = pup + } + + private async pipeToLogger(category: string, reader: Deno.Reader) { + const logger = this.pup.logger + + // Write to log + try { + for await (const line of readLines(reader)) { + if (category === "stderr") { + logger.error(category, line, this.processConfig) + } else { + logger.log(category, line, this.processConfig) + } + } + } catch (_e) { + logger.error("error", "Pipe error") + } + } + + async run(runningCallback: RunnerCallback) { + // Extend enviroment config with PUP_PROCESS_ID + const env = this.processConfig.env ? structuredClone(this.processConfig.env) : {} + env.PUP_PROCESS_ID = this.processConfig.id + + // Start the process + const process = Deno.run({ + cmd: this.processConfig.cmd, + cwd: this.processConfig.cwd, + env, + stdout: "piped", + stderr: "piped", + }) + this.process = process + + runningCallback(process.pid) + + this.pipeToLogger("stdout", process.stdout) + this.pipeToLogger("stderr", process.stderr) + + const result = await process.status() + + // Important! Close streams + process.stderr.close() + process.stdout.close() + + // Exited + return result + } + + public kill = (signal: Deno.Signal) => { + this.process?.kill(signal) + } +} + +export { Runner } diff --git a/lib/core/status.ts b/lib/core/status.ts index fa3a627..074baf0 100644 --- a/lib/core/status.ts +++ b/lib/core/status.ts @@ -1,28 +1,13 @@ -interface TaskInfo { - name: string - pid?: number - lastStdout?: string - lastStderr?: string - started?: string - exited?: string - exitCode?: number - signal?: number - lastUpdate?: string -} - -type TaskList = Record +import { Process } from "./process.ts" class Status { - /* Keeps track of the status of individual processes */ - private taskRegistry: TaskList = {} - /* Keeps track of the last sign of life */ private lastHeartBeat: Date = new Date() /* Properties related to disk write */ private lastWrite: Date = new Date() private statusFileName?: string - private writeInterval = 1000 + private writeInterval = 2000 constructor(fileName?: string, writeInterval?: number) { if (fileName) { @@ -34,13 +19,15 @@ class Status { } /* Internal methods */ - private writeToDisk() { + public writeToDisk(processes: Process[]) { if (this.statusFileName && new Date().getTime() - this.lastWrite.getTime() > this.writeInterval) { + const processStatuses = processes.map((p) => p.getStatus()) + // Prepare the object to write const pupStatus = { pid: Deno.pid, heartbeat: this.lastHeartBeat, - taskRegistry: this.taskRegistry, + processes: processStatuses, } const result = new TextEncoder().encode(JSON.stringify(pupStatus)) @@ -56,61 +43,6 @@ class Status { } } - private update(taskName: string) { - // Update heartbeat - this.updateHeartBeat() - - // Update lastUpdate - this.taskRegistry[taskName] = { ...this.taskRegistry[taskName], lastUpdate: new Date().toISOString() } - - // Request disk write - this.writeToDisk() - } - - /* Manage tasks */ - public resetTask(taskName: string): void { - this.taskRegistry[taskName] = { name: taskName } - } - - public updatePid(taskName: string, pid: number): void { - this.taskRegistry[taskName] = { ...this.taskRegistry[taskName], pid } - this.update(taskName) - } - - public updateLastStdout(taskName: string, lastStdout: string): void { - this.taskRegistry[taskName] = { ...this.taskRegistry[taskName], lastStdout } - this.update(taskName) - } - - public updateLastStderr(taskName: string, lastStderr: string): void { - this.taskRegistry[taskName] = { ...this.taskRegistry[taskName], lastStderr } - this.update(taskName) - } - - public updateStarted(taskName: string, started: Date): void { - this.taskRegistry[taskName] = { ...this.taskRegistry[taskName], started: started.toISOString() } - this.update(taskName) - } - - public updateExited(taskName: string, exited: Date): void { - this.taskRegistry[taskName] = { ...this.taskRegistry[taskName], exited: exited.toISOString() } - this.update(taskName) - } - - public updateExitCode(taskName: string, exitCode: number): void { - this.taskRegistry[taskName] = { ...this.taskRegistry[taskName], exitCode } - this.update(taskName) - } - - public updateSignal(taskName: string, signal: number | undefined): void { - this.taskRegistry[taskName] = { ...this.taskRegistry[taskName], signal } - this.update(taskName) - } - - public getTaskList() { - return this.taskRegistry - } - /* Manage heart beat */ public updateHeartBeat() { this.lastHeartBeat = new Date() @@ -122,4 +54,3 @@ class Status { } export { Status } -export type { TaskInfo } diff --git a/lib/core/subprocess.ts b/lib/core/subprocess.ts deleted file mode 100644 index 80cd290..0000000 --- a/lib/core/subprocess.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { ProcessConfiguration, Pup } from "./pup.ts" - -import { readLines } from "../../deps.ts" - -class SubProcess { - private readonly processConfig: ProcessConfiguration - private readonly pup: Pup - - constructor(pup: Pup, processConfig: ProcessConfiguration) { - this.processConfig = processConfig - this.pup = pup - } - - private async pipeToLogger(category: string, reader: Deno.Reader) { - const logger = this.pup.logger - const status = this.pup.status - - let lastStderr, lastStdout - // Write to log - try { - for await (const line of readLines(reader)) { - if (category === "stderr") { - lastStderr = line - logger.error(category, line, this.processConfig) - } else { - lastStdout = line - logger.log(category, line, this.processConfig) - } - } - } catch (_e) { - logger.error("error", "Pipe error") - } - if (lastStderr) status.updateLastStderr(this.processConfig.id, lastStderr) - if (lastStdout) status.updateLastStdout(this.processConfig.id, lastStdout) - } - - async run(reason: string) { - const status = this.pup.status - const logger = this.pup.logger - - // Extend enviroment config with PUP_PROCESS_NAME - const env = this.processConfig.env ? structuredClone(this.processConfig.env) : {}; - env.PUP_PROCESS_ID = this.processConfig.id - - // Start the process - logger.log("starting", `Process starting, reason: ${reason}`, this.processConfig) - const cat = Deno.run({ - cmd: this.processConfig.cmd, - cwd: this.processConfig.cwd, - env, - stdout: "piped", - stderr: "piped", - }) - - status.resetTask(this.processConfig.id) - status.updatePid(this.processConfig.id, cat.pid) - status.updateStarted(this.processConfig.id, new Date()) - - this.pipeToLogger("stdout", cat.stdout); - this.pipeToLogger("stderr", cat.stderr); - - const result = await cat.status() - - if (result.code > 0) { - logger.error("finished", `Process finished with error code ${result.code}`, this.processConfig) - } else { - logger.log("finished", `Process finished with code ${result.code}`, this.processConfig) - } - - // Important! Close streams - cat.stderr.close() - cat.stdout.close() - - status.updateExitCode(this.processConfig.id, result.code) - status.updateSignal(this.processConfig.id, result.signal) - status.updateExited(this.processConfig.id, new Date()) - - return result - } -} - -export { SubProcess } diff --git a/pup.ts b/pup.ts index 2cd759f..646359c 100644 --- a/pup.ts +++ b/pup.ts @@ -14,7 +14,7 @@ import { jsonc } from "./deps.ts" import { checkArguments } from "./lib/cli/checks.ts" import { parseArguments } from "./lib/cli/args.ts" import { fileExists, isRunning } from "./lib/common/utils.ts" -import { TaskInfo } from "./lib/core/status.ts" +import { ProcessInformationParsed } from "./lib/core/process.ts" /** * Define the main entry point of the CLI application @@ -123,19 +123,18 @@ async function printStatus(configFile: string) { Deno.exit(1) } - console.log(`\nMain process \t${status.pid} (${isRunning(status.pid, Date.parse(status.heartbeat), 20000)})`) + console.log(`\nMain process \t${status.pid} (${isRunning(status.pid, new Date(Date.parse(status.heartbeat)), 20000)})`) - for (const taskInfo of Object.values(status.taskRegistry)) { - const currentTask = taskInfo as TaskInfo - const processRunning = currentTask.pid ? isRunning(currentTask.pid, currentTask.lastUpdate ? Date.parse(currentTask.lastUpdate) : 0, 30000) : "Not started" - console.log(`\n Task: ${currentTask.name} (PID: ${currentTask.pid || ""}, ${processRunning})\n`) - if (currentTask.lastUpdate) console.log(` Last update:\t${currentTask.lastUpdate}`) - if (currentTask.exitCode) console.log(` Code:\t\t${currentTask.exitCode}`) + for (const taskInfo of Object.values(status.processes)) { + const currentTask = taskInfo as ProcessInformationParsed + const processRunning = currentTask.pid ? isRunning(currentTask.pid, new Date(Date.parse(currentTask.updated)), 30000) : "Not running" + console.log(`\n Task: ${currentTask.id} (PID: ${currentTask.pid || ""}, ${processRunning})\n`) + if (currentTask.status !== undefined) console.log(` Status:\t\t${currentTask.status}`) + if (currentTask.updated) console.log(` Last update:\t${currentTask.updated}`) + if (currentTask.code) console.log(` Code:\t\t${currentTask.code}`) if (currentTask.signal) console.log(` Signal:\t\t${currentTask.signal}`) if (currentTask.started) console.log(` Started:\t\t${currentTask.started.toLocaleString()}`) if (currentTask.exited) console.log(` Exited:\t\t${currentTask.exited.toLocaleString()}`) - if (currentTask.lastStdout) console.log(` Last stdout:\t${currentTask.lastStdout}`) - if (currentTask.lastStderr) console.log(` Last stderr:\t${currentTask.lastStderr}`) } console.log("\n") } @@ -144,7 +143,7 @@ main() const Application = { "name": "pup", - "version": "1.0.0-alpha-3", + "version": "1.0.0-alpha-4", "repository": "https://github.com/Hexagon/pup", } export { Application }