pidnap 0.0.0-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +1166 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/client.d.mts +169 -0
- package/dist/client.d.mts.map +1 -0
- package/dist/client.mjs +15 -0
- package/dist/client.mjs.map +1 -0
- package/dist/index.d.mts +230 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +10 -0
- package/dist/index.mjs.map +1 -0
- package/dist/logger-crc5neL8.mjs +966 -0
- package/dist/logger-crc5neL8.mjs.map +1 -0
- package/dist/task-list-CIdbB3wM.d.mts +230 -0
- package/dist/task-list-CIdbB3wM.d.mts.map +1 -0
- package/package.json +50 -0
- package/src/api/client.ts +13 -0
- package/src/api/contract.ts +117 -0
- package/src/api/server.ts +180 -0
- package/src/cli.ts +462 -0
- package/src/cron-process.ts +255 -0
- package/src/env-manager.ts +237 -0
- package/src/index.ts +12 -0
- package/src/lazy-process.ts +228 -0
- package/src/logger.ts +108 -0
- package/src/manager.ts +859 -0
- package/src/restarting-process.ts +397 -0
- package/src/task-list.ts +236 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { Cron } from "croner";
|
|
2
|
+
import * as v from "valibot";
|
|
3
|
+
import { LazyProcess, type ProcessDefinition } from "./lazy-process.ts";
|
|
4
|
+
import type { Logger } from "./logger.ts";
|
|
5
|
+
|
|
6
|
+
// Retry configuration schema
|
|
7
|
+
export const RetryConfigSchema = v.object({
|
|
8
|
+
maxRetries: v.number(),
|
|
9
|
+
delayMs: v.optional(v.number()),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export type RetryConfig = v.InferOutput<typeof RetryConfigSchema>;
|
|
13
|
+
|
|
14
|
+
// Cron process options schema
|
|
15
|
+
export const CronProcessOptionsSchema = v.object({
|
|
16
|
+
schedule: v.string(),
|
|
17
|
+
retry: v.optional(RetryConfigSchema),
|
|
18
|
+
runOnStart: v.optional(v.boolean()),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export type CronProcessOptions = v.InferOutput<typeof CronProcessOptionsSchema>;
|
|
22
|
+
|
|
23
|
+
// State
|
|
24
|
+
export const CronProcessStateSchema = v.picklist([
|
|
25
|
+
"idle",
|
|
26
|
+
"scheduled",
|
|
27
|
+
"running",
|
|
28
|
+
"retrying",
|
|
29
|
+
"queued",
|
|
30
|
+
"stopping",
|
|
31
|
+
"stopped",
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
export type CronProcessState = v.InferOutput<typeof CronProcessStateSchema>;
|
|
35
|
+
|
|
36
|
+
const DEFAULT_RETRY_DELAY = 1000;
|
|
37
|
+
|
|
38
|
+
export class CronProcess {
|
|
39
|
+
readonly name: string;
|
|
40
|
+
private lazyProcess: LazyProcess;
|
|
41
|
+
private options: CronProcessOptions;
|
|
42
|
+
private logger: Logger;
|
|
43
|
+
private cronJob: Cron | null = null;
|
|
44
|
+
|
|
45
|
+
// State tracking
|
|
46
|
+
private _state: CronProcessState = "idle";
|
|
47
|
+
private _runCount: number = 0;
|
|
48
|
+
private _failCount: number = 0;
|
|
49
|
+
private currentRetryAttempt: number = 0;
|
|
50
|
+
private queuedRun: boolean = false;
|
|
51
|
+
private stopRequested: boolean = false;
|
|
52
|
+
private retryTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
53
|
+
|
|
54
|
+
constructor(
|
|
55
|
+
name: string,
|
|
56
|
+
definition: ProcessDefinition,
|
|
57
|
+
options: CronProcessOptions,
|
|
58
|
+
logger: Logger,
|
|
59
|
+
) {
|
|
60
|
+
this.name = name;
|
|
61
|
+
this.options = options;
|
|
62
|
+
this.logger = logger;
|
|
63
|
+
this.lazyProcess = new LazyProcess(name, definition, logger);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get state(): CronProcessState {
|
|
67
|
+
return this._state;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
get runCount(): number {
|
|
71
|
+
return this._runCount;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
get failCount(): number {
|
|
75
|
+
return this._failCount;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
get nextRun(): Date | null {
|
|
79
|
+
if (!this.cronJob) return null;
|
|
80
|
+
const next = this.cronJob.nextRun();
|
|
81
|
+
return next ?? null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
start(): void {
|
|
85
|
+
if (this._state === "scheduled" || this._state === "running" || this._state === "queued") {
|
|
86
|
+
throw new Error(`CronProcess "${this.name}" is already ${this._state}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (this._state === "stopping") {
|
|
90
|
+
throw new Error(`CronProcess "${this.name}" is currently stopping`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this.stopRequested = false;
|
|
94
|
+
this.logger.info(`Starting cron schedule: ${this.options.schedule}`);
|
|
95
|
+
|
|
96
|
+
// Create cron job with UTC timezone
|
|
97
|
+
this.cronJob = new Cron(this.options.schedule, { timezone: "UTC" }, () => {
|
|
98
|
+
this.onCronTick();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
this._state = "scheduled";
|
|
102
|
+
|
|
103
|
+
// Run immediately if configured
|
|
104
|
+
if (this.options.runOnStart) {
|
|
105
|
+
this.executeJob();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async stop(timeout?: number): Promise<void> {
|
|
110
|
+
this.stopRequested = true;
|
|
111
|
+
|
|
112
|
+
// Stop the cron job
|
|
113
|
+
if (this.cronJob) {
|
|
114
|
+
this.cronJob.stop();
|
|
115
|
+
this.cronJob = null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Clear any pending retry timeout
|
|
119
|
+
if (this.retryTimeout) {
|
|
120
|
+
clearTimeout(this.retryTimeout);
|
|
121
|
+
this.retryTimeout = null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (this._state === "idle" || this._state === "stopped") {
|
|
125
|
+
this._state = "stopped";
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// If running, stop the current job
|
|
130
|
+
if (this._state === "running" || this._state === "retrying" || this._state === "queued") {
|
|
131
|
+
this._state = "stopping";
|
|
132
|
+
await this.lazyProcess.stop(timeout);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
this._state = "stopped";
|
|
136
|
+
this.queuedRun = false;
|
|
137
|
+
this.logger.info(`CronProcess stopped`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async trigger(): Promise<void> {
|
|
141
|
+
if (this.stopRequested) {
|
|
142
|
+
throw new Error(`CronProcess "${this.name}" is stopped`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// If already queued, just return (already have a run pending)
|
|
146
|
+
if (this._state === "queued") {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// If already running, queue this trigger
|
|
151
|
+
if (this._state === "running" || this._state === "retrying") {
|
|
152
|
+
this.queuedRun = true;
|
|
153
|
+
this._state = "queued";
|
|
154
|
+
this.logger.info(`Run queued (current job still running)`);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
await this.executeJob();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private onCronTick(): void {
|
|
162
|
+
if (this.stopRequested) return;
|
|
163
|
+
|
|
164
|
+
// If already running, queue the next run
|
|
165
|
+
if (this._state === "running" || this._state === "retrying" || this._state === "queued") {
|
|
166
|
+
this.queuedRun = true;
|
|
167
|
+
if (this._state !== "queued") {
|
|
168
|
+
this._state = "queued";
|
|
169
|
+
}
|
|
170
|
+
this.logger.info(`Cron tick: run queued (current job still running)`);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
this.executeJob();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private async executeJob(): Promise<void> {
|
|
178
|
+
if (this.stopRequested) return;
|
|
179
|
+
|
|
180
|
+
this._state = "running";
|
|
181
|
+
this.currentRetryAttempt = 0;
|
|
182
|
+
this.logger.info(`Executing job`);
|
|
183
|
+
|
|
184
|
+
await this.runJobWithRetry();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private async runJobWithRetry(): Promise<void> {
|
|
188
|
+
if (this.stopRequested) return;
|
|
189
|
+
|
|
190
|
+
// Reset and start the process
|
|
191
|
+
await this.lazyProcess.reset();
|
|
192
|
+
this.lazyProcess.start();
|
|
193
|
+
|
|
194
|
+
const exitState = await this.lazyProcess.waitForExit();
|
|
195
|
+
if (this.stopRequested && exitState === "error") {
|
|
196
|
+
this._state = "stopped";
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
this.handleJobComplete(exitState === "error");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private handleJobComplete(failed: boolean): void {
|
|
203
|
+
if (this.stopRequested) {
|
|
204
|
+
this._state = "stopped";
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (failed) {
|
|
209
|
+
const maxRetries = this.options.retry?.maxRetries ?? 0;
|
|
210
|
+
|
|
211
|
+
if (this.currentRetryAttempt < maxRetries) {
|
|
212
|
+
// Retry
|
|
213
|
+
this.currentRetryAttempt++;
|
|
214
|
+
this._state = "retrying";
|
|
215
|
+
const delayMs = this.options.retry?.delayMs ?? DEFAULT_RETRY_DELAY;
|
|
216
|
+
|
|
217
|
+
this.logger.warn(
|
|
218
|
+
`Job failed, retrying in ${delayMs}ms (attempt ${this.currentRetryAttempt}/${maxRetries})`,
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
this.retryTimeout = setTimeout(() => {
|
|
222
|
+
this.retryTimeout = null;
|
|
223
|
+
if (this.stopRequested) {
|
|
224
|
+
this._state = "stopped";
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
this.runJobWithRetry();
|
|
228
|
+
}, delayMs);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// All retries exhausted
|
|
233
|
+
this._failCount++;
|
|
234
|
+
this.logger.error(`Job failed after ${this.currentRetryAttempt} retries`);
|
|
235
|
+
} else {
|
|
236
|
+
this._runCount++;
|
|
237
|
+
this.logger.info(`Job completed successfully`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Check for queued run
|
|
241
|
+
if (this.queuedRun) {
|
|
242
|
+
this.queuedRun = false;
|
|
243
|
+
this.logger.info(`Starting queued run`);
|
|
244
|
+
this.executeJob();
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Back to scheduled state
|
|
249
|
+
if (this.cronJob) {
|
|
250
|
+
this._state = "scheduled";
|
|
251
|
+
} else {
|
|
252
|
+
this._state = "stopped";
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { parse } from "dotenv";
|
|
2
|
+
import { existsSync, globSync, watch, readFileSync } from "node:fs";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { resolve, join, basename } from "node:path";
|
|
5
|
+
|
|
6
|
+
export type EnvChangeCallback = (changedKeys: string[]) => void;
|
|
7
|
+
|
|
8
|
+
export interface EnvManagerConfig {
|
|
9
|
+
/**
|
|
10
|
+
* Directory to search for .env files
|
|
11
|
+
* Defaults to process.cwd()
|
|
12
|
+
*/
|
|
13
|
+
cwd?: string;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Explicit env file paths to load
|
|
17
|
+
* Key is the identifier (e.g., "global", "app1")
|
|
18
|
+
* Value is the file path relative to cwd or absolute
|
|
19
|
+
*/
|
|
20
|
+
files?: Record<string, string>;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Enable file watching for env files
|
|
24
|
+
* Defaults to false
|
|
25
|
+
*/
|
|
26
|
+
watch?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class EnvManager {
|
|
30
|
+
private env: Map<string, Record<string, string>> = new Map();
|
|
31
|
+
private cwd: string;
|
|
32
|
+
private watchEnabled: boolean;
|
|
33
|
+
private watchers: Map<string, ReturnType<typeof watch>> = new Map();
|
|
34
|
+
private fileToKeys: Map<string, Set<string>> = new Map();
|
|
35
|
+
private changeCallbacks: Set<EnvChangeCallback> = new Set();
|
|
36
|
+
private reloadDebounceTimers: Map<string, ReturnType<typeof setTimeout>> = new Map();
|
|
37
|
+
|
|
38
|
+
constructor(config: EnvManagerConfig = {}) {
|
|
39
|
+
this.cwd = config.cwd ?? process.cwd();
|
|
40
|
+
this.watchEnabled = config.watch ?? false;
|
|
41
|
+
|
|
42
|
+
// Load .env and .env.* files from cwd
|
|
43
|
+
this.loadEnvFilesFromCwd();
|
|
44
|
+
|
|
45
|
+
// Load explicitly specified files
|
|
46
|
+
if (config.files) {
|
|
47
|
+
for (const [key, filePath] of Object.entries(config.files)) {
|
|
48
|
+
this.loadEnvFile(key, filePath);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
registerFile(key: string, filePath: string): void {
|
|
54
|
+
this.loadEnvFile(key, filePath);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getEnvForKey(key: string): Record<string, string> {
|
|
58
|
+
return this.env.get(key) ?? {};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Load .env and .env.* files from the cwd
|
|
63
|
+
*/
|
|
64
|
+
private loadEnvFilesFromCwd(): void {
|
|
65
|
+
// Load .env file as global
|
|
66
|
+
const dotEnvPath = resolve(this.cwd, ".env");
|
|
67
|
+
if (existsSync(dotEnvPath)) {
|
|
68
|
+
this.loadEnvFile("global", dotEnvPath);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Load .env.* files
|
|
72
|
+
try {
|
|
73
|
+
const pattern = join(this.cwd, ".env.*");
|
|
74
|
+
const envFiles = globSync(pattern);
|
|
75
|
+
|
|
76
|
+
for (const filePath of envFiles) {
|
|
77
|
+
// Extract the suffix after .env.
|
|
78
|
+
const fileName = basename(filePath);
|
|
79
|
+
const match = fileName.match(/^\.env\.(.+)$/);
|
|
80
|
+
if (match) {
|
|
81
|
+
const suffix = match[1];
|
|
82
|
+
this.loadEnvFile(suffix, filePath);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch (err) {
|
|
86
|
+
console.warn("Failed to scan env files:", err);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Load a single env file and store it in the map
|
|
92
|
+
*/
|
|
93
|
+
private loadEnvFile(key: string, filePath: string): void {
|
|
94
|
+
const absolutePath = resolve(this.cwd, filePath);
|
|
95
|
+
|
|
96
|
+
if (!existsSync(absolutePath)) {
|
|
97
|
+
return; // Silently skip non-existent files
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const content = readFileSync(absolutePath, "utf-8");
|
|
102
|
+
const parsed = parse(content);
|
|
103
|
+
this.env.set(key, parsed);
|
|
104
|
+
|
|
105
|
+
// Track which file maps to which key
|
|
106
|
+
if (!this.fileToKeys.has(absolutePath)) {
|
|
107
|
+
this.fileToKeys.set(absolutePath, new Set());
|
|
108
|
+
}
|
|
109
|
+
this.fileToKeys.get(absolutePath)!.add(key);
|
|
110
|
+
|
|
111
|
+
// Start watching if enabled and not already watching
|
|
112
|
+
if (this.watchEnabled && !this.watchers.has(absolutePath)) {
|
|
113
|
+
this.watchFile(absolutePath);
|
|
114
|
+
}
|
|
115
|
+
} catch (err) {
|
|
116
|
+
console.warn(`Failed to load env file: ${absolutePath}`, err);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Watch a file for changes
|
|
122
|
+
*/
|
|
123
|
+
private watchFile(absolutePath: string): void {
|
|
124
|
+
try {
|
|
125
|
+
const watcher = watch(absolutePath, (eventType) => {
|
|
126
|
+
if (eventType === "change") {
|
|
127
|
+
this.handleFileChange(absolutePath);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
this.watchers.set(absolutePath, watcher);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
console.warn(`Failed to watch env file: ${absolutePath}`, err);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Handle file change with debouncing
|
|
139
|
+
*/
|
|
140
|
+
private handleFileChange(absolutePath: string): void {
|
|
141
|
+
// Clear existing timer if any
|
|
142
|
+
const existingTimer = this.reloadDebounceTimers.get(absolutePath);
|
|
143
|
+
if (existingTimer) {
|
|
144
|
+
clearTimeout(existingTimer);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Debounce reload by 100ms to avoid multiple rapid reloads
|
|
148
|
+
const timer = setTimeout(() => {
|
|
149
|
+
this.reloadFile(absolutePath);
|
|
150
|
+
this.reloadDebounceTimers.delete(absolutePath);
|
|
151
|
+
}, 100);
|
|
152
|
+
|
|
153
|
+
this.reloadDebounceTimers.set(absolutePath, timer);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Reload a file and notify callbacks
|
|
158
|
+
*/
|
|
159
|
+
private reloadFile(absolutePath: string): void {
|
|
160
|
+
const keys = this.fileToKeys.get(absolutePath);
|
|
161
|
+
if (!keys) return;
|
|
162
|
+
|
|
163
|
+
readFile(absolutePath, "utf-8")
|
|
164
|
+
.then((content) => parse(content))
|
|
165
|
+
.then((parsed) => {
|
|
166
|
+
const changedKeys: string[] = [];
|
|
167
|
+
for (const key of keys) {
|
|
168
|
+
this.env.set(key, parsed);
|
|
169
|
+
changedKeys.push(key);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Notify all callbacks
|
|
173
|
+
if (changedKeys.length > 0) {
|
|
174
|
+
for (const callback of this.changeCallbacks) {
|
|
175
|
+
callback(changedKeys);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
.catch((err) => {
|
|
180
|
+
console.warn(`Failed to reload env file: ${absolutePath}`, err);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Register a callback to be called when env files change
|
|
186
|
+
* Returns a function to unregister the callback
|
|
187
|
+
*/
|
|
188
|
+
onChange(callback: EnvChangeCallback): () => void {
|
|
189
|
+
this.changeCallbacks.add(callback);
|
|
190
|
+
return () => {
|
|
191
|
+
this.changeCallbacks.delete(callback);
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Stop watching all files and cleanup
|
|
197
|
+
*/
|
|
198
|
+
dispose(): void {
|
|
199
|
+
// Clear all timers
|
|
200
|
+
for (const timer of this.reloadDebounceTimers.values()) {
|
|
201
|
+
clearTimeout(timer);
|
|
202
|
+
}
|
|
203
|
+
this.reloadDebounceTimers.clear();
|
|
204
|
+
|
|
205
|
+
// Close all watchers
|
|
206
|
+
for (const watcher of this.watchers.values()) {
|
|
207
|
+
watcher.close();
|
|
208
|
+
}
|
|
209
|
+
this.watchers.clear();
|
|
210
|
+
|
|
211
|
+
// Clear callbacks
|
|
212
|
+
this.changeCallbacks.clear();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Get environment variables for a specific process
|
|
217
|
+
* Merges global env with process-specific env
|
|
218
|
+
* Process-specific env variables override global ones
|
|
219
|
+
*/
|
|
220
|
+
getEnvVars(processKey?: string): Record<string, string> {
|
|
221
|
+
const globalEnv = this.env.get("global") ?? {};
|
|
222
|
+
|
|
223
|
+
if (!processKey) {
|
|
224
|
+
return { ...globalEnv };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const processEnv = this.env.get(processKey) ?? {};
|
|
228
|
+
return { ...globalEnv, ...processEnv };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Get all loaded env maps (for debugging/inspection)
|
|
233
|
+
*/
|
|
234
|
+
getAllEnv(): ReadonlyMap<string, Record<string, string>> {
|
|
235
|
+
return this.env;
|
|
236
|
+
}
|
|
237
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ManagerConfig } from "./manager.ts";
|
|
2
|
+
|
|
3
|
+
export function defineConfig(config: ManagerConfig) {
|
|
4
|
+
return config;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export * from "./restarting-process.ts";
|
|
8
|
+
export * from "./cron-process.ts";
|
|
9
|
+
export * from "./task-list.ts";
|
|
10
|
+
export * from "./lazy-process.ts";
|
|
11
|
+
export * from "./env-manager.ts";
|
|
12
|
+
export * from "./logger.ts";
|