lsh-framework 2.2.1 → 2.2.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.
@@ -74,10 +74,11 @@ export class IPFSSecretsStorage {
74
74
  logger.info(` ☁️ Synced to Storacha network`);
75
75
  // Upload registry file if this is a git repo
76
76
  // This allows detection on new machines without local metadata
77
+ // Include the secrets CID so other hosts can fetch the latest version
77
78
  if (gitRepo) {
78
79
  try {
79
- await storacha.uploadRegistry(gitRepo, environment);
80
- logger.debug(` 📝 Registry uploaded for ${gitRepo}`);
80
+ await storacha.uploadRegistry(gitRepo, environment, cid);
81
+ logger.debug(` 📝 Registry uploaded for ${gitRepo} (CID: ${cid})`);
81
82
  }
82
83
  catch (regError) {
83
84
  // Registry upload failed, but secrets are still uploaded
@@ -106,10 +107,35 @@ export class IPFSSecretsStorage {
106
107
  async pull(environment, encryptionKey, gitRepo) {
107
108
  try {
108
109
  const metadataKey = this.getMetadataKey(gitRepo, environment);
109
- const metadata = this.metadata[metadataKey];
110
+ let metadata = this.metadata[metadataKey];
110
111
  if (!metadata) {
111
112
  throw new Error(`No secrets found for environment: ${environment}`);
112
113
  }
114
+ // Check if there's a newer version in the registry (for git repos)
115
+ if (gitRepo) {
116
+ try {
117
+ const storacha = getStorachaClient();
118
+ if (storacha.isEnabled() && await storacha.isAuthenticated()) {
119
+ const latestCid = await storacha.getLatestCID(gitRepo);
120
+ if (latestCid && latestCid !== metadata.cid) {
121
+ logger.info(` 🔄 Found newer version in registry (CID: ${latestCid})`);
122
+ // Update metadata with latest CID
123
+ metadata = {
124
+ ...metadata,
125
+ cid: latestCid,
126
+ timestamp: new Date().toISOString(),
127
+ };
128
+ this.metadata[metadataKey] = metadata;
129
+ this.saveMetadata();
130
+ }
131
+ }
132
+ }
133
+ catch (error) {
134
+ // Registry check failed, continue with local metadata
135
+ const err = error;
136
+ logger.debug(` Registry check failed: ${err.message}`);
137
+ }
138
+ }
113
139
  // Try to load from local cache
114
140
  let cachedData = await this.loadLocally(metadata.cid);
115
141
  // If not in cache, try downloading from Storacha
@@ -235,9 +235,9 @@ export class StorachaClient {
235
235
  }
236
236
  /**
237
237
  * Upload registry file for a repo
238
- * Registry files mark that secrets exist for a repo without exposing the secrets themselves
238
+ * Registry files mark that secrets exist and include the latest secrets CID
239
239
  */
240
- async uploadRegistry(repoName, environment) {
240
+ async uploadRegistry(repoName, environment, secretsCid) {
241
241
  if (!this.isEnabled()) {
242
242
  throw new Error('Storacha is not enabled');
243
243
  }
@@ -247,8 +247,9 @@ export class StorachaClient {
247
247
  const registry = {
248
248
  repoName,
249
249
  environment,
250
+ cid: secretsCid, // Include the secrets CID
250
251
  timestamp: new Date().toISOString(),
251
- version: '2.2.1',
252
+ version: '2.2.2',
252
253
  };
253
254
  const content = JSON.stringify(registry, null, 2);
254
255
  const buffer = Buffer.from(content, 'utf-8');
@@ -258,9 +259,74 @@ export class StorachaClient {
258
259
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
259
260
  const file = new File([uint8Array], filename, { type: 'application/json' });
260
261
  const cid = await client.uploadFile(file);
261
- logger.debug(`📝 Uploaded registry for ${repoName}: ${cid}`);
262
+ logger.debug(`📝 Uploaded registry for ${repoName} (secrets CID: ${secretsCid}): ${cid}`);
262
263
  return cid.toString();
263
264
  }
265
+ /**
266
+ * Get the latest secrets CID from registry
267
+ * Returns the CID of the latest secrets if registry exists, null otherwise
268
+ */
269
+ async getLatestCID(repoName) {
270
+ if (!this.isEnabled()) {
271
+ return null;
272
+ }
273
+ if (!await this.isAuthenticated()) {
274
+ return null;
275
+ }
276
+ try {
277
+ const client = await this.getClient();
278
+ // Only check recent uploads (limit to 20 for performance)
279
+ const pageSize = 20;
280
+ // Get first page of uploads
281
+ const results = await client.capability.upload.list({
282
+ cursor: '',
283
+ size: pageSize,
284
+ });
285
+ // Collect all registry files for this repo
286
+ const registries = [];
287
+ for (const upload of results.results) {
288
+ try {
289
+ const cid = upload.root.toString();
290
+ // Download with timeout
291
+ const downloadPromise = this.download(cid);
292
+ const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 5000));
293
+ const content = await Promise.race([downloadPromise, timeoutPromise]);
294
+ // Skip large files (registry should be < 1KB)
295
+ if (content.length > 1024) {
296
+ continue;
297
+ }
298
+ // Try to parse as JSON
299
+ const json = JSON.parse(content.toString('utf-8'));
300
+ // Check if it's an LSH registry file for our repo
301
+ if (json.repoName === repoName && json.version && json.cid && json.timestamp) {
302
+ registries.push({
303
+ cid: cid,
304
+ timestamp: json.timestamp,
305
+ secretsCid: json.cid,
306
+ });
307
+ }
308
+ }
309
+ catch {
310
+ // Not an LSH registry file or failed to download
311
+ continue;
312
+ }
313
+ }
314
+ // Sort by timestamp (newest first) and return the most recent secrets CID
315
+ if (registries.length > 0) {
316
+ registries.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
317
+ const latest = registries[0];
318
+ logger.debug(`✅ Found latest CID for ${repoName}: ${latest.secretsCid} (timestamp: ${latest.timestamp})`);
319
+ return latest.secretsCid;
320
+ }
321
+ // No registry found
322
+ return null;
323
+ }
324
+ catch (error) {
325
+ const err = error;
326
+ logger.debug(`Failed to get latest CID: ${err.message}`);
327
+ return null;
328
+ }
329
+ }
264
330
  /**
265
331
  * Check if registry exists for a repo by listing uploads
266
332
  * Returns true if a registry file for this repo exists in Storacha
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lsh-framework",
3
- "version": "2.2.1",
3
+ "version": "2.2.3",
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": {