lsh-framework 3.1.7 → 3.1.8

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.
@@ -3,6 +3,7 @@
3
3
  * Stores encrypted secrets on IPFS using Storacha (formerly web3.storage)
4
4
  */
5
5
  import * as fs from 'fs';
6
+ import * as fsPromises from 'fs/promises';
6
7
  import * as path from 'path';
7
8
  import * as os from 'os';
8
9
  import * as crypto from 'crypto';
@@ -30,12 +31,19 @@ export class IPFSSecretsStorage {
30
31
  const lshDir = path.join(homeDir, '.lsh');
31
32
  this.cacheDir = path.join(lshDir, 'secrets-cache');
32
33
  this.metadataPath = path.join(lshDir, 'secrets-metadata.json');
34
+ // Initialize metadata - will be loaded on first use
35
+ this.metadata = {};
36
+ }
37
+ /**
38
+ * Initialize async parts
39
+ */
40
+ async initialize() {
33
41
  // Ensure directories exist
34
42
  if (!fs.existsSync(this.cacheDir)) {
35
43
  fs.mkdirSync(this.cacheDir, { recursive: true });
36
44
  }
37
45
  // Load metadata
38
- this.metadata = this.loadMetadata();
46
+ this.metadata = await this.loadMetadataAsync();
39
47
  }
40
48
  /**
41
49
  * Store secrets on IPFS
@@ -59,7 +67,7 @@ export class IPFSSecretsStorage {
59
67
  encrypted: true,
60
68
  };
61
69
  this.metadata[this.getMetadataKey(gitRepo, environment)] = metadata;
62
- this.saveMetadata();
70
+ await this.saveMetadata();
63
71
  logger.info(`📦 Stored ${secrets.length} secrets on IPFS: ${cid}`);
64
72
  logger.info(` Environment: ${environment}`);
65
73
  if (gitRepo) {
@@ -132,7 +140,7 @@ export class IPFSSecretsStorage {
132
140
  encrypted: true,
133
141
  };
134
142
  this.metadata[metadataKey] = metadata;
135
- this.saveMetadata();
143
+ await this.saveMetadata();
136
144
  }
137
145
  }
138
146
  }
@@ -162,7 +170,7 @@ export class IPFSSecretsStorage {
162
170
  timestamp: new Date().toISOString(),
163
171
  };
164
172
  this.metadata[metadataKey] = metadata;
165
- this.saveMetadata();
173
+ await this.saveMetadata();
166
174
  }
167
175
  }
168
176
  }
@@ -236,20 +244,24 @@ export class IPFSSecretsStorage {
236
244
  return Object.values(this.metadata);
237
245
  }
238
246
  /**
239
- * Delete secrets for environment
247
+ * Delete local cached secrets for an environment
240
248
  */
241
- async delete(environment, gitRepo) {
249
+ async deleteLocal(environment, gitRepo) {
242
250
  const metadataKey = this.getMetadataKey(gitRepo, environment);
243
251
  const metadata = this.metadata[metadataKey];
244
252
  if (metadata) {
245
253
  // Delete local cache
246
254
  const cachePath = path.join(this.cacheDir, `${metadata.cid}.encrypted`);
247
- if (fs.existsSync(cachePath)) {
248
- fs.unlinkSync(cachePath);
255
+ try {
256
+ await fsPromises.access(cachePath);
257
+ await fsPromises.unlink(cachePath);
258
+ }
259
+ catch {
260
+ // File doesn't exist, which is fine
249
261
  }
250
262
  // Remove metadata
251
263
  delete this.metadata[metadataKey];
252
- this.saveMetadata();
264
+ await this.saveMetadata();
253
265
  logger.info(`🗑️ Deleted secrets for ${environment}`);
254
266
  }
255
267
  }
@@ -310,7 +322,10 @@ export class IPFSSecretsStorage {
310
322
  */
311
323
  async storeLocally(cid, encryptedData, _environment) {
312
324
  const cachePath = path.join(this.cacheDir, `${cid}.encrypted`);
313
- fs.writeFileSync(cachePath, encryptedData, 'utf8');
325
+ // Ensure parent directory exists
326
+ await fsPromises.mkdir(this.cacheDir, { recursive: true });
327
+ // Write file without locking (simpler approach)
328
+ await fsPromises.writeFile(cachePath, encryptedData, 'utf8');
314
329
  logger.debug(`Cached secrets locally: ${cachePath}`);
315
330
  }
316
331
  /**
@@ -318,10 +333,14 @@ export class IPFSSecretsStorage {
318
333
  */
319
334
  async loadLocally(cid) {
320
335
  const cachePath = path.join(this.cacheDir, `${cid}.encrypted`);
321
- if (!fs.existsSync(cachePath)) {
336
+ try {
337
+ await fsPromises.access(cachePath);
338
+ }
339
+ catch {
322
340
  return null;
323
341
  }
324
- return fs.readFileSync(cachePath, 'utf8');
342
+ // Simple read without locking for now
343
+ return await fsPromises.readFile(cachePath, 'utf8');
325
344
  }
326
345
  /**
327
346
  * Get metadata key for environment
@@ -344,10 +363,31 @@ export class IPFSSecretsStorage {
344
363
  return {};
345
364
  }
346
365
  }
366
+ /**
367
+ * Load metadata from disk asynchronously
368
+ */
369
+ async loadMetadataAsync() {
370
+ try {
371
+ await fsPromises.access(this.metadataPath);
372
+ }
373
+ catch {
374
+ return {};
375
+ }
376
+ try {
377
+ const content = await fsPromises.readFile(this.metadataPath, 'utf8');
378
+ return JSON.parse(content);
379
+ }
380
+ catch {
381
+ return {};
382
+ }
383
+ }
347
384
  /**
348
385
  * Save metadata to disk
349
386
  */
350
- saveMetadata() {
351
- fs.writeFileSync(this.metadataPath, JSON.stringify(this.metadata, null, 2), 'utf8');
387
+ async saveMetadata() {
388
+ // Ensure parent directory exists
389
+ const parentDir = path.dirname(this.metadataPath);
390
+ await fsPromises.mkdir(parentDir, { recursive: true });
391
+ await fsPromises.writeFile(this.metadataPath, JSON.stringify(this.metadata, null, 2), 'utf8');
352
392
  }
353
393
  }
@@ -458,6 +458,7 @@ API_KEY=
458
458
  .option('--export', 'Output in export format for shell evaluation (alias for --format export)')
459
459
  .option('--format <type>', 'Output format: env, json, yaml, toml, export', 'env')
460
460
  .option('--exact', 'Require exact key match (disable fuzzy matching)')
461
+ .option('--no-mask', 'Show full values in fuzzy match results')
461
462
  .action(async (key, options) => {
462
463
  try {
463
464
  const manager = new SecretsManager({ globalMode: options.global });
@@ -546,11 +547,13 @@ API_KEY=
546
547
  // Multiple matches - show all matches for user to choose
547
548
  console.error(`🔍 Found ${matches.length} matches for '${key}':\n`);
548
549
  for (const match of matches) {
549
- // Mask value for display
550
- const maskedValue = match.value.length > 4
551
- ? match.value.substring(0, 4) + '*'.repeat(Math.min(match.value.length - 4, 10))
552
- : '****';
553
- console.error(` ${match.key}=${maskedValue}`);
550
+ // Mask value for display unless --no-mask is set
551
+ const displayValue = options.mask === false
552
+ ? match.value
553
+ : (match.value.length > 4
554
+ ? match.value.substring(0, 4) + '*'.repeat(Math.min(match.value.length - 4, 10))
555
+ : '****');
556
+ console.error(` ${match.key}=${displayValue}`);
554
557
  }
555
558
  console.error('');
556
559
  console.error('💡 Please specify the exact key name or use one of:');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lsh-framework",
3
- "version": "3.1.7",
3
+ "version": "3.1.8",
4
4
  "description": "Simple, cross-platform encrypted secrets manager with automatic sync, IPFS audit logs, and multi-environment support. Just run lsh sync and start managing your secrets.",
5
5
  "main": "dist/app.js",
6
6
  "bin": {
@@ -65,7 +65,8 @@
65
65
  "dependencies": {
66
66
  "@storacha/client": "^1.8.18",
67
67
  "@supabase/supabase-js": "^2.57.4",
68
- "bcrypt": "^5.1.1",
68
+ "@types/proper-lockfile": "^4.1.4",
69
+ "bcrypt": "^6.0.0",
69
70
  "chalk": "^5.3.0",
70
71
  "chokidar": "^5.0.0",
71
72
  "commander": "^14.0.2",
@@ -80,6 +81,7 @@
80
81
  "node-cron": "^4.2.1",
81
82
  "ora": "^9.0.0",
82
83
  "pg": "^8.16.3",
84
+ "proper-lockfile": "^4.1.2",
83
85
  "smol-toml": "^1.3.1",
84
86
  "uuid": "^13.0.0"
85
87
  },