lsh-framework 0.8.2 → 0.8.3

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.
@@ -6,7 +6,7 @@ import * as fs from 'fs';
6
6
  import * as path from 'path';
7
7
  import * as crypto from 'crypto';
8
8
  import DatabasePersistence from './database-persistence.js';
9
- import { createLogger } from './logger.js';
9
+ import { createLogger, LogLevel } from './logger.js';
10
10
  import { getGitRepoInfo, hasEnvExample, ensureEnvInGitignore } from './git-utils.js';
11
11
  const logger = createLogger('SecretsManager');
12
12
  export class SecretsManager {
@@ -476,117 +476,161 @@ LSH_SECRETS_KEY=${this.encryptionKey}
476
476
  * This is the new enhanced sync that does everything automatically
477
477
  */
478
478
  async smartSync(envFilePath = '.env', environment = 'dev', autoExecute = true, loadMode = false) {
479
- // Use repo-aware environment if in git repo
480
- const effectiveEnv = this.getRepoAwareEnvironment(environment);
481
- const displayEnv = this.gitInfo?.repoName ? `${this.gitInfo.repoName}/${environment}` : environment;
482
- // In load mode, redirect output to stderr so stdout only has export commands
483
- const out = loadMode ? console.error : console.log;
484
- out(`\nšŸ” Smart sync for: ${displayEnv}\n`);
485
- // Show git repo context if detected
486
- if (this.gitInfo?.isGitRepo) {
487
- out('šŸ“ Git Repository:');
488
- out(` Repo: ${this.gitInfo.repoName || 'unknown'}`);
489
- if (this.gitInfo.currentBranch) {
490
- out(` Branch: ${this.gitInfo.currentBranch}`);
491
- }
492
- out();
493
- }
494
- // Step 1: Ensure encryption key exists
495
- if (!process.env.LSH_SECRETS_KEY) {
496
- logger.info('šŸ”‘ No encryption key found...');
497
- await this.ensureEncryptionKey();
498
- out();
499
- }
500
- // Step 2: Ensure .gitignore includes .env
501
- if (this.gitInfo?.isGitRepo) {
502
- ensureEnvInGitignore(process.cwd());
503
- }
504
- // Step 3: Check current status
505
- const status = await this.status(envFilePath, effectiveEnv);
506
- out('šŸ“Š Current Status:');
507
- out(` Encryption key: ${status.keySet ? 'āœ…' : 'āŒ'}`);
508
- out(` Local ${envFilePath}: ${status.localExists ? `āœ… (${status.localKeys} keys)` : 'āŒ'}`);
509
- out(` Cloud storage: ${status.cloudExists ? `āœ… (${status.cloudKeys} keys)` : 'āŒ'}`);
510
- if (status.cloudExists && status.keyMatches !== undefined) {
511
- out(` Key matches: ${status.keyMatches ? 'āœ…' : 'āŒ'}`);
512
- }
513
- out();
514
- // Step 4: Determine action and execute if auto mode
515
- let action = 'in-sync';
516
- if (status.cloudExists && status.keyMatches === false) {
517
- action = 'key-mismatch';
518
- out('āš ļø Encryption key mismatch!');
519
- out(' The local key does not match the cloud storage.');
520
- out(' Please use the original key or push new secrets with:');
521
- out(` lsh lib secrets push -f ${envFilePath} -e ${environment}`);
522
- out();
523
- return;
479
+ // In load mode, suppress all logger output to prevent zsh glob interpretation
480
+ // Save original level and restore at the end
481
+ const originalLogLevel = loadMode ? logger['config'].level : undefined;
482
+ if (loadMode) {
483
+ logger.setLevel(LogLevel.NONE);
524
484
  }
525
- if (!status.localExists && !status.cloudExists) {
526
- action = 'create-and-push';
527
- out('šŸ†• No secrets found locally or in cloud');
528
- out(' Creating new .env file...');
529
- if (autoExecute) {
530
- await this.createEnvFromExample(envFilePath);
531
- out(' Pushing to cloud...');
532
- await this.push(envFilePath, effectiveEnv);
485
+ try {
486
+ // Use repo-aware environment if in git repo
487
+ const effectiveEnv = this.getRepoAwareEnvironment(environment);
488
+ const displayEnv = this.gitInfo?.repoName ? `${this.gitInfo.repoName}/${environment}` : environment;
489
+ // In load mode, suppress all output except the final export commands
490
+ const out = loadMode ? () => { } : console.log;
491
+ out(`\nšŸ” Smart sync for: ${displayEnv}\n`);
492
+ // Show git repo context if detected
493
+ if (this.gitInfo?.isGitRepo) {
494
+ out('šŸ“ Git Repository:');
495
+ out(` Repo: ${this.gitInfo.repoName || 'unknown'}`);
496
+ if (this.gitInfo.currentBranch) {
497
+ out(` Branch: ${this.gitInfo.currentBranch}`);
498
+ }
533
499
  out();
534
- out('āœ… Setup complete! Edit your .env and run sync again to update.');
535
- }
536
- else {
537
- out('šŸ’” Run: lsh lib secrets create && lsh lib secrets push');
538
500
  }
539
- out();
540
- // Output export commands in load mode
541
- if (loadMode && fs.existsSync(envFilePath)) {
542
- console.log(this.generateExportCommands(envFilePath));
501
+ // Step 1: Ensure encryption key exists
502
+ if (!process.env.LSH_SECRETS_KEY) {
503
+ logger.info('šŸ”‘ No encryption key found...');
504
+ await this.ensureEncryptionKey();
505
+ out();
543
506
  }
544
- return;
545
- }
546
- if (status.localExists && !status.cloudExists) {
547
- action = 'push';
548
- out('ā¬†ļø Local .env exists but not in cloud');
549
- if (autoExecute) {
550
- out(' Pushing to cloud...');
551
- await this.push(envFilePath, effectiveEnv);
552
- out('āœ… Secrets pushed to cloud!');
507
+ // Step 2: Ensure .gitignore includes .env
508
+ if (this.gitInfo?.isGitRepo) {
509
+ ensureEnvInGitignore(process.cwd());
553
510
  }
554
- else {
555
- out(`šŸ’” Run: lsh lib secrets push -f ${envFilePath} -e ${environment}`);
511
+ // Step 3: Check current status
512
+ const status = await this.status(envFilePath, effectiveEnv);
513
+ out('šŸ“Š Current Status:');
514
+ out(` Encryption key: ${status.keySet ? 'āœ…' : 'āŒ'}`);
515
+ out(` Local ${envFilePath}: ${status.localExists ? `āœ… (${status.localKeys} keys)` : 'āŒ'}`);
516
+ out(` Cloud storage: ${status.cloudExists ? `āœ… (${status.cloudKeys} keys)` : 'āŒ'}`);
517
+ if (status.cloudExists && status.keyMatches !== undefined) {
518
+ out(` Key matches: ${status.keyMatches ? 'āœ…' : 'āŒ'}`);
556
519
  }
557
520
  out();
558
- // Output export commands in load mode
559
- if (loadMode && fs.existsSync(envFilePath)) {
560
- console.log(this.generateExportCommands(envFilePath));
521
+ // Step 4: Determine action and execute if auto mode
522
+ let action = 'in-sync';
523
+ if (status.cloudExists && status.keyMatches === false) {
524
+ action = 'key-mismatch';
525
+ out('āš ļø Encryption key mismatch!');
526
+ out(' The local key does not match the cloud storage.');
527
+ out(' Please use the original key or push new secrets with:');
528
+ out(` lsh lib secrets push -f ${envFilePath} -e ${environment}`);
529
+ out();
530
+ return;
561
531
  }
562
- return;
563
- }
564
- if (!status.localExists && status.cloudExists && status.keyMatches) {
565
- action = 'pull';
566
- out('ā¬‡ļø Cloud secrets available but no local file');
567
- if (autoExecute) {
568
- out(' Pulling from cloud...');
569
- await this.pull(envFilePath, effectiveEnv, false);
570
- out('āœ… Secrets pulled from cloud!');
532
+ if (!status.localExists && !status.cloudExists) {
533
+ action = 'create-and-push';
534
+ out('šŸ†• No secrets found locally or in cloud');
535
+ out(' Creating new .env file...');
536
+ if (autoExecute) {
537
+ await this.createEnvFromExample(envFilePath);
538
+ out(' Pushing to cloud...');
539
+ await this.push(envFilePath, effectiveEnv);
540
+ out();
541
+ out('āœ… Setup complete! Edit your .env and run sync again to update.');
542
+ }
543
+ else {
544
+ out('šŸ’” Run: lsh lib secrets create && lsh lib secrets push');
545
+ }
546
+ out();
547
+ // Output export commands in load mode
548
+ if (loadMode && fs.existsSync(envFilePath)) {
549
+ console.log(this.generateExportCommands(envFilePath));
550
+ }
551
+ return;
571
552
  }
572
- else {
573
- out(`šŸ’” Run: lsh lib secrets pull -f ${envFilePath} -e ${environment}`);
553
+ if (status.localExists && !status.cloudExists) {
554
+ action = 'push';
555
+ out('ā¬†ļø Local .env exists but not in cloud');
556
+ if (autoExecute) {
557
+ out(' Pushing to cloud...');
558
+ await this.push(envFilePath, effectiveEnv);
559
+ out('āœ… Secrets pushed to cloud!');
560
+ }
561
+ else {
562
+ out(`šŸ’” Run: lsh lib secrets push -f ${envFilePath} -e ${environment}`);
563
+ }
564
+ out();
565
+ // Output export commands in load mode
566
+ if (loadMode && fs.existsSync(envFilePath)) {
567
+ console.log(this.generateExportCommands(envFilePath));
568
+ }
569
+ return;
574
570
  }
575
- out();
576
- // Output export commands in load mode
577
- if (loadMode && fs.existsSync(envFilePath)) {
578
- console.log(this.generateExportCommands(envFilePath));
571
+ if (!status.localExists && status.cloudExists && status.keyMatches) {
572
+ action = 'pull';
573
+ out('ā¬‡ļø Cloud secrets available but no local file');
574
+ if (autoExecute) {
575
+ out(' Pulling from cloud...');
576
+ await this.pull(envFilePath, effectiveEnv, false);
577
+ out('āœ… Secrets pulled from cloud!');
578
+ }
579
+ else {
580
+ out(`šŸ’” Run: lsh lib secrets pull -f ${envFilePath} -e ${environment}`);
581
+ }
582
+ out();
583
+ // Output export commands in load mode
584
+ if (loadMode && fs.existsSync(envFilePath)) {
585
+ console.log(this.generateExportCommands(envFilePath));
586
+ }
587
+ return;
579
588
  }
580
- return;
581
- }
582
- if (status.localExists && status.cloudExists && status.keyMatches) {
583
- if (status.localModified && status.cloudModified) {
584
- const localNewer = status.localModified > status.cloudModified;
585
- const timeDiff = Math.abs(status.localModified.getTime() - status.cloudModified.getTime());
586
- const minutesDiff = Math.floor(timeDiff / (1000 * 60));
587
- // If difference is less than 1 minute, consider in sync
588
- if (minutesDiff < 1) {
589
- out('āœ… Local and cloud are in sync!');
589
+ if (status.localExists && status.cloudExists && status.keyMatches) {
590
+ if (status.localModified && status.cloudModified) {
591
+ const localNewer = status.localModified > status.cloudModified;
592
+ const timeDiff = Math.abs(status.localModified.getTime() - status.cloudModified.getTime());
593
+ const minutesDiff = Math.floor(timeDiff / (1000 * 60));
594
+ // If difference is less than 1 minute, consider in sync
595
+ if (minutesDiff < 1) {
596
+ out('āœ… Local and cloud are in sync!');
597
+ out();
598
+ if (!loadMode) {
599
+ this.showLoadInstructions(envFilePath);
600
+ }
601
+ else if (fs.existsSync(envFilePath)) {
602
+ console.log(this.generateExportCommands(envFilePath));
603
+ }
604
+ return;
605
+ }
606
+ if (localNewer) {
607
+ action = 'push';
608
+ out('ā¬†ļø Local file is newer than cloud');
609
+ out(` Local: ${status.localModified.toLocaleString()}`);
610
+ out(` Cloud: ${status.cloudModified.toLocaleString()}`);
611
+ if (autoExecute) {
612
+ out(' Pushing to cloud...');
613
+ await this.push(envFilePath, effectiveEnv);
614
+ out('āœ… Secrets synced to cloud!');
615
+ }
616
+ else {
617
+ out(`šŸ’” Run: lsh lib secrets push -f ${envFilePath} -e ${environment}`);
618
+ }
619
+ }
620
+ else {
621
+ action = 'pull';
622
+ out('ā¬‡ļø Cloud is newer than local file');
623
+ out(` Local: ${status.localModified.toLocaleString()}`);
624
+ out(` Cloud: ${status.cloudModified.toLocaleString()}`);
625
+ if (autoExecute) {
626
+ out(' Pulling from cloud (backup created)...');
627
+ await this.pull(envFilePath, effectiveEnv, false);
628
+ out('āœ… Secrets synced from cloud!');
629
+ }
630
+ else {
631
+ out(`šŸ’” Run: lsh lib secrets pull -f ${envFilePath} -e ${environment}`);
632
+ }
633
+ }
590
634
  out();
591
635
  if (!loadMode) {
592
636
  this.showLoadInstructions(envFilePath);
@@ -596,52 +640,22 @@ LSH_SECRETS_KEY=${this.encryptionKey}
596
640
  }
597
641
  return;
598
642
  }
599
- if (localNewer) {
600
- action = 'push';
601
- out('ā¬†ļø Local file is newer than cloud');
602
- out(` Local: ${status.localModified.toLocaleString()}`);
603
- out(` Cloud: ${status.cloudModified.toLocaleString()}`);
604
- if (autoExecute) {
605
- out(' Pushing to cloud...');
606
- await this.push(envFilePath, effectiveEnv);
607
- out('āœ… Secrets synced to cloud!');
608
- }
609
- else {
610
- out(`šŸ’” Run: lsh lib secrets push -f ${envFilePath} -e ${environment}`);
611
- }
612
- }
613
- else {
614
- action = 'pull';
615
- out('ā¬‡ļø Cloud is newer than local file');
616
- out(` Local: ${status.localModified.toLocaleString()}`);
617
- out(` Cloud: ${status.cloudModified.toLocaleString()}`);
618
- if (autoExecute) {
619
- out(' Pulling from cloud (backup created)...');
620
- await this.pull(envFilePath, effectiveEnv, false);
621
- out('āœ… Secrets synced from cloud!');
622
- }
623
- else {
624
- out(`šŸ’” Run: lsh lib secrets pull -f ${envFilePath} -e ${environment}`);
625
- }
626
- }
627
- out();
628
- if (!loadMode) {
629
- this.showLoadInstructions(envFilePath);
630
- }
631
- else if (fs.existsSync(envFilePath)) {
632
- console.log(this.generateExportCommands(envFilePath));
633
- }
634
- return;
643
+ }
644
+ // Default: everything is in sync
645
+ out('āœ… Secrets are synchronized!');
646
+ out();
647
+ if (!loadMode) {
648
+ this.showLoadInstructions(envFilePath);
649
+ }
650
+ else if (fs.existsSync(envFilePath)) {
651
+ console.log(this.generateExportCommands(envFilePath));
635
652
  }
636
653
  }
637
- // Default: everything is in sync
638
- out('āœ… Secrets are synchronized!');
639
- out();
640
- if (!loadMode) {
641
- this.showLoadInstructions(envFilePath);
642
- }
643
- else if (fs.existsSync(envFilePath)) {
644
- console.log(this.generateExportCommands(envFilePath));
654
+ finally {
655
+ // Restore original logger level if it was changed
656
+ if (loadMode && originalLogLevel !== undefined) {
657
+ logger.setLevel(originalLogLevel);
658
+ }
645
659
  }
646
660
  }
647
661
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lsh-framework",
3
- "version": "0.8.2",
3
+ "version": "0.8.3",
4
4
  "description": "Encrypted secrets manager with automatic rotation, team sync, and multi-environment support. Built on a powerful shell with daemon scheduling and CI/CD integration.",
5
5
  "main": "dist/app.js",
6
6
  "bin": {
@@ -106,6 +106,7 @@
106
106
  "ioredis": "^5.8.0",
107
107
  "jsonwebtoken": "^9.0.2",
108
108
  "lodash": "^4.17.21",
109
+ "lsh-framework": "^0.8.2",
109
110
  "node-cron": "^3.0.3",
110
111
  "node-fetch": "^3.3.2",
111
112
  "ora": "^8.0.1",