lsh-framework 1.0.0 ā 1.2.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 +66 -1
- package/dist/services/secrets/secrets.js +310 -38
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -104,6 +104,34 @@ lsh sync # Stored as: app1_dev
|
|
|
104
104
|
|
|
105
105
|
cd ~/repos/app2
|
|
106
106
|
lsh sync # Stored as: app2_dev (separate!)
|
|
107
|
+
|
|
108
|
+
# See what's tracked in the current directory
|
|
109
|
+
lsh info
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Example `lsh info` output:**
|
|
113
|
+
```
|
|
114
|
+
š Current Directory Context
|
|
115
|
+
|
|
116
|
+
š Git Repository:
|
|
117
|
+
Name: myapp
|
|
118
|
+
Branch: main
|
|
119
|
+
|
|
120
|
+
š Environment Tracking:
|
|
121
|
+
Base environment: dev
|
|
122
|
+
Cloud storage name: myapp_dev
|
|
123
|
+
Namespace: myapp
|
|
124
|
+
ā¹ļø Repo-based isolation enabled
|
|
125
|
+
|
|
126
|
+
š Local .env File:
|
|
127
|
+
Keys: 12
|
|
128
|
+
Has encryption key: ā
|
|
129
|
+
|
|
130
|
+
āļø Cloud Storage:
|
|
131
|
+
Environment: myapp_dev
|
|
132
|
+
Keys stored: 12
|
|
133
|
+
Last updated: 11/6/2025, 10:15:23 PM
|
|
134
|
+
Key matches: ā
|
|
107
135
|
```
|
|
108
136
|
|
|
109
137
|
No more conflicts between projects using the same environment names!
|
|
@@ -179,6 +207,38 @@ lsh pull --env staging # for testing
|
|
|
179
207
|
lsh pull --env prod # for production debugging
|
|
180
208
|
```
|
|
181
209
|
|
|
210
|
+
### š Batch Upsert Secrets
|
|
211
|
+
|
|
212
|
+
**New in v1.1.0:** Pipe environment variables directly into your `.env` file!
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
# Copy all current environment variables
|
|
216
|
+
printenv | lsh set
|
|
217
|
+
|
|
218
|
+
# Import from another .env file
|
|
219
|
+
cat .env.backup | lsh set
|
|
220
|
+
|
|
221
|
+
# Import specific variables
|
|
222
|
+
printenv | grep "^AWS_" | lsh set
|
|
223
|
+
|
|
224
|
+
# Merge multiple sources
|
|
225
|
+
cat .env.base .env.local | lsh set
|
|
226
|
+
|
|
227
|
+
# From file with --stdin flag
|
|
228
|
+
lsh set --stdin < .env.production
|
|
229
|
+
|
|
230
|
+
# Single key-value still works
|
|
231
|
+
lsh set API_KEY sk_live_12345
|
|
232
|
+
lsh set DATABASE_URL postgres://localhost/db
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**Features:**
|
|
236
|
+
- ā
Automatic upsert (updates existing, adds new)
|
|
237
|
+
- ā
Preserves comments and formatting
|
|
238
|
+
- ā
Handles quoted values
|
|
239
|
+
- ā
Validates key names
|
|
240
|
+
- ā
Shows summary of changes
|
|
241
|
+
|
|
182
242
|
## Secrets Commands
|
|
183
243
|
|
|
184
244
|
| Command | Description |
|
|
@@ -191,7 +251,12 @@ lsh pull --env prod # for production debugging
|
|
|
191
251
|
| `lsh create` | Create new .env file |
|
|
192
252
|
| `lsh delete` | Delete .env file (with confirmation) |
|
|
193
253
|
| `lsh sync` | Smart sync (auto-setup and sync) |
|
|
194
|
-
| `lsh status` | Get detailed secrets status |
|
|
254
|
+
| `lsh status` | Get detailed secrets status (JSON) |
|
|
255
|
+
| `lsh info` | Show current context and tracked environment |
|
|
256
|
+
| `lsh get <key>` | Get a specific secret value |
|
|
257
|
+
| `lsh set <key> <value>` | Set a single secret value |
|
|
258
|
+
| `printenv \| lsh set` | Batch upsert from stdin (pipe) |
|
|
259
|
+
| `lsh set --stdin < file` | Batch upsert from file |
|
|
195
260
|
|
|
196
261
|
See the complete guide: [SECRETS_GUIDE.md](docs/features/secrets/SECRETS_GUIDE.md)
|
|
197
262
|
|
|
@@ -6,6 +6,7 @@ import SecretsManager from '../../lib/secrets-manager.js';
|
|
|
6
6
|
import * as fs from 'fs';
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
import * as readline from 'readline';
|
|
9
|
+
import { getGitRepoInfo } from '../../lib/git-utils.js';
|
|
9
10
|
export async function init_secrets(program) {
|
|
10
11
|
// Push secrets to cloud
|
|
11
12
|
program
|
|
@@ -272,6 +273,105 @@ API_KEY=
|
|
|
272
273
|
process.exit(1);
|
|
273
274
|
}
|
|
274
275
|
});
|
|
276
|
+
// Info command - show relevant context information
|
|
277
|
+
program
|
|
278
|
+
.command('info')
|
|
279
|
+
.description('Show current directory context and tracked environment')
|
|
280
|
+
.option('-f, --file <path>', 'Path to .env file', '.env')
|
|
281
|
+
.option('-e, --env <name>', 'Environment name', 'dev')
|
|
282
|
+
.action(async (options) => {
|
|
283
|
+
try {
|
|
284
|
+
const gitInfo = getGitRepoInfo();
|
|
285
|
+
const manager = new SecretsManager();
|
|
286
|
+
const envPath = path.resolve(options.file);
|
|
287
|
+
console.log('\nš Current Directory Context\n');
|
|
288
|
+
// Git Repository Info
|
|
289
|
+
if (gitInfo.isGitRepo) {
|
|
290
|
+
console.log('š Git Repository:');
|
|
291
|
+
console.log(` Root: ${gitInfo.rootPath || 'unknown'}`);
|
|
292
|
+
console.log(` Name: ${gitInfo.repoName || 'unknown'}`);
|
|
293
|
+
if (gitInfo.currentBranch) {
|
|
294
|
+
console.log(` Branch: ${gitInfo.currentBranch}`);
|
|
295
|
+
}
|
|
296
|
+
if (gitInfo.remoteUrl) {
|
|
297
|
+
console.log(` Remote: ${gitInfo.remoteUrl}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
console.log('š Not in a git repository');
|
|
302
|
+
}
|
|
303
|
+
console.log('');
|
|
304
|
+
// Environment Tracking
|
|
305
|
+
console.log('š Environment Tracking:');
|
|
306
|
+
// Show the effective environment name used for cloud storage
|
|
307
|
+
const effectiveEnv = gitInfo.repoName
|
|
308
|
+
? `${gitInfo.repoName}_${options.env}`
|
|
309
|
+
: options.env;
|
|
310
|
+
console.log(` Base environment: ${options.env}`);
|
|
311
|
+
console.log(` Cloud storage name: ${effectiveEnv}`);
|
|
312
|
+
if (gitInfo.repoName) {
|
|
313
|
+
console.log(` Namespace: ${gitInfo.repoName}`);
|
|
314
|
+
console.log(' ā¹ļø Repo-based isolation enabled');
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
console.log(' Namespace: (none - not in git repo)');
|
|
318
|
+
console.log(' ā ļø No isolation - shared environment name');
|
|
319
|
+
}
|
|
320
|
+
console.log('');
|
|
321
|
+
// Local File Status
|
|
322
|
+
console.log('š Local .env File:');
|
|
323
|
+
if (fs.existsSync(envPath)) {
|
|
324
|
+
const content = fs.readFileSync(envPath, 'utf8');
|
|
325
|
+
const lines = content.split('\n').filter(line => {
|
|
326
|
+
const trimmed = line.trim();
|
|
327
|
+
return trimmed && !trimmed.startsWith('#') && trimmed.includes('=');
|
|
328
|
+
});
|
|
329
|
+
console.log(` Path: ${envPath}`);
|
|
330
|
+
console.log(` Keys: ${lines.length}`);
|
|
331
|
+
console.log(` Size: ${Math.round(content.length / 1024 * 10) / 10} KB`);
|
|
332
|
+
// Check for encryption key
|
|
333
|
+
const hasKey = content.includes('LSH_SECRETS_KEY=');
|
|
334
|
+
console.log(` Has encryption key: ${hasKey ? 'ā
' : 'ā'}`);
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
console.log(` Path: ${envPath}`);
|
|
338
|
+
console.log(' Status: ā Not found');
|
|
339
|
+
}
|
|
340
|
+
console.log('');
|
|
341
|
+
// Cloud Status
|
|
342
|
+
console.log('āļø Cloud Storage:');
|
|
343
|
+
try {
|
|
344
|
+
const status = await manager.status(options.file, options.env);
|
|
345
|
+
if (status.cloudExists) {
|
|
346
|
+
console.log(` Environment: ${effectiveEnv}`);
|
|
347
|
+
console.log(` Keys stored: ${status.cloudKeys}`);
|
|
348
|
+
console.log(` Last updated: ${status.cloudModified ? new Date(status.cloudModified).toLocaleString() : 'unknown'}`);
|
|
349
|
+
if (status.keyMatches !== undefined) {
|
|
350
|
+
console.log(` Key matches: ${status.keyMatches ? 'ā
' : 'ā MISMATCH'}`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
console.log(` Environment: ${effectiveEnv}`);
|
|
355
|
+
console.log(' Status: ā Not synced yet');
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
catch (_error) {
|
|
359
|
+
console.log(' Status: ā ļø Unable to check (Supabase not configured)');
|
|
360
|
+
}
|
|
361
|
+
console.log('');
|
|
362
|
+
// Quick Actions
|
|
363
|
+
console.log('š” Quick Actions:');
|
|
364
|
+
console.log(` Push: lsh push --env ${options.env}`);
|
|
365
|
+
console.log(` Pull: lsh pull --env ${options.env}`);
|
|
366
|
+
console.log(` Sync: lsh sync --env ${options.env}`);
|
|
367
|
+
console.log('');
|
|
368
|
+
}
|
|
369
|
+
catch (error) {
|
|
370
|
+
const err = error;
|
|
371
|
+
console.error('ā Failed to get info:', err.message);
|
|
372
|
+
process.exit(1);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
275
375
|
// Get a specific secret value
|
|
276
376
|
program
|
|
277
377
|
.command('get [key]')
|
|
@@ -351,52 +451,31 @@ API_KEY=
|
|
|
351
451
|
process.exit(1);
|
|
352
452
|
}
|
|
353
453
|
});
|
|
354
|
-
// Set a specific secret value
|
|
454
|
+
// Set a specific secret value or batch upsert from stdin
|
|
355
455
|
program
|
|
356
|
-
.command('set
|
|
357
|
-
.description('Set a specific secret value in .env file')
|
|
456
|
+
.command('set [key] [value]')
|
|
457
|
+
.description('Set a specific secret value in .env file, or batch upsert from stdin (KEY=VALUE format)')
|
|
358
458
|
.option('-f, --file <path>', 'Path to .env file', '.env')
|
|
459
|
+
.option('--stdin', 'Read KEY=VALUE pairs from stdin (one per line)')
|
|
359
460
|
.action(async (key, value, options) => {
|
|
360
461
|
try {
|
|
361
462
|
const envPath = path.resolve(options.file);
|
|
362
|
-
//
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
463
|
+
// Check if we should read from stdin
|
|
464
|
+
const isStdin = options.stdin || (!key && !value);
|
|
465
|
+
if (isStdin) {
|
|
466
|
+
// Batch mode: read from stdin
|
|
467
|
+
await batchSetSecrets(envPath);
|
|
366
468
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
if (line.trim().startsWith('#') || !line.trim()) {
|
|
375
|
-
newLines.push(line);
|
|
376
|
-
continue;
|
|
377
|
-
}
|
|
378
|
-
const match = line.match(/^([^=]+)=(.*)$/);
|
|
379
|
-
if (match && match[1].trim() === key) {
|
|
380
|
-
// Quote values with spaces or special characters
|
|
381
|
-
const needsQuotes = /[\s#]/.test(value);
|
|
382
|
-
const quotedValue = needsQuotes ? `"${value}"` : value;
|
|
383
|
-
newLines.push(`${key}=${quotedValue}`);
|
|
384
|
-
found = true;
|
|
385
|
-
}
|
|
386
|
-
else {
|
|
387
|
-
newLines.push(line);
|
|
388
|
-
}
|
|
469
|
+
else {
|
|
470
|
+
// Single mode: set one key-value pair
|
|
471
|
+
if (!key || value === undefined) {
|
|
472
|
+
console.error('ā Usage: lsh set <key> <value>');
|
|
473
|
+
console.error(' Or pipe input: printenv | lsh set');
|
|
474
|
+
console.error(' Or use stdin: lsh set --stdin < file.env');
|
|
475
|
+
process.exit(1);
|
|
389
476
|
}
|
|
390
|
-
|
|
391
|
-
}
|
|
392
|
-
// If key wasn't found, append it
|
|
393
|
-
if (!found) {
|
|
394
|
-
const needsQuotes = /[\s#]/.test(value);
|
|
395
|
-
const quotedValue = needsQuotes ? `"${value}"` : value;
|
|
396
|
-
content = content.trimRight() + `\n${key}=${quotedValue}\n`;
|
|
477
|
+
await setSingleSecret(envPath, key, value);
|
|
397
478
|
}
|
|
398
|
-
fs.writeFileSync(envPath, content, 'utf8');
|
|
399
|
-
console.log(`ā
Set ${key} in ${options.file}`);
|
|
400
479
|
}
|
|
401
480
|
catch (error) {
|
|
402
481
|
const err = error;
|
|
@@ -404,6 +483,199 @@ API_KEY=
|
|
|
404
483
|
process.exit(1);
|
|
405
484
|
}
|
|
406
485
|
});
|
|
486
|
+
/**
|
|
487
|
+
* Set a single secret value
|
|
488
|
+
*/
|
|
489
|
+
async function setSingleSecret(envPath, key, value) {
|
|
490
|
+
// Validate key format
|
|
491
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
492
|
+
console.error(`ā Invalid key format: ${key}. Must be a valid environment variable name.`);
|
|
493
|
+
process.exit(1);
|
|
494
|
+
}
|
|
495
|
+
let content = '';
|
|
496
|
+
let found = false;
|
|
497
|
+
if (fs.existsSync(envPath)) {
|
|
498
|
+
content = fs.readFileSync(envPath, 'utf8');
|
|
499
|
+
const lines = content.split('\n');
|
|
500
|
+
const newLines = [];
|
|
501
|
+
for (const line of lines) {
|
|
502
|
+
if (line.trim().startsWith('#') || !line.trim()) {
|
|
503
|
+
newLines.push(line);
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
const match = line.match(/^([^=]+)=(.*)$/);
|
|
507
|
+
if (match && match[1].trim() === key) {
|
|
508
|
+
// Quote values with spaces or special characters
|
|
509
|
+
const needsQuotes = /[\s#]/.test(value);
|
|
510
|
+
const quotedValue = needsQuotes ? `"${value}"` : value;
|
|
511
|
+
newLines.push(`${key}=${quotedValue}`);
|
|
512
|
+
found = true;
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
newLines.push(line);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
content = newLines.join('\n');
|
|
519
|
+
}
|
|
520
|
+
// If key wasn't found, append it
|
|
521
|
+
if (!found) {
|
|
522
|
+
const needsQuotes = /[\s#]/.test(value);
|
|
523
|
+
const quotedValue = needsQuotes ? `"${value}"` : value;
|
|
524
|
+
content = content.trimRight() + `\n${key}=${quotedValue}\n`;
|
|
525
|
+
}
|
|
526
|
+
fs.writeFileSync(envPath, content, 'utf8');
|
|
527
|
+
console.log(`ā
Set ${key}`);
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Batch upsert secrets from stdin
|
|
531
|
+
*/
|
|
532
|
+
async function batchSetSecrets(envPath) {
|
|
533
|
+
return new Promise((resolve, reject) => {
|
|
534
|
+
let inputData = '';
|
|
535
|
+
const stdin = process.stdin;
|
|
536
|
+
// Check if stdin is a TTY (interactive terminal)
|
|
537
|
+
if (stdin.isTTY) {
|
|
538
|
+
console.error('ā No input provided. Please pipe data or use --stdin flag.');
|
|
539
|
+
console.error('');
|
|
540
|
+
console.error('Examples:');
|
|
541
|
+
console.error(' printenv | lsh set');
|
|
542
|
+
console.error(' lsh set --stdin < .env.backup');
|
|
543
|
+
console.error(' echo "API_KEY=secret123" | lsh set');
|
|
544
|
+
process.exit(1);
|
|
545
|
+
}
|
|
546
|
+
stdin.setEncoding('utf8');
|
|
547
|
+
stdin.on('data', (chunk) => {
|
|
548
|
+
inputData += chunk;
|
|
549
|
+
});
|
|
550
|
+
stdin.on('end', () => {
|
|
551
|
+
try {
|
|
552
|
+
const lines = inputData.split('\n').filter(line => line.trim());
|
|
553
|
+
if (lines.length === 0) {
|
|
554
|
+
console.error('ā No valid KEY=VALUE pairs found in input');
|
|
555
|
+
process.exit(1);
|
|
556
|
+
}
|
|
557
|
+
// Read existing .env file
|
|
558
|
+
let content = '';
|
|
559
|
+
const existingKeys = new Map();
|
|
560
|
+
if (fs.existsSync(envPath)) {
|
|
561
|
+
content = fs.readFileSync(envPath, 'utf8');
|
|
562
|
+
const existingLines = content.split('\n');
|
|
563
|
+
for (const line of existingLines) {
|
|
564
|
+
if (line.trim().startsWith('#') || !line.trim())
|
|
565
|
+
continue;
|
|
566
|
+
const match = line.match(/^([^=]+)=(.*)$/);
|
|
567
|
+
if (match) {
|
|
568
|
+
existingKeys.set(match[1].trim(), line);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
const updates = [];
|
|
573
|
+
const errors = [];
|
|
574
|
+
const newKeys = new Map();
|
|
575
|
+
// Parse input lines
|
|
576
|
+
for (const line of lines) {
|
|
577
|
+
const trimmed = line.trim();
|
|
578
|
+
// Skip comments and empty lines
|
|
579
|
+
if (trimmed.startsWith('#') || !trimmed)
|
|
580
|
+
continue;
|
|
581
|
+
// Parse KEY=VALUE format
|
|
582
|
+
const match = trimmed.match(/^([^=]+)=(.*)$/);
|
|
583
|
+
if (!match) {
|
|
584
|
+
errors.push(`Invalid format: ${trimmed}`);
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
const key = match[1].trim();
|
|
588
|
+
let value = match[2].trim();
|
|
589
|
+
// Validate key format
|
|
590
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
591
|
+
errors.push(`Invalid key format: ${key}`);
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
// Remove surrounding quotes if present
|
|
595
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
596
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
597
|
+
value = value.slice(1, -1);
|
|
598
|
+
}
|
|
599
|
+
// Track if this is an update or addition
|
|
600
|
+
const action = existingKeys.has(key) ? 'updated' : 'added';
|
|
601
|
+
updates.push({ key, value, action });
|
|
602
|
+
newKeys.set(key, value);
|
|
603
|
+
}
|
|
604
|
+
// Build new content
|
|
605
|
+
const newLines = [];
|
|
606
|
+
let hasContent = false;
|
|
607
|
+
if (fs.existsSync(envPath)) {
|
|
608
|
+
const existingLines = content.split('\n');
|
|
609
|
+
for (const line of existingLines) {
|
|
610
|
+
if (line.trim().startsWith('#') || !line.trim()) {
|
|
611
|
+
newLines.push(line);
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
const match = line.match(/^([^=]+)=(.*)$/);
|
|
615
|
+
if (match) {
|
|
616
|
+
const key = match[1].trim();
|
|
617
|
+
if (newKeys.has(key)) {
|
|
618
|
+
// Update existing key
|
|
619
|
+
const value = newKeys.get(key);
|
|
620
|
+
const needsQuotes = /[\s#]/.test(value);
|
|
621
|
+
const quotedValue = needsQuotes ? `"${value}"` : value;
|
|
622
|
+
newLines.push(`${key}=${quotedValue}`);
|
|
623
|
+
newKeys.delete(key); // Mark as processed
|
|
624
|
+
hasContent = true;
|
|
625
|
+
}
|
|
626
|
+
else {
|
|
627
|
+
// Keep existing line
|
|
628
|
+
newLines.push(line);
|
|
629
|
+
hasContent = true;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
else {
|
|
633
|
+
newLines.push(line);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
// Add new keys that weren't in the existing file
|
|
638
|
+
for (const [key, value] of newKeys.entries()) {
|
|
639
|
+
const needsQuotes = /[\s#]/.test(value);
|
|
640
|
+
const quotedValue = needsQuotes ? `"${value}"` : value;
|
|
641
|
+
if (hasContent) {
|
|
642
|
+
newLines.push(`${key}=${quotedValue}`);
|
|
643
|
+
}
|
|
644
|
+
else {
|
|
645
|
+
newLines.push(`${key}=${quotedValue}`);
|
|
646
|
+
hasContent = true;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
// Write updated content
|
|
650
|
+
let finalContent = newLines.join('\n');
|
|
651
|
+
if (hasContent && !finalContent.endsWith('\n')) {
|
|
652
|
+
finalContent += '\n';
|
|
653
|
+
}
|
|
654
|
+
fs.writeFileSync(envPath, finalContent, 'utf8');
|
|
655
|
+
// Report results
|
|
656
|
+
const added = updates.filter(u => u.action === 'added').length;
|
|
657
|
+
const updated = updates.filter(u => u.action === 'updated').length;
|
|
658
|
+
console.log(`ā
Batch upsert complete:`);
|
|
659
|
+
if (added > 0)
|
|
660
|
+
console.log(` Added: ${added} key(s)`);
|
|
661
|
+
if (updated > 0)
|
|
662
|
+
console.log(` Updated: ${updated} key(s)`);
|
|
663
|
+
if (errors.length > 0) {
|
|
664
|
+
console.log('');
|
|
665
|
+
console.log('ā ļø Skipped invalid entries:');
|
|
666
|
+
errors.forEach(err => console.log(` ${err}`));
|
|
667
|
+
}
|
|
668
|
+
resolve();
|
|
669
|
+
}
|
|
670
|
+
catch (error) {
|
|
671
|
+
reject(error);
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
stdin.on('error', (error) => {
|
|
675
|
+
reject(error);
|
|
676
|
+
});
|
|
677
|
+
});
|
|
678
|
+
}
|
|
407
679
|
// Delete .env file with confirmation
|
|
408
680
|
program
|
|
409
681
|
.command('delete')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lsh-framework",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
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": {
|