lsh-framework 0.6.0 → 0.8.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 +493 -245
- package/dist/cli.js +191 -45
- package/dist/commands/self.js +59 -3
- package/dist/commands/theme.js +2 -1
- package/dist/lib/base-job-manager.js +2 -1
- package/dist/lib/job-manager.js +55 -7
- package/dist/lib/lshrc-init.js +3 -1
- package/dist/lib/secrets-manager.js +161 -11
- package/dist/services/lib/lib.js +3 -1
- package/dist/services/secrets/secrets.js +155 -4
- package/package.json +26 -22
package/dist/cli.js
CHANGED
|
@@ -36,8 +36,12 @@ function getVersion() {
|
|
|
36
36
|
const program = new Command();
|
|
37
37
|
program
|
|
38
38
|
.name('lsh')
|
|
39
|
-
.description('LSH -
|
|
40
|
-
.version(getVersion())
|
|
39
|
+
.description('LSH - Encrypted secrets manager with automatic rotation and team sync')
|
|
40
|
+
.version(getVersion())
|
|
41
|
+
.showSuggestionAfterError(true)
|
|
42
|
+
.showHelpAfterError('(add --help for additional information)')
|
|
43
|
+
.allowUnknownOption(false)
|
|
44
|
+
.enablePositionalOptions();
|
|
41
45
|
// Options for main command
|
|
42
46
|
program
|
|
43
47
|
.option('-i, --interactive', 'Start interactive shell')
|
|
@@ -64,8 +68,39 @@ program
|
|
|
64
68
|
await startInteractiveShell(options);
|
|
65
69
|
}
|
|
66
70
|
else {
|
|
67
|
-
// No arguments - show help
|
|
68
|
-
|
|
71
|
+
// No arguments - show secrets-focused help
|
|
72
|
+
console.log('LSH - Encrypted Secrets Manager with Automatic Rotation');
|
|
73
|
+
console.log('');
|
|
74
|
+
console.log('🔐 Secrets Management (Primary Features):');
|
|
75
|
+
console.log(' secrets sync Check sync status & get recommendations');
|
|
76
|
+
console.log(' secrets push Upload .env to encrypted cloud storage');
|
|
77
|
+
console.log(' secrets pull Download .env from cloud storage');
|
|
78
|
+
console.log(' secrets list List all stored environments');
|
|
79
|
+
console.log(' secrets show View secrets (masked)');
|
|
80
|
+
console.log(' secrets key Generate encryption key');
|
|
81
|
+
console.log(' secrets create Create new .env file');
|
|
82
|
+
console.log('');
|
|
83
|
+
console.log('🔄 Automation (Schedule secret rotation):');
|
|
84
|
+
console.log(' lib cron add Schedule automatic tasks');
|
|
85
|
+
console.log(' lib cron list List scheduled jobs');
|
|
86
|
+
console.log(' lib daemon start Start persistent daemon');
|
|
87
|
+
console.log('');
|
|
88
|
+
console.log('🚀 Quick Start:');
|
|
89
|
+
console.log(' lsh secrets key # Generate encryption key');
|
|
90
|
+
console.log(' lsh secrets push --env dev # Push your secrets');
|
|
91
|
+
console.log(' lsh secrets pull --env dev # Pull on another machine');
|
|
92
|
+
console.log('');
|
|
93
|
+
console.log('📚 More Commands:');
|
|
94
|
+
console.log(' lib api API server management');
|
|
95
|
+
console.log(' lib supabase Supabase database management');
|
|
96
|
+
console.log(' lib daemon Daemon management');
|
|
97
|
+
console.log(' lib cron Cron job management');
|
|
98
|
+
console.log(' self Self-management commands');
|
|
99
|
+
console.log(' self zsh ZSH compatibility commands');
|
|
100
|
+
console.log(' -i, --interactive Start interactive shell');
|
|
101
|
+
console.log(' --help Show all options');
|
|
102
|
+
console.log('');
|
|
103
|
+
console.log('📖 Documentation: https://github.com/gwicho38/lsh');
|
|
69
104
|
}
|
|
70
105
|
}
|
|
71
106
|
catch (error) {
|
|
@@ -105,8 +140,10 @@ program
|
|
|
105
140
|
process.exit(1);
|
|
106
141
|
}
|
|
107
142
|
});
|
|
108
|
-
//
|
|
109
|
-
program
|
|
143
|
+
// Self-management commands
|
|
144
|
+
program.addCommand(selfCommand);
|
|
145
|
+
// ZSH compatibility commands (under self)
|
|
146
|
+
selfCommand
|
|
110
147
|
.command('zsh')
|
|
111
148
|
.description('ZSH compatibility commands')
|
|
112
149
|
.option('--migrate', 'Migrate ZSH configuration to LSH')
|
|
@@ -121,8 +158,6 @@ program
|
|
|
121
158
|
process.exit(1);
|
|
122
159
|
}
|
|
123
160
|
});
|
|
124
|
-
// Self-management commands
|
|
125
|
-
program.addCommand(selfCommand);
|
|
126
161
|
// Help subcommand
|
|
127
162
|
program
|
|
128
163
|
.command('help')
|
|
@@ -130,26 +165,123 @@ program
|
|
|
130
165
|
.action(() => {
|
|
131
166
|
showDetailedHelp();
|
|
132
167
|
});
|
|
168
|
+
/**
|
|
169
|
+
* Calculate string similarity (Levenshtein distance)
|
|
170
|
+
*/
|
|
171
|
+
function similarity(s1, s2) {
|
|
172
|
+
const longer = s1.length > s2.length ? s1 : s2;
|
|
173
|
+
const shorter = s1.length > s2.length ? s2 : s1;
|
|
174
|
+
if (longer.length === 0)
|
|
175
|
+
return 1.0;
|
|
176
|
+
const editDistance = levenshtein(longer, shorter);
|
|
177
|
+
return (longer.length - editDistance) / longer.length;
|
|
178
|
+
}
|
|
179
|
+
function levenshtein(s1, s2) {
|
|
180
|
+
const costs = [];
|
|
181
|
+
for (let i = 0; i <= s1.length; i++) {
|
|
182
|
+
let lastValue = i;
|
|
183
|
+
for (let j = 0; j <= s2.length; j++) {
|
|
184
|
+
if (i === 0) {
|
|
185
|
+
costs[j] = j;
|
|
186
|
+
}
|
|
187
|
+
else if (j > 0) {
|
|
188
|
+
let newValue = costs[j - 1];
|
|
189
|
+
if (s1.charAt(i - 1) !== s2.charAt(j - 1)) {
|
|
190
|
+
newValue = Math.min(Math.min(newValue, lastValue), costs[j]) + 1;
|
|
191
|
+
}
|
|
192
|
+
costs[j - 1] = lastValue;
|
|
193
|
+
lastValue = newValue;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (i > 0)
|
|
197
|
+
costs[s2.length] = lastValue;
|
|
198
|
+
}
|
|
199
|
+
return costs[s2.length];
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Find similar commands for suggestions
|
|
203
|
+
*/
|
|
204
|
+
function findSimilarCommands(input, validCommands) {
|
|
205
|
+
const similarities = validCommands
|
|
206
|
+
.map(cmd => ({ cmd, score: similarity(input, cmd) }))
|
|
207
|
+
.filter(item => item.score > 0.5) // Only suggest if similarity > 50%
|
|
208
|
+
.sort((a, b) => b.score - a.score)
|
|
209
|
+
.slice(0, 3); // Top 3 suggestions
|
|
210
|
+
return similarities.map(item => item.cmd);
|
|
211
|
+
}
|
|
133
212
|
// Register async command modules
|
|
134
213
|
(async () => {
|
|
135
214
|
// REPL interactive shell
|
|
136
215
|
await init_ishell(program);
|
|
137
|
-
// Library commands
|
|
138
|
-
await init_lib(program);
|
|
139
|
-
//
|
|
140
|
-
await init_supabase(
|
|
141
|
-
|
|
142
|
-
await
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
// Secrets management commands
|
|
216
|
+
// Library commands (parent for service commands)
|
|
217
|
+
const libCommand = await init_lib(program);
|
|
218
|
+
// Nest service commands under lib
|
|
219
|
+
await init_supabase(libCommand);
|
|
220
|
+
await init_daemon(libCommand);
|
|
221
|
+
await init_cron(libCommand);
|
|
222
|
+
registerApiCommands(libCommand);
|
|
223
|
+
// Secrets as top-level command
|
|
146
224
|
await init_secrets(program);
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
225
|
+
// Self-management commands with nested utilities
|
|
226
|
+
registerZshImportCommands(selfCommand);
|
|
227
|
+
registerThemeCommands(selfCommand);
|
|
228
|
+
// Pre-parse check for unknown commands
|
|
229
|
+
const args = process.argv.slice(2);
|
|
230
|
+
if (args.length > 0) {
|
|
231
|
+
const firstArg = args[0];
|
|
232
|
+
const validCommands = program.commands.map(cmd => cmd.name());
|
|
233
|
+
const validOptions = ['-i', '--interactive', '-c', '--command', '-s', '--script',
|
|
234
|
+
'--rc', '--zsh-compat', '--source-zshrc', '--package-manager',
|
|
235
|
+
'-v', '--verbose', '-d', '--debug', '-h', '--help', '-V', '--version'];
|
|
236
|
+
// Check if first argument looks like a command but isn't valid
|
|
237
|
+
if (!firstArg.startsWith('-') &&
|
|
238
|
+
!validCommands.includes(firstArg) &&
|
|
239
|
+
!validOptions.some(opt => args.includes(opt))) {
|
|
240
|
+
const suggestions = findSimilarCommands(firstArg, validCommands);
|
|
241
|
+
console.error(`error: unknown command '${firstArg}'`);
|
|
242
|
+
if (suggestions.length > 0) {
|
|
243
|
+
console.error(`\nDid you mean one of these?`);
|
|
244
|
+
suggestions.forEach(cmd => console.error(` ${cmd}`));
|
|
245
|
+
}
|
|
246
|
+
console.error(`\nRun 'lsh --help' to see available commands.`);
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// Configure custom error output for better suggestions
|
|
251
|
+
program.configureOutput({
|
|
252
|
+
writeErr: (str) => {
|
|
253
|
+
// Intercept error messages to add suggestions
|
|
254
|
+
if (str.includes('error: unknown command')) {
|
|
255
|
+
const match = str.match(/unknown command '([^']+)'/);
|
|
256
|
+
if (match) {
|
|
257
|
+
const unknownCommand = match[1];
|
|
258
|
+
const validCommands = program.commands.map(cmd => cmd.name());
|
|
259
|
+
const suggestions = findSimilarCommands(unknownCommand, validCommands);
|
|
260
|
+
process.stderr.write(str);
|
|
261
|
+
if (suggestions.length > 0) {
|
|
262
|
+
process.stderr.write(`\nDid you mean one of these?\n`);
|
|
263
|
+
suggestions.forEach(cmd => process.stderr.write(` ${cmd}\n`));
|
|
264
|
+
}
|
|
265
|
+
process.stderr.write(`\nRun 'lsh --help' to see available commands.\n`);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
process.stderr.write(str);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
// Add custom error handler for unknown commands
|
|
273
|
+
program.on('command:*', (operands) => {
|
|
274
|
+
const unknownCommand = operands[0];
|
|
275
|
+
const validCommands = program.commands.map(cmd => cmd.name());
|
|
276
|
+
const suggestions = findSimilarCommands(unknownCommand, validCommands);
|
|
277
|
+
console.error(`error: unknown command '${unknownCommand}'`);
|
|
278
|
+
if (suggestions.length > 0) {
|
|
279
|
+
console.error(`\nDid you mean one of these?`);
|
|
280
|
+
suggestions.forEach(cmd => console.error(` ${cmd}`));
|
|
281
|
+
}
|
|
282
|
+
console.error(`\nRun 'lsh --help' to see available commands.`);
|
|
283
|
+
process.exit(1);
|
|
284
|
+
});
|
|
153
285
|
// Parse command line arguments after all commands are registered
|
|
154
286
|
program.parse(process.argv);
|
|
155
287
|
})();
|
|
@@ -445,22 +577,28 @@ function showDetailedHelp() {
|
|
|
445
577
|
console.log(' -V, --version Show version');
|
|
446
578
|
console.log('');
|
|
447
579
|
console.log('Subcommands:');
|
|
580
|
+
console.log(' secrets Secrets management (primary feature)');
|
|
448
581
|
console.log(' repl JavaScript REPL interactive shell');
|
|
449
|
-
console.log(' lib Library commands');
|
|
450
|
-
console.log(' supabase Supabase database management');
|
|
451
582
|
console.log(' script <file> Execute shell script');
|
|
452
583
|
console.log(' config Manage configuration');
|
|
453
|
-
console.log(' zsh ZSH compatibility commands');
|
|
454
|
-
console.log(' zsh-import Import ZSH configs (aliases, functions, exports)');
|
|
455
|
-
console.log(' theme Manage themes (import Oh-My-Zsh themes)');
|
|
456
|
-
console.log(' self Self-management (update, version)');
|
|
457
|
-
console.log(' daemon Daemon management');
|
|
458
|
-
console.log(' daemon job Job management');
|
|
459
|
-
console.log(' daemon db Database integration');
|
|
460
|
-
console.log(' cron Cron job management');
|
|
461
|
-
console.log(' api API server management');
|
|
462
584
|
console.log(' help Show detailed help');
|
|
463
585
|
console.log('');
|
|
586
|
+
console.log('Self-Management (lsh self <command>):');
|
|
587
|
+
console.log(' self update Update to latest version');
|
|
588
|
+
console.log(' self version Show version information');
|
|
589
|
+
console.log(' self uninstall Uninstall LSH from system');
|
|
590
|
+
console.log(' self theme Manage themes (import Oh-My-Zsh themes)');
|
|
591
|
+
console.log(' self zsh ZSH compatibility commands');
|
|
592
|
+
console.log(' self zsh-import Import ZSH configs (aliases, functions, exports)');
|
|
593
|
+
console.log('');
|
|
594
|
+
console.log('Library Commands (lsh lib <command>):');
|
|
595
|
+
console.log(' lib api API server management');
|
|
596
|
+
console.log(' lib supabase Supabase database management');
|
|
597
|
+
console.log(' lib daemon Daemon management');
|
|
598
|
+
console.log(' lib daemon job Job management');
|
|
599
|
+
console.log(' lib daemon db Database integration');
|
|
600
|
+
console.log(' lib cron Cron job management');
|
|
601
|
+
console.log('');
|
|
464
602
|
console.log('Examples:');
|
|
465
603
|
console.log('');
|
|
466
604
|
console.log(' Shell Usage:');
|
|
@@ -476,18 +614,26 @@ function showDetailedHelp() {
|
|
|
476
614
|
console.log(' lsh self version # Show version');
|
|
477
615
|
console.log(' lsh self update # Update to latest');
|
|
478
616
|
console.log('');
|
|
479
|
-
console.log('
|
|
480
|
-
console.log(' lsh
|
|
481
|
-
console.log(' lsh
|
|
482
|
-
console.log(' lsh
|
|
483
|
-
console.log(' lsh
|
|
484
|
-
console.log(' lsh
|
|
617
|
+
console.log(' Self-Management:');
|
|
618
|
+
console.log(' lsh self update # Update to latest version');
|
|
619
|
+
console.log(' lsh self version # Show version');
|
|
620
|
+
console.log(' lsh self theme list # List available themes');
|
|
621
|
+
console.log(' lsh self theme import robbyrussell # Import Oh-My-Zsh theme');
|
|
622
|
+
console.log(' lsh self zsh-import aliases # Import ZSH aliases');
|
|
623
|
+
console.log('');
|
|
624
|
+
console.log(' Secrets Management:');
|
|
625
|
+
console.log(' lsh secrets sync # Check sync status');
|
|
626
|
+
console.log(' lsh secrets push # Push secrets to cloud');
|
|
627
|
+
console.log(' lsh secrets pull # Pull secrets from cloud');
|
|
628
|
+
console.log(' lsh secrets list # List environments');
|
|
485
629
|
console.log('');
|
|
486
|
-
console.log('
|
|
487
|
-
console.log(' lsh
|
|
488
|
-
console.log(' lsh
|
|
489
|
-
console.log(' lsh
|
|
490
|
-
console.log(' lsh
|
|
630
|
+
console.log(' Library Services:');
|
|
631
|
+
console.log(' lsh lib daemon start # Start daemon');
|
|
632
|
+
console.log(' lsh lib daemon status # Check daemon status');
|
|
633
|
+
console.log(' lsh lib daemon job list # List all jobs');
|
|
634
|
+
console.log(' lsh lib cron list # List cron jobs');
|
|
635
|
+
console.log(' lsh lib api start # Start API server');
|
|
636
|
+
console.log(' lsh lib api key # Generate API key');
|
|
491
637
|
console.log('');
|
|
492
638
|
console.log('Features:');
|
|
493
639
|
console.log(' ✅ POSIX Shell Compliance (85-95%)');
|
package/dist/commands/self.js
CHANGED
|
@@ -60,7 +60,7 @@ async function fetchLatestVersion() {
|
|
|
60
60
|
const options = {
|
|
61
61
|
hostname: 'registry.npmjs.org',
|
|
62
62
|
port: 443,
|
|
63
|
-
path: '/
|
|
63
|
+
path: '/lsh-framework',
|
|
64
64
|
method: 'GET',
|
|
65
65
|
headers: {
|
|
66
66
|
'User-Agent': 'lsh-cli',
|
|
@@ -239,7 +239,7 @@ selfCommand
|
|
|
239
239
|
// Install update
|
|
240
240
|
console.log(chalk.cyan(`📦 Installing lsh ${latestVersion}...`));
|
|
241
241
|
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
242
|
-
const updateProcess = spawn(npmCmd, ['install', '-g', '
|
|
242
|
+
const updateProcess = spawn(npmCmd, ['install', '-g', 'lsh-framework@latest'], {
|
|
243
243
|
stdio: 'inherit',
|
|
244
244
|
});
|
|
245
245
|
updateProcess.on('close', (code) => {
|
|
@@ -249,7 +249,7 @@ selfCommand
|
|
|
249
249
|
}
|
|
250
250
|
else {
|
|
251
251
|
console.log(chalk.red('✗ Update failed'));
|
|
252
|
-
console.log(chalk.yellow('ℹ Try running with sudo: sudo npm install -g
|
|
252
|
+
console.log(chalk.yellow('ℹ Try running with sudo: sudo npm install -g lsh-framework@latest'));
|
|
253
253
|
}
|
|
254
254
|
});
|
|
255
255
|
}
|
|
@@ -315,4 +315,60 @@ selfCommand
|
|
|
315
315
|
console.log();
|
|
316
316
|
console.log(chalk.dim('For more info, visit: https://github.com/gwicho38/lsh'));
|
|
317
317
|
});
|
|
318
|
+
/**
|
|
319
|
+
* Uninstall command - remove LSH from the system
|
|
320
|
+
*/
|
|
321
|
+
selfCommand
|
|
322
|
+
.command('uninstall')
|
|
323
|
+
.description('Uninstall LSH from your system')
|
|
324
|
+
.option('-y, --yes', 'Skip confirmation prompt')
|
|
325
|
+
.action(async (options) => {
|
|
326
|
+
try {
|
|
327
|
+
console.log(chalk.yellow('╔════════════════════════════════════╗'));
|
|
328
|
+
console.log(chalk.yellow('║ Uninstall LSH Framework ║'));
|
|
329
|
+
console.log(chalk.yellow('╚════════════════════════════════════╝'));
|
|
330
|
+
console.log();
|
|
331
|
+
// Ask for confirmation unless --yes flag is used
|
|
332
|
+
if (!options.yes) {
|
|
333
|
+
const readline = await import('readline');
|
|
334
|
+
const rl = readline.createInterface({
|
|
335
|
+
input: process.stdin,
|
|
336
|
+
output: process.stdout,
|
|
337
|
+
});
|
|
338
|
+
const answer = await new Promise((resolve) => {
|
|
339
|
+
rl.question(chalk.yellow('Are you sure you want to uninstall LSH? (y/N) '), (ans) => {
|
|
340
|
+
rl.close();
|
|
341
|
+
resolve(ans);
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
|
|
345
|
+
console.log(chalk.yellow('Uninstall cancelled'));
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
console.log(chalk.cyan('📦 Uninstalling lsh-framework...'));
|
|
350
|
+
console.log();
|
|
351
|
+
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
352
|
+
const uninstallProcess = spawn(npmCmd, ['uninstall', '-g', 'lsh-framework'], {
|
|
353
|
+
stdio: 'inherit',
|
|
354
|
+
});
|
|
355
|
+
uninstallProcess.on('close', (code) => {
|
|
356
|
+
if (code === 0) {
|
|
357
|
+
console.log();
|
|
358
|
+
console.log(chalk.green('✓ LSH has been uninstalled successfully'));
|
|
359
|
+
console.log();
|
|
360
|
+
console.log(chalk.dim('Thank you for using LSH!'));
|
|
361
|
+
console.log(chalk.dim('You can reinstall anytime with: npm install -g lsh-framework'));
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
console.log();
|
|
365
|
+
console.log(chalk.red('✗ Uninstall failed'));
|
|
366
|
+
console.log(chalk.yellow('ℹ Try running with sudo: sudo npm uninstall -g lsh-framework'));
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
catch (error) {
|
|
371
|
+
console.error(chalk.red('✗ Error during uninstall:'), error);
|
|
372
|
+
}
|
|
373
|
+
});
|
|
318
374
|
export default selfCommand;
|
package/dist/commands/theme.js
CHANGED
|
@@ -61,7 +61,8 @@ export function registerThemeCommands(program) {
|
|
|
61
61
|
console.log(chalk.dim(' lsh theme import <name> # For Oh-My-Zsh themes'));
|
|
62
62
|
console.log(chalk.dim(' lsh theme apply <name>'));
|
|
63
63
|
console.log('');
|
|
64
|
-
process.exit(0)
|
|
64
|
+
// Note: Removed process.exit(0) to allow proper Jest testing
|
|
65
|
+
// Commander will handle exit automatically
|
|
65
66
|
});
|
|
66
67
|
themeCommand
|
|
67
68
|
.command('import <name>')
|
|
@@ -203,7 +203,8 @@ export class BaseJobManager extends EventEmitter {
|
|
|
203
203
|
async removeJob(jobId, force = false) {
|
|
204
204
|
const job = await this.getJob(jobId);
|
|
205
205
|
if (!job) {
|
|
206
|
-
|
|
206
|
+
// Return false instead of throwing when job doesn't exist
|
|
207
|
+
return false;
|
|
207
208
|
}
|
|
208
209
|
// Check if job is running
|
|
209
210
|
if (job.status === 'running' && !force) {
|
package/dist/lib/job-manager.js
CHANGED
|
@@ -14,13 +14,54 @@ export class JobManager extends BaseJobManager {
|
|
|
14
14
|
nextJobId = 1;
|
|
15
15
|
persistenceFile;
|
|
16
16
|
schedulerInterval;
|
|
17
|
+
initPromise;
|
|
17
18
|
constructor(persistenceFile = '/tmp/lsh-jobs.json') {
|
|
18
19
|
super(new MemoryJobStorage(), 'JobManager');
|
|
19
20
|
this.persistenceFile = persistenceFile;
|
|
20
|
-
this.loadPersistedJobs();
|
|
21
|
+
this.initPromise = this.loadPersistedJobs();
|
|
21
22
|
this.startScheduler();
|
|
22
23
|
this.setupCleanupHandlers();
|
|
23
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
|
+
}
|
|
24
65
|
/**
|
|
25
66
|
* Start a job (execute it as a process)
|
|
26
67
|
*/
|
|
@@ -67,14 +108,19 @@ export class JobManager extends BaseJobManager {
|
|
|
67
108
|
this.emit('jobOutput', job.id, 'stderr', data.toString());
|
|
68
109
|
});
|
|
69
110
|
// Handle completion
|
|
70
|
-
job.process.on('exit', (code, signal) => {
|
|
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
|
+
}
|
|
71
118
|
const status = code === 0 ? 'completed' : (signal === 'SIGKILL' ? 'killed' : 'failed');
|
|
72
|
-
this.updateJobStatus(job.id, status, {
|
|
119
|
+
await this.updateJobStatus(job.id, status, {
|
|
73
120
|
completedAt: new Date(),
|
|
74
121
|
exitCode: code || undefined,
|
|
75
122
|
});
|
|
76
123
|
this.emit('jobCompleted', job, code, signal);
|
|
77
|
-
this.persistJobs();
|
|
78
124
|
});
|
|
79
125
|
// Set timeout if specified
|
|
80
126
|
if (job.timeout) {
|
|
@@ -87,7 +133,6 @@ export class JobManager extends BaseJobManager {
|
|
|
87
133
|
startedAt: new Date(),
|
|
88
134
|
pid: job.pid,
|
|
89
135
|
});
|
|
90
|
-
await this.persistJobs();
|
|
91
136
|
return updatedJob;
|
|
92
137
|
}
|
|
93
138
|
catch (error) {
|
|
@@ -96,7 +141,6 @@ export class JobManager extends BaseJobManager {
|
|
|
96
141
|
stderr: error.message,
|
|
97
142
|
});
|
|
98
143
|
this.emit('jobFailed', job, error);
|
|
99
|
-
await this.persistJobs();
|
|
100
144
|
throw error;
|
|
101
145
|
}
|
|
102
146
|
}
|
|
@@ -131,7 +175,6 @@ export class JobManager extends BaseJobManager {
|
|
|
131
175
|
const updatedJob = await this.updateJobStatus(jobId, 'stopped', {
|
|
132
176
|
completedAt: new Date(),
|
|
133
177
|
});
|
|
134
|
-
await this.persistJobs();
|
|
135
178
|
return updatedJob;
|
|
136
179
|
}
|
|
137
180
|
/**
|
|
@@ -289,6 +332,11 @@ export class JobManager extends BaseJobManager {
|
|
|
289
332
|
try {
|
|
290
333
|
if (fs.existsSync(this.persistenceFile)) {
|
|
291
334
|
const data = fs.readFileSync(this.persistenceFile, 'utf8');
|
|
335
|
+
// Handle empty file
|
|
336
|
+
if (!data || data.trim() === '') {
|
|
337
|
+
this.logger.info('Persistence file is empty, starting fresh');
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
292
340
|
const persistedJobs = JSON.parse(data);
|
|
293
341
|
for (const job of persistedJobs) {
|
|
294
342
|
// Convert date strings back to Date objects
|
package/dist/lib/lshrc-init.js
CHANGED
|
@@ -11,7 +11,9 @@ const __dirname = path.dirname(__filename);
|
|
|
11
11
|
export class LshrcManager {
|
|
12
12
|
lshrcPath;
|
|
13
13
|
constructor(lshrcPath) {
|
|
14
|
-
|
|
14
|
+
// Use process.env.HOME if set (for testability), fallback to os.homedir()
|
|
15
|
+
const homeDir = process.env.HOME || os.homedir();
|
|
16
|
+
this.lshrcPath = lshrcPath || path.join(homeDir, '.lshrc');
|
|
15
17
|
}
|
|
16
18
|
/**
|
|
17
19
|
* Initialize .lshrc if it doesn't exist
|