lsh-framework 3.2.5 → 3.5.1

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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +72 -34
  3. package/dist/commands/ipfs.js +7 -12
  4. package/dist/commands/self.js +22 -16
  5. package/dist/commands/sync.js +49 -38
  6. package/dist/constants/config.js +3 -0
  7. package/dist/lib/floating-point-arithmetic.js +2 -2
  8. package/dist/lib/ipfs-client-manager.js +51 -13
  9. package/dist/lib/ipfs-secrets-storage.js +21 -16
  10. package/dist/lib/ipfs-sync.js +88 -14
  11. package/dist/lib/secrets-manager.js +117 -47
  12. package/dist/lib/sync-key-store.js +87 -0
  13. package/dist/services/secrets/secrets.js +77 -39
  14. package/package.json +16 -16
  15. package/dist/__tests__/fixtures/job-fixtures.js +0 -204
  16. package/dist/__tests__/fixtures/supabase-mocks.js +0 -252
  17. package/dist/daemon/job-registry.js +0 -556
  18. package/dist/daemon/lshd.js +0 -968
  19. package/dist/daemon/saas-api-routes.js +0 -599
  20. package/dist/daemon/saas-api-server.js +0 -231
  21. package/dist/examples/supabase-integration.js +0 -106
  22. package/dist/lib/api-response.js +0 -226
  23. package/dist/lib/base-command-registrar.js +0 -287
  24. package/dist/lib/base-job-manager.js +0 -295
  25. package/dist/lib/cloud-config-manager.js +0 -348
  26. package/dist/lib/cron-job-manager.js +0 -368
  27. package/dist/lib/daemon-client-helper.js +0 -145
  28. package/dist/lib/daemon-client.js +0 -513
  29. package/dist/lib/database-persistence.js +0 -727
  30. package/dist/lib/database-schema.js +0 -259
  31. package/dist/lib/database-types.js +0 -90
  32. package/dist/lib/enhanced-history-system.js +0 -247
  33. package/dist/lib/history-system.js +0 -246
  34. package/dist/lib/job-manager.js +0 -436
  35. package/dist/lib/job-storage-database.js +0 -164
  36. package/dist/lib/job-storage-memory.js +0 -73
  37. package/dist/lib/local-storage-adapter.js +0 -507
  38. package/dist/lib/optimized-job-scheduler.js +0 -356
  39. package/dist/lib/saas-audit.js +0 -215
  40. package/dist/lib/saas-auth.js +0 -465
  41. package/dist/lib/saas-billing.js +0 -503
  42. package/dist/lib/saas-email.js +0 -403
  43. package/dist/lib/saas-encryption.js +0 -221
  44. package/dist/lib/saas-organizations.js +0 -662
  45. package/dist/lib/saas-secrets.js +0 -408
  46. package/dist/lib/saas-types.js +0 -165
  47. package/dist/lib/supabase-client.js +0 -125
  48. package/dist/lib/supabase-utils.js +0 -396
  49. package/dist/services/cron/cron-registrar.js +0 -240
  50. package/dist/services/cron/cron.js +0 -9
  51. package/dist/services/daemon/daemon-registrar.js +0 -585
  52. package/dist/services/daemon/daemon.js +0 -9
  53. package/dist/services/supabase/supabase-registrar.js +0 -375
  54. package/dist/services/supabase/supabase.js +0 -9
@@ -1,246 +0,0 @@
1
- /**
2
- * Command History System Implementation
3
- * Provides ZSH-compatible history functionality
4
- */
5
- import * as fs from 'fs';
6
- import * as path from 'path';
7
- import * as os from 'os';
8
- import { DEFAULTS } from '../constants/index.js';
9
- export class HistorySystem {
10
- entries = [];
11
- currentIndex = -1;
12
- config;
13
- isEnabled = true;
14
- constructor(config) {
15
- this.config = {
16
- maxSize: DEFAULTS.MAX_HISTORY_SIZE,
17
- filePath: path.join(os.homedir(), '.lsh_history'),
18
- shareHistory: false,
19
- ignoreDups: true,
20
- ignoreSpace: false,
21
- expireDuplicatesFirst: true,
22
- ...config,
23
- };
24
- this.loadHistory();
25
- }
26
- /**
27
- * Add a command to history
28
- */
29
- addCommand(command, exitCode) {
30
- if (!this.isEnabled)
31
- return;
32
- // Skip empty commands
33
- if (!command.trim())
34
- return;
35
- // Skip commands starting with space if ignoreSpace is enabled
36
- if (this.config.ignoreSpace && command.startsWith(' '))
37
- return;
38
- // Remove duplicates if configured
39
- if (this.config.ignoreDups) {
40
- this.removeDuplicateCommand(command);
41
- }
42
- const entry = {
43
- lineNumber: this.entries.length + 1,
44
- command: command.trim(),
45
- timestamp: Date.now(),
46
- exitCode,
47
- };
48
- this.entries.push(entry);
49
- // Trim history if it exceeds max size
50
- if (this.entries.length > this.config.maxSize) {
51
- this.entries = this.entries.slice(-this.config.maxSize);
52
- this.renumberEntries();
53
- }
54
- this.currentIndex = this.entries.length - 1;
55
- this.saveHistory();
56
- }
57
- /**
58
- * Get history entry by line number
59
- */
60
- getEntry(lineNumber) {
61
- return this.entries.find(entry => entry.lineNumber === lineNumber);
62
- }
63
- /**
64
- * Get history entry by command prefix
65
- */
66
- getEntryByPrefix(prefix) {
67
- return this.entries
68
- .slice()
69
- .reverse()
70
- .find(entry => entry.command.startsWith(prefix));
71
- }
72
- /**
73
- * Get all history entries
74
- */
75
- getAllEntries() {
76
- return [...this.entries];
77
- }
78
- /**
79
- * Search history for commands matching pattern
80
- */
81
- searchHistory(pattern) {
82
- const regex = new RegExp(pattern, 'i');
83
- return this.entries.filter(entry => regex.test(entry.command));
84
- }
85
- /**
86
- * Get previous command in history
87
- */
88
- getPreviousCommand() {
89
- if (this.currentIndex > 0) {
90
- this.currentIndex--;
91
- return this.entries[this.currentIndex].command;
92
- }
93
- return null;
94
- }
95
- /**
96
- * Get next command in history
97
- */
98
- getNextCommand() {
99
- if (this.currentIndex < this.entries.length - 1) {
100
- this.currentIndex++;
101
- return this.entries[this.currentIndex].command;
102
- }
103
- return null;
104
- }
105
- /**
106
- * Reset history navigation index
107
- */
108
- resetIndex() {
109
- this.currentIndex = this.entries.length - 1;
110
- }
111
- /**
112
- * Expand history references like !! !n !string
113
- */
114
- expandHistory(command) {
115
- let result = command;
116
- // Handle !! (last command)
117
- result = result.replace(/!!/g, () => {
118
- const lastEntry = this.entries[this.entries.length - 1];
119
- return lastEntry ? lastEntry.command : '!!';
120
- });
121
- // Handle !n (command number n)
122
- result = result.replace(/!(\d+)/g, (match, numStr) => {
123
- const num = parseInt(numStr, 10);
124
- const entry = this.getEntry(num);
125
- return entry ? entry.command : match;
126
- });
127
- // Handle !string (last command starting with string)
128
- result = result.replace(/!([a-zA-Z0-9_]+)/g, (match, prefix) => {
129
- const entry = this.getEntryByPrefix(prefix);
130
- return entry ? entry.command : match;
131
- });
132
- // Handle ^old^new (quick substitution)
133
- result = result.replace(/\^([^^]+)\^([^^]*)/g, (match, old, replacement) => {
134
- const lastEntry = this.entries[this.entries.length - 1];
135
- if (lastEntry) {
136
- return lastEntry.command.replace(new RegExp(old, 'g'), replacement);
137
- }
138
- return match;
139
- });
140
- return result;
141
- }
142
- /**
143
- * Clear history
144
- */
145
- clearHistory() {
146
- this.entries = [];
147
- this.currentIndex = -1;
148
- this.saveHistory();
149
- }
150
- /**
151
- * Get history statistics
152
- */
153
- getStats() {
154
- const unique = new Set(this.entries.map(e => e.command)).size;
155
- const oldest = this.entries.length > 0 ? new Date(this.entries[0].timestamp) : null;
156
- const newest = this.entries.length > 0 ? new Date(this.entries[this.entries.length - 1].timestamp) : null;
157
- return {
158
- total: this.entries.length,
159
- unique,
160
- oldest,
161
- newest,
162
- };
163
- }
164
- /**
165
- * Enable/disable history
166
- */
167
- setEnabled(enabled) {
168
- this.isEnabled = enabled;
169
- }
170
- /**
171
- * Update configuration
172
- */
173
- updateConfig(newConfig) {
174
- this.config = { ...this.config, ...newConfig };
175
- }
176
- /**
177
- * Load history from file
178
- */
179
- loadHistory() {
180
- try {
181
- if (fs.existsSync(this.config.filePath)) {
182
- const content = fs.readFileSync(this.config.filePath, 'utf8');
183
- const lines = content.split('\n').filter(line => line.trim());
184
- this.entries = lines.map((line, index) => {
185
- // Parse history line format: timestamp:command
186
- const colonIndex = line.indexOf(':');
187
- if (colonIndex > 0) {
188
- const timestamp = parseInt(line.substring(0, colonIndex), 10);
189
- const command = line.substring(colonIndex + 1);
190
- return {
191
- lineNumber: index + 1,
192
- command,
193
- timestamp: isNaN(timestamp) ? Date.now() : timestamp,
194
- };
195
- }
196
- else {
197
- return {
198
- lineNumber: index + 1,
199
- command: line,
200
- timestamp: Date.now(),
201
- };
202
- }
203
- });
204
- }
205
- }
206
- catch (_error) {
207
- // If loading fails, start with empty history
208
- this.entries = [];
209
- }
210
- }
211
- /**
212
- * Save history to file
213
- */
214
- saveHistory() {
215
- try {
216
- const content = this.entries
217
- .map(entry => `${entry.timestamp}:${entry.command}`)
218
- .join('\n');
219
- // Ensure directory exists
220
- const dir = path.dirname(this.config.filePath);
221
- if (!fs.existsSync(dir)) {
222
- fs.mkdirSync(dir, { recursive: true });
223
- }
224
- fs.writeFileSync(this.config.filePath, content, 'utf8');
225
- }
226
- catch (_error) {
227
- // Silently fail if we can't save history
228
- }
229
- }
230
- /**
231
- * Remove duplicate command from history
232
- */
233
- removeDuplicateCommand(command) {
234
- const trimmedCommand = command.trim();
235
- this.entries = this.entries.filter(entry => entry.command !== trimmedCommand);
236
- }
237
- /**
238
- * Renumber entries after trimming
239
- */
240
- renumberEntries() {
241
- this.entries.forEach((entry, index) => {
242
- entry.lineNumber = index + 1;
243
- });
244
- }
245
- }
246
- export default HistorySystem;
@@ -1,436 +0,0 @@
1
- /**
2
- * Job Management System for LSH Shell
3
- * Supports CRUD operations on shell jobs and system processes
4
- *
5
- * REFACTORED: Now extends BaseJobManager to eliminate duplication
6
- */
7
- import { spawn, exec } from 'child_process';
8
- import { promisify } from 'util';
9
- import * as fs from 'fs';
10
- import { BaseJobManager, } from './base-job-manager.js';
11
- import MemoryJobStorage from './job-storage-memory.js';
12
- const execAsync = promisify(exec);
13
- export class JobManager extends BaseJobManager {
14
- nextJobId = 1;
15
- persistenceFile;
16
- schedulerInterval;
17
- initPromise;
18
- constructor(persistenceFile = '/tmp/lsh-jobs.json') {
19
- super(new MemoryJobStorage(), 'JobManager');
20
- this.persistenceFile = persistenceFile;
21
- this.initPromise = this.loadPersistedJobs();
22
- this.startScheduler();
23
- this.setupCleanupHandlers();
24
- }
25
- /**
26
- * Wait for initialization to complete
27
- */
28
- async ready() {
29
- await this.initPromise;
30
- }
31
- /**
32
- * Create a job and persist to filesystem
33
- */
34
- async createJob(spec) {
35
- const job = await super.createJob(spec);
36
- await this.persistJobs();
37
- return job;
38
- }
39
- /**
40
- * Update a job and persist to filesystem
41
- */
42
- async updateJob(jobId, updates) {
43
- const job = await super.updateJob(jobId, updates);
44
- await this.persistJobs();
45
- return job;
46
- }
47
- /**
48
- * Remove a job and persist to filesystem
49
- */
50
- async removeJob(jobId, force = false) {
51
- const result = await super.removeJob(jobId, force);
52
- if (result) {
53
- await this.persistJobs();
54
- }
55
- return result;
56
- }
57
- /**
58
- * Update job status and persist to filesystem
59
- */
60
- async updateJobStatus(jobId, status, additionalUpdates) {
61
- const job = await super.updateJobStatus(jobId, status, additionalUpdates);
62
- await this.persistJobs();
63
- return job;
64
- }
65
- /**
66
- * Start a job (execute it as a process)
67
- */
68
- async startJob(jobId) {
69
- const baseJob = await this.getJob(jobId);
70
- if (!baseJob) {
71
- throw new Error(`Job ${jobId} not found`);
72
- }
73
- const job = baseJob;
74
- if (job.status === 'running') {
75
- throw new Error(`Job ${jobId} is already running`);
76
- }
77
- try {
78
- // Spawn the process
79
- if (job.type === 'shell') {
80
- job.process = spawn('sh', ['-c', job.command], {
81
- cwd: job.cwd,
82
- env: job.env,
83
- stdio: ['pipe', 'pipe', 'pipe'],
84
- });
85
- }
86
- else {
87
- const [cmd, ...args] = job.command.split(' ');
88
- job.process = spawn(cmd, args.concat(job.args || []), {
89
- cwd: job.cwd,
90
- env: job.env,
91
- stdio: ['pipe', 'pipe', 'pipe'],
92
- });
93
- }
94
- job.pid = job.process.pid;
95
- // Handle output
96
- job.process.stdout?.on('data', (data) => {
97
- job.stdout = (job.stdout || '') + data.toString();
98
- if (job.logFile) {
99
- fs.appendFileSync(job.logFile, data);
100
- }
101
- this.emit('jobOutput', job.id, 'stdout', data.toString());
102
- });
103
- job.process.stderr?.on('data', (data) => {
104
- job.stderr = (job.stderr || '') + data.toString();
105
- if (job.logFile) {
106
- fs.appendFileSync(job.logFile, data);
107
- }
108
- this.emit('jobOutput', job.id, 'stderr', data.toString());
109
- });
110
- // Handle completion
111
- job.process.on('exit', async (code, signal) => {
112
- // Check if job still exists (might have been removed during cleanup)
113
- const existingJob = await this.getJob(job.id);
114
- if (!existingJob) {
115
- this.logger.debug(`Job ${job.id} already removed, skipping status update`);
116
- return;
117
- }
118
- const status = code === 0 ? 'completed' : (signal === 'SIGKILL' ? 'killed' : 'failed');
119
- await this.updateJobStatus(job.id, status, {
120
- completedAt: new Date(),
121
- exitCode: code || undefined,
122
- });
123
- this.emit('jobCompleted', job, code, signal);
124
- });
125
- // Set timeout if specified
126
- if (job.timeout) {
127
- job.timer = setTimeout(() => {
128
- this.stopJob(job.id, 'SIGKILL');
129
- }, job.timeout);
130
- }
131
- // Update status to running
132
- const updatedJob = await this.updateJobStatus(job.id, 'running', {
133
- startedAt: new Date(),
134
- pid: job.pid,
135
- });
136
- return updatedJob;
137
- }
138
- catch (error) {
139
- const err = error;
140
- await this.updateJobStatus(job.id, 'failed', {
141
- completedAt: new Date(),
142
- stderr: err.message,
143
- });
144
- this.emit('jobFailed', job, error);
145
- throw error;
146
- }
147
- }
148
- /**
149
- * Stop a running job
150
- */
151
- async stopJob(jobId, signal = 'SIGTERM') {
152
- const baseJob = await this.getJob(jobId);
153
- if (!baseJob) {
154
- throw new Error(`Job ${jobId} not found`);
155
- }
156
- const job = baseJob;
157
- if (job.status !== 'running') {
158
- throw new Error(`Job ${jobId} is not running`);
159
- }
160
- if (!job.process || !job.pid) {
161
- throw new Error(`Job ${jobId} has no associated process`);
162
- }
163
- // Clear timeout if exists
164
- if (job.timer) {
165
- clearTimeout(job.timer);
166
- job.timer = undefined;
167
- }
168
- // Kill the process
169
- try {
170
- job.process.kill(signal);
171
- }
172
- catch (error) {
173
- this.logger.error(`Failed to kill job ${jobId}`, error);
174
- }
175
- // Update status
176
- const updatedJob = await this.updateJobStatus(jobId, 'stopped', {
177
- completedAt: new Date(),
178
- });
179
- return updatedJob;
180
- }
181
- /**
182
- * Create and immediately start a job
183
- */
184
- async runJob(spec) {
185
- const job = await this.createJob(spec);
186
- return await this.startJob(job.id);
187
- }
188
- /**
189
- * Pause a job (stop it but keep for later resumption)
190
- */
191
- async pauseJob(jobId) {
192
- await this.stopJob(jobId, 'SIGSTOP');
193
- return await this.updateJobStatus(jobId, 'paused');
194
- }
195
- /**
196
- * Resume a paused job
197
- */
198
- async resumeJob(jobId) {
199
- const baseJob = await this.getJob(jobId);
200
- if (!baseJob) {
201
- throw new Error(`Job ${jobId} not found`);
202
- }
203
- const job = baseJob;
204
- if (job.status !== 'paused') {
205
- throw new Error(`Job ${jobId} is not paused`);
206
- }
207
- if (!job.process || !job.pid) {
208
- throw new Error(`Job ${jobId} has no associated process`);
209
- }
210
- // Send SIGCONT to resume
211
- try {
212
- job.process.kill('SIGCONT');
213
- return await this.updateJobStatus(jobId, 'running');
214
- }
215
- catch (error) {
216
- throw new Error(`Failed to resume job ${jobId}: ${error}`);
217
- }
218
- }
219
- /**
220
- * Kill a job forcefully
221
- */
222
- async killJob(jobId, signal = 'SIGKILL') {
223
- return await this.stopJob(jobId, signal);
224
- }
225
- /**
226
- * Monitor a job's resource usage
227
- */
228
- async monitorJob(jobId) {
229
- const baseJob = await this.getJob(jobId);
230
- if (!baseJob) {
231
- throw new Error(`Job ${jobId} not found`);
232
- }
233
- const job = baseJob;
234
- if (!job.pid) {
235
- throw new Error(`Job ${jobId} is not running`);
236
- }
237
- try {
238
- const { stdout } = await execAsync(`ps -p ${job.pid} -o pid,ppid,pcpu,pmem,etime,state`);
239
- const lines = stdout.split('\n');
240
- if (lines.length < 2) {
241
- return null; // Process not found
242
- }
243
- const parts = lines[1].trim().split(/\s+/);
244
- const monitoring = {
245
- pid: parseInt(parts[0]),
246
- ppid: parseInt(parts[1]),
247
- cpu: parseFloat(parts[2]),
248
- memory: parseFloat(parts[3]),
249
- elapsed: parts[4],
250
- state: parts[5],
251
- timestamp: new Date(),
252
- };
253
- // Update job with current resource usage
254
- job.cpuUsage = monitoring.cpu;
255
- job.memoryUsage = monitoring.memory;
256
- this.emit('jobMonitoring', job, monitoring);
257
- return monitoring;
258
- }
259
- catch (_error) {
260
- return null; // Process likely terminated
261
- }
262
- }
263
- /**
264
- * Get system processes
265
- */
266
- async getSystemProcesses() {
267
- try {
268
- const { stdout } = await execAsync('ps -eo pid,ppid,user,pcpu,pmem,lstart,comm,args');
269
- const lines = stdout.split('\n').slice(1); // Skip header
270
- return lines
271
- .filter(line => line.trim())
272
- .map(line => {
273
- const parts = line.trim().split(/\s+/);
274
- return {
275
- pid: parseInt(parts[0]),
276
- ppid: parseInt(parts[1]),
277
- user: parts[2],
278
- cpu: parseFloat(parts[3]),
279
- memory: parseFloat(parts[4]),
280
- startTime: new Date(parts.slice(5, 9).join(' ')),
281
- name: parts[9],
282
- command: parts.slice(10).join(' ') || parts[9],
283
- status: 'running'
284
- };
285
- });
286
- }
287
- catch (error) {
288
- this.logger.error('Failed to get system processes', error);
289
- return [];
290
- }
291
- }
292
- /**
293
- * Get job statistics
294
- */
295
- getJobStats() {
296
- const jobs = Array.from(this.jobs.values());
297
- const stats = {
298
- total: jobs.length,
299
- byStatus: {},
300
- byType: {},
301
- running: jobs.filter(j => j.status === 'running').length,
302
- completed: jobs.filter(j => j.status === 'completed').length,
303
- failed: jobs.filter(j => j.status === 'failed').length,
304
- };
305
- jobs.forEach(job => {
306
- stats.byStatus[job.status] = (stats.byStatus[job.status] || 0) + 1;
307
- stats.byType[job.type] = (stats.byType[job.type] || 0) + 1;
308
- });
309
- return stats;
310
- }
311
- /**
312
- * Clean up old jobs
313
- */
314
- async cleanupJobs(olderThanHours = 24) {
315
- const cutoff = new Date(Date.now() - olderThanHours * 60 * 60 * 1000);
316
- const jobs = await this.listJobs();
317
- let cleaned = 0;
318
- for (const job of jobs) {
319
- if (job.status === 'completed' || job.status === 'failed') {
320
- if (job.completedAt && job.completedAt < cutoff) {
321
- await this.removeJob(job.id, true);
322
- cleaned++;
323
- }
324
- }
325
- }
326
- this.logger.info(`Cleaned up ${cleaned} old jobs`);
327
- return cleaned;
328
- }
329
- // ================================
330
- // PRIVATE: Persistence & Scheduling
331
- // ================================
332
- async loadPersistedJobs() {
333
- try {
334
- if (fs.existsSync(this.persistenceFile)) {
335
- const data = fs.readFileSync(this.persistenceFile, 'utf8');
336
- // Handle empty file
337
- if (!data || data.trim() === '') {
338
- this.logger.info('Persistence file is empty, starting fresh');
339
- return;
340
- }
341
- const persistedJobs = JSON.parse(data);
342
- for (const job of persistedJobs) {
343
- // Convert date strings back to Date objects
344
- job.createdAt = new Date(job.createdAt);
345
- if (job.startedAt)
346
- job.startedAt = new Date(job.startedAt);
347
- if (job.completedAt)
348
- job.completedAt = new Date(job.completedAt);
349
- // Don't restore running processes - mark them as stopped
350
- if (job.status === 'running') {
351
- job.status = 'stopped';
352
- }
353
- await this.storage.save(job);
354
- this.jobs.set(job.id, job);
355
- }
356
- this.logger.info(`Loaded ${persistedJobs.length} persisted jobs`);
357
- }
358
- }
359
- catch (error) {
360
- this.logger.error('Failed to load persisted jobs', error);
361
- }
362
- }
363
- async persistJobs() {
364
- try {
365
- const jobs = Array.from(this.jobs.values()).map(job => {
366
- const { process: _process, timer: _timer, ...serializable } = job;
367
- return serializable;
368
- });
369
- // Write with secure permissions (mode 0o600 = rw-------)
370
- fs.writeFileSync(this.persistenceFile, JSON.stringify(jobs, null, 2), { mode: 0o600 });
371
- }
372
- catch (error) {
373
- this.logger.error('Failed to persist jobs', error);
374
- }
375
- }
376
- startScheduler() {
377
- // Check for scheduled jobs every minute
378
- this.schedulerInterval = setInterval(() => {
379
- this.checkScheduledJobs();
380
- }, 60000);
381
- // Run immediately on startup
382
- this.checkScheduledJobs();
383
- }
384
- async checkScheduledJobs() {
385
- const jobs = await this.listJobs({ status: 'created' });
386
- const now = new Date();
387
- for (const job of jobs) {
388
- if (job.schedule?.nextRun && job.schedule.nextRun <= now) {
389
- this.logger.info(`Starting scheduled job: ${job.id}`);
390
- try {
391
- await this.startJob(job.id);
392
- // Calculate next run time
393
- if (job.schedule.interval) {
394
- job.schedule.nextRun = new Date(now.getTime() + job.schedule.interval);
395
- await this.updateJob(job.id, { schedule: job.schedule });
396
- }
397
- }
398
- catch (error) {
399
- this.logger.error(`Failed to start scheduled job ${job.id}`, error);
400
- }
401
- }
402
- }
403
- }
404
- setupCleanupHandlers() {
405
- const cleanup = async () => {
406
- this.logger.info('JobManager shutting down...');
407
- if (this.schedulerInterval) {
408
- clearInterval(this.schedulerInterval);
409
- }
410
- // Stop all running jobs
411
- const jobs = await this.listJobs({ status: 'running' });
412
- for (const job of jobs) {
413
- try {
414
- await this.stopJob(job.id);
415
- }
416
- catch (error) {
417
- this.logger.error(`Failed to stop job ${job.id}`, error);
418
- }
419
- }
420
- await this.persistJobs();
421
- await this.cleanup();
422
- };
423
- process.on('SIGTERM', cleanup);
424
- process.on('SIGINT', cleanup);
425
- }
426
- /**
427
- * Override cleanup to include scheduler
428
- */
429
- async cleanup() {
430
- if (this.schedulerInterval) {
431
- clearInterval(this.schedulerInterval);
432
- }
433
- await super.cleanup();
434
- }
435
- }
436
- export default JobManager;