@calimero-network/registry-cli 1.0.0 → 1.1.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 +131 -0
- package/dist/index.js +1116 -69
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -1,20 +1,664 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
-
import
|
|
4
|
-
import
|
|
3
|
+
import chalk5 from 'chalk';
|
|
4
|
+
import ora6 from 'ora';
|
|
5
5
|
import { table } from 'table';
|
|
6
6
|
import { SSAppRegistryClient } from '@calimero-network/registry-client';
|
|
7
|
-
import
|
|
7
|
+
import fs3, { existsSync, writeFileSync } from 'fs';
|
|
8
8
|
import path from 'path';
|
|
9
|
+
import os from 'os';
|
|
10
|
+
import crypto from 'crypto';
|
|
11
|
+
import fastify from 'fastify';
|
|
12
|
+
import cors from '@fastify/cors';
|
|
9
13
|
|
|
14
|
+
var LocalConfig = class {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.configPath = path.join(
|
|
17
|
+
os.homedir(),
|
|
18
|
+
".calimero-registry",
|
|
19
|
+
"config.json"
|
|
20
|
+
);
|
|
21
|
+
this.dataDir = path.join(os.homedir(), ".calimero-registry");
|
|
22
|
+
this.config = this.loadConfig();
|
|
23
|
+
}
|
|
24
|
+
loadConfig() {
|
|
25
|
+
const defaultConfig = {
|
|
26
|
+
server: {
|
|
27
|
+
port: 8082,
|
|
28
|
+
host: "localhost"
|
|
29
|
+
},
|
|
30
|
+
data: {
|
|
31
|
+
dir: path.join(os.homedir(), ".calimero-registry", "data"),
|
|
32
|
+
artifactsDir: path.join(
|
|
33
|
+
os.homedir(),
|
|
34
|
+
".calimero-registry",
|
|
35
|
+
"artifacts"
|
|
36
|
+
)
|
|
37
|
+
},
|
|
38
|
+
artifacts: {
|
|
39
|
+
storageDir: path.join(os.homedir(), ".calimero-registry", "artifacts"),
|
|
40
|
+
serveLocal: true,
|
|
41
|
+
copyArtifacts: true,
|
|
42
|
+
maxFileSize: "100MB",
|
|
43
|
+
allowedTypes: ["wasm", "js", "html"]
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
if (fs3.existsSync(this.configPath)) {
|
|
47
|
+
try {
|
|
48
|
+
const existingConfig = JSON.parse(
|
|
49
|
+
fs3.readFileSync(this.configPath, "utf8")
|
|
50
|
+
);
|
|
51
|
+
return { ...defaultConfig, ...existingConfig };
|
|
52
|
+
} catch {
|
|
53
|
+
console.warn("Failed to load existing config, using defaults");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return defaultConfig;
|
|
57
|
+
}
|
|
58
|
+
saveConfig() {
|
|
59
|
+
const configDir = path.dirname(this.configPath);
|
|
60
|
+
if (!fs3.existsSync(configDir)) {
|
|
61
|
+
fs3.mkdirSync(configDir, { recursive: true });
|
|
62
|
+
}
|
|
63
|
+
fs3.writeFileSync(this.configPath, JSON.stringify(this.config, null, 2));
|
|
64
|
+
}
|
|
65
|
+
// Server configuration
|
|
66
|
+
getPort() {
|
|
67
|
+
return this.config.server.port;
|
|
68
|
+
}
|
|
69
|
+
setPort(port) {
|
|
70
|
+
this.config.server.port = port;
|
|
71
|
+
this.saveConfig();
|
|
72
|
+
}
|
|
73
|
+
getHost() {
|
|
74
|
+
return this.config.server.host;
|
|
75
|
+
}
|
|
76
|
+
setHost(host) {
|
|
77
|
+
this.config.server.host = host;
|
|
78
|
+
this.saveConfig();
|
|
79
|
+
}
|
|
80
|
+
// Data directory configuration
|
|
81
|
+
getDataDir() {
|
|
82
|
+
return this.config.data.dir;
|
|
83
|
+
}
|
|
84
|
+
setDataDir(dir) {
|
|
85
|
+
this.config.data.dir = dir;
|
|
86
|
+
this.config.artifacts.storageDir = path.join(dir, "artifacts");
|
|
87
|
+
this.saveConfig();
|
|
88
|
+
}
|
|
89
|
+
getArtifactsDir() {
|
|
90
|
+
return this.config.data.artifactsDir;
|
|
91
|
+
}
|
|
92
|
+
setArtifactsDir(dir) {
|
|
93
|
+
this.config.data.artifactsDir = dir;
|
|
94
|
+
this.config.artifacts.storageDir = dir;
|
|
95
|
+
this.saveConfig();
|
|
96
|
+
}
|
|
97
|
+
// Artifact configuration
|
|
98
|
+
getArtifactsConfig() {
|
|
99
|
+
return this.config.artifacts;
|
|
100
|
+
}
|
|
101
|
+
setArtifactsConfig(config) {
|
|
102
|
+
this.config.artifacts = { ...this.config.artifacts, ...config };
|
|
103
|
+
this.saveConfig();
|
|
104
|
+
}
|
|
105
|
+
// Utility methods
|
|
106
|
+
ensureDirectories() {
|
|
107
|
+
const dirs = [
|
|
108
|
+
this.getDataDir(),
|
|
109
|
+
this.getArtifactsDir(),
|
|
110
|
+
path.join(this.getDataDir(), "backups"),
|
|
111
|
+
path.join(this.getDataDir(), "logs")
|
|
112
|
+
];
|
|
113
|
+
dirs.forEach((dir) => {
|
|
114
|
+
if (!fs3.existsSync(dir)) {
|
|
115
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
getConfigPath() {
|
|
120
|
+
return this.configPath;
|
|
121
|
+
}
|
|
122
|
+
// Get full configuration
|
|
123
|
+
getConfig() {
|
|
124
|
+
return { ...this.config };
|
|
125
|
+
}
|
|
126
|
+
// Reset to defaults
|
|
127
|
+
reset() {
|
|
128
|
+
this.config = {
|
|
129
|
+
server: {
|
|
130
|
+
port: 8082,
|
|
131
|
+
host: "localhost"
|
|
132
|
+
},
|
|
133
|
+
data: {
|
|
134
|
+
dir: path.join(os.homedir(), ".calimero-registry", "data"),
|
|
135
|
+
artifactsDir: path.join(
|
|
136
|
+
os.homedir(),
|
|
137
|
+
".calimero-registry",
|
|
138
|
+
"artifacts"
|
|
139
|
+
)
|
|
140
|
+
},
|
|
141
|
+
artifacts: {
|
|
142
|
+
storageDir: path.join(os.homedir(), ".calimero-registry", "artifacts"),
|
|
143
|
+
serveLocal: true,
|
|
144
|
+
copyArtifacts: true,
|
|
145
|
+
maxFileSize: "100MB",
|
|
146
|
+
allowedTypes: ["wasm", "js", "html"]
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
this.saveConfig();
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
var LocalDataStore = class {
|
|
153
|
+
constructor(config) {
|
|
154
|
+
this.config = config;
|
|
155
|
+
this.appsFile = path.join(config.getDataDir(), "apps.json");
|
|
156
|
+
this.manifestsFile = path.join(config.getDataDir(), "manifests.json");
|
|
157
|
+
this.artifactsFile = path.join(config.getDataDir(), "artifacts.json");
|
|
158
|
+
this.data = {
|
|
159
|
+
apps: /* @__PURE__ */ new Map(),
|
|
160
|
+
manifests: /* @__PURE__ */ new Map(),
|
|
161
|
+
artifacts: /* @__PURE__ */ new Map()
|
|
162
|
+
};
|
|
163
|
+
this.loadData();
|
|
164
|
+
}
|
|
165
|
+
loadData() {
|
|
166
|
+
try {
|
|
167
|
+
if (fs3.existsSync(this.appsFile)) {
|
|
168
|
+
const appsData = JSON.parse(fs3.readFileSync(this.appsFile, "utf8"));
|
|
169
|
+
this.data.apps = new Map(Object.entries(appsData));
|
|
170
|
+
}
|
|
171
|
+
if (fs3.existsSync(this.manifestsFile)) {
|
|
172
|
+
const manifestsData = JSON.parse(
|
|
173
|
+
fs3.readFileSync(this.manifestsFile, "utf8")
|
|
174
|
+
);
|
|
175
|
+
this.data.manifests = new Map(Object.entries(manifestsData));
|
|
176
|
+
}
|
|
177
|
+
if (fs3.existsSync(this.artifactsFile)) {
|
|
178
|
+
const artifactsData = JSON.parse(
|
|
179
|
+
fs3.readFileSync(this.artifactsFile, "utf8")
|
|
180
|
+
);
|
|
181
|
+
this.data.artifacts = new Map(Object.entries(artifactsData));
|
|
182
|
+
}
|
|
183
|
+
} catch {
|
|
184
|
+
console.warn("Failed to load existing data, starting fresh");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
saveData() {
|
|
188
|
+
try {
|
|
189
|
+
this.config.ensureDirectories();
|
|
190
|
+
const appsObj = Object.fromEntries(this.data.apps);
|
|
191
|
+
fs3.writeFileSync(this.appsFile, JSON.stringify(appsObj, null, 2));
|
|
192
|
+
const manifestsObj = Object.fromEntries(this.data.manifests);
|
|
193
|
+
fs3.writeFileSync(
|
|
194
|
+
this.manifestsFile,
|
|
195
|
+
JSON.stringify(manifestsObj, null, 2)
|
|
196
|
+
);
|
|
197
|
+
const artifactsObj = Object.fromEntries(this.data.artifacts);
|
|
198
|
+
fs3.writeFileSync(
|
|
199
|
+
this.artifactsFile,
|
|
200
|
+
JSON.stringify(artifactsObj, null, 2)
|
|
201
|
+
);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
throw new Error(
|
|
204
|
+
`Failed to save data: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// Apps management
|
|
209
|
+
getApps(filters) {
|
|
210
|
+
let apps = Array.from(this.data.apps.values());
|
|
211
|
+
if (filters?.dev) {
|
|
212
|
+
apps = apps.filter((app) => app.developer_pubkey === filters.dev);
|
|
213
|
+
}
|
|
214
|
+
if (filters?.name) {
|
|
215
|
+
apps = apps.filter((app) => app.name.includes(filters.name));
|
|
216
|
+
}
|
|
217
|
+
return apps;
|
|
218
|
+
}
|
|
219
|
+
getApp(appKey) {
|
|
220
|
+
return this.data.apps.get(appKey);
|
|
221
|
+
}
|
|
222
|
+
setApp(appKey, app) {
|
|
223
|
+
this.data.apps.set(appKey, app);
|
|
224
|
+
this.saveData();
|
|
225
|
+
}
|
|
226
|
+
// Versions management
|
|
227
|
+
getAppVersions(appId) {
|
|
228
|
+
const versions = [];
|
|
229
|
+
for (const [key, manifest] of this.data.manifests.entries()) {
|
|
230
|
+
if (manifest.app.id === appId) {
|
|
231
|
+
const semver = key.split("/").pop();
|
|
232
|
+
versions.push({
|
|
233
|
+
semver,
|
|
234
|
+
cid: manifest.artifacts[0]?.cid || "",
|
|
235
|
+
yanked: false
|
|
236
|
+
// TODO: Implement yanking
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return versions.sort(
|
|
241
|
+
(a, b) => a.semver.localeCompare(b.semver, void 0, { numeric: true })
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
// Manifest management
|
|
245
|
+
getManifest(appId, semver) {
|
|
246
|
+
for (const [, manifest] of this.data.manifests.entries()) {
|
|
247
|
+
if (manifest.app.id === appId && manifest.version.semver === semver) {
|
|
248
|
+
return manifest;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return void 0;
|
|
252
|
+
}
|
|
253
|
+
setManifest(manifestKey, manifest) {
|
|
254
|
+
this.data.manifests.set(manifestKey, manifest);
|
|
255
|
+
this.saveData();
|
|
256
|
+
}
|
|
257
|
+
// Artifact management
|
|
258
|
+
getArtifactPath(hash) {
|
|
259
|
+
return this.data.artifacts.get(hash);
|
|
260
|
+
}
|
|
261
|
+
setArtifactPath(hash, filePath) {
|
|
262
|
+
this.data.artifacts.set(hash, filePath);
|
|
263
|
+
this.saveData();
|
|
264
|
+
}
|
|
265
|
+
// Statistics
|
|
266
|
+
getStats() {
|
|
267
|
+
return {
|
|
268
|
+
publishedApps: this.data.apps.size,
|
|
269
|
+
totalVersions: this.data.manifests.size,
|
|
270
|
+
totalArtifacts: this.data.artifacts.size
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
// Backup and restore
|
|
274
|
+
async backup(outputPath) {
|
|
275
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
276
|
+
const backupPath = outputPath || path.join(
|
|
277
|
+
this.config.getDataDir(),
|
|
278
|
+
"backups",
|
|
279
|
+
`backup-${timestamp}.json`
|
|
280
|
+
);
|
|
281
|
+
const backupDir = path.dirname(backupPath);
|
|
282
|
+
if (!fs3.existsSync(backupDir)) {
|
|
283
|
+
fs3.mkdirSync(backupDir, { recursive: true });
|
|
284
|
+
}
|
|
285
|
+
const backupData = {
|
|
286
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
287
|
+
apps: Object.fromEntries(this.data.apps),
|
|
288
|
+
manifests: Object.fromEntries(this.data.manifests),
|
|
289
|
+
artifacts: Object.fromEntries(this.data.artifacts)
|
|
290
|
+
};
|
|
291
|
+
fs3.writeFileSync(backupPath, JSON.stringify(backupData, null, 2));
|
|
292
|
+
return backupPath;
|
|
293
|
+
}
|
|
294
|
+
async restore(backupPath) {
|
|
295
|
+
if (!fs3.existsSync(backupPath)) {
|
|
296
|
+
throw new Error(`Backup file not found: ${backupPath}`);
|
|
297
|
+
}
|
|
298
|
+
const backupData = JSON.parse(fs3.readFileSync(backupPath, "utf8"));
|
|
299
|
+
this.data.apps = new Map(Object.entries(backupData.apps || {}));
|
|
300
|
+
this.data.manifests = new Map(Object.entries(backupData.manifests || {}));
|
|
301
|
+
this.data.artifacts = new Map(Object.entries(backupData.artifacts || {}));
|
|
302
|
+
this.saveData();
|
|
303
|
+
}
|
|
304
|
+
// Reset data
|
|
305
|
+
reset() {
|
|
306
|
+
this.data.apps.clear();
|
|
307
|
+
this.data.manifests.clear();
|
|
308
|
+
this.data.artifacts.clear();
|
|
309
|
+
this.saveData();
|
|
310
|
+
}
|
|
311
|
+
// Seed with sample data
|
|
312
|
+
async seed() {
|
|
313
|
+
const sampleApps = [
|
|
314
|
+
{
|
|
315
|
+
name: "sample-wallet",
|
|
316
|
+
developer_pubkey: "ed25519:5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
|
|
317
|
+
latest_version: "1.0.0",
|
|
318
|
+
latest_cid: "QmSampleWallet123",
|
|
319
|
+
alias: "Sample Wallet"
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
name: "demo-dex",
|
|
323
|
+
developer_pubkey: "ed25519:5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty",
|
|
324
|
+
latest_version: "2.1.0",
|
|
325
|
+
latest_cid: "QmDemoDex456",
|
|
326
|
+
alias: "Demo DEX"
|
|
327
|
+
}
|
|
328
|
+
];
|
|
329
|
+
const sampleManifests = [
|
|
330
|
+
{
|
|
331
|
+
manifest_version: "1.0",
|
|
332
|
+
app: {
|
|
333
|
+
name: "sample-wallet",
|
|
334
|
+
namespace: "com.example",
|
|
335
|
+
developer_pubkey: "ed25519:5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
|
|
336
|
+
id: "sample-wallet-id",
|
|
337
|
+
alias: "Sample Wallet"
|
|
338
|
+
},
|
|
339
|
+
version: {
|
|
340
|
+
semver: "1.0.0"
|
|
341
|
+
},
|
|
342
|
+
supported_chains: ["mainnet", "testnet"],
|
|
343
|
+
permissions: [
|
|
344
|
+
{ cap: "wallet", bytes: 1024 },
|
|
345
|
+
{ cap: "network", bytes: 512 }
|
|
346
|
+
],
|
|
347
|
+
artifacts: [
|
|
348
|
+
{
|
|
349
|
+
type: "wasm",
|
|
350
|
+
target: "node",
|
|
351
|
+
path: "/artifacts/sample-wallet/1.0.0/app.wasm",
|
|
352
|
+
size: 1024e3,
|
|
353
|
+
sha256: "abc123def456"
|
|
354
|
+
}
|
|
355
|
+
],
|
|
356
|
+
metadata: {
|
|
357
|
+
description: "A sample wallet application for testing",
|
|
358
|
+
author: "Sample Developer"
|
|
359
|
+
},
|
|
360
|
+
distribution: "local",
|
|
361
|
+
signature: {
|
|
362
|
+
alg: "ed25519",
|
|
363
|
+
sig: "sample-signature",
|
|
364
|
+
signed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
];
|
|
368
|
+
sampleApps.forEach((app) => {
|
|
369
|
+
const appKey = `${app.developer_pubkey}/${app.name}`;
|
|
370
|
+
this.setApp(appKey, app);
|
|
371
|
+
});
|
|
372
|
+
sampleManifests.forEach((manifest) => {
|
|
373
|
+
const manifestKey = `${manifest.app.developer_pubkey}/${manifest.app.name}/${manifest.version.semver}`;
|
|
374
|
+
this.setManifest(manifestKey, manifest);
|
|
375
|
+
});
|
|
376
|
+
this.saveData();
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
var LocalArtifactServer = class {
|
|
380
|
+
constructor(config, dataStore) {
|
|
381
|
+
this.config = config;
|
|
382
|
+
this.dataStore = dataStore;
|
|
383
|
+
this.artifactsDir = config.getArtifactsDir();
|
|
384
|
+
this.ensureArtifactsDir();
|
|
385
|
+
}
|
|
386
|
+
ensureArtifactsDir() {
|
|
387
|
+
if (!fs3.existsSync(this.artifactsDir)) {
|
|
388
|
+
fs3.mkdirSync(this.artifactsDir, { recursive: true });
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
// Copy artifact to local storage
|
|
392
|
+
async copyArtifactToLocal(sourcePath, appId, version, filename) {
|
|
393
|
+
if (!fs3.existsSync(sourcePath)) {
|
|
394
|
+
throw new Error(`Source file not found: ${sourcePath}`);
|
|
395
|
+
}
|
|
396
|
+
const appVersionDir = path.join(this.artifactsDir, appId, version);
|
|
397
|
+
if (!fs3.existsSync(appVersionDir)) {
|
|
398
|
+
fs3.mkdirSync(appVersionDir, { recursive: true });
|
|
399
|
+
}
|
|
400
|
+
const targetPath = path.join(appVersionDir, filename);
|
|
401
|
+
fs3.copyFileSync(sourcePath, targetPath);
|
|
402
|
+
const fileHash = await this.calculateFileHash(targetPath);
|
|
403
|
+
this.dataStore.setArtifactPath(fileHash, targetPath);
|
|
404
|
+
return targetPath;
|
|
405
|
+
}
|
|
406
|
+
// Serve artifact by app ID, version, and filename
|
|
407
|
+
async serveArtifact(appId, version, filename) {
|
|
408
|
+
const artifactPath = path.join(this.artifactsDir, appId, version, filename);
|
|
409
|
+
if (!fs3.existsSync(artifactPath)) {
|
|
410
|
+
throw new Error(`Artifact not found: ${artifactPath}`);
|
|
411
|
+
}
|
|
412
|
+
return fs3.readFileSync(artifactPath);
|
|
413
|
+
}
|
|
414
|
+
// Serve artifact by hash
|
|
415
|
+
async serveByHash(hash) {
|
|
416
|
+
const artifactPath = this.dataStore.getArtifactPath(hash);
|
|
417
|
+
if (!artifactPath || !fs3.existsSync(artifactPath)) {
|
|
418
|
+
throw new Error(`Artifact not found for hash: ${hash}`);
|
|
419
|
+
}
|
|
420
|
+
return fs3.readFileSync(artifactPath);
|
|
421
|
+
}
|
|
422
|
+
// Validate artifact file
|
|
423
|
+
async validateArtifact(filePath) {
|
|
424
|
+
if (!fs3.existsSync(filePath)) {
|
|
425
|
+
throw new Error(`File not found: ${filePath}`);
|
|
426
|
+
}
|
|
427
|
+
const stats = fs3.statSync(filePath);
|
|
428
|
+
const fileBuffer = fs3.readFileSync(filePath);
|
|
429
|
+
const sha256 = crypto.createHash("sha256").update(fileBuffer).digest("hex");
|
|
430
|
+
return {
|
|
431
|
+
size: stats.size,
|
|
432
|
+
sha256
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
// Calculate file hash
|
|
436
|
+
async calculateFileHash(filePath) {
|
|
437
|
+
const fileBuffer = fs3.readFileSync(filePath);
|
|
438
|
+
return crypto.createHash("sha256").update(fileBuffer).digest("hex");
|
|
439
|
+
}
|
|
440
|
+
// Get artifact URL for local serving
|
|
441
|
+
getArtifactUrl(appId, version, filename) {
|
|
442
|
+
const baseUrl = `http://${this.config.getHost()}:${this.config.getPort()}`;
|
|
443
|
+
return `${baseUrl}/artifacts/${appId}/${version}/${filename}`;
|
|
444
|
+
}
|
|
445
|
+
// Get artifact URL by hash
|
|
446
|
+
getArtifactUrlByHash(hash) {
|
|
447
|
+
const baseUrl = `http://${this.config.getHost()}:${this.config.getPort()}`;
|
|
448
|
+
return `${baseUrl}/artifacts/${hash}`;
|
|
449
|
+
}
|
|
450
|
+
// Update manifest artifacts to use local URLs
|
|
451
|
+
updateManifestArtifacts(manifest) {
|
|
452
|
+
const updatedManifest = { ...manifest };
|
|
453
|
+
if (updatedManifest.artifacts) {
|
|
454
|
+
updatedManifest.artifacts = updatedManifest.artifacts.map(
|
|
455
|
+
(artifact) => {
|
|
456
|
+
const updatedArtifact = { ...artifact };
|
|
457
|
+
if (artifact.path) {
|
|
458
|
+
const filename = path.basename(artifact.path);
|
|
459
|
+
updatedArtifact.mirrors = [
|
|
460
|
+
this.getArtifactUrl(
|
|
461
|
+
manifest.app.id || manifest.app.name,
|
|
462
|
+
manifest.version.semver,
|
|
463
|
+
filename
|
|
464
|
+
)
|
|
465
|
+
];
|
|
466
|
+
delete updatedArtifact.path;
|
|
467
|
+
}
|
|
468
|
+
return updatedArtifact;
|
|
469
|
+
}
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
return updatedManifest;
|
|
473
|
+
}
|
|
474
|
+
// Clean up old artifacts
|
|
475
|
+
async cleanupOldArtifacts(maxAge = 30 * 24 * 60 * 60 * 1e3) {
|
|
476
|
+
const now = Date.now();
|
|
477
|
+
const getAllFiles = (dir) => {
|
|
478
|
+
let files = [];
|
|
479
|
+
const items = fs3.readdirSync(dir);
|
|
480
|
+
for (const item of items) {
|
|
481
|
+
const fullPath = path.join(dir, item);
|
|
482
|
+
const stat = fs3.statSync(fullPath);
|
|
483
|
+
if (stat.isDirectory()) {
|
|
484
|
+
files = files.concat(getAllFiles(fullPath));
|
|
485
|
+
} else {
|
|
486
|
+
files.push(fullPath);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return files;
|
|
490
|
+
};
|
|
491
|
+
const allFiles = getAllFiles(this.artifactsDir);
|
|
492
|
+
for (const file of allFiles) {
|
|
493
|
+
const stats = fs3.statSync(file);
|
|
494
|
+
const age = now - stats.mtime.getTime();
|
|
495
|
+
if (age > maxAge) {
|
|
496
|
+
fs3.unlinkSync(file);
|
|
497
|
+
console.log(`Cleaned up old artifact: ${file}`);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
// Get artifact statistics
|
|
502
|
+
getArtifactStats() {
|
|
503
|
+
let totalFiles = 0;
|
|
504
|
+
let totalSize = 0;
|
|
505
|
+
let oldestFile = null;
|
|
506
|
+
const getAllFiles = (dir) => {
|
|
507
|
+
let files = [];
|
|
508
|
+
const items = fs3.readdirSync(dir);
|
|
509
|
+
for (const item of items) {
|
|
510
|
+
const fullPath = path.join(dir, item);
|
|
511
|
+
const stat = fs3.statSync(fullPath);
|
|
512
|
+
if (stat.isDirectory()) {
|
|
513
|
+
files = files.concat(getAllFiles(fullPath));
|
|
514
|
+
} else {
|
|
515
|
+
files.push(fullPath);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return files;
|
|
519
|
+
};
|
|
520
|
+
if (fs3.existsSync(this.artifactsDir)) {
|
|
521
|
+
const allFiles = getAllFiles(this.artifactsDir);
|
|
522
|
+
for (const file of allFiles) {
|
|
523
|
+
const stats = fs3.statSync(file);
|
|
524
|
+
totalFiles++;
|
|
525
|
+
totalSize += stats.size;
|
|
526
|
+
if (!oldestFile || stats.mtime < oldestFile) {
|
|
527
|
+
oldestFile = stats.mtime;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return {
|
|
532
|
+
totalFiles,
|
|
533
|
+
totalSize,
|
|
534
|
+
oldestFile
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
var RemoteRegistryClient = class {
|
|
539
|
+
constructor(baseURL, timeout) {
|
|
540
|
+
this.client = new SSAppRegistryClient({
|
|
541
|
+
baseURL,
|
|
542
|
+
timeout
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
async getApps(filters) {
|
|
546
|
+
return await this.client.getApps(filters);
|
|
547
|
+
}
|
|
548
|
+
async getAppVersions(appId) {
|
|
549
|
+
return await this.client.getAppVersions(appId);
|
|
550
|
+
}
|
|
551
|
+
async getAppManifest(appId, semver) {
|
|
552
|
+
return await this.client.getAppManifest(appId, semver);
|
|
553
|
+
}
|
|
554
|
+
async submitAppManifest(manifest) {
|
|
555
|
+
return await this.client.submitAppManifest(manifest);
|
|
556
|
+
}
|
|
557
|
+
async healthCheck() {
|
|
558
|
+
return await this.client.healthCheck();
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
var LocalRegistryClient = class {
|
|
562
|
+
constructor() {
|
|
563
|
+
this.config = new LocalConfig();
|
|
564
|
+
this.dataStore = new LocalDataStore(this.config);
|
|
565
|
+
this.artifactServer = new LocalArtifactServer(this.config, this.dataStore);
|
|
566
|
+
}
|
|
567
|
+
async getApps(filters) {
|
|
568
|
+
return this.dataStore.getApps(filters);
|
|
569
|
+
}
|
|
570
|
+
async getAppVersions(appId) {
|
|
571
|
+
return this.dataStore.getAppVersions(appId);
|
|
572
|
+
}
|
|
573
|
+
async getAppManifest(appId, semver) {
|
|
574
|
+
const manifest = this.dataStore.getManifest(appId, semver);
|
|
575
|
+
if (!manifest) {
|
|
576
|
+
throw new Error("Manifest not found");
|
|
577
|
+
}
|
|
578
|
+
return this.artifactServer.updateManifestArtifacts(manifest);
|
|
579
|
+
}
|
|
580
|
+
async submitAppManifest(manifest) {
|
|
581
|
+
if (!this.validateManifest(manifest)) {
|
|
582
|
+
throw new Error("Invalid manifest structure");
|
|
583
|
+
}
|
|
584
|
+
const processedManifest = await this.processManifestArtifacts(manifest);
|
|
585
|
+
const manifestKey = `${manifest.app.developer_pubkey}/${manifest.app.name}/${manifest.version.semver}`;
|
|
586
|
+
this.dataStore.setManifest(manifestKey, processedManifest);
|
|
587
|
+
const appKey = `${manifest.app.developer_pubkey}/${manifest.app.name}`;
|
|
588
|
+
const appSummary = {
|
|
589
|
+
name: manifest.app.name,
|
|
590
|
+
developer_pubkey: manifest.app.developer_pubkey,
|
|
591
|
+
latest_version: manifest.version.semver,
|
|
592
|
+
latest_cid: manifest.artifacts[0]?.cid || "",
|
|
593
|
+
alias: manifest.app.alias
|
|
594
|
+
};
|
|
595
|
+
this.dataStore.setApp(appKey, appSummary);
|
|
596
|
+
return {
|
|
597
|
+
success: true,
|
|
598
|
+
message: "App version registered successfully"
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
async healthCheck() {
|
|
602
|
+
return { status: "ok" };
|
|
603
|
+
}
|
|
604
|
+
validateManifest(manifest) {
|
|
605
|
+
return !!(manifest.manifest_version && manifest.app && manifest.app.name && manifest.app.developer_pubkey && manifest.version && manifest.version.semver && manifest.artifacts && manifest.artifacts.length > 0);
|
|
606
|
+
}
|
|
607
|
+
async processManifestArtifacts(manifest) {
|
|
608
|
+
const processedManifest = { ...manifest };
|
|
609
|
+
if (processedManifest.artifacts) {
|
|
610
|
+
processedManifest.artifacts = await Promise.all(
|
|
611
|
+
processedManifest.artifacts.map(async (artifact) => {
|
|
612
|
+
const processedArtifact = { ...artifact };
|
|
613
|
+
if (artifact.path && fs3.existsSync(artifact.path)) {
|
|
614
|
+
const filename = path.basename(artifact.path);
|
|
615
|
+
const appId = manifest.app.id || manifest.app.name;
|
|
616
|
+
try {
|
|
617
|
+
await this.artifactServer.copyArtifactToLocal(
|
|
618
|
+
artifact.path,
|
|
619
|
+
appId,
|
|
620
|
+
manifest.version.semver,
|
|
621
|
+
filename
|
|
622
|
+
);
|
|
623
|
+
processedArtifact.mirrors = [
|
|
624
|
+
this.artifactServer.getArtifactUrl(
|
|
625
|
+
appId,
|
|
626
|
+
manifest.version.semver,
|
|
627
|
+
filename
|
|
628
|
+
)
|
|
629
|
+
];
|
|
630
|
+
delete processedArtifact.path;
|
|
631
|
+
} catch (error) {
|
|
632
|
+
console.warn(`Failed to copy artifact ${artifact.path}:`, error);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
return processedArtifact;
|
|
636
|
+
})
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
return processedManifest;
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
function createRegistryClient(useLocal, baseURL, timeout) {
|
|
643
|
+
if (useLocal) {
|
|
644
|
+
return new LocalRegistryClient();
|
|
645
|
+
} else {
|
|
646
|
+
return new RemoteRegistryClient(
|
|
647
|
+
baseURL || "http://localhost:8082",
|
|
648
|
+
timeout || 1e4
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
10
652
|
var appsCommand = new Command("apps").description("Manage SSApp applications").addCommand(
|
|
11
653
|
new Command("list").description("List all applications").option("-d, --dev <pubkey>", "Filter by developer public key").option("-n, --name <name>", "Filter by application name").action(async (options, command) => {
|
|
12
654
|
const globalOpts = command.parent?.parent?.opts();
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
655
|
+
const useLocal = globalOpts?.local || false;
|
|
656
|
+
const client = createRegistryClient(
|
|
657
|
+
useLocal,
|
|
658
|
+
globalOpts?.url,
|
|
659
|
+
parseInt(globalOpts?.timeout || "10000")
|
|
660
|
+
);
|
|
661
|
+
const spinner2 = ora6("Fetching applications...").start();
|
|
18
662
|
try {
|
|
19
663
|
const apps = await client.getApps({
|
|
20
664
|
dev: options.dev,
|
|
@@ -22,7 +666,7 @@ var appsCommand = new Command("apps").description("Manage SSApp applications").a
|
|
|
22
666
|
});
|
|
23
667
|
spinner2.succeed(`Found ${apps.length} application(s)`);
|
|
24
668
|
if (apps.length === 0) {
|
|
25
|
-
console.log(
|
|
669
|
+
console.log(chalk5.yellow("No applications found"));
|
|
26
670
|
return;
|
|
27
671
|
}
|
|
28
672
|
const tableData = [
|
|
@@ -39,7 +683,7 @@ var appsCommand = new Command("apps").description("Manage SSApp applications").a
|
|
|
39
683
|
} catch (error) {
|
|
40
684
|
spinner2.fail("Failed to fetch applications");
|
|
41
685
|
if (error instanceof Error) {
|
|
42
|
-
console.error(
|
|
686
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
43
687
|
}
|
|
44
688
|
process.exit(1);
|
|
45
689
|
}
|
|
@@ -47,16 +691,18 @@ var appsCommand = new Command("apps").description("Manage SSApp applications").a
|
|
|
47
691
|
).addCommand(
|
|
48
692
|
new Command("versions").description("List versions of a specific application").argument("<appId>", "Application ID").action(async (appId, options, command) => {
|
|
49
693
|
const globalOpts = command.parent?.parent?.opts();
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
694
|
+
const useLocal = globalOpts?.local || false;
|
|
695
|
+
const client = createRegistryClient(
|
|
696
|
+
useLocal,
|
|
697
|
+
globalOpts?.url,
|
|
698
|
+
parseInt(globalOpts?.timeout || "10000")
|
|
699
|
+
);
|
|
700
|
+
const spinner2 = ora6(`Fetching versions for ${appId}...`).start();
|
|
55
701
|
try {
|
|
56
702
|
const versions = await client.getAppVersions(appId);
|
|
57
703
|
spinner2.succeed(`Found ${versions.length} version(s)`);
|
|
58
704
|
if (versions.length === 0) {
|
|
59
|
-
console.log(
|
|
705
|
+
console.log(chalk5.yellow("No versions found"));
|
|
60
706
|
return;
|
|
61
707
|
}
|
|
62
708
|
const tableData = [
|
|
@@ -64,14 +710,14 @@ var appsCommand = new Command("apps").description("Manage SSApp applications").a
|
|
|
64
710
|
...versions.map((version) => [
|
|
65
711
|
version.semver,
|
|
66
712
|
version.cid.substring(0, 12) + "...",
|
|
67
|
-
version.yanked ?
|
|
713
|
+
version.yanked ? chalk5.red("Yes") : chalk5.green("No")
|
|
68
714
|
])
|
|
69
715
|
];
|
|
70
716
|
console.log(table(tableData));
|
|
71
717
|
} catch (error) {
|
|
72
718
|
spinner2.fail("Failed to fetch versions");
|
|
73
719
|
if (error instanceof Error) {
|
|
74
|
-
console.error(
|
|
720
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
75
721
|
}
|
|
76
722
|
process.exit(1);
|
|
77
723
|
}
|
|
@@ -79,22 +725,24 @@ var appsCommand = new Command("apps").description("Manage SSApp applications").a
|
|
|
79
725
|
).addCommand(
|
|
80
726
|
new Command("manifest").description("Get manifest for a specific application version").argument("<appId>", "Application ID").argument("<version>", "Application version").action(async (appId, version, options, command) => {
|
|
81
727
|
const globalOpts = command.parent?.parent?.opts();
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
728
|
+
const useLocal = globalOpts?.local || false;
|
|
729
|
+
const client = createRegistryClient(
|
|
730
|
+
useLocal,
|
|
731
|
+
globalOpts?.url,
|
|
732
|
+
parseInt(globalOpts?.timeout || "10000")
|
|
733
|
+
);
|
|
734
|
+
const spinner2 = ora6(
|
|
87
735
|
`Fetching manifest for ${appId}@${version}...`
|
|
88
736
|
).start();
|
|
89
737
|
try {
|
|
90
738
|
const manifest = await client.getAppManifest(appId, version);
|
|
91
739
|
spinner2.succeed("Manifest fetched successfully");
|
|
92
|
-
console.log(
|
|
740
|
+
console.log(chalk5.blue("\nApplication Manifest:"));
|
|
93
741
|
console.log(JSON.stringify(manifest, null, 2));
|
|
94
742
|
} catch (error) {
|
|
95
743
|
spinner2.fail("Failed to fetch manifest");
|
|
96
744
|
if (error instanceof Error) {
|
|
97
|
-
console.error(
|
|
745
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
98
746
|
}
|
|
99
747
|
process.exit(1);
|
|
100
748
|
}
|
|
@@ -102,37 +750,39 @@ var appsCommand = new Command("apps").description("Manage SSApp applications").a
|
|
|
102
750
|
).addCommand(
|
|
103
751
|
new Command("submit").description("Submit a new application manifest").argument("<manifest-file>", "Path to the manifest JSON file").action(async (manifestFile, options, command) => {
|
|
104
752
|
const globalOpts = command.parent?.parent?.opts();
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
753
|
+
const useLocal = globalOpts?.local || false;
|
|
754
|
+
const client = createRegistryClient(
|
|
755
|
+
useLocal,
|
|
756
|
+
globalOpts?.url,
|
|
757
|
+
parseInt(globalOpts?.timeout || "10000")
|
|
758
|
+
);
|
|
759
|
+
const spinner2 = ora6("Reading manifest file...").start();
|
|
110
760
|
try {
|
|
111
761
|
const manifestPath = path.resolve(manifestFile);
|
|
112
|
-
if (!
|
|
762
|
+
if (!fs3.existsSync(manifestPath)) {
|
|
113
763
|
spinner2.fail("Manifest file not found");
|
|
114
|
-
console.error(
|
|
764
|
+
console.error(chalk5.red(`File not found: ${manifestFile}`));
|
|
115
765
|
process.exit(1);
|
|
116
766
|
}
|
|
117
|
-
const manifestContent =
|
|
767
|
+
const manifestContent = fs3.readFileSync(manifestPath, "utf8");
|
|
118
768
|
const manifest = JSON.parse(manifestContent);
|
|
119
769
|
spinner2.text = "Submitting application manifest...";
|
|
120
770
|
const result = await client.submitAppManifest(manifest);
|
|
121
771
|
spinner2.succeed("Application submitted successfully");
|
|
122
|
-
console.log(
|
|
772
|
+
console.log(chalk5.green(`
|
|
123
773
|
\u2705 ${result.message}`));
|
|
124
774
|
if (manifest.app?.name) {
|
|
125
|
-
console.log(
|
|
775
|
+
console.log(chalk5.blue(`
|
|
126
776
|
\u{1F4F1} App: ${manifest.app.name}`));
|
|
127
777
|
console.log(
|
|
128
|
-
|
|
778
|
+
chalk5.blue(`\u{1F464} Developer: ${manifest.app.developer_pubkey}`)
|
|
129
779
|
);
|
|
130
|
-
console.log(
|
|
780
|
+
console.log(chalk5.blue(`\u{1F4E6} Version: ${manifest.version?.semver}`));
|
|
131
781
|
}
|
|
132
782
|
} catch (error) {
|
|
133
783
|
spinner2.fail("Failed to submit application");
|
|
134
784
|
if (error instanceof Error) {
|
|
135
|
-
console.error(
|
|
785
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
136
786
|
}
|
|
137
787
|
process.exit(1);
|
|
138
788
|
}
|
|
@@ -145,33 +795,33 @@ var developersCommand = new Command("developers").description("Manage developer
|
|
|
145
795
|
baseURL: globalOpts?.url || "http://localhost:8082",
|
|
146
796
|
timeout: parseInt(globalOpts?.timeout || "10000")
|
|
147
797
|
});
|
|
148
|
-
const spinner2 =
|
|
798
|
+
const spinner2 = ora6("Fetching developer profile...").start();
|
|
149
799
|
try {
|
|
150
800
|
const profile = await client.getDeveloper(pubkey);
|
|
151
801
|
spinner2.succeed("Developer profile fetched successfully");
|
|
152
|
-
console.log(
|
|
153
|
-
console.log(
|
|
802
|
+
console.log(chalk5.blue("\nDeveloper Profile:"));
|
|
803
|
+
console.log(chalk5.green("Display Name:"), profile.display_name);
|
|
154
804
|
if (profile.website) {
|
|
155
|
-
console.log(
|
|
805
|
+
console.log(chalk5.green("Website:"), profile.website);
|
|
156
806
|
}
|
|
157
807
|
if (profile.proofs.length > 0) {
|
|
158
|
-
console.log(
|
|
808
|
+
console.log(chalk5.green("\nProofs:"));
|
|
159
809
|
const tableData = [
|
|
160
810
|
["Type", "Value", "Verified"],
|
|
161
811
|
...profile.proofs.map((proof) => [
|
|
162
812
|
proof.type,
|
|
163
813
|
proof.value.substring(0, 20) + "...",
|
|
164
|
-
proof.verified ?
|
|
814
|
+
proof.verified ? chalk5.green("Yes") : chalk5.red("No")
|
|
165
815
|
])
|
|
166
816
|
];
|
|
167
817
|
console.log(table(tableData));
|
|
168
818
|
} else {
|
|
169
|
-
console.log(
|
|
819
|
+
console.log(chalk5.yellow("\nNo proofs found"));
|
|
170
820
|
}
|
|
171
821
|
} catch (error) {
|
|
172
822
|
spinner2.fail("Failed to fetch developer profile");
|
|
173
823
|
if (error instanceof Error) {
|
|
174
|
-
console.error(
|
|
824
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
175
825
|
}
|
|
176
826
|
process.exit(1);
|
|
177
827
|
}
|
|
@@ -183,7 +833,7 @@ var developersCommand = new Command("developers").description("Manage developer
|
|
|
183
833
|
baseURL: globalOpts?.url || "http://localhost:8082",
|
|
184
834
|
timeout: parseInt(globalOpts?.timeout || "10000")
|
|
185
835
|
});
|
|
186
|
-
const spinner2 =
|
|
836
|
+
const spinner2 = ora6("Creating developer profile...").start();
|
|
187
837
|
try {
|
|
188
838
|
let proofs = [];
|
|
189
839
|
if (options.proofs) {
|
|
@@ -191,7 +841,7 @@ var developersCommand = new Command("developers").description("Manage developer
|
|
|
191
841
|
proofs = JSON.parse(options.proofs);
|
|
192
842
|
} catch {
|
|
193
843
|
spinner2.fail("Invalid proofs JSON format");
|
|
194
|
-
console.error(
|
|
844
|
+
console.error(chalk5.red("Proofs must be a valid JSON array"));
|
|
195
845
|
process.exit(1);
|
|
196
846
|
}
|
|
197
847
|
}
|
|
@@ -203,18 +853,18 @@ var developersCommand = new Command("developers").description("Manage developer
|
|
|
203
853
|
};
|
|
204
854
|
const result = await client.submitDeveloperProfile(pubkey, profile);
|
|
205
855
|
spinner2.succeed("Developer profile created successfully");
|
|
206
|
-
console.log(
|
|
856
|
+
console.log(chalk5.green(`
|
|
207
857
|
\u2705 ${result.message}`));
|
|
208
|
-
console.log(
|
|
858
|
+
console.log(chalk5.blue(`
|
|
209
859
|
\u{1F464} Developer: ${displayName}`));
|
|
210
|
-
console.log(
|
|
860
|
+
console.log(chalk5.blue(`\u{1F511} Public Key: ${pubkey}`));
|
|
211
861
|
if (options.website) {
|
|
212
|
-
console.log(
|
|
862
|
+
console.log(chalk5.blue(`\u{1F310} Website: ${options.website}`));
|
|
213
863
|
}
|
|
214
864
|
} catch (error) {
|
|
215
865
|
spinner2.fail("Failed to create developer profile");
|
|
216
866
|
if (error instanceof Error) {
|
|
217
|
-
console.error(
|
|
867
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
218
868
|
}
|
|
219
869
|
process.exit(1);
|
|
220
870
|
}
|
|
@@ -227,7 +877,7 @@ var attestationsCommand = new Command("attestations").description("Manage applic
|
|
|
227
877
|
baseURL: globalOpts?.url || "http://localhost:8082",
|
|
228
878
|
timeout: parseInt(globalOpts?.timeout || "10000")
|
|
229
879
|
});
|
|
230
|
-
const spinner2 =
|
|
880
|
+
const spinner2 = ora6(
|
|
231
881
|
`Fetching attestation for ${name}@${version}...`
|
|
232
882
|
).start();
|
|
233
883
|
try {
|
|
@@ -237,16 +887,16 @@ var attestationsCommand = new Command("attestations").description("Manage applic
|
|
|
237
887
|
version
|
|
238
888
|
);
|
|
239
889
|
spinner2.succeed("Attestation fetched successfully");
|
|
240
|
-
console.log(
|
|
241
|
-
console.log(
|
|
242
|
-
console.log(
|
|
890
|
+
console.log(chalk5.blue("\nAttestation:"));
|
|
891
|
+
console.log(chalk5.green("Status:"), attestation.status);
|
|
892
|
+
console.log(chalk5.green("Timestamp:"), attestation.timestamp);
|
|
243
893
|
if (attestation.comment) {
|
|
244
|
-
console.log(
|
|
894
|
+
console.log(chalk5.green("Comment:"), attestation.comment);
|
|
245
895
|
}
|
|
246
896
|
} catch (error) {
|
|
247
897
|
spinner2.fail("Failed to fetch attestation");
|
|
248
898
|
if (error instanceof Error) {
|
|
249
|
-
console.error(
|
|
899
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
250
900
|
}
|
|
251
901
|
process.exit(1);
|
|
252
902
|
}
|
|
@@ -254,19 +904,21 @@ var attestationsCommand = new Command("attestations").description("Manage applic
|
|
|
254
904
|
);
|
|
255
905
|
var healthCommand = new Command("health").description("Check the health of the SSApp Registry API").action(async (options, command) => {
|
|
256
906
|
const globalOpts = command.parent?.opts();
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
907
|
+
const useLocal = globalOpts?.local || false;
|
|
908
|
+
const client = createRegistryClient(
|
|
909
|
+
useLocal,
|
|
910
|
+
globalOpts?.url,
|
|
911
|
+
parseInt(globalOpts?.timeout || "10000")
|
|
912
|
+
);
|
|
913
|
+
const spinner2 = ora6("Checking API health...").start();
|
|
262
914
|
try {
|
|
263
915
|
const health = await client.healthCheck();
|
|
264
916
|
spinner2.succeed("API is healthy");
|
|
265
|
-
console.log(
|
|
917
|
+
console.log(chalk5.green(`Status: ${health.status}`));
|
|
266
918
|
} catch (error) {
|
|
267
919
|
spinner2.fail("API health check failed");
|
|
268
920
|
if (error instanceof Error) {
|
|
269
|
-
console.error(
|
|
921
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
270
922
|
}
|
|
271
923
|
process.exit(1);
|
|
272
924
|
}
|
|
@@ -301,7 +953,7 @@ This is a demo file that would normally contain the actual application data.`;
|
|
|
301
953
|
);
|
|
302
954
|
}
|
|
303
955
|
}
|
|
304
|
-
var spinner = (text) =>
|
|
956
|
+
var spinner = (text) => ora6(text);
|
|
305
957
|
|
|
306
958
|
// src/commands/ipfs.ts
|
|
307
959
|
var ipfsCommand = new Command("ipfs").description("IPFS operations").addCommand(
|
|
@@ -339,6 +991,399 @@ var ipfsCommand = new Command("ipfs").description("IPFS operations").addCommand(
|
|
|
339
991
|
}
|
|
340
992
|
})
|
|
341
993
|
);
|
|
994
|
+
var LocalRegistryServer = class {
|
|
995
|
+
constructor(config) {
|
|
996
|
+
this.server = null;
|
|
997
|
+
this.isRunning = false;
|
|
998
|
+
this.config = config;
|
|
999
|
+
this.dataStore = new LocalDataStore(config);
|
|
1000
|
+
this.artifactServer = new LocalArtifactServer(config, this.dataStore);
|
|
1001
|
+
}
|
|
1002
|
+
async start(port) {
|
|
1003
|
+
if (this.isRunning) {
|
|
1004
|
+
throw new Error("Local registry is already running");
|
|
1005
|
+
}
|
|
1006
|
+
const serverPort = port || this.config.getPort();
|
|
1007
|
+
const host = this.config.getHost();
|
|
1008
|
+
this.config.ensureDirectories();
|
|
1009
|
+
this.server = fastify({
|
|
1010
|
+
logger: {
|
|
1011
|
+
level: "info"
|
|
1012
|
+
}
|
|
1013
|
+
});
|
|
1014
|
+
await this.server.register(cors, {
|
|
1015
|
+
origin: true,
|
|
1016
|
+
credentials: true
|
|
1017
|
+
});
|
|
1018
|
+
await this.registerRoutes();
|
|
1019
|
+
await this.server.listen({ port: serverPort, host });
|
|
1020
|
+
this.isRunning = true;
|
|
1021
|
+
console.log(
|
|
1022
|
+
`Local registry server started on http://${host}:${serverPort}`
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
async stop() {
|
|
1026
|
+
if (!this.server || !this.isRunning) {
|
|
1027
|
+
throw new Error("Local registry is not running");
|
|
1028
|
+
}
|
|
1029
|
+
await this.server.close();
|
|
1030
|
+
this.isRunning = false;
|
|
1031
|
+
this.server = null;
|
|
1032
|
+
}
|
|
1033
|
+
async getStatus() {
|
|
1034
|
+
const stats = this.dataStore.getStats();
|
|
1035
|
+
const artifactStats = this.artifactServer.getArtifactStats();
|
|
1036
|
+
return {
|
|
1037
|
+
running: this.isRunning,
|
|
1038
|
+
url: `http://${this.config.getHost()}:${this.config.getPort()}`,
|
|
1039
|
+
dataDir: this.config.getDataDir(),
|
|
1040
|
+
appsCount: stats.publishedApps,
|
|
1041
|
+
artifactsCount: artifactStats.totalFiles
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
async reset() {
|
|
1045
|
+
this.dataStore.reset();
|
|
1046
|
+
const artifactsDir = this.config.getArtifactsDir();
|
|
1047
|
+
if (fs3.existsSync(artifactsDir)) {
|
|
1048
|
+
fs3.rmSync(artifactsDir, { recursive: true, force: true });
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
async backup(outputPath) {
|
|
1052
|
+
return await this.dataStore.backup(outputPath);
|
|
1053
|
+
}
|
|
1054
|
+
async restore(backupPath) {
|
|
1055
|
+
await this.dataStore.restore(backupPath);
|
|
1056
|
+
}
|
|
1057
|
+
async seed() {
|
|
1058
|
+
await this.dataStore.seed();
|
|
1059
|
+
}
|
|
1060
|
+
async registerRoutes() {
|
|
1061
|
+
if (!this.server) {
|
|
1062
|
+
throw new Error("Server not initialized");
|
|
1063
|
+
}
|
|
1064
|
+
this.server.get("/healthz", async () => {
|
|
1065
|
+
return { status: "ok" };
|
|
1066
|
+
});
|
|
1067
|
+
this.server.get("/stats", async () => {
|
|
1068
|
+
const stats = this.dataStore.getStats();
|
|
1069
|
+
const artifactStats = this.artifactServer.getArtifactStats();
|
|
1070
|
+
return {
|
|
1071
|
+
publishedApps: stats.publishedApps,
|
|
1072
|
+
totalVersions: stats.totalVersions,
|
|
1073
|
+
totalArtifacts: artifactStats.totalFiles,
|
|
1074
|
+
totalSize: artifactStats.totalSize
|
|
1075
|
+
};
|
|
1076
|
+
});
|
|
1077
|
+
this.server.get("/apps", async (request) => {
|
|
1078
|
+
const query = request.query;
|
|
1079
|
+
return this.dataStore.getApps(query);
|
|
1080
|
+
});
|
|
1081
|
+
this.server.get("/apps/:appId", async (request) => {
|
|
1082
|
+
const { appId } = request.params;
|
|
1083
|
+
return this.dataStore.getAppVersions(appId);
|
|
1084
|
+
});
|
|
1085
|
+
this.server.get("/apps/:appId/:semver", async (request) => {
|
|
1086
|
+
const { appId, semver } = request.params;
|
|
1087
|
+
const manifest = this.dataStore.getManifest(appId, semver);
|
|
1088
|
+
if (!manifest) {
|
|
1089
|
+
throw this.server.httpErrors.notFound("Manifest not found");
|
|
1090
|
+
}
|
|
1091
|
+
return this.artifactServer.updateManifestArtifacts(manifest);
|
|
1092
|
+
});
|
|
1093
|
+
this.server.post("/apps", async (request) => {
|
|
1094
|
+
const manifest = request.body;
|
|
1095
|
+
if (!this.validateManifest(manifest)) {
|
|
1096
|
+
throw this.server.httpErrors.badRequest("Invalid manifest structure");
|
|
1097
|
+
}
|
|
1098
|
+
const processedManifest = await this.processManifestArtifacts(manifest);
|
|
1099
|
+
const manifestKey = `${manifest.app.developer_pubkey}/${manifest.app.name}/${manifest.version.semver}`;
|
|
1100
|
+
this.dataStore.setManifest(manifestKey, processedManifest);
|
|
1101
|
+
const appKey = `${manifest.app.developer_pubkey}/${manifest.app.name}`;
|
|
1102
|
+
const appSummary = {
|
|
1103
|
+
name: manifest.app.name,
|
|
1104
|
+
developer_pubkey: manifest.app.developer_pubkey,
|
|
1105
|
+
latest_version: manifest.version.semver,
|
|
1106
|
+
latest_cid: manifest.artifacts[0]?.cid || "",
|
|
1107
|
+
alias: manifest.app.alias
|
|
1108
|
+
};
|
|
1109
|
+
this.dataStore.setApp(appKey, appSummary);
|
|
1110
|
+
return {
|
|
1111
|
+
success: true,
|
|
1112
|
+
message: "App version registered successfully",
|
|
1113
|
+
manifest_key: manifestKey
|
|
1114
|
+
};
|
|
1115
|
+
});
|
|
1116
|
+
this.server.get(
|
|
1117
|
+
"/artifacts/:appId/:version/:filename",
|
|
1118
|
+
async (request, reply) => {
|
|
1119
|
+
const { appId, version, filename } = request.params;
|
|
1120
|
+
try {
|
|
1121
|
+
const artifactData = await this.artifactServer.serveArtifact(
|
|
1122
|
+
appId,
|
|
1123
|
+
version,
|
|
1124
|
+
filename
|
|
1125
|
+
);
|
|
1126
|
+
reply.header("Content-Type", "application/octet-stream");
|
|
1127
|
+
reply.header(
|
|
1128
|
+
"Content-Disposition",
|
|
1129
|
+
`attachment; filename="${filename}"`
|
|
1130
|
+
);
|
|
1131
|
+
return artifactData;
|
|
1132
|
+
} catch {
|
|
1133
|
+
throw this.server.httpErrors.notFound("Artifact not found");
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
);
|
|
1137
|
+
this.server.get("/artifacts/:hash", async (request, reply) => {
|
|
1138
|
+
const { hash } = request.params;
|
|
1139
|
+
try {
|
|
1140
|
+
const artifactData = await this.artifactServer.serveByHash(hash);
|
|
1141
|
+
reply.header("Content-Type", "application/octet-stream");
|
|
1142
|
+
return artifactData;
|
|
1143
|
+
} catch {
|
|
1144
|
+
throw this.server.httpErrors.notFound("Artifact not found");
|
|
1145
|
+
}
|
|
1146
|
+
});
|
|
1147
|
+
this.server.get("/local/status", async () => {
|
|
1148
|
+
return await this.getStatus();
|
|
1149
|
+
});
|
|
1150
|
+
this.server.post("/local/reset", async () => {
|
|
1151
|
+
await this.reset();
|
|
1152
|
+
return { message: "Local registry data reset successfully" };
|
|
1153
|
+
});
|
|
1154
|
+
this.server.get("/local/backup", async (request) => {
|
|
1155
|
+
const query = request.query;
|
|
1156
|
+
const backupPath = await this.backup(query.output);
|
|
1157
|
+
return { backupPath };
|
|
1158
|
+
});
|
|
1159
|
+
this.server.post("/local/restore", async (request) => {
|
|
1160
|
+
const { backupPath } = request.body;
|
|
1161
|
+
await this.restore(backupPath);
|
|
1162
|
+
return { message: "Data restored successfully" };
|
|
1163
|
+
});
|
|
1164
|
+
this.server.post("/local/seed", async () => {
|
|
1165
|
+
await this.seed();
|
|
1166
|
+
return { message: "Sample data seeded successfully" };
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
validateManifest(manifest) {
|
|
1170
|
+
return !!(manifest.manifest_version && manifest.app && manifest.app.name && manifest.app.developer_pubkey && manifest.version && manifest.version.semver && manifest.artifacts && manifest.artifacts.length > 0);
|
|
1171
|
+
}
|
|
1172
|
+
async processManifestArtifacts(manifest) {
|
|
1173
|
+
const processedManifest = { ...manifest };
|
|
1174
|
+
if (processedManifest.artifacts) {
|
|
1175
|
+
processedManifest.artifacts = await Promise.all(
|
|
1176
|
+
processedManifest.artifacts.map(async (artifact) => {
|
|
1177
|
+
const processedArtifact = { ...artifact };
|
|
1178
|
+
if (artifact.path && fs3.existsSync(artifact.path)) {
|
|
1179
|
+
const filename = path.basename(artifact.path);
|
|
1180
|
+
const appId = manifest.app.id || manifest.app.name;
|
|
1181
|
+
try {
|
|
1182
|
+
await this.artifactServer.copyArtifactToLocal(
|
|
1183
|
+
artifact.path,
|
|
1184
|
+
appId,
|
|
1185
|
+
manifest.version.semver,
|
|
1186
|
+
filename
|
|
1187
|
+
);
|
|
1188
|
+
processedArtifact.mirrors = [
|
|
1189
|
+
this.artifactServer.getArtifactUrl(
|
|
1190
|
+
appId,
|
|
1191
|
+
manifest.version.semver,
|
|
1192
|
+
filename
|
|
1193
|
+
)
|
|
1194
|
+
];
|
|
1195
|
+
delete processedArtifact.path;
|
|
1196
|
+
} catch (error) {
|
|
1197
|
+
console.warn(`Failed to copy artifact ${artifact.path}:`, error);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
return processedArtifact;
|
|
1201
|
+
})
|
|
1202
|
+
);
|
|
1203
|
+
}
|
|
1204
|
+
return processedManifest;
|
|
1205
|
+
}
|
|
1206
|
+
};
|
|
1207
|
+
var localCommand = new Command("local").description(
|
|
1208
|
+
"Manage local registry for development"
|
|
1209
|
+
);
|
|
1210
|
+
localCommand.addCommand(
|
|
1211
|
+
new Command("start").description("Start local registry server").option("-p, --port <port>", "Port to run the server on", "8082").option("-h, --host <host>", "Host to bind the server to", "localhost").action(async (options) => {
|
|
1212
|
+
const spinner2 = ora6("Starting local registry...").start();
|
|
1213
|
+
try {
|
|
1214
|
+
const config = new LocalConfig();
|
|
1215
|
+
const server = new LocalRegistryServer(config);
|
|
1216
|
+
await server.start(parseInt(options.port));
|
|
1217
|
+
spinner2.succeed(
|
|
1218
|
+
`Local registry started on http://${options.host}:${options.port}`
|
|
1219
|
+
);
|
|
1220
|
+
console.log(chalk5.blue("\n\u{1F4F1} Local Registry Status:"));
|
|
1221
|
+
console.log(
|
|
1222
|
+
chalk5.green(`\u2705 Server: http://${options.host}:${options.port}`)
|
|
1223
|
+
);
|
|
1224
|
+
console.log(chalk5.green(`\u{1F4C1} Data: ${config.getDataDir()}`));
|
|
1225
|
+
console.log(
|
|
1226
|
+
chalk5.green(
|
|
1227
|
+
`\u{1F4CB} Health: http://${options.host}:${options.port}/healthz`
|
|
1228
|
+
)
|
|
1229
|
+
);
|
|
1230
|
+
console.log(
|
|
1231
|
+
chalk5.green(`\u{1F4CA} Stats: http://${options.host}:${options.port}/stats`)
|
|
1232
|
+
);
|
|
1233
|
+
console.log(
|
|
1234
|
+
chalk5.blue(
|
|
1235
|
+
"\n\u{1F4A1} Use --local flag with other commands to use local registry"
|
|
1236
|
+
)
|
|
1237
|
+
);
|
|
1238
|
+
} catch (error) {
|
|
1239
|
+
spinner2.fail("Failed to start local registry");
|
|
1240
|
+
if (error instanceof Error) {
|
|
1241
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
1242
|
+
}
|
|
1243
|
+
process.exit(1);
|
|
1244
|
+
}
|
|
1245
|
+
})
|
|
1246
|
+
);
|
|
1247
|
+
localCommand.addCommand(
|
|
1248
|
+
new Command("stop").description("Stop local registry server").action(async () => {
|
|
1249
|
+
const spinner2 = ora6("Stopping local registry...").start();
|
|
1250
|
+
try {
|
|
1251
|
+
const config = new LocalConfig();
|
|
1252
|
+
const server = new LocalRegistryServer(config);
|
|
1253
|
+
await server.stop();
|
|
1254
|
+
spinner2.succeed("Local registry stopped");
|
|
1255
|
+
} catch (error) {
|
|
1256
|
+
spinner2.fail("Failed to stop local registry");
|
|
1257
|
+
if (error instanceof Error) {
|
|
1258
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
1259
|
+
}
|
|
1260
|
+
process.exit(1);
|
|
1261
|
+
}
|
|
1262
|
+
})
|
|
1263
|
+
);
|
|
1264
|
+
localCommand.addCommand(
|
|
1265
|
+
new Command("status").description("Check local registry status").action(async () => {
|
|
1266
|
+
const spinner2 = ora6("Checking local registry status...").start();
|
|
1267
|
+
try {
|
|
1268
|
+
const config = new LocalConfig();
|
|
1269
|
+
const server = new LocalRegistryServer(config);
|
|
1270
|
+
const status = await server.getStatus();
|
|
1271
|
+
if (status.running) {
|
|
1272
|
+
spinner2.succeed("Local registry is running");
|
|
1273
|
+
console.log(chalk5.green(`\u2705 Server: ${status.url}`));
|
|
1274
|
+
console.log(chalk5.green(`\u{1F4C1} Data: ${status.dataDir}`));
|
|
1275
|
+
console.log(chalk5.green(`\u{1F4CA} Apps: ${status.appsCount} applications`));
|
|
1276
|
+
console.log(
|
|
1277
|
+
chalk5.green(`\u{1F4E6} Artifacts: ${status.artifactsCount} artifacts`)
|
|
1278
|
+
);
|
|
1279
|
+
} else {
|
|
1280
|
+
spinner2.warn("Local registry is not running");
|
|
1281
|
+
console.log(
|
|
1282
|
+
chalk5.yellow(
|
|
1283
|
+
'\u{1F4A1} Run "calimero-registry local start" to start the local registry'
|
|
1284
|
+
)
|
|
1285
|
+
);
|
|
1286
|
+
}
|
|
1287
|
+
} catch (error) {
|
|
1288
|
+
spinner2.fail("Failed to check local registry status");
|
|
1289
|
+
if (error instanceof Error) {
|
|
1290
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
1291
|
+
}
|
|
1292
|
+
process.exit(1);
|
|
1293
|
+
}
|
|
1294
|
+
})
|
|
1295
|
+
);
|
|
1296
|
+
localCommand.addCommand(
|
|
1297
|
+
new Command("reset").description("Reset local registry data").option("-f, --force", "Force reset without confirmation").action(async (options) => {
|
|
1298
|
+
if (!options.force) {
|
|
1299
|
+
console.log(
|
|
1300
|
+
chalk5.yellow("\u26A0\uFE0F This will delete all local registry data!")
|
|
1301
|
+
);
|
|
1302
|
+
console.log(chalk5.yellow(" Use --force flag to confirm"));
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
const spinner2 = ora6("Resetting local registry data...").start();
|
|
1306
|
+
try {
|
|
1307
|
+
const config = new LocalConfig();
|
|
1308
|
+
const server = new LocalRegistryServer(config);
|
|
1309
|
+
await server.reset();
|
|
1310
|
+
spinner2.succeed("Local registry data reset");
|
|
1311
|
+
console.log(chalk5.green("\u2705 All local data has been cleared"));
|
|
1312
|
+
} catch (error) {
|
|
1313
|
+
spinner2.fail("Failed to reset local registry data");
|
|
1314
|
+
if (error instanceof Error) {
|
|
1315
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
1316
|
+
}
|
|
1317
|
+
process.exit(1);
|
|
1318
|
+
}
|
|
1319
|
+
})
|
|
1320
|
+
);
|
|
1321
|
+
localCommand.addCommand(
|
|
1322
|
+
new Command("backup").description("Backup local registry data").option("-o, --output <file>", "Output file path").action(async (options) => {
|
|
1323
|
+
const spinner2 = ora6("Creating backup...").start();
|
|
1324
|
+
try {
|
|
1325
|
+
const config = new LocalConfig();
|
|
1326
|
+
const server = new LocalRegistryServer(config);
|
|
1327
|
+
const backupPath = await server.backup(options.output);
|
|
1328
|
+
spinner2.succeed("Backup created successfully");
|
|
1329
|
+
console.log(chalk5.green(`\u{1F4E6} Backup saved to: ${backupPath}`));
|
|
1330
|
+
} catch (error) {
|
|
1331
|
+
spinner2.fail("Failed to create backup");
|
|
1332
|
+
if (error instanceof Error) {
|
|
1333
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
1334
|
+
}
|
|
1335
|
+
process.exit(1);
|
|
1336
|
+
}
|
|
1337
|
+
})
|
|
1338
|
+
);
|
|
1339
|
+
localCommand.addCommand(
|
|
1340
|
+
new Command("restore").description("Restore local registry data from backup").argument("<backup-file>", "Path to backup file").action(async (backupFile) => {
|
|
1341
|
+
const spinner2 = ora6("Restoring from backup...").start();
|
|
1342
|
+
try {
|
|
1343
|
+
if (!fs3.existsSync(backupFile)) {
|
|
1344
|
+
spinner2.fail("Backup file not found");
|
|
1345
|
+
console.error(chalk5.red(`File not found: ${backupFile}`));
|
|
1346
|
+
process.exit(1);
|
|
1347
|
+
}
|
|
1348
|
+
const config = new LocalConfig();
|
|
1349
|
+
const server = new LocalRegistryServer(config);
|
|
1350
|
+
await server.restore(backupFile);
|
|
1351
|
+
spinner2.succeed("Data restored successfully");
|
|
1352
|
+
console.log(chalk5.green(`\u2705 Restored from: ${backupFile}`));
|
|
1353
|
+
} catch (error) {
|
|
1354
|
+
spinner2.fail("Failed to restore from backup");
|
|
1355
|
+
if (error instanceof Error) {
|
|
1356
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
1357
|
+
}
|
|
1358
|
+
process.exit(1);
|
|
1359
|
+
}
|
|
1360
|
+
})
|
|
1361
|
+
);
|
|
1362
|
+
localCommand.addCommand(
|
|
1363
|
+
new Command("seed").description("Seed local registry with sample data").action(async () => {
|
|
1364
|
+
const spinner2 = ora6("Seeding local registry with sample data...").start();
|
|
1365
|
+
try {
|
|
1366
|
+
const config = new LocalConfig();
|
|
1367
|
+
const server = new LocalRegistryServer(config);
|
|
1368
|
+
await server.seed();
|
|
1369
|
+
spinner2.succeed("Sample data seeded successfully");
|
|
1370
|
+
console.log(
|
|
1371
|
+
chalk5.green("\u2705 Local registry populated with sample applications")
|
|
1372
|
+
);
|
|
1373
|
+
console.log(
|
|
1374
|
+
chalk5.blue(
|
|
1375
|
+
'\u{1F4A1} Run "calimero-registry apps list --local" to see the sample apps'
|
|
1376
|
+
)
|
|
1377
|
+
);
|
|
1378
|
+
} catch (error) {
|
|
1379
|
+
spinner2.fail("Failed to seed sample data");
|
|
1380
|
+
if (error instanceof Error) {
|
|
1381
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
1382
|
+
}
|
|
1383
|
+
process.exit(1);
|
|
1384
|
+
}
|
|
1385
|
+
})
|
|
1386
|
+
);
|
|
342
1387
|
|
|
343
1388
|
// src/index.ts
|
|
344
1389
|
var program = new Command();
|
|
@@ -351,19 +1396,21 @@ program.option(
|
|
|
351
1396
|
"Request timeout in milliseconds",
|
|
352
1397
|
"10000"
|
|
353
1398
|
);
|
|
1399
|
+
program.option("--local", "Use local registry instead of remote API");
|
|
354
1400
|
program.addCommand(appsCommand);
|
|
355
1401
|
program.addCommand(developersCommand);
|
|
356
1402
|
program.addCommand(attestationsCommand);
|
|
357
1403
|
program.addCommand(healthCommand);
|
|
358
1404
|
program.addCommand(ipfsCommand);
|
|
1405
|
+
program.addCommand(localCommand);
|
|
359
1406
|
program.exitOverride();
|
|
360
1407
|
try {
|
|
361
1408
|
program.parse();
|
|
362
1409
|
} catch (err) {
|
|
363
1410
|
if (err instanceof Error) {
|
|
364
|
-
console.error(
|
|
1411
|
+
console.error(chalk5.red("Error:"), err.message);
|
|
365
1412
|
} else {
|
|
366
|
-
console.error(
|
|
1413
|
+
console.error(chalk5.red("Unknown error occurred"));
|
|
367
1414
|
}
|
|
368
1415
|
process.exit(1);
|
|
369
1416
|
}
|