lsh-framework 0.6.0 → 0.7.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 +179 -41
- 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 +19 -15
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,36 @@ 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(' lib secrets sync Check sync status & get recommendations');
|
|
76
|
+
console.log(' lib secrets push Upload .env to encrypted cloud storage');
|
|
77
|
+
console.log(' lib secrets pull Download .env from cloud storage');
|
|
78
|
+
console.log(' lib secrets list List all stored environments');
|
|
79
|
+
console.log(' lib secrets show View secrets (masked)');
|
|
80
|
+
console.log(' lib secrets key Generate encryption key');
|
|
81
|
+
console.log(' lib 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 lib secrets key # Generate encryption key');
|
|
90
|
+
console.log(' lsh lib secrets push --env dev # Push your secrets');
|
|
91
|
+
console.log(' lsh lib 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(' self Self-management commands');
|
|
97
|
+
console.log(' -i, --interactive Start interactive shell');
|
|
98
|
+
console.log(' --help Show all options');
|
|
99
|
+
console.log('');
|
|
100
|
+
console.log('📖 Documentation: https://github.com/gwicho38/lsh');
|
|
69
101
|
}
|
|
70
102
|
}
|
|
71
103
|
catch (error) {
|
|
@@ -130,26 +162,122 @@ program
|
|
|
130
162
|
.action(() => {
|
|
131
163
|
showDetailedHelp();
|
|
132
164
|
});
|
|
165
|
+
/**
|
|
166
|
+
* Calculate string similarity (Levenshtein distance)
|
|
167
|
+
*/
|
|
168
|
+
function similarity(s1, s2) {
|
|
169
|
+
const longer = s1.length > s2.length ? s1 : s2;
|
|
170
|
+
const shorter = s1.length > s2.length ? s2 : s1;
|
|
171
|
+
if (longer.length === 0)
|
|
172
|
+
return 1.0;
|
|
173
|
+
const editDistance = levenshtein(longer, shorter);
|
|
174
|
+
return (longer.length - editDistance) / longer.length;
|
|
175
|
+
}
|
|
176
|
+
function levenshtein(s1, s2) {
|
|
177
|
+
const costs = [];
|
|
178
|
+
for (let i = 0; i <= s1.length; i++) {
|
|
179
|
+
let lastValue = i;
|
|
180
|
+
for (let j = 0; j <= s2.length; j++) {
|
|
181
|
+
if (i === 0) {
|
|
182
|
+
costs[j] = j;
|
|
183
|
+
}
|
|
184
|
+
else if (j > 0) {
|
|
185
|
+
let newValue = costs[j - 1];
|
|
186
|
+
if (s1.charAt(i - 1) !== s2.charAt(j - 1)) {
|
|
187
|
+
newValue = Math.min(Math.min(newValue, lastValue), costs[j]) + 1;
|
|
188
|
+
}
|
|
189
|
+
costs[j - 1] = lastValue;
|
|
190
|
+
lastValue = newValue;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (i > 0)
|
|
194
|
+
costs[s2.length] = lastValue;
|
|
195
|
+
}
|
|
196
|
+
return costs[s2.length];
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Find similar commands for suggestions
|
|
200
|
+
*/
|
|
201
|
+
function findSimilarCommands(input, validCommands) {
|
|
202
|
+
const similarities = validCommands
|
|
203
|
+
.map(cmd => ({ cmd, score: similarity(input, cmd) }))
|
|
204
|
+
.filter(item => item.score > 0.5) // Only suggest if similarity > 50%
|
|
205
|
+
.sort((a, b) => b.score - a.score)
|
|
206
|
+
.slice(0, 3); // Top 3 suggestions
|
|
207
|
+
return similarities.map(item => item.cmd);
|
|
208
|
+
}
|
|
133
209
|
// Register async command modules
|
|
134
210
|
(async () => {
|
|
135
211
|
// REPL interactive shell
|
|
136
212
|
await init_ishell(program);
|
|
137
|
-
// Library commands
|
|
138
|
-
await init_lib(program);
|
|
139
|
-
//
|
|
140
|
-
await init_supabase(
|
|
141
|
-
|
|
142
|
-
await
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
213
|
+
// Library commands (parent for service commands)
|
|
214
|
+
const libCommand = await init_lib(program);
|
|
215
|
+
// Nest service commands under lib
|
|
216
|
+
await init_supabase(libCommand);
|
|
217
|
+
await init_daemon(libCommand);
|
|
218
|
+
await init_cron(libCommand);
|
|
219
|
+
await init_secrets(libCommand);
|
|
220
|
+
registerApiCommands(libCommand);
|
|
221
|
+
// Self-management commands with nested utilities
|
|
222
|
+
registerZshImportCommands(selfCommand);
|
|
223
|
+
registerThemeCommands(selfCommand);
|
|
224
|
+
// Pre-parse check for unknown commands
|
|
225
|
+
const args = process.argv.slice(2);
|
|
226
|
+
if (args.length > 0) {
|
|
227
|
+
const firstArg = args[0];
|
|
228
|
+
const validCommands = program.commands.map(cmd => cmd.name());
|
|
229
|
+
const validOptions = ['-i', '--interactive', '-c', '--command', '-s', '--script',
|
|
230
|
+
'--rc', '--zsh-compat', '--source-zshrc', '--package-manager',
|
|
231
|
+
'-v', '--verbose', '-d', '--debug', '-h', '--help', '-V', '--version'];
|
|
232
|
+
// Check if first argument looks like a command but isn't valid
|
|
233
|
+
if (!firstArg.startsWith('-') &&
|
|
234
|
+
!validCommands.includes(firstArg) &&
|
|
235
|
+
!validOptions.some(opt => args.includes(opt))) {
|
|
236
|
+
const suggestions = findSimilarCommands(firstArg, validCommands);
|
|
237
|
+
console.error(`error: unknown command '${firstArg}'`);
|
|
238
|
+
if (suggestions.length > 0) {
|
|
239
|
+
console.error(`\nDid you mean one of these?`);
|
|
240
|
+
suggestions.forEach(cmd => console.error(` ${cmd}`));
|
|
241
|
+
}
|
|
242
|
+
console.error(`\nRun 'lsh --help' to see available commands.`);
|
|
243
|
+
process.exit(1);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// Configure custom error output for better suggestions
|
|
247
|
+
program.configureOutput({
|
|
248
|
+
writeErr: (str) => {
|
|
249
|
+
// Intercept error messages to add suggestions
|
|
250
|
+
if (str.includes('error: unknown command')) {
|
|
251
|
+
const match = str.match(/unknown command '([^']+)'/);
|
|
252
|
+
if (match) {
|
|
253
|
+
const unknownCommand = match[1];
|
|
254
|
+
const validCommands = program.commands.map(cmd => cmd.name());
|
|
255
|
+
const suggestions = findSimilarCommands(unknownCommand, validCommands);
|
|
256
|
+
process.stderr.write(str);
|
|
257
|
+
if (suggestions.length > 0) {
|
|
258
|
+
process.stderr.write(`\nDid you mean one of these?\n`);
|
|
259
|
+
suggestions.forEach(cmd => process.stderr.write(` ${cmd}\n`));
|
|
260
|
+
}
|
|
261
|
+
process.stderr.write(`\nRun 'lsh --help' to see available commands.\n`);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
process.stderr.write(str);
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
// Add custom error handler for unknown commands
|
|
269
|
+
program.on('command:*', (operands) => {
|
|
270
|
+
const unknownCommand = operands[0];
|
|
271
|
+
const validCommands = program.commands.map(cmd => cmd.name());
|
|
272
|
+
const suggestions = findSimilarCommands(unknownCommand, validCommands);
|
|
273
|
+
console.error(`error: unknown command '${unknownCommand}'`);
|
|
274
|
+
if (suggestions.length > 0) {
|
|
275
|
+
console.error(`\nDid you mean one of these?`);
|
|
276
|
+
suggestions.forEach(cmd => console.error(` ${cmd}`));
|
|
277
|
+
}
|
|
278
|
+
console.error(`\nRun 'lsh --help' to see available commands.`);
|
|
279
|
+
process.exit(1);
|
|
280
|
+
});
|
|
153
281
|
// Parse command line arguments after all commands are registered
|
|
154
282
|
program.parse(process.argv);
|
|
155
283
|
})();
|
|
@@ -446,21 +574,27 @@ function showDetailedHelp() {
|
|
|
446
574
|
console.log('');
|
|
447
575
|
console.log('Subcommands:');
|
|
448
576
|
console.log(' repl JavaScript REPL interactive shell');
|
|
449
|
-
console.log(' lib Library commands');
|
|
450
|
-
console.log(' supabase Supabase database management');
|
|
451
577
|
console.log(' script <file> Execute shell script');
|
|
452
578
|
console.log(' config Manage configuration');
|
|
453
579
|
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
580
|
console.log(' help Show detailed help');
|
|
463
581
|
console.log('');
|
|
582
|
+
console.log('Self-Management (lsh self <command>):');
|
|
583
|
+
console.log(' self update Update to latest version');
|
|
584
|
+
console.log(' self version Show version information');
|
|
585
|
+
console.log(' self uninstall Uninstall LSH from system');
|
|
586
|
+
console.log(' self theme Manage themes (import Oh-My-Zsh themes)');
|
|
587
|
+
console.log(' self zsh-import Import ZSH configs (aliases, functions, exports)');
|
|
588
|
+
console.log('');
|
|
589
|
+
console.log('Library Commands (lsh lib <command>):');
|
|
590
|
+
console.log(' lib api API server management');
|
|
591
|
+
console.log(' lib supabase Supabase database management');
|
|
592
|
+
console.log(' lib daemon Daemon management');
|
|
593
|
+
console.log(' lib daemon job Job management');
|
|
594
|
+
console.log(' lib daemon db Database integration');
|
|
595
|
+
console.log(' lib cron Cron job management');
|
|
596
|
+
console.log(' lib secrets Secrets management');
|
|
597
|
+
console.log('');
|
|
464
598
|
console.log('Examples:');
|
|
465
599
|
console.log('');
|
|
466
600
|
console.log(' Shell Usage:');
|
|
@@ -476,18 +610,22 @@ function showDetailedHelp() {
|
|
|
476
610
|
console.log(' lsh self version # Show version');
|
|
477
611
|
console.log(' lsh self update # Update to latest');
|
|
478
612
|
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
|
|
613
|
+
console.log(' Self-Management:');
|
|
614
|
+
console.log(' lsh self update # Update to latest version');
|
|
615
|
+
console.log(' lsh self version # Show version');
|
|
616
|
+
console.log(' lsh self theme list # List available themes');
|
|
617
|
+
console.log(' lsh self theme import robbyrussell # Import Oh-My-Zsh theme');
|
|
618
|
+
console.log(' lsh self zsh-import aliases # Import ZSH aliases');
|
|
485
619
|
console.log('');
|
|
486
|
-
console.log('
|
|
487
|
-
console.log(' lsh
|
|
488
|
-
console.log(' lsh
|
|
489
|
-
console.log(' lsh
|
|
490
|
-
console.log(' lsh
|
|
620
|
+
console.log(' Library Services:');
|
|
621
|
+
console.log(' lsh lib daemon start # Start daemon');
|
|
622
|
+
console.log(' lsh lib daemon status # Check daemon status');
|
|
623
|
+
console.log(' lsh lib daemon job list # List all jobs');
|
|
624
|
+
console.log(' lsh lib cron list # List cron jobs');
|
|
625
|
+
console.log(' lsh lib secrets push # Push secrets to cloud');
|
|
626
|
+
console.log(' lsh lib secrets list # List environments');
|
|
627
|
+
console.log(' lsh lib api start # Start API server');
|
|
628
|
+
console.log(' lsh lib api key # Generate API key');
|
|
491
629
|
console.log('');
|
|
492
630
|
console.log('Features:');
|
|
493
631
|
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
|