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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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": {
|