lsh-framework 2.3.0 â 2.3.2
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.
|
@@ -108,8 +108,43 @@ export class IPFSSecretsStorage {
|
|
|
108
108
|
try {
|
|
109
109
|
const metadataKey = this.getMetadataKey(gitRepo, environment);
|
|
110
110
|
let metadata = this.metadata[metadataKey];
|
|
111
|
+
// Construct display name for error messages
|
|
112
|
+
const displayEnv = gitRepo
|
|
113
|
+
? (environment ? `${gitRepo}_${environment}` : gitRepo)
|
|
114
|
+
: (environment || 'default');
|
|
115
|
+
// If no local metadata, try to fetch from Storacha registry first (for git repos)
|
|
116
|
+
if (!metadata && gitRepo) {
|
|
117
|
+
try {
|
|
118
|
+
const storacha = getStorachaClient();
|
|
119
|
+
if (storacha.isEnabled() && await storacha.isAuthenticated()) {
|
|
120
|
+
logger.info(` đ No local metadata found, checking Storacha registry...`);
|
|
121
|
+
const latestCid = await storacha.getLatestCID(gitRepo);
|
|
122
|
+
if (latestCid) {
|
|
123
|
+
logger.info(` â
Found secrets in registry (CID: ${latestCid})`);
|
|
124
|
+
// Create metadata from registry
|
|
125
|
+
metadata = {
|
|
126
|
+
environment,
|
|
127
|
+
git_repo: gitRepo,
|
|
128
|
+
cid: latestCid,
|
|
129
|
+
timestamp: new Date().toISOString(),
|
|
130
|
+
keys_count: 0, // Unknown until decrypted
|
|
131
|
+
encrypted: true,
|
|
132
|
+
};
|
|
133
|
+
this.metadata[metadataKey] = metadata;
|
|
134
|
+
this.saveMetadata();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
// Registry check failed, continue to error
|
|
140
|
+
const err = error;
|
|
141
|
+
logger.debug(` Registry check failed: ${err.message}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
111
144
|
if (!metadata) {
|
|
112
|
-
throw new Error(`No secrets found for environment: ${
|
|
145
|
+
throw new Error(`No secrets found for environment: ${displayEnv}\n\n` +
|
|
146
|
+
`đĄ Tip: Check available environments with: lsh env\n` +
|
|
147
|
+
` Or push secrets first with: lsh push`);
|
|
113
148
|
}
|
|
114
149
|
// Check if there's a newer version in the registry (for git repos)
|
|
115
150
|
if (gitRepo) {
|
|
@@ -253,7 +288,7 @@ export class IPFSSecretsStorage {
|
|
|
253
288
|
/**
|
|
254
289
|
* Store encrypted data locally
|
|
255
290
|
*/
|
|
256
|
-
async storeLocally(cid, encryptedData,
|
|
291
|
+
async storeLocally(cid, encryptedData, _environment) {
|
|
257
292
|
const cachePath = path.join(this.cacheDir, `${cid}.encrypted`);
|
|
258
293
|
fs.writeFileSync(cachePath, encryptedData, 'utf8');
|
|
259
294
|
logger.debug(`Cached secrets locally: ${cachePath}`);
|
|
@@ -282,7 +282,7 @@ export class SecretsManager {
|
|
|
282
282
|
` Or push secrets first with: lsh push --env ${environment}`);
|
|
283
283
|
}
|
|
284
284
|
// Preserve local LSH-internal keys before overwriting
|
|
285
|
-
|
|
285
|
+
const localLshKeys = {};
|
|
286
286
|
if (fs.existsSync(envFilePath)) {
|
|
287
287
|
const existingContent = fs.readFileSync(envFilePath, 'utf8');
|
|
288
288
|
const existingEnv = this.parseEnvFile(existingContent);
|
|
@@ -244,12 +244,24 @@ export class StorachaClient {
|
|
|
244
244
|
if (!await this.isAuthenticated()) {
|
|
245
245
|
throw new Error('Not authenticated');
|
|
246
246
|
}
|
|
247
|
+
// Get the latest registry version and increment it
|
|
248
|
+
let registryVersion = 1;
|
|
249
|
+
try {
|
|
250
|
+
const latestRegistry = await this.getLatestRegistry(repoName);
|
|
251
|
+
if (latestRegistry && latestRegistry.registryVersion) {
|
|
252
|
+
registryVersion = latestRegistry.registryVersion + 1;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
catch (err) {
|
|
256
|
+
logger.debug(`Could not fetch latest registry version, using version 1: ${err.message}`);
|
|
257
|
+
}
|
|
247
258
|
const registry = {
|
|
248
259
|
repoName,
|
|
249
260
|
environment,
|
|
250
261
|
cid: secretsCid, // Include the secrets CID
|
|
251
262
|
timestamp: new Date().toISOString(),
|
|
252
|
-
version: '2.
|
|
263
|
+
version: '2.3.0', // LSH version
|
|
264
|
+
registryVersion, // Incremental version counter
|
|
253
265
|
};
|
|
254
266
|
const content = JSON.stringify(registry, null, 2);
|
|
255
267
|
const buffer = Buffer.from(content, 'utf-8');
|
|
@@ -259,7 +271,7 @@ export class StorachaClient {
|
|
|
259
271
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
260
272
|
const file = new File([uint8Array], filename, { type: 'application/json' });
|
|
261
273
|
const cid = await client.uploadFile(file);
|
|
262
|
-
logger.debug(`đ Uploaded registry for ${repoName} (secrets CID: ${secretsCid}): ${cid}`);
|
|
274
|
+
logger.debug(`đ Uploaded registry v${registryVersion} for ${repoName} (secrets CID: ${secretsCid}): ${cid}`);
|
|
263
275
|
return cid.toString();
|
|
264
276
|
}
|
|
265
277
|
/**
|
|
@@ -303,6 +315,7 @@ export class StorachaClient {
|
|
|
303
315
|
cid: cid,
|
|
304
316
|
timestamp: json.timestamp,
|
|
305
317
|
secretsCid: json.cid,
|
|
318
|
+
registryVersion: json.registryVersion || 0, // Default to 0 for old registries without version
|
|
306
319
|
});
|
|
307
320
|
}
|
|
308
321
|
}
|
|
@@ -311,11 +324,18 @@ export class StorachaClient {
|
|
|
311
324
|
continue;
|
|
312
325
|
}
|
|
313
326
|
}
|
|
314
|
-
// Sort by
|
|
327
|
+
// Sort by registryVersion (highest first), then timestamp as tie-breaker
|
|
315
328
|
if (registries.length > 0) {
|
|
316
|
-
registries.sort((a, b) =>
|
|
329
|
+
registries.sort((a, b) => {
|
|
330
|
+
// First compare by registryVersion (higher is newer)
|
|
331
|
+
if (b.registryVersion !== a.registryVersion) {
|
|
332
|
+
return b.registryVersion - a.registryVersion;
|
|
333
|
+
}
|
|
334
|
+
// If versions match, use timestamp as tie-breaker
|
|
335
|
+
return new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime();
|
|
336
|
+
});
|
|
317
337
|
const latest = registries[0];
|
|
318
|
-
logger.debug(`â
Found latest CID for ${repoName}: ${latest.secretsCid} (timestamp: ${latest.timestamp})`);
|
|
338
|
+
logger.debug(`â
Found latest CID for ${repoName}: ${latest.secretsCid} (v${latest.registryVersion}, timestamp: ${latest.timestamp})`);
|
|
319
339
|
return latest.secretsCid;
|
|
320
340
|
}
|
|
321
341
|
// No registry found
|
|
@@ -327,6 +347,76 @@ export class StorachaClient {
|
|
|
327
347
|
return null;
|
|
328
348
|
}
|
|
329
349
|
}
|
|
350
|
+
/**
|
|
351
|
+
* Get the latest registry object for a repo
|
|
352
|
+
* Returns the full registry object including registryVersion
|
|
353
|
+
*/
|
|
354
|
+
async getLatestRegistry(repoName) {
|
|
355
|
+
if (!this.isEnabled()) {
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
if (!await this.isAuthenticated()) {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
try {
|
|
362
|
+
const client = await this.getClient();
|
|
363
|
+
// Only check recent uploads (limit to 20 for performance)
|
|
364
|
+
const pageSize = 20;
|
|
365
|
+
// Get first page of uploads
|
|
366
|
+
const results = await client.capability.upload.list({
|
|
367
|
+
cursor: '',
|
|
368
|
+
size: pageSize,
|
|
369
|
+
});
|
|
370
|
+
// Collect all registry files for this repo
|
|
371
|
+
const registries = [];
|
|
372
|
+
for (const upload of results.results) {
|
|
373
|
+
try {
|
|
374
|
+
const cid = upload.root.toString();
|
|
375
|
+
// Download with timeout
|
|
376
|
+
const downloadPromise = this.download(cid);
|
|
377
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 5000));
|
|
378
|
+
const content = await Promise.race([downloadPromise, timeoutPromise]);
|
|
379
|
+
// Skip large files (registry should be < 1KB)
|
|
380
|
+
if (content.length > 1024) {
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
// Try to parse as JSON
|
|
384
|
+
const json = JSON.parse(content.toString('utf-8'));
|
|
385
|
+
// Check if it's an LSH registry file for our repo
|
|
386
|
+
if (json.repoName === repoName && json.version && json.cid && json.timestamp) {
|
|
387
|
+
registries.push({
|
|
388
|
+
repoName: json.repoName,
|
|
389
|
+
environment: json.environment,
|
|
390
|
+
cid: json.cid,
|
|
391
|
+
timestamp: json.timestamp,
|
|
392
|
+
version: json.version,
|
|
393
|
+
registryVersion: json.registryVersion || 0,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
// Not an LSH registry file or failed to download
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
// Sort by registryVersion (highest first), then timestamp as tie-breaker
|
|
403
|
+
if (registries.length > 0) {
|
|
404
|
+
registries.sort((a, b) => {
|
|
405
|
+
if (b.registryVersion !== a.registryVersion) {
|
|
406
|
+
return b.registryVersion - a.registryVersion;
|
|
407
|
+
}
|
|
408
|
+
return new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime();
|
|
409
|
+
});
|
|
410
|
+
return registries[0];
|
|
411
|
+
}
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
catch (error) {
|
|
415
|
+
const err = error;
|
|
416
|
+
logger.debug(`Failed to get latest registry: ${err.message}`);
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
330
420
|
/**
|
|
331
421
|
* Check if registry exists for a repo by listing uploads
|
|
332
422
|
* Returns true if a registry file for this repo exists in Storacha
|
package/dist/services/lib/lib.js
CHANGED
|
@@ -18,53 +18,6 @@ export async function loadCommands() {
|
|
|
18
18
|
const cmdMap = await parseCommands(files);
|
|
19
19
|
return cmdMap;
|
|
20
20
|
}
|
|
21
|
-
async function _makeCommand(commander) {
|
|
22
|
-
const _commands = await loadCommands();
|
|
23
|
-
commander.command("jug").action(() => {
|
|
24
|
-
console.log("heat jug");
|
|
25
|
-
});
|
|
26
|
-
commander.command("pot").action(() => {
|
|
27
|
-
console.log("heat pot");
|
|
28
|
-
});
|
|
29
|
-
return commander;
|
|
30
|
-
}
|
|
31
|
-
// export async function init_lib_cmd(program: Command) {
|
|
32
|
-
// const brew = program.command("lib");
|
|
33
|
-
// // const commands = await loadCommands();
|
|
34
|
-
// // await set(lsh.commands, commands);
|
|
35
|
-
// brew.command("tea").action(() => {
|
|
36
|
-
// console.log("brew tea");
|
|
37
|
-
// });
|
|
38
|
-
// brew.command("coffee").action(() => {
|
|
39
|
-
// console.log("brew coffee");
|
|
40
|
-
// });
|
|
41
|
-
// await makeCommand(brew);
|
|
42
|
-
// // for (let c in commands) {
|
|
43
|
-
// // brew.command(c).action(() => console.log(c));
|
|
44
|
-
// // }
|
|
45
|
-
// // .command("lib").description("lsh lib commands");
|
|
46
|
-
// // lib
|
|
47
|
-
// // .showHelpAfterError(true)
|
|
48
|
-
// // .showSuggestionAfterError(true);
|
|
49
|
-
// // const commands = await loadCommands();
|
|
50
|
-
// // set(lsh.commands, commands);
|
|
51
|
-
// // for (const [key, value] of Object.entries(get(lsh.commands))) {
|
|
52
|
-
// // // console.log(`${key} : ${value}`);
|
|
53
|
-
// // lib.command(key).action(() => {console.log(value)});
|
|
54
|
-
// // };
|
|
55
|
-
// // .action(async (type: String, action: String, spec: Spec) => {
|
|
56
|
-
// // const commands = await loadCommands();
|
|
57
|
-
// // set(lsh.commands, commands);
|
|
58
|
-
// // switch (type) {
|
|
59
|
-
// // case "ls":
|
|
60
|
-
// // // console.log("lsh called");
|
|
61
|
-
// // // console.log(get(lsh.commands)['rand']());
|
|
62
|
-
// // break;
|
|
63
|
-
// // default:
|
|
64
|
-
// // console.log("default");
|
|
65
|
-
// // }
|
|
66
|
-
// // });
|
|
67
|
-
// }
|
|
68
21
|
export async function init_lib(program) {
|
|
69
22
|
const lib = program
|
|
70
23
|
.command("lib")
|
|
@@ -842,5 +842,208 @@ API_KEY=
|
|
|
842
842
|
process.exit(1);
|
|
843
843
|
}
|
|
844
844
|
});
|
|
845
|
+
// Clear stuck registries and local metadata
|
|
846
|
+
program
|
|
847
|
+
.command('clear')
|
|
848
|
+
.description('Clear local metadata and cache to resolve stuck registries')
|
|
849
|
+
.option('--repo <name>', 'Clear metadata for specific repo only')
|
|
850
|
+
.option('--cache', 'Also clear local encrypted secrets cache')
|
|
851
|
+
.option('--storacha', 'Also delete old Storacha uploads (registries and secrets)')
|
|
852
|
+
.option('--all', 'Clear all metadata and cache (requires confirmation)')
|
|
853
|
+
.option('-y, --yes', 'Skip confirmation prompts')
|
|
854
|
+
.action(async (options) => {
|
|
855
|
+
try {
|
|
856
|
+
const lshDir = path.join(process.env.HOME || process.env.USERPROFILE || '', '.lsh');
|
|
857
|
+
const metadataPath = path.join(lshDir, 'secrets-metadata.json');
|
|
858
|
+
const cacheDir = path.join(lshDir, 'secrets-cache');
|
|
859
|
+
// Determine what we're clearing
|
|
860
|
+
if (!options.repo && !options.all) {
|
|
861
|
+
console.error('â Please specify either --repo <name> or --all');
|
|
862
|
+
console.log('');
|
|
863
|
+
console.log('Examples:');
|
|
864
|
+
console.log(' lsh clear --repo lsh_test_repo # Clear metadata for specific repo');
|
|
865
|
+
console.log(' lsh clear --all # Clear all metadata');
|
|
866
|
+
console.log(' lsh clear --all --cache # Clear metadata and cache');
|
|
867
|
+
process.exit(1);
|
|
868
|
+
}
|
|
869
|
+
// Load metadata
|
|
870
|
+
if (!fs.existsSync(metadataPath)) {
|
|
871
|
+
console.log('âšī¸ No metadata file found - nothing to clear');
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf-8'));
|
|
875
|
+
const keys = Object.keys(metadata);
|
|
876
|
+
if (keys.length === 0) {
|
|
877
|
+
console.log('âšī¸ Metadata is already empty');
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
// Show what will be cleared
|
|
881
|
+
console.log('đ Current metadata entries:');
|
|
882
|
+
console.log('');
|
|
883
|
+
if (options.repo) {
|
|
884
|
+
const repoKeys = keys.filter(k => metadata[k].git_repo === options.repo);
|
|
885
|
+
if (repoKeys.length === 0) {
|
|
886
|
+
console.log(`âšī¸ No metadata found for repo: ${options.repo}`);
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
console.log(`Repo: ${options.repo}`);
|
|
890
|
+
repoKeys.forEach(key => {
|
|
891
|
+
console.log(` - ${key} (CID: ${metadata[key].cid.substring(0, 12)}...)`);
|
|
892
|
+
});
|
|
893
|
+
console.log('');
|
|
894
|
+
console.log(`Will clear ${repoKeys.length} ${repoKeys.length === 1 ? 'entry' : 'entries'}`);
|
|
895
|
+
}
|
|
896
|
+
else {
|
|
897
|
+
const repoCount = new Set(keys.map(k => metadata[k].git_repo)).size;
|
|
898
|
+
console.log(`Total entries: ${keys.length} across ${repoCount} ${repoCount === 1 ? 'repo' : 'repos'}`);
|
|
899
|
+
}
|
|
900
|
+
if (options.cache) {
|
|
901
|
+
if (fs.existsSync(cacheDir)) {
|
|
902
|
+
const cacheFiles = fs.readdirSync(cacheDir);
|
|
903
|
+
console.log(`Cache files: ${cacheFiles.length}`);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
console.log('');
|
|
907
|
+
// Confirmation
|
|
908
|
+
if (!options.yes) {
|
|
909
|
+
console.log('â ī¸ WARNING: This will clear local metadata!');
|
|
910
|
+
console.log('');
|
|
911
|
+
console.log('This is useful when:');
|
|
912
|
+
console.log(' âĸ Registry is returning stale/old CIDs');
|
|
913
|
+
console.log(' âĸ Pull fails with "bad decrypt" errors');
|
|
914
|
+
console.log(' âĸ You need to force a fresh sync');
|
|
915
|
+
console.log('');
|
|
916
|
+
console.log('After clearing, you will need to push secrets again.');
|
|
917
|
+
console.log('');
|
|
918
|
+
const rl = readline.createInterface({
|
|
919
|
+
input: process.stdin,
|
|
920
|
+
output: process.stdout,
|
|
921
|
+
});
|
|
922
|
+
const answer = await new Promise((resolve) => {
|
|
923
|
+
rl.question('Continue? (yes/no): ', (ans) => {
|
|
924
|
+
rl.close();
|
|
925
|
+
resolve(ans.trim().toLowerCase());
|
|
926
|
+
});
|
|
927
|
+
});
|
|
928
|
+
if (answer !== 'yes' && answer !== 'y') {
|
|
929
|
+
console.log('');
|
|
930
|
+
console.log('â Cancelled');
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
console.log('');
|
|
935
|
+
// Clear metadata
|
|
936
|
+
if (options.repo) {
|
|
937
|
+
const repoKeys = keys.filter(k => metadata[k].git_repo === options.repo);
|
|
938
|
+
repoKeys.forEach(key => delete metadata[key]);
|
|
939
|
+
fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));
|
|
940
|
+
console.log(`â
Cleared ${repoKeys.length} metadata ${repoKeys.length === 1 ? 'entry' : 'entries'} for ${options.repo}`);
|
|
941
|
+
}
|
|
942
|
+
else {
|
|
943
|
+
fs.writeFileSync(metadataPath, '{}');
|
|
944
|
+
console.log(`â
Cleared all ${keys.length} metadata ${keys.length === 1 ? 'entry' : 'entries'}`);
|
|
945
|
+
}
|
|
946
|
+
// Clear cache if requested
|
|
947
|
+
if (options.cache && fs.existsSync(cacheDir)) {
|
|
948
|
+
const cacheFiles = fs.readdirSync(cacheDir);
|
|
949
|
+
let cleared = 0;
|
|
950
|
+
for (const file of cacheFiles) {
|
|
951
|
+
const filePath = path.join(cacheDir, file);
|
|
952
|
+
if (fs.statSync(filePath).isFile()) {
|
|
953
|
+
fs.unlinkSync(filePath);
|
|
954
|
+
cleared++;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
console.log(`â
Cleared ${cleared} cache ${cleared === 1 ? 'file' : 'files'}`);
|
|
958
|
+
}
|
|
959
|
+
// Clear Storacha uploads if requested
|
|
960
|
+
if (options.storacha && options.repo) {
|
|
961
|
+
console.log('');
|
|
962
|
+
console.log('đ Clearing Storacha uploads...');
|
|
963
|
+
try {
|
|
964
|
+
const { StorachaClient } = await import('../../lib/storacha-client.js');
|
|
965
|
+
const storacha = new StorachaClient();
|
|
966
|
+
if (!storacha.isEnabled()) {
|
|
967
|
+
console.log('âšī¸ Storacha is not enabled - skipping cloud cleanup');
|
|
968
|
+
}
|
|
969
|
+
else if (!(await storacha.isAuthenticated())) {
|
|
970
|
+
console.log('âšī¸ Not authenticated with Storacha - skipping cloud cleanup');
|
|
971
|
+
}
|
|
972
|
+
else {
|
|
973
|
+
// Get all uploads
|
|
974
|
+
const client = await storacha.getClient();
|
|
975
|
+
const pageSize = 50;
|
|
976
|
+
const results = await client.capability.upload.list({
|
|
977
|
+
cursor: '',
|
|
978
|
+
size: pageSize,
|
|
979
|
+
});
|
|
980
|
+
// Find LSH-related uploads for this repo
|
|
981
|
+
const toDelete = [];
|
|
982
|
+
for (const upload of results.results) {
|
|
983
|
+
try {
|
|
984
|
+
const cid = upload.root.toString();
|
|
985
|
+
// Download with timeout
|
|
986
|
+
const downloadPromise = storacha.download(cid);
|
|
987
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 5000));
|
|
988
|
+
const content = await Promise.race([downloadPromise, timeoutPromise]);
|
|
989
|
+
// Check if it's a registry file for this repo
|
|
990
|
+
if (content.length < 2048) {
|
|
991
|
+
try {
|
|
992
|
+
const json = JSON.parse(content.toString('utf-8'));
|
|
993
|
+
if (json.repoName === options.repo) {
|
|
994
|
+
toDelete.push({ cid, type: 'registry', size: content.length });
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
catch {
|
|
998
|
+
// Not JSON, might be encrypted secrets
|
|
999
|
+
// Check filename pattern
|
|
1000
|
+
const _filename = `lsh-secrets-${options.repo}`;
|
|
1001
|
+
if (cid.includes(options.repo) || content.toString().includes(options.repo)) {
|
|
1002
|
+
toDelete.push({ cid, type: 'secrets', size: content.length });
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
catch {
|
|
1008
|
+
// Failed to download or parse, skip
|
|
1009
|
+
continue;
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
if (toDelete.length > 0) {
|
|
1013
|
+
console.log(`Found ${toDelete.length} Storacha ${toDelete.length === 1 ? 'upload' : 'uploads'} for ${options.repo}:`);
|
|
1014
|
+
toDelete.forEach((item) => {
|
|
1015
|
+
console.log(` - ${item.type}: ${item.cid.substring(0, 16)}... (${item.size} bytes)`);
|
|
1016
|
+
});
|
|
1017
|
+
// Note: Storacha doesn't currently support deletion via SDK
|
|
1018
|
+
// The uploads will remain but won't be used after metadata is cleared
|
|
1019
|
+
console.log('');
|
|
1020
|
+
console.log('â ī¸ Note: Storacha uploads cannot be deleted programmatically.');
|
|
1021
|
+
console.log(' These files will remain in Storacha but won\'t be used after metadata is cleared.');
|
|
1022
|
+
console.log(' To fully remove them, use the Storacha web console:');
|
|
1023
|
+
console.log(' https://console.storacha.network/');
|
|
1024
|
+
}
|
|
1025
|
+
else {
|
|
1026
|
+
console.log(`âšī¸ No Storacha uploads found for ${options.repo}`);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
catch (storageError) {
|
|
1031
|
+
const storErr = storageError;
|
|
1032
|
+
console.error(`â ī¸ Failed to check Storacha uploads: ${storErr.message}`);
|
|
1033
|
+
console.log(' Local metadata has been cleared, but cloud uploads may remain.');
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
console.log('');
|
|
1037
|
+
console.log('đĄ Next steps:');
|
|
1038
|
+
console.log(' 1. lsh push .env # Push secrets with current key');
|
|
1039
|
+
console.log(' 2. lsh pull .env # Verify pull works');
|
|
1040
|
+
console.log('');
|
|
1041
|
+
}
|
|
1042
|
+
catch (error) {
|
|
1043
|
+
const err = error;
|
|
1044
|
+
console.error('â Failed to clear metadata:', err.message);
|
|
1045
|
+
process.exit(1);
|
|
1046
|
+
}
|
|
1047
|
+
});
|
|
845
1048
|
}
|
|
846
1049
|
export default init_secrets;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lsh-framework",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.2",
|
|
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": {
|