@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/dist/index.js CHANGED
@@ -1,20 +1,664 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
- import chalk from 'chalk';
4
- import ora from 'ora';
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 fs, { existsSync, writeFileSync } from 'fs';
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 client = new SSAppRegistryClient({
14
- baseURL: globalOpts?.url || "http://localhost:8082",
15
- timeout: parseInt(globalOpts?.timeout || "10000")
16
- });
17
- const spinner2 = ora("Fetching applications...").start();
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(chalk.yellow("No applications found"));
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(chalk.red(`Error: ${error.message}`));
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 client = new SSAppRegistryClient({
51
- baseURL: globalOpts?.url || "http://localhost:8082",
52
- timeout: parseInt(globalOpts?.timeout || "10000")
53
- });
54
- const spinner2 = ora(`Fetching versions for ${appId}...`).start();
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(chalk.yellow("No versions found"));
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 ? chalk.red("Yes") : chalk.green("No")
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(chalk.red(`Error: ${error.message}`));
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 client = new SSAppRegistryClient({
83
- baseURL: globalOpts?.url || "http://localhost:8082",
84
- timeout: parseInt(globalOpts?.timeout || "10000")
85
- });
86
- const spinner2 = ora(
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(chalk.blue("\nApplication Manifest:"));
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(chalk.red(`Error: ${error.message}`));
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 client = new SSAppRegistryClient({
106
- baseURL: globalOpts?.url || "http://localhost:8082",
107
- timeout: parseInt(globalOpts?.timeout || "10000")
108
- });
109
- const spinner2 = ora("Reading manifest file...").start();
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 (!fs.existsSync(manifestPath)) {
762
+ if (!fs3.existsSync(manifestPath)) {
113
763
  spinner2.fail("Manifest file not found");
114
- console.error(chalk.red(`File not found: ${manifestFile}`));
764
+ console.error(chalk5.red(`File not found: ${manifestFile}`));
115
765
  process.exit(1);
116
766
  }
117
- const manifestContent = fs.readFileSync(manifestPath, "utf8");
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(chalk.green(`
772
+ console.log(chalk5.green(`
123
773
  \u2705 ${result.message}`));
124
774
  if (manifest.app?.name) {
125
- console.log(chalk.blue(`
775
+ console.log(chalk5.blue(`
126
776
  \u{1F4F1} App: ${manifest.app.name}`));
127
777
  console.log(
128
- chalk.blue(`\u{1F464} Developer: ${manifest.app.developer_pubkey}`)
778
+ chalk5.blue(`\u{1F464} Developer: ${manifest.app.developer_pubkey}`)
129
779
  );
130
- console.log(chalk.blue(`\u{1F4E6} Version: ${manifest.version?.semver}`));
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(chalk.red(`Error: ${error.message}`));
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 = ora("Fetching developer profile...").start();
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(chalk.blue("\nDeveloper Profile:"));
153
- console.log(chalk.green("Display Name:"), profile.display_name);
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(chalk.green("Website:"), profile.website);
805
+ console.log(chalk5.green("Website:"), profile.website);
156
806
  }
157
807
  if (profile.proofs.length > 0) {
158
- console.log(chalk.green("\nProofs:"));
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 ? chalk.green("Yes") : chalk.red("No")
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(chalk.yellow("\nNo proofs found"));
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(chalk.red(`Error: ${error.message}`));
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 = ora("Creating developer profile...").start();
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(chalk.red("Proofs must be a valid JSON array"));
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(chalk.green(`
856
+ console.log(chalk5.green(`
207
857
  \u2705 ${result.message}`));
208
- console.log(chalk.blue(`
858
+ console.log(chalk5.blue(`
209
859
  \u{1F464} Developer: ${displayName}`));
210
- console.log(chalk.blue(`\u{1F511} Public Key: ${pubkey}`));
860
+ console.log(chalk5.blue(`\u{1F511} Public Key: ${pubkey}`));
211
861
  if (options.website) {
212
- console.log(chalk.blue(`\u{1F310} Website: ${options.website}`));
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(chalk.red(`Error: ${error.message}`));
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 = ora(
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(chalk.blue("\nAttestation:"));
241
- console.log(chalk.green("Status:"), attestation.status);
242
- console.log(chalk.green("Timestamp:"), attestation.timestamp);
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(chalk.green("Comment:"), attestation.comment);
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(chalk.red(`Error: ${error.message}`));
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 client = new SSAppRegistryClient({
258
- baseURL: globalOpts?.url || "http://localhost:8082",
259
- timeout: parseInt(globalOpts?.timeout || "10000")
260
- });
261
- const spinner2 = ora("Checking API health...").start();
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(chalk.green(`Status: ${health.status}`));
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(chalk.red(`Error: ${error.message}`));
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) => ora(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(chalk.red("Error:"), err.message);
1411
+ console.error(chalk5.red("Error:"), err.message);
365
1412
  } else {
366
- console.error(chalk.red("Unknown error occurred"));
1413
+ console.error(chalk5.red("Unknown error occurred"));
367
1414
  }
368
1415
  process.exit(1);
369
1416
  }