pidnap 0.0.0-dev.6 → 0.0.0-dev.7

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.
@@ -2,6 +2,7 @@ import { parse } from "dotenv";
2
2
  import { existsSync, globSync, readFileSync } from "node:fs";
3
3
  import { resolve, basename, isAbsolute } from "node:path";
4
4
  import { watch } from "chokidar";
5
+ import { type Logger } from "./logger";
5
6
 
6
7
  export type EnvChangeEvent =
7
8
  | {
@@ -29,8 +30,13 @@ export class EnvManager {
29
30
  private changeCallbacks: Set<EnvChangeCallback> = new Set();
30
31
  private cwdWatcher: ReturnType<typeof watch> | null = null;
31
32
  private customKeys: Set<string> = new Set();
33
+ private logger: Logger;
32
34
 
33
- constructor(private config: EnvManagerConfig) {
35
+ constructor(
36
+ private config: EnvManagerConfig,
37
+ logger: Logger,
38
+ ) {
39
+ this.logger = logger;
34
40
  this.globalEnvPath = config.globalEnvFile
35
41
  ? isAbsolute(config.globalEnvFile)
36
42
  ? config.globalEnvFile
@@ -49,6 +55,7 @@ export class EnvManager {
49
55
  */
50
56
  public registerFile(key: string, filePath: string): void {
51
57
  const absolutePath = isAbsolute(filePath) ? filePath : resolve(this.config.cwd, filePath);
58
+ this.logger.debug(`Registering custom env file for "${key}": ${absolutePath}`);
52
59
  this.customKeys.add(key);
53
60
  this.loadEnvFile(key, absolutePath);
54
61
  }
@@ -80,28 +87,39 @@ export class EnvManager {
80
87
  }
81
88
 
82
89
  private loadEnvFilesFromCwd(): void {
90
+ this.logger.debug(`Scanning for env files in: ${this.config.cwd}`);
83
91
  if (existsSync(this.globalEnvPath)) this.loadGlobalEnv(this.globalEnvPath);
84
92
 
85
93
  try {
86
94
  const envFiles = globSync(".env.*", { cwd: this.config.cwd });
95
+ this.logger.debug(`Found ${envFiles.length} env file(s): ${envFiles.join(", ") || "(none)"}`);
87
96
  for (const filePath of envFiles) {
88
97
  const key = this.getEnvKeySuffix(basename(filePath));
89
98
  // Skip if key has a custom file registered
90
99
  if (key && !this.customKeys.has(key)) {
91
100
  this.loadEnvFile(key, resolve(this.config.cwd, filePath));
101
+ } else if (key && this.customKeys.has(key)) {
102
+ this.logger.debug(
103
+ `Skipping auto-discovered "${filePath}" (custom file registered for "${key}")`,
104
+ );
92
105
  }
93
106
  }
94
107
  } catch (err) {
95
- console.warn("Failed to scan env files:", err);
108
+ this.logger.warn("Failed to scan env files:", err);
96
109
  }
97
110
  }
98
111
 
99
112
  private parseEnvFile(absolutePath: string): Record<string, string> | null {
100
113
  try {
101
114
  const content = readFileSync(absolutePath, "utf-8");
102
- return parse(content) ?? {};
115
+ const parsed = parse(content) ?? {};
116
+ const keys = Object.keys(parsed);
117
+ this.logger.debug(
118
+ `Parsed env file "${absolutePath}": ${keys.length} variable(s) [${keys.join(", ")}]`,
119
+ );
120
+ return parsed;
103
121
  } catch (err) {
104
- console.warn(`Failed to parse env file: ${absolutePath}`, err);
122
+ this.logger.warn(`Failed to parse env file: ${absolutePath}`, err);
105
123
  return null;
106
124
  }
107
125
  }
@@ -112,7 +130,11 @@ export class EnvManager {
112
130
  }
113
131
 
114
132
  private loadGlobalEnv(absolutePath: string) {
115
- if (!existsSync(absolutePath)) return;
133
+ if (!existsSync(absolutePath)) {
134
+ this.logger.debug(`Global env file not found: ${absolutePath}`);
135
+ return;
136
+ }
137
+ this.logger.debug(`Loading global env file: ${absolutePath}`);
116
138
  const parsed = this.parseEnvFile(absolutePath);
117
139
  if (parsed) {
118
140
  this.globalEnv = parsed;
@@ -121,6 +143,7 @@ export class EnvManager {
121
143
  }
122
144
 
123
145
  private loadEnvFile(key: string, absolutePath: string) {
146
+ this.logger.debug(`Loading env file for "${key}": ${absolutePath}`);
124
147
  const parsed = this.parseEnvFile(absolutePath);
125
148
  if (!parsed) return;
126
149
 
@@ -142,7 +165,7 @@ export class EnvManager {
142
165
 
143
166
  this.watchers.set(absolutePath, watcher);
144
167
  } catch (err) {
145
- console.warn(`Failed to watch env file: ${absolutePath}`, err);
168
+ this.logger.warn(`Failed to watch env file: ${absolutePath}`, err);
146
169
  }
147
170
  }
148
171
 
@@ -158,12 +181,14 @@ export class EnvManager {
158
181
  this.handleNewFile(filePath);
159
182
  });
160
183
  } catch (err) {
161
- console.warn(`Failed to watch cwd for new env files:`, err);
184
+ this.logger.warn(`Failed to watch cwd for new env files:`, err);
162
185
  }
163
186
  }
164
187
 
165
188
  private handleFileChange(absolutePath: string): void {
189
+ this.logger.debug(`File changed: ${absolutePath}`);
166
190
  if (absolutePath === this.globalEnvPath) {
191
+ this.logger.debug(`Global env file changed, reloading`);
167
192
  this.loadGlobalEnv(absolutePath);
168
193
  this.notifyCallbacks({ type: "global" });
169
194
  return;
@@ -172,6 +197,7 @@ export class EnvManager {
172
197
  const key = this.fileToKey.get(absolutePath);
173
198
  if (!key) return;
174
199
 
200
+ this.logger.debug(`Env file changed for "${key}", reloading`);
175
201
  const parsed = this.parseEnvFile(absolutePath);
176
202
  if (!parsed) return;
177
203
 
@@ -180,6 +206,7 @@ export class EnvManager {
180
206
  }
181
207
 
182
208
  private handleFileDelete(absolutePath: string): void {
209
+ this.logger.debug(`File deleted: ${absolutePath}`);
183
210
  const watcher = this.watchers.get(absolutePath);
184
211
  if (watcher) {
185
212
  watcher.close();
@@ -187,6 +214,7 @@ export class EnvManager {
187
214
  }
188
215
 
189
216
  if (absolutePath === this.globalEnvPath) {
217
+ this.logger.debug(`Global env file deleted, clearing global env`);
190
218
  this.globalEnv = {};
191
219
  this.notifyCallbacks({ type: "global" });
192
220
  return;
@@ -194,6 +222,7 @@ export class EnvManager {
194
222
 
195
223
  const key = this.fileToKey.get(absolutePath);
196
224
  if (key) {
225
+ this.logger.debug(`Env file deleted for "${key}", removing from registry`);
197
226
  this.env.delete(key);
198
227
  this.fileToKey.delete(absolutePath);
199
228
  this.notifyCallbacks({ type: "process", key });
@@ -202,8 +231,10 @@ export class EnvManager {
202
231
 
203
232
  private handleNewFile(filePath: string): void {
204
233
  const absolutePath = isAbsolute(filePath) ? filePath : resolve(this.config.cwd, filePath);
234
+ this.logger.debug(`New file detected: ${absolutePath}`);
205
235
 
206
236
  if (absolutePath === this.globalEnvPath) {
237
+ this.logger.debug(`New global env file detected, loading`);
207
238
  this.loadGlobalEnv(absolutePath);
208
239
  this.notifyCallbacks({ type: "global" });
209
240
  return;
@@ -212,8 +243,13 @@ export class EnvManager {
212
243
  const key = this.getEnvKeySuffix(basename(filePath));
213
244
  // Skip if key has a custom file registered or already loaded
214
245
  if (key && !this.customKeys.has(key) && !this.env.has(key)) {
246
+ this.logger.debug(`New env file detected for "${key}", loading`);
215
247
  this.loadEnvFile(key, absolutePath);
216
248
  this.notifyCallbacks({ type: "process", key });
249
+ } else if (key && this.customKeys.has(key)) {
250
+ this.logger.debug(`Ignoring new file "${filePath}" (custom file registered for "${key}")`);
251
+ } else if (key && this.env.has(key)) {
252
+ this.logger.debug(`Ignoring new file "${filePath}" (env for "${key}" already loaded)`);
217
253
  }
218
254
  }
219
255
 
package/src/manager.ts CHANGED
@@ -110,11 +110,14 @@ export class Manager {
110
110
  if (proc.envFile) customEnvFiles[proc.name] = proc.envFile;
111
111
  }
112
112
 
113
- this.envManager = new EnvManager({
114
- cwd,
115
- globalEnvFile: config.envFile,
116
- customEnvFiles,
117
- });
113
+ this.envManager = new EnvManager(
114
+ {
115
+ cwd,
116
+ globalEnvFile: config.envFile,
117
+ customEnvFiles,
118
+ },
119
+ this.logger.child("env-manager"),
120
+ );
118
121
 
119
122
  this.envChangeUnsubscribe = this.envManager.onChange((event) => this.handleEnvChange(event));
120
123
 
@@ -1 +0,0 @@
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":";;;;;;;;;;;;AAMA,MAAa,oBAAoB,EAAE,OAAO;CACxC,SAAS,EAAE,QAAQ;CACnB,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;CACrC,KAAK,EAAE,SAAS,EAAE,QAAQ,CAAC;CAC3B,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,QAAQ,CAAC,CAAC;CAClD,CAAC;AAGF,MAAa,eAAe,EAAE,SAAS;CACrC;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;AAOF,SAAS,iBAAiB,KAAa,QAAiC;AACtE,KAAI;AAEF,UAAQ,KAAK,CAAC,KAAK,OAAO;AAC1B,SAAO;UACA,KAAK;EACZ,MAAM,OAAQ,IAA8B;AAG5C,MAAI,SAAS,WAAW,SAAS,QAC/B,QAAO;AAET,SAAO;;;AAIX,IAAa,cAAb,MAAyB;CACvB,AAAS;CACT;CACA,AAAQ;CACR,AAAQ,eAAoC;CAC5C,AAAQ,SAAuB;CAC/B,AAAQ,cAAc,QAAQ,eAAqB;CACnD,AAAO,WAA0B;CAEjC,YAAY,MAAc,YAA+B,QAAgB;AACvE,OAAK,OAAO;AACZ,OAAK,aAAa;AAClB,OAAK,SAAS,OAAO,WAAW,MAAM;;CAGxC,IAAI,QAAsB;AACxB,SAAO,KAAK;;CAGd,MAAM,QAAQ;AACZ,MAAI,KAAK,WAAW,aAAa,KAAK,WAAW,WAC/C,OAAM,IAAI,MAAM,YAAY,KAAK,KAAK,eAAe,KAAK,SAAS;AAGrE,MAAI,KAAK,WAAW,WAClB,OAAM,IAAI,MAAM,YAAY,KAAK,KAAK,yBAAyB;AAGjE,OAAK,SAAS;AACd,OAAK,cAAc,QAAQ,eAAqB;AAChD,OAAK,OAAO,MAAM,qBAAqB,KAAK,WAAW,UAAU;AAEjE,MAAI;GACF,MAAM,MAAM,KAAK,WAAW,MAAM;IAAE,GAAG,QAAQ;IAAK,GAAG,KAAK,WAAW;IAAK,GAAG,QAAQ;AAEvF,QAAK,eAAe,MAAM,KAAK,WAAW,SAAS,KAAK,WAAW,QAAQ,EAAE,EAAE;IAC7E,KAAK,KAAK,WAAW;IACrB;IACA,OAAO;KAAC;KAAU;KAAQ;KAAO;IACjC,UAAU;IACX,CAAC;AAEF,QAAK,SAAS;AAEd,OAAI,KAAK,aAAa,QAAQ;IAC5B,MAAM,KAAK,gBAAgB,EAAE,OAAO,KAAK,aAAa,QAAQ,CAAC;AAC/D,OAAG,GAAG,SAAS,SAAS,KAAK,OAAO,WAAW,MAAM,CAAC,KAAK,KAAK,CAAC;AACjE,SAAK,YAAY,QAAQ,WAAW,GAAG,OAAO,CAAC;;AAGjD,OAAI,KAAK,aAAa,QAAQ;IAC5B,MAAM,KAAK,gBAAgB,EAAE,OAAO,KAAK,aAAa,QAAQ,CAAC;AAC/D,OAAG,GAAG,SAAS,SAAS,KAAK,OAAO,WAAW,MAAM,CAAC,KAAK,KAAK,CAAC;AACjE,SAAK,YAAY,QAAQ,WAAW,GAAG,OAAO,CAAC;;AAGjD,QAAK,aAAa,GAAG,SAAS,MAAM,WAAW;AAC7C,SAAK,WAAW;AAEhB,QAAI,KAAK,WAAW,UAClB,KAAI,SAAS,GAAG;AACd,UAAK,SAAS;AACd,UAAK,OAAO,KAAK,4BAA4B,OAAO;eAC3C,QAAQ;AACjB,UAAK,SAAS;AACd,UAAK,OAAO,KAAK,8BAA8B,SAAS;WACnD;AACL,UAAK,SAAS;AACd,UAAK,OAAO,MAAM,4BAA4B,OAAO;;AAIzD,SAAK,YAAY,SAAS;KAC1B;AAEF,QAAK,aAAa,GAAG,UAAU,QAAQ;AACrC,QAAI,KAAK,WAAW,cAAc,KAAK,WAAW,WAAW;AAC3D,UAAK,SAAS;AACd,UAAK,OAAO,MAAM,kBAAkB,IAAI;;AAE1C,SAAK,YAAY,SAAS;KAC1B;WACK,KAAK;AACZ,QAAK,SAAS;AACd,QAAK,OAAO,MAAM,4BAA4B,IAAI;AAClD,SAAM;;;CAIV,MAAM,KAAK,SAAiC;AAC1C,MAAI,KAAK,WAAW,UAAU,KAAK,WAAW,aAAa,KAAK,WAAW,QACzE;AAGF,MAAI,KAAK,WAAW,YAAY;AAE9B,SAAM,KAAK,YAAY;AACvB;;AAGF,MAAI,CAAC,KAAK,cAAc;AACtB,QAAK,SAAS;AACd;;AAGF,OAAK,SAAS;EACd,MAAM,MAAM,KAAK,aAAa;AAE9B,MAAI,QAAQ,QAAW;AACrB,QAAK,SAAS;AACd,QAAK,SAAS;AACd;;AAGF,OAAK,OAAO,MAAM,gCAAgC,IAAI,gBAAgB;EAEtE,MAAM,YAAY,WAAW;EAC7B,MAAM,aAAa,QAAQ,KAAK,CAC9B,KAAK,YAAY,QAAQ,WAAW,SAAkB,EACtDA,aAAW,WAAW,UAAU,CACjC,CAAC;AAEF,mBAAiB,KAAK,UAAU;AAEhC,OAAK,OAAO,MAAM,sCAAsC,UAAU,KAAK;EACvE,MAAM,SAAS,MAAM;AACrB,OAAK,OAAO,MAAM,wBAAwB,SAAS;AAEnD,MAAI,WAAW,WAAW;AACxB,QAAK,OAAO,KAAK,+BAA+B,UAAU,4BAA4B,IAAI,GAAG;GAC7F,MAAM,SAAS,iBAAiB,KAAK,UAAU;AAC/C,QAAK,OAAO,KAAK,kCAAkC,SAAS,YAAY,WAAW;GAEnF,MAAM,cAAcA,aAAW,KAAM,UAAU;AAC/C,SAAM,QAAQ,KAAK,CAAC,KAAK,YAAY,SAAS,YAAY,CAAC;AAE3D,OAAI,KAAK,gBAAgB,CAAC,KAAK,aAAa,OAC1C,MAAK,OAAO,MAAM,2CAA2C,IAAI,GAAG;;AAIxE,OAAK,SAAS;AACd,OAAK,SAAS;AACd,OAAK,OAAO,KAAK,kBAAkB;;CAGrC,MAAM,QAAuB;AAC3B,MAAI,KAAK,cAAc,QAAQ,QAAW;AAExC,oBAAiB,KAAK,aAAa,KAAK,UAAU;AAClD,SAAM,KAAK,YAAY;AACvB,QAAK,SAAS;;AAGhB,OAAK,SAAS;AAEd,OAAK,cAAc,QAAQ,eAAqB;AAChD,OAAK,OAAO,KAAK,wBAAwB;;CAG3C,iBAAiB,YAAqC;AACpD,OAAK,aAAa;;CAGpB,MAAM,cAAqC;AACzC,MAAI,CAAC,KAAK,aAAc,QAAO,KAAK;AACpC,QAAM,KAAK,YAAY;AACvB,SAAO,KAAK;;CAGd,AAAQ,UAAgB;AACtB,MAAI,KAAK,cAAc;AACrB,QAAK,aAAa,oBAAoB;AACtC,QAAK,eAAe;;AAGtB,OAAK,WAAW;;;;;;ACxNpB,MAAa,gBAAgB,EAAE,SAAS;CACtC;CACA;CACA;CACA;CACA;CACD,CAAC;AAGF,MAAa,kBAAkB,EAAE,MAAM,CACrC,EAAE,OAAO;CACP,MAAM,EAAE,QAAQ,QAAQ;CACxB,SAAS,EAAE,QAAQ;CACpB,CAAC,EACF,EAAE,OAAO;CACP,MAAM,EAAE,QAAQ,cAAc;CAC9B,gBAAgB,EAAE,QAAQ;CAC1B,YAAY,EAAE,QAAQ;CACtB,YAAY,EAAE,SAAS,EAAE,QAAQ,CAAC;CACnC,CAAC,CACH,CAAC;AAGF,MAAa,kBAAkB,EAAE,OAAO;CACtC,aAAa,EAAE,QAAQ;CACvB,UAAU,EAAE,QAAQ;CACpB,WAAW,EAAE,QAAQ;CACtB,CAAC;AAGF,MAAa,2BAA2B,EAAE,OAAO;CAC/C,eAAe;CACf,SAAS,EAAE,SAAS,gBAAgB;CACpC,WAAW,EAAE,SAAS,gBAAgB;CACtC,aAAa,EAAE,SAAS,EAAE,QAAQ,CAAC;CACnC,kBAAkB,EAAE,SAAS,EAAE,QAAQ,CAAC;CACzC,CAAC;AAGF,MAAa,yBAAyB,EAAE,SAAS;CAC/C;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAGF,MAAM,kBAAmC;CAAE,MAAM;CAAS,SAAS;CAAM;AACzE,MAAM,qBAAsC;CAAE,aAAa;CAAG,UAAU;CAAO,WAAW;CAAO;AAEjG,IAAa,oBAAb,MAA+B;CAC7B,AAAS;CACT,AAAS;CACT,AAAQ;CAGR,AAAQ;CAGR,AAAQ,SAAiC;CACzC,AAAQ,gBAAwB;CAChC,AAAQ,oBAA8B,EAAE;CACxC,AAAQ,sBAA8B;CACtC,AAAQ,gBAA+B;CACvC,AAAQ,gBAAyB;CACjC,AAAQ,sBAA4D;CAEpE,YACE,MACA,YACA,SACA,QACA;AACA,OAAK,OAAO;AACZ,OAAK,SAAS;AACd,OAAK,UAAU;GACb,eAAe,QAAQ;GACvB,SAAS,QAAQ,WAAW;GAC5B,WAAW,QAAQ,aAAa;GAChC,aAAa,QAAQ,eAAe;GACpC,kBAAkB,QAAQ;GAC3B;AACD,OAAK,cAAc,IAAI,YAAY,MAAM,YAAY,OAAO;;CAG9D,IAAI,QAAgC;AAClC,SAAO,KAAK;;CAGd,IAAI,WAAmB;AACrB,SAAO,KAAK;;CAGd,QAAc;AACZ,MAAI,KAAK,WAAW,aAAa,KAAK,WAAW,aAC/C,OAAM,IAAI,MAAM,YAAY,KAAK,KAAK,eAAe,KAAK,SAAS;AAGrE,MAAI,KAAK,WAAW,WAClB,OAAM,IAAI,MAAM,YAAY,KAAK,KAAK,yBAAyB;AAIjE,MACE,KAAK,WAAW,aAChB,KAAK,WAAW,UAChB,KAAK,WAAW,uBAEhB,MAAK,eAAe;AAGtB,OAAK,gBAAgB;AACrB,OAAK,cAAc;;CAGrB,MAAM,KAAK,SAAiC;AAC1C,OAAK,gBAAgB;AAGrB,MAAI,KAAK,qBAAqB;AAC5B,gBAAa,KAAK,oBAAoB;AACtC,QAAK,sBAAsB;;AAG7B,MACE,KAAK,WAAW,UAChB,KAAK,WAAW,aAChB,KAAK,WAAW,wBAChB;AACA,QAAK,SAAS;AACd;;AAGF,OAAK,SAAS;AACd,QAAM,KAAK,YAAY,KAAK,QAAQ;AACpC,OAAK,SAAS;AACd,OAAK,OAAO,KAAK,4BAA4B;;CAG/C,MAAM,QAAQ,QAAiB,OAAsB;AAEnD,MACE,KAAK,WAAW,aAChB,KAAK,WAAW,UAChB,KAAK,WAAW,wBAChB;AACA,QAAK,eAAe;AACpB,QAAK,gBAAgB;AACrB,QAAK,cAAc;AACnB;;AAIF,QAAM,KAAK,MAAM;AAEjB,OAAK,gBAAgB;AAErB,MAAI,MAEF,MAAK,cAAc;OACd;GAEL,MAAM,QAAQ,KAAK,gBAAgB;AACnC,OAAI,QAAQ,GAAG;AACb,SAAK,SAAS;AACd,SAAK,OAAO,KAAK,iBAAiB,MAAM,IAAI;AAC5C,UAAM,KAAK,MAAM,MAAM;AACvB,QAAI,KAAK,cAAe;;AAE1B,QAAK,cAAc;;;;;;CAOvB,MAAM,OACJ,eACA,qBAA8B,MACf;AACf,OAAK,OAAO,KAAK,wCAAwC;AACzD,OAAK,YAAY,iBAAiB,cAAc;AAEhD,MAAI,mBAEF,OAAM,KAAK,QAAQ,KAAK;;;;;CAO5B,cAAc,YAAqD;AACjE,OAAK,OAAO,KAAK,2BAA2B;AAC5C,OAAK,UAAU;GACb,GAAG,KAAK;GACR,eAAe,WAAW,iBAAiB,KAAK,QAAQ;GACxD,SAAS,WAAW,WAAW,KAAK,QAAQ;GAC5C,WAAW,WAAW,aAAa,KAAK,QAAQ;GAChD,aAAa,WAAW,eAAe,KAAK,QAAQ;GACpD,kBAAkB,WAAW,oBAAoB,KAAK,QAAQ;GAC/D;;CAGH,AAAQ,gBAAsB;AAC5B,OAAK,gBAAgB;AACrB,OAAK,sBAAsB;AAC3B,OAAK,oBAAoB,EAAE;;CAG7B,AAAQ,eAAqB;AAC3B,OAAK,gBAAgB,KAAK,KAAK;AAC/B,OAAK,SAAS;AAEd,OAAK,YACF,OAAO,CACP,KAAK,YAAY;AAChB,OAAI,KAAK,cAAe;AACxB,SAAM,KAAK,YAAY,OAAO;AAC9B,UAAO,KAAK,YAAY,aAAa;IACrC,CACD,MAAM,cAAc;AACnB,OAAI,CAAC,UAAW;AAChB,OAAI,KAAK,iBAAiB,cAAc,SAAS;AAC/C,SAAK,SAAS;AACd;;AAEF,OAAI,cAAc,aAAa,cAAc,QAC3C,MAAK,kBAAkB,UAAU;IAEnC,CACD,OAAO,QAAQ;AACd,OAAI,KAAK,cAAe;AACxB,QAAK,SAAS;AACd,QAAK,OAAO,MAAM,4BAA4B,IAAI;IAClD;;CAGN,AAAQ,kBAAkB,WAA+B;AACvD,MAAI,KAAK,eAAe;AACtB,QAAK,SAAS;AACd;;EAIF,MAAM,cADS,KAAK,gBAAgB,KAAK,KAAK,GAAG,KAAK,gBAAgB,MACzC,KAAK,QAAQ;EAC1C,MAAM,kBAAkB,cAAc;AAGtC,MAAI,WACF,MAAK,sBAAsB;MAE3B,MAAK;AAIP,MAAI,CAAC,KAAK,cAAc,gBAAgB,EAAE;AACxC,QAAK,SAAS;AACd,QAAK,OAAO,KACV,2BAA2B,KAAK,QAAQ,cAAc,0BACvD;AACD;;AAIF,MACE,KAAK,QAAQ,qBAAqB,UAClC,KAAK,iBAAiB,KAAK,QAAQ,kBACnC;AACA,QAAK,SAAS;AACd,QAAK,OAAO,KAAK,uBAAuB,KAAK,QAAQ,iBAAiB,WAAW;AACjF;;EAIF,MAAM,MAAM,KAAK,KAAK;AACtB,OAAK,kBAAkB,KAAK,IAAI;AAGhC,MAAI,KAAK,eAAe,EAAE;AACxB,QAAK,SAAS;AACd,QAAK,OAAO,KACV,wBAAwB,KAAK,QAAQ,UAAU,YAAY,eAAe,KAAK,QAAQ,UAAU,SAAS,uBAAuB,KAAK,QAAQ,UAAU,UAAU,IACnK;AACD,QAAK,2BAA2B;AAChC;;AAIF,OAAK;AACL,OAAK,iBAAiB;;CAGxB,AAAQ,cAAc,iBAAmC;AACvD,UAAQ,KAAK,QAAQ,eAArB;GACE,KAAK,SACH,QAAO;GACT,KAAK,QACH,QAAO;GACT,KAAK,aACH,QAAO;GACT,KAAK,aACH,QAAO,CAAC;GACV,KAAK,iBACH,QAAO,CAAC,KAAK;GACf,QACE,QAAO;;;CAIb,AAAQ,gBAAyB;EAC/B,MAAM,EAAE,aAAa,aAAa,KAAK,QAAQ;EAE/C,MAAM,SADM,KAAK,KAAK,GACD;AAGrB,OAAK,oBAAoB,KAAK,kBAAkB,QAAQ,OAAO,KAAK,OAAO;AAE3E,SAAO,KAAK,kBAAkB,UAAU;;CAG1C,AAAQ,iBAAyB;EAC/B,MAAM,EAAE,YAAY,KAAK;AAEzB,MAAI,QAAQ,SAAS,QACnB,QAAO,QAAQ;EAIjB,MAAM,aAAa,QAAQ,cAAc;EACzC,MAAM,QAAQ,QAAQ,iBAAiB,KAAK,IAAI,YAAY,KAAK,oBAAoB;AACrF,SAAO,KAAK,IAAI,OAAO,QAAQ,WAAW;;CAG5C,AAAQ,kBAAwB;AAC9B,OAAK,SAAS;EACd,MAAM,QAAQ,KAAK,gBAAgB;AAEnC,OAAK,OAAO,KAAK,iBAAiB,MAAM,eAAe,KAAK,cAAc,GAAG;AAE7E,OAAK,sBAAsB,iBAAiB;AAC1C,QAAK,sBAAsB;AAC3B,OAAI,KAAK,eAAe;AACtB,SAAK,SAAS;AACd;;AAEF,QAAK,cAAc;KAClB,MAAM;;CAGX,AAAQ,4BAAkC;EACxC,MAAM,EAAE,cAAc,KAAK,QAAQ;AAEnC,OAAK,sBAAsB,iBAAiB;AAC1C,QAAK,sBAAsB;AAC3B,OAAI,KAAK,eAAe;AACtB,SAAK,SAAS;AACd;;AAIF,QAAK,oBAAoB,EAAE;AAC3B,QAAK;AACL,QAAK,OAAO,KAAK,qDAAqD,KAAK,cAAc,GAAG;AAC5F,QAAK,cAAc;KAClB,UAAU;;CAGf,AAAQ,MAAM,IAA2B;AACvC,SAAO,IAAI,SAAS,YAAY;AAC9B,QAAK,sBAAsB,iBAAiB;AAC1C,SAAK,sBAAsB;AAC3B,aAAS;MACR,GAAG;IACN;;;;;;ACxXN,MAAa,cAAc,EAAE,OAAO;CAClC,YAAY,EAAE,QAAQ;CACtB,SAAS,EAAE,SAAS,EAAE,QAAQ,CAAC;CAChC,CAAC;AAGF,MAAa,qBAAqB,EAAE,OAAO;CACzC,UAAU,EAAE,QAAQ;CACpB,OAAO,EAAE,SAAS,YAAY;CAC9B,YAAY,EAAE,SAAS,EAAE,SAAS,CAAC;CACpC,CAAC;AAGF,MAAa,mBAAmB,EAAE,SAAS;CACzC;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAGF,MAAM,sBAAsB;AAE5B,IAAa,cAAb,MAAyB;CACvB,AAAS;CACT,AAAS;CACT,AAAQ;CACR,AAAQ;CACR,AAAQ,UAAuB;CAE/B,AAAQ,SAA2B;CACnC,AAAQ,YAAoB;CAC5B,AAAQ,aAAqB;CAC7B,AAAQ,sBAA8B;CACtC,AAAQ,YAAqB;CAC7B,AAAQ,gBAAyB;CACjC,AAAQ,eAAqD;CAE7D,YACE,MACA,YACA,SACA,QACA;AACA,OAAK,OAAO;AACZ,OAAK,UAAU;AACf,OAAK,SAAS;AACd,OAAK,cAAc,IAAI,YAAY,MAAM,YAAY,OAAO;;CAG9D,IAAI,QAA0B;AAC5B,SAAO,KAAK;;CAGd,IAAI,WAAmB;AACrB,SAAO,KAAK;;CAGd,IAAI,YAAoB;AACtB,SAAO,KAAK;;CAGd,IAAI,UAAuB;AACzB,MAAI,CAAC,KAAK,QAAS,QAAO;AAE1B,SADa,KAAK,QAAQ,SAAS,IACpB;;CAGjB,QAAc;AACZ,MAAI,KAAK,WAAW,eAAe,KAAK,WAAW,aAAa,KAAK,WAAW,SAC9E,OAAM,IAAI,MAAM,gBAAgB,KAAK,KAAK,eAAe,KAAK,SAAS;AAGzE,MAAI,KAAK,WAAW,WAClB,OAAM,IAAI,MAAM,gBAAgB,KAAK,KAAK,yBAAyB;AAGrE,OAAK,gBAAgB;AACrB,OAAK,OAAO,KAAK,2BAA2B,KAAK,QAAQ,WAAW;AAEpE,OAAK,UAAU,IAAI,KAAK,KAAK,QAAQ,UAAU,EAAE,UAAU,OAAO,QAAQ;AACxE,QAAK,YAAY;IACjB;AAEF,OAAK,SAAS;AAEd,MAAI,KAAK,QAAQ,WACf,MAAK,YAAY;;CAIrB,MAAM,KAAK,SAAiC;AAC1C,OAAK,gBAAgB;AAGrB,MAAI,KAAK,SAAS;AAChB,QAAK,QAAQ,MAAM;AACnB,QAAK,UAAU;;AAIjB,MAAI,KAAK,cAAc;AACrB,gBAAa,KAAK,aAAa;AAC/B,QAAK,eAAe;;AAGtB,MAAI,KAAK,WAAW,UAAU,KAAK,WAAW,WAAW;AACvD,QAAK,SAAS;AACd;;AAIF,MAAI,KAAK,WAAW,aAAa,KAAK,WAAW,cAAc,KAAK,WAAW,UAAU;AACvF,QAAK,SAAS;AACd,SAAM,KAAK,YAAY,KAAK,QAAQ;;AAGtC,OAAK,SAAS;AACd,OAAK,YAAY;AACjB,OAAK,OAAO,KAAK,sBAAsB;;CAGzC,MAAM,UAAyB;AAC7B,MAAI,KAAK,cACP,OAAM,IAAI,MAAM,gBAAgB,KAAK,KAAK,cAAc;AAI1D,MAAI,KAAK,WAAW,SAClB;AAIF,MAAI,KAAK,WAAW,aAAa,KAAK,WAAW,YAAY;AAC3D,QAAK,YAAY;AACjB,QAAK,SAAS;AACd,QAAK,OAAO,KAAK,yCAAyC;AAC1D;;AAGF,QAAM,KAAK,YAAY;;CAGzB,AAAQ,aAAmB;AACzB,MAAI,KAAK,cAAe;AAGxB,MAAI,KAAK,WAAW,aAAa,KAAK,WAAW,cAAc,KAAK,WAAW,UAAU;AACvF,QAAK,YAAY;AACjB,OAAI,KAAK,WAAW,SAClB,MAAK,SAAS;AAEhB,QAAK,OAAO,KAAK,oDAAoD;AACrE;;AAGF,OAAK,YAAY;;CAGnB,MAAc,aAA4B;AACxC,MAAI,KAAK,cAAe;AAExB,OAAK,SAAS;AACd,OAAK,sBAAsB;AAC3B,OAAK,OAAO,KAAK,gBAAgB;AAEjC,QAAM,KAAK,iBAAiB;;CAG9B,MAAc,kBAAiC;AAC7C,MAAI,KAAK,cAAe;AAGxB,QAAM,KAAK,YAAY,OAAO;AAC9B,QAAM,KAAK,YAAY,OAAO;EAE9B,MAAM,YAAY,MAAM,KAAK,YAAY,aAAa;AACtD,MAAI,KAAK,iBAAiB,cAAc,SAAS;AAC/C,QAAK,SAAS;AACd;;AAEF,OAAK,kBAAkB,cAAc,QAAQ;;CAG/C,AAAQ,kBAAkB,QAAuB;AAC/C,MAAI,KAAK,eAAe;AACtB,QAAK,SAAS;AACd;;AAGF,MAAI,QAAQ;GACV,MAAM,aAAa,KAAK,QAAQ,OAAO,cAAc;AAErD,OAAI,KAAK,sBAAsB,YAAY;AAEzC,SAAK;AACL,SAAK,SAAS;IACd,MAAM,UAAU,KAAK,QAAQ,OAAO,WAAW;AAE/C,SAAK,OAAO,KACV,2BAA2B,QAAQ,cAAc,KAAK,oBAAoB,GAAG,WAAW,GACzF;AAED,SAAK,eAAe,iBAAiB;AACnC,UAAK,eAAe;AACpB,SAAI,KAAK,eAAe;AACtB,WAAK,SAAS;AACd;;AAEF,UAAK,iBAAiB;OACrB,QAAQ;AACX;;AAIF,QAAK;AACL,QAAK,OAAO,MAAM,oBAAoB,KAAK,oBAAoB,UAAU;SACpE;AACL,QAAK;AACL,QAAK,OAAO,KAAK,6BAA6B;;AAIhD,MAAI,KAAK,WAAW;AAClB,QAAK,YAAY;AACjB,QAAK,OAAO,KAAK,sBAAsB;AACvC,QAAK,YAAY;AACjB;;AAIF,MAAI,KAAK,QACP,MAAK,SAAS;MAEd,MAAK,SAAS;;;;;;AC7OpB,MAAa,kBAAkB,EAAE,SAAS;CAAC;CAAW;CAAW;CAAa;CAAU;CAAU,CAAC;AAKnG,MAAa,+BAA+B,EAAE,OAAO;CACnD,MAAM,EAAE,QAAQ;CAChB,SAAS;CACV,CAAC;AAcF,IAAa,WAAb,MAAsB;CACpB,AAAS;CACT,AAAQ,SAAsB,EAAE;CAChC,AAAQ,SAAwB;CAChC,AAAQ;CACR,AAAQ;CACR,AAAQ,gBAAwB;CAChC,AAAQ,mBAAkC,EAAE;CAC5C,AAAQ,gBAAyB;CACjC,AAAQ,iBAAuC;CAE/C,YACE,MACA,QACA,cACA,iBACA;AACA,OAAK,OAAO;AACZ,OAAK,SAAS;AACd,OAAK,kBAAkB;AAGvB,MAAI,aACF,MAAK,MAAM,QAAQ,aACjB,MAAK,QAAQ,KAAK;;CAKxB,IAAI,QAAuB;AACzB,SAAO,KAAK;;CAGd,IAAI,QAAkC;AACpC,SAAO,KAAK;;CAGd,mBAAmB,QAAoC;EACrD,MAAM,QACJ,OAAO,WAAW,WAAW,SAAS,KAAK,OAAO,WAAW,MAAM,EAAE,OAAO,OAAO;AACrF,MAAI,QAAQ,KAAK,SAAS,KAAK,OAAO,OACpC,OAAM,IAAI,MAAM,mBAAmB,SAAS;EAG9C,MAAM,OAAO,KAAK,OAAO;AACzB,MAAI,KAAK,UAAU,UACjB,OAAM,IAAI,MAAM,+BAA+B,KAAK,KAAK;AAG3D,OAAK,OAAO,OAAO,OAAO,EAAE;AAC5B,OAAK,OAAO,KAAK,SAAS,KAAK,GAAG,WAAW;AAC7C,SAAO;;;;;;CAOT,QAAQ,MAAiE;EACvE,MAAM,KAAK,QAAQ,EAAE,KAAK;EAC1B,MAAM,YAAY,MAAM,QAAQ,KAAK,GAAG,OAAO,CAAC,KAAK;EAErD,MAAM,QAAmB;GACvB;GACA;GACA,OAAO;GACR;AAED,OAAK,OAAO,KAAK,MAAM;AACvB,OAAK,OAAO,KAAK,SAAS,GAAG,eAAe,UAAU,OAAO,cAAc;AAE3E,SAAO;;;;;CAMT,QAAc;AACZ,MAAI,KAAK,WAAW,UAClB,OAAM,IAAI,MAAM,aAAa,KAAK,KAAK,sBAAsB;AAG/D,OAAK,gBAAgB;AACrB,OAAK,SAAS;AACd,OAAK,OAAO,KAAK,mBAAmB;AAGpC,OAAK,iBAAiB,KAAK,SAAS;;;;;CAMtC,MAAM,gBAA+B;AACnC,MAAI,KAAK,WAAW,UAAU,KAAK,WAAW,UAC5C;AAIF,MAAI,KAAK,eACP,OAAM,KAAK;;;;;CAOf,MAAM,KAAK,SAAiC;AAC1C,MAAI,KAAK,WAAW,UAAU,KAAK,WAAW,WAAW;AACvD,QAAK,SAAS;AACd;;AAGF,OAAK,gBAAgB;AACrB,OAAK,OAAO,KAAK,uBAAuB;EAGxC,MAAM,eAAe,KAAK,iBAAiB,KAAK,MAAM,EAAE,KAAK,QAAQ,CAAC;AACtE,QAAM,QAAQ,IAAI,aAAa;AAC/B,OAAK,mBAAmB,EAAE;AAG1B,OAAK,MAAM,QAAQ,KAAK,OACtB,KAAI,KAAK,UAAU,UACjB,MAAK,QAAQ;AAKjB,MAAI,KAAK,gBAAgB;AACvB,SAAM,KAAK;AACX,QAAK,iBAAiB;;AAGxB,OAAK,SAAS;AACd,OAAK,OAAO,KAAK,mBAAmB;;CAGtC,MAAc,UAAyB;AACrC,SAAO,KAAK,WAAW,aAAa,CAAC,KAAK,eAAe;GAEvD,MAAM,WAAW,KAAK,OAAO,MAAM,MAAM,EAAE,UAAU,UAAU;AAE/D,OAAI,CAAC,UAAU;AAEb,SAAK,SAAS;AACd,SAAK,OAAO,KAAK,wCAAwC;AACzD;;AAGF,SAAM,KAAK,YAAY,SAAS;;;CAIpC,MAAc,YAAY,MAAgC;AACxD,MAAI,KAAK,eAAe;AACtB,QAAK,QAAQ;AACb;;AAGF,OAAK,QAAQ;EACb,MAAM,YAAY,KAAK,UAAU,KAAK,MAAM,EAAE,KAAK,CAAC,KAAK,KAAK;AAC9D,OAAK,OAAO,KAAK,mBAAmB,KAAK,GAAG,MAAM,UAAU,GAAG;EAG/D,MAAM,gBAA+B,KAAK,UAAU,KAAK,MAAM;GAC7D,MAAM,UAAU,KAAK,kBAAkB,EAAE,KAAK;GAC9C,MAAM,cAAc,UAChB,KAAK,OAAO,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,GACtC,KAAK,OAAO,MAAM,EAAE,KAAK;AAC7B,UAAO,IAAI,YAAY,EAAE,MAAM,EAAE,SAAS,YAAY;IACtD;AAEF,OAAK,mBAAmB;AAExB,MAAI;AAEF,SAAM,QAAQ,IAAI,cAAc,KAAK,OAAO,GAAG,OAAO,CAAC,CAAC;GAMxD,MAAM,aAHU,MAAM,QAAQ,IAAI,cAAc,KAAK,OAAO,KAAK,eAAe,GAAG,CAAC,CAAC,EAG3D,MAAM,MAAM,MAAM,QAAQ;AAEpD,OAAI,KAAK,cACP,MAAK,QAAQ;YACJ,WAAW;AACpB,SAAK,QAAQ;AACb,SAAK,OAAO,KAAK,SAAS,KAAK,GAAG,UAAU;UACvC;AACL,SAAK,QAAQ;AACb,SAAK,OAAO,KAAK,SAAS,KAAK,GAAG,aAAa;;WAE1C,KAAK;AACZ,QAAK,QAAQ;AACb,QAAK,OAAO,MAAM,SAAS,KAAK,GAAG,WAAW,IAAI;YAC1C;AACR,QAAK,mBAAmB,EAAE;;;CAI9B,MAAc,eAAe,IAA+C;AAE1E,SADc,MAAM,GAAG,aAAa,KACnB,UAAU,UAAU;;;;;;ACjNzC,IAAa,aAAb,MAAwB;CACtB,AAAQ,YAAoC,EAAE;CAC9C,AAAQ;CACR,AAAQ,sBAA2C,IAAI,KAAK;CAC5D,AAAQ,2BAAkD,IAAI,KAAK;CACnE,AAAQ,4BAAiC,IAAI,KAAK;CAClD,AAAQ,kCAA0C,IAAI,KAAK;CAC3D,AAAQ,aAA8C;CACtD,AAAQ,6BAA0B,IAAI,KAAK;CAE3C,YAAY,AAAQ,QAA0B;EAA1B;AAClB,OAAK,gBAAgB,OAAO,gBACxB,WAAW,OAAO,cAAc,GAC9B,OAAO,gBACP,QAAQ,OAAO,KAAK,OAAO,cAAc,GAC3C,QAAQ,OAAO,KAAK,OAAO;AAG/B,OAAK,oBAAoB;AACzB,OAAK,qBAAqB;AAC1B,OAAK,qBAAqB;;;;;;CAO5B,AAAO,aAAa,KAAa,UAAwB;EACvD,MAAM,eAAe,WAAW,SAAS,GAAG,WAAW,QAAQ,KAAK,OAAO,KAAK,SAAS;AACzF,OAAK,WAAW,IAAI,IAAI;AACxB,OAAK,YAAY,KAAK,aAAa;;;;;CAMrC,AAAO,cAAc,KAAsB;AACzC,SAAO,KAAK,WAAW,IAAI,IAAI;;;;;CAMjC,AAAQ,qBAA2B;AACjC,MAAI,CAAC,KAAK,OAAO,eAAgB;AAEjC,OAAK,MAAM,CAAC,KAAK,aAAa,OAAO,QAAQ,KAAK,OAAO,eAAe,CACtE,MAAK,aAAa,KAAK,SAAS;;CAIpC,AAAO,WAAW,KAAqC;EACrD,MAAM,cAAc,KAAK,IAAI,IAAI,IAAI;AACrC,SAAO;GACL,GAAG,KAAK;GACR,GAAG;GACJ;;CAGH,AAAQ,sBAA4B;AAClC,MAAI,WAAW,KAAK,cAAc,CAAE,MAAK,cAAc,KAAK,cAAc;AAE1E,MAAI;GACF,MAAM,WAAW,SAAS,UAAU,EAAE,KAAK,KAAK,OAAO,KAAK,CAAC;AAC7D,QAAK,MAAM,YAAY,UAAU;IAC/B,MAAM,MAAM,KAAK,gBAAgB,SAAS,SAAS,CAAC;AAEpD,QAAI,OAAO,CAAC,KAAK,WAAW,IAAI,IAAI,CAClC,MAAK,YAAY,KAAK,QAAQ,KAAK,OAAO,KAAK,SAAS,CAAC;;WAGtD,KAAK;AACZ,WAAQ,KAAK,6BAA6B,IAAI;;;CAIlD,AAAQ,aAAa,cAAqD;AACxE,MAAI;AAEF,UAAO,MADS,aAAa,cAAc,QAAQ,CAC9B,IAAI,EAAE;WACpB,KAAK;AACZ,WAAQ,KAAK,6BAA6B,gBAAgB,IAAI;AAC9D,UAAO;;;CAIX,AAAQ,gBAAgB,UAAiC;EACvD,MAAM,QAAQ,SAAS,MAAM,gBAAgB;AAC7C,SAAO,QAAQ,MAAM,KAAK;;CAG5B,AAAQ,cAAc,cAAsB;AAC1C,MAAI,CAAC,WAAW,aAAa,CAAE;EAC/B,MAAM,SAAS,KAAK,aAAa,aAAa;AAC9C,MAAI,QAAQ;AACV,QAAK,YAAY;AACjB,QAAK,UAAU,aAAa;;;CAIhC,AAAQ,YAAY,KAAa,cAAsB;EACrD,MAAM,SAAS,KAAK,aAAa,aAAa;AAC9C,MAAI,CAAC,OAAQ;AAEb,OAAK,IAAI,IAAI,KAAK,OAAO;AACzB,OAAK,UAAU,IAAI,cAAc,IAAI;AACrC,OAAK,UAAU,aAAa;;CAG9B,AAAQ,UAAU,cAA4B;AAC5C,MAAI,KAAK,SAAS,IAAI,aAAa,CAAE;AACrC,MAAI;GACF,MAAM,UAAU,MAAM,cAAc,EAAE,eAAe,MAAM,CAAC,CACzD,GAAG,gBAAgB;AAClB,SAAK,iBAAiB,aAAa;KACnC,CACD,GAAG,gBAAgB;AAClB,SAAK,iBAAiB,aAAa;KACnC;AAEJ,QAAK,SAAS,IAAI,cAAc,QAAQ;WACjC,KAAK;AACZ,WAAQ,KAAK,6BAA6B,gBAAgB,IAAI;;;;;;CAOlE,AAAQ,sBAA4B;AAClC,MAAI;AACF,QAAK,aAAa,MAAM,KAAK,OAAO,KAAK;IACvC,eAAe;IACf,OAAO;IACR,CAAC,CAAC,GAAG,QAAQ,aAAa;AACzB,SAAK,cAAc,SAAS;KAC5B;WACK,KAAK;AACZ,WAAQ,KAAK,0CAA0C,IAAI;;;CAI/D,AAAQ,iBAAiB,cAA4B;AACnD,MAAI,iBAAiB,KAAK,eAAe;AACvC,QAAK,cAAc,aAAa;AAChC,QAAK,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACxC;;EAGF,MAAM,MAAM,KAAK,UAAU,IAAI,aAAa;AAC5C,MAAI,CAAC,IAAK;EAEV,MAAM,SAAS,KAAK,aAAa,aAAa;AAC9C,MAAI,CAAC,OAAQ;AAEb,OAAK,IAAI,IAAI,KAAK,OAAO;AACzB,OAAK,gBAAgB;GAAE,MAAM;GAAW;GAAK,CAAC;;CAGhD,AAAQ,iBAAiB,cAA4B;EACnD,MAAM,UAAU,KAAK,SAAS,IAAI,aAAa;AAC/C,MAAI,SAAS;AACX,WAAQ,OAAO;AACf,QAAK,SAAS,OAAO,aAAa;;AAGpC,MAAI,iBAAiB,KAAK,eAAe;AACvC,QAAK,YAAY,EAAE;AACnB,QAAK,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACxC;;EAGF,MAAM,MAAM,KAAK,UAAU,IAAI,aAAa;AAC5C,MAAI,KAAK;AACP,QAAK,IAAI,OAAO,IAAI;AACpB,QAAK,UAAU,OAAO,aAAa;AACnC,QAAK,gBAAgB;IAAE,MAAM;IAAW;IAAK,CAAC;;;CAIlD,AAAQ,cAAc,UAAwB;EAC5C,MAAM,eAAe,WAAW,SAAS,GAAG,WAAW,QAAQ,KAAK,OAAO,KAAK,SAAS;AAEzF,MAAI,iBAAiB,KAAK,eAAe;AACvC,QAAK,cAAc,aAAa;AAChC,QAAK,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACxC;;EAGF,MAAM,MAAM,KAAK,gBAAgB,SAAS,SAAS,CAAC;AAEpD,MAAI,OAAO,CAAC,KAAK,WAAW,IAAI,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,IAAI,EAAE;AAC1D,QAAK,YAAY,KAAK,aAAa;AACnC,QAAK,gBAAgB;IAAE,MAAM;IAAW;IAAK,CAAC;;;CAIlD,AAAQ,gBAAgB,OAA6B;AACnD,OAAK,MAAM,YAAY,KAAK,gBAAiB,UAAS,MAAM;;CAG9D,SAAS,UAAyC;AAChD,OAAK,gBAAgB,IAAI,SAAS;AAClC,eAAa;AACX,QAAK,gBAAgB,OAAO,SAAS;;;CAIzC,QAAc;AACZ,OAAK,MAAM,WAAW,KAAK,SAAS,QAAQ,CAC1C,SAAQ,OAAO;AAEjB,OAAK,SAAS,OAAO;AAErB,MAAI,KAAK,YAAY;AACnB,QAAK,WAAW,OAAO;AACvB,QAAK,aAAa;;AAGpB,OAAK,gBAAgB,OAAO;;;;;;AC9OhC,MAAM,SAAS;CACb,OAAO;CACP,MAAM;CACN,OAAO;CACP,OAAO;CACP,QAAQ;CACR,KAAK;CACL,MAAM;CACP;AAED,MAAM,cAAc;CAClB,OAAO,OAAO;CACd,MAAM,OAAO;CACb,MAAM,OAAO;CACb,OAAO,OAAO;CACf;AAED,MAAM,cAAc,SAClB,KAAK,eAAe,SAAS;CAC3B,MAAM;CACN,QAAQ;CACR,QAAQ;CACR,wBAAwB;CACxB,WAAW;CACZ,CAAC,CAAC,OAAO,KAAK;AAEjB,MAAM,uBAAuB,OAAe,MAAc,SAAe;CACvE,MAAM,iBAAiB,MAAM,aAAa,CAAC,SAAS,EAAE;AAEtD,QAAO,IADW,WAAW,KAAK,CACb,IAAI,eAAe,IAAI,KAAK;;AAGnD,MAAM,yBAAyB,OAAe,MAAc,SAAe;CACzE,MAAM,iBAAiB,MAAM,aAAa,CAAC,SAAS,EAAE;CACtD,MAAM,YAAY,WAAW,KAAK;CAClC,MAAM,YAAY,YAAY,UAAsC;AACpE,QAAO,GAAG,OAAO,KAAK,GAAG,UAAU,GAAG,OAAO,MAAM,GAAG,YAAY,iBAAiB,OAAO,MAAM,IAAI,KAAK;;AAU3G,MAAM,uBAAuB,QAA4B,YAAqB;AAC5E,KAAI,CAAC,OAAQ,QAAO;AACpB,KAAI,QAAS,QAAO,GAAG,OAAO,OAAO,OAAO,MAAM,GAAG,OAAO,GAAG,OAAO,MAAM;AAC5E,QAAO,IAAI,OAAO;;AAGpB,MAAM,WAAW,QAAsB,OAA4C,SAAgB;CACjG,MAAM,UAAU,OAAO,GAAG,KAAK;CAC/B,MAAM,uBAAO,IAAI,MAAM;AAEvB,KAAI,OAAO,SAAS;EAGlB,MAAM,YAAY;GAFE,oBAAoB,OAAO,OAAO,MAAM,KAAK;GACtC,oBAAoB,OAAO,QAAQ,MAAM;GAChB;GAAQ,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;AACtF,MAAI;AACF,kBAAe,OAAO,SAAS,GAAG,UAAU,IAAI;UAC1C;;AAKV,KAAI,OAAO,QAAQ;EAGjB,MAAM,cAAc;GAFE,sBAAsB,OAAO,OAAO,MAAM,KAAK;GACxC,oBAAoB,OAAO,QAAQ,KAAK;GACX;GAAQ,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;AAC5F,UAAQ,OAAO,YAAY;;;AAI/B,MAAa,UAAU,YAA0B;CAC/C,MAAM,SAAS;EAAE,QAAQ;EAAM,GAAG;EAAS;AAC3C,QAAO;EACL,OAAO,GAAG,SAAgB,QAAQ,QAAQ,QAAQ,KAAK;EACvD,QAAQ,GAAG,SAAgB,QAAQ,QAAQ,SAAS,KAAK;EACzD,OAAO,GAAG,SAAgB,QAAQ,QAAQ,QAAQ,KAAK;EACvD,QAAQ,GAAG,SAAgB,QAAQ,QAAQ,SAAS,KAAK;EACzD,QAAQ,QAAgB,YAAiD,EAAE,KACzE,OAAO;GACL,GAAG;GACH,GAAG;GACH,MAAM,GAAG,OAAO,KAAK,GAAG;GACzB,CAAC;EACJ,aAAa,WACX,OAAO;GACL,GAAG;GACH;GACD,CAAC;EACL"}