@calimero-network/registry-cli 1.0.0 → 1.2.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 +1501 -69
- package/dist/index.js.map +1 -1
- package/package.json +7 -4
package/dist/index.js
CHANGED
|
@@ -1,20 +1,662 @@
|
|
|
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';
|
|
13
|
+
import { Buffer } from 'buffer';
|
|
14
|
+
import fetch from 'node-fetch';
|
|
9
15
|
|
|
16
|
+
var LocalConfig = class {
|
|
17
|
+
constructor() {
|
|
18
|
+
this.configPath = path.join(
|
|
19
|
+
os.homedir(),
|
|
20
|
+
".calimero-registry",
|
|
21
|
+
"config.json"
|
|
22
|
+
);
|
|
23
|
+
this.dataDir = path.join(os.homedir(), ".calimero-registry");
|
|
24
|
+
this.config = this.loadConfig();
|
|
25
|
+
}
|
|
26
|
+
loadConfig() {
|
|
27
|
+
const defaultConfig = {
|
|
28
|
+
server: {
|
|
29
|
+
port: 8082,
|
|
30
|
+
host: "localhost"
|
|
31
|
+
},
|
|
32
|
+
data: {
|
|
33
|
+
dir: path.join(os.homedir(), ".calimero-registry", "data"),
|
|
34
|
+
artifactsDir: path.join(
|
|
35
|
+
os.homedir(),
|
|
36
|
+
".calimero-registry",
|
|
37
|
+
"artifacts"
|
|
38
|
+
)
|
|
39
|
+
},
|
|
40
|
+
artifacts: {
|
|
41
|
+
storageDir: path.join(os.homedir(), ".calimero-registry", "artifacts"),
|
|
42
|
+
serveLocal: true,
|
|
43
|
+
copyArtifacts: true,
|
|
44
|
+
maxFileSize: "100MB",
|
|
45
|
+
allowedTypes: ["wasm", "js", "html"]
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
if (fs3.existsSync(this.configPath)) {
|
|
49
|
+
try {
|
|
50
|
+
const existingConfig = JSON.parse(
|
|
51
|
+
fs3.readFileSync(this.configPath, "utf8")
|
|
52
|
+
);
|
|
53
|
+
return { ...defaultConfig, ...existingConfig };
|
|
54
|
+
} catch {
|
|
55
|
+
console.warn("Failed to load existing config, using defaults");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return defaultConfig;
|
|
59
|
+
}
|
|
60
|
+
saveConfig() {
|
|
61
|
+
const configDir = path.dirname(this.configPath);
|
|
62
|
+
if (!fs3.existsSync(configDir)) {
|
|
63
|
+
fs3.mkdirSync(configDir, { recursive: true });
|
|
64
|
+
}
|
|
65
|
+
fs3.writeFileSync(this.configPath, JSON.stringify(this.config, null, 2));
|
|
66
|
+
}
|
|
67
|
+
// Server configuration
|
|
68
|
+
getPort() {
|
|
69
|
+
return this.config.server.port;
|
|
70
|
+
}
|
|
71
|
+
setPort(port) {
|
|
72
|
+
this.config.server.port = port;
|
|
73
|
+
this.saveConfig();
|
|
74
|
+
}
|
|
75
|
+
getHost() {
|
|
76
|
+
return this.config.server.host;
|
|
77
|
+
}
|
|
78
|
+
setHost(host) {
|
|
79
|
+
this.config.server.host = host;
|
|
80
|
+
this.saveConfig();
|
|
81
|
+
}
|
|
82
|
+
// Data directory configuration
|
|
83
|
+
getDataDir() {
|
|
84
|
+
return this.config.data.dir;
|
|
85
|
+
}
|
|
86
|
+
setDataDir(dir) {
|
|
87
|
+
this.config.data.dir = dir;
|
|
88
|
+
this.config.artifacts.storageDir = path.join(dir, "artifacts");
|
|
89
|
+
this.saveConfig();
|
|
90
|
+
}
|
|
91
|
+
getArtifactsDir() {
|
|
92
|
+
return this.config.data.artifactsDir;
|
|
93
|
+
}
|
|
94
|
+
setArtifactsDir(dir) {
|
|
95
|
+
this.config.data.artifactsDir = dir;
|
|
96
|
+
this.config.artifacts.storageDir = dir;
|
|
97
|
+
this.saveConfig();
|
|
98
|
+
}
|
|
99
|
+
// Artifact configuration
|
|
100
|
+
getArtifactsConfig() {
|
|
101
|
+
return this.config.artifacts;
|
|
102
|
+
}
|
|
103
|
+
setArtifactsConfig(config) {
|
|
104
|
+
this.config.artifacts = { ...this.config.artifacts, ...config };
|
|
105
|
+
this.saveConfig();
|
|
106
|
+
}
|
|
107
|
+
// Utility methods
|
|
108
|
+
ensureDirectories() {
|
|
109
|
+
const dirs = [
|
|
110
|
+
this.getDataDir(),
|
|
111
|
+
this.getArtifactsDir(),
|
|
112
|
+
path.join(this.getDataDir(), "backups"),
|
|
113
|
+
path.join(this.getDataDir(), "logs")
|
|
114
|
+
];
|
|
115
|
+
dirs.forEach((dir) => {
|
|
116
|
+
if (!fs3.existsSync(dir)) {
|
|
117
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
getConfigPath() {
|
|
122
|
+
return this.configPath;
|
|
123
|
+
}
|
|
124
|
+
// Get full configuration
|
|
125
|
+
getConfig() {
|
|
126
|
+
return { ...this.config };
|
|
127
|
+
}
|
|
128
|
+
// Reset to defaults
|
|
129
|
+
reset() {
|
|
130
|
+
this.config = {
|
|
131
|
+
server: {
|
|
132
|
+
port: 8082,
|
|
133
|
+
host: "localhost"
|
|
134
|
+
},
|
|
135
|
+
data: {
|
|
136
|
+
dir: path.join(os.homedir(), ".calimero-registry", "data"),
|
|
137
|
+
artifactsDir: path.join(
|
|
138
|
+
os.homedir(),
|
|
139
|
+
".calimero-registry",
|
|
140
|
+
"artifacts"
|
|
141
|
+
)
|
|
142
|
+
},
|
|
143
|
+
artifacts: {
|
|
144
|
+
storageDir: path.join(os.homedir(), ".calimero-registry", "artifacts"),
|
|
145
|
+
serveLocal: true,
|
|
146
|
+
copyArtifacts: true,
|
|
147
|
+
maxFileSize: "100MB",
|
|
148
|
+
allowedTypes: ["wasm", "js", "html"]
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
this.saveConfig();
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
var LocalDataStore = class {
|
|
155
|
+
constructor(config) {
|
|
156
|
+
this.config = config;
|
|
157
|
+
this.appsFile = path.join(config.getDataDir(), "apps.json");
|
|
158
|
+
this.manifestsFile = path.join(config.getDataDir(), "manifests.json");
|
|
159
|
+
this.artifactsFile = path.join(config.getDataDir(), "artifacts.json");
|
|
160
|
+
this.data = {
|
|
161
|
+
apps: /* @__PURE__ */ new Map(),
|
|
162
|
+
manifests: /* @__PURE__ */ new Map(),
|
|
163
|
+
artifacts: /* @__PURE__ */ new Map()
|
|
164
|
+
};
|
|
165
|
+
this.loadData();
|
|
166
|
+
}
|
|
167
|
+
loadData() {
|
|
168
|
+
try {
|
|
169
|
+
if (fs3.existsSync(this.appsFile)) {
|
|
170
|
+
const appsData = JSON.parse(fs3.readFileSync(this.appsFile, "utf8"));
|
|
171
|
+
this.data.apps = new Map(Object.entries(appsData));
|
|
172
|
+
}
|
|
173
|
+
if (fs3.existsSync(this.manifestsFile)) {
|
|
174
|
+
const manifestsData = JSON.parse(
|
|
175
|
+
fs3.readFileSync(this.manifestsFile, "utf8")
|
|
176
|
+
);
|
|
177
|
+
this.data.manifests = new Map(Object.entries(manifestsData));
|
|
178
|
+
}
|
|
179
|
+
if (fs3.existsSync(this.artifactsFile)) {
|
|
180
|
+
const artifactsData = JSON.parse(
|
|
181
|
+
fs3.readFileSync(this.artifactsFile, "utf8")
|
|
182
|
+
);
|
|
183
|
+
this.data.artifacts = new Map(Object.entries(artifactsData));
|
|
184
|
+
}
|
|
185
|
+
} catch {
|
|
186
|
+
console.warn("Failed to load existing data, starting fresh");
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
saveData() {
|
|
190
|
+
try {
|
|
191
|
+
this.config.ensureDirectories();
|
|
192
|
+
const appsObj = Object.fromEntries(this.data.apps);
|
|
193
|
+
fs3.writeFileSync(this.appsFile, JSON.stringify(appsObj, null, 2));
|
|
194
|
+
const manifestsObj = Object.fromEntries(this.data.manifests);
|
|
195
|
+
fs3.writeFileSync(
|
|
196
|
+
this.manifestsFile,
|
|
197
|
+
JSON.stringify(manifestsObj, null, 2)
|
|
198
|
+
);
|
|
199
|
+
const artifactsObj = Object.fromEntries(this.data.artifacts);
|
|
200
|
+
fs3.writeFileSync(
|
|
201
|
+
this.artifactsFile,
|
|
202
|
+
JSON.stringify(artifactsObj, null, 2)
|
|
203
|
+
);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
throw new Error(
|
|
206
|
+
`Failed to save data: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// Apps management
|
|
211
|
+
getApps(filters) {
|
|
212
|
+
let apps = Array.from(this.data.apps.values());
|
|
213
|
+
if (filters?.dev) {
|
|
214
|
+
apps = apps.filter((app) => app.developer_pubkey === filters.dev);
|
|
215
|
+
}
|
|
216
|
+
if (filters?.name) {
|
|
217
|
+
apps = apps.filter((app) => app.name.includes(filters.name));
|
|
218
|
+
}
|
|
219
|
+
return apps;
|
|
220
|
+
}
|
|
221
|
+
getApp(appKey) {
|
|
222
|
+
return this.data.apps.get(appKey);
|
|
223
|
+
}
|
|
224
|
+
setApp(appKey, app) {
|
|
225
|
+
this.data.apps.set(appKey, app);
|
|
226
|
+
this.saveData();
|
|
227
|
+
}
|
|
228
|
+
// Versions management
|
|
229
|
+
getAppVersions(appId) {
|
|
230
|
+
const versions = [];
|
|
231
|
+
for (const [key, manifest] of this.data.manifests.entries()) {
|
|
232
|
+
if (key.startsWith(`${appId}/`)) {
|
|
233
|
+
const semver = key.split("/").pop();
|
|
234
|
+
versions.push({
|
|
235
|
+
semver,
|
|
236
|
+
cid: manifest.artifacts[0]?.cid || "",
|
|
237
|
+
yanked: false
|
|
238
|
+
// TODO: Implement yanking
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return versions.sort(
|
|
243
|
+
(a, b) => a.semver.localeCompare(b.semver, void 0, { numeric: true })
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
// Manifest management
|
|
247
|
+
getManifest(appId, semver) {
|
|
248
|
+
const manifestKey = `${appId}/${semver}`;
|
|
249
|
+
return this.data.manifests.get(manifestKey);
|
|
250
|
+
}
|
|
251
|
+
setManifest(manifestKey, manifest) {
|
|
252
|
+
this.data.manifests.set(manifestKey, manifest);
|
|
253
|
+
this.saveData();
|
|
254
|
+
}
|
|
255
|
+
// Artifact management
|
|
256
|
+
getArtifactPath(hash) {
|
|
257
|
+
return this.data.artifacts.get(hash);
|
|
258
|
+
}
|
|
259
|
+
setArtifactPath(hash, filePath) {
|
|
260
|
+
this.data.artifacts.set(hash, filePath);
|
|
261
|
+
this.saveData();
|
|
262
|
+
}
|
|
263
|
+
// Statistics
|
|
264
|
+
getStats() {
|
|
265
|
+
return {
|
|
266
|
+
publishedApps: this.data.apps.size,
|
|
267
|
+
totalVersions: this.data.manifests.size,
|
|
268
|
+
totalArtifacts: this.data.artifacts.size
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
// Backup and restore
|
|
272
|
+
async backup(outputPath) {
|
|
273
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
274
|
+
const backupPath = outputPath || path.join(
|
|
275
|
+
this.config.getDataDir(),
|
|
276
|
+
"backups",
|
|
277
|
+
`backup-${timestamp}.json`
|
|
278
|
+
);
|
|
279
|
+
const backupDir = path.dirname(backupPath);
|
|
280
|
+
if (!fs3.existsSync(backupDir)) {
|
|
281
|
+
fs3.mkdirSync(backupDir, { recursive: true });
|
|
282
|
+
}
|
|
283
|
+
const backupData = {
|
|
284
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
285
|
+
apps: Object.fromEntries(this.data.apps),
|
|
286
|
+
manifests: Object.fromEntries(this.data.manifests),
|
|
287
|
+
artifacts: Object.fromEntries(this.data.artifacts)
|
|
288
|
+
};
|
|
289
|
+
fs3.writeFileSync(backupPath, JSON.stringify(backupData, null, 2));
|
|
290
|
+
return backupPath;
|
|
291
|
+
}
|
|
292
|
+
async restore(backupPath) {
|
|
293
|
+
if (!fs3.existsSync(backupPath)) {
|
|
294
|
+
throw new Error(`Backup file not found: ${backupPath}`);
|
|
295
|
+
}
|
|
296
|
+
const backupData = JSON.parse(fs3.readFileSync(backupPath, "utf8"));
|
|
297
|
+
this.data.apps = new Map(Object.entries(backupData.apps || {}));
|
|
298
|
+
this.data.manifests = new Map(Object.entries(backupData.manifests || {}));
|
|
299
|
+
this.data.artifacts = new Map(Object.entries(backupData.artifacts || {}));
|
|
300
|
+
this.saveData();
|
|
301
|
+
}
|
|
302
|
+
// Reset data
|
|
303
|
+
reset() {
|
|
304
|
+
this.data.apps.clear();
|
|
305
|
+
this.data.manifests.clear();
|
|
306
|
+
this.data.artifacts.clear();
|
|
307
|
+
this.saveData();
|
|
308
|
+
}
|
|
309
|
+
// Seed with sample data
|
|
310
|
+
async seed() {
|
|
311
|
+
const sampleApps = [
|
|
312
|
+
{
|
|
313
|
+
name: "sample-wallet",
|
|
314
|
+
developer_pubkey: "ed25519:5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
|
|
315
|
+
latest_version: "1.0.0",
|
|
316
|
+
latest_cid: "QmSampleWallet123",
|
|
317
|
+
alias: "Sample Wallet"
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
name: "demo-dex",
|
|
321
|
+
developer_pubkey: "ed25519:5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty",
|
|
322
|
+
latest_version: "2.1.0",
|
|
323
|
+
latest_cid: "QmDemoDex456",
|
|
324
|
+
alias: "Demo DEX"
|
|
325
|
+
}
|
|
326
|
+
];
|
|
327
|
+
const sampleManifests = [
|
|
328
|
+
{
|
|
329
|
+
manifest_version: "1.0",
|
|
330
|
+
app: {
|
|
331
|
+
name: "sample-wallet",
|
|
332
|
+
namespace: "com.example",
|
|
333
|
+
developer_pubkey: "ed25519:5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
|
|
334
|
+
id: "sample-wallet-id",
|
|
335
|
+
alias: "Sample Wallet"
|
|
336
|
+
},
|
|
337
|
+
version: {
|
|
338
|
+
semver: "1.0.0"
|
|
339
|
+
},
|
|
340
|
+
supported_chains: ["mainnet", "testnet"],
|
|
341
|
+
permissions: [
|
|
342
|
+
{ cap: "wallet", bytes: 1024 },
|
|
343
|
+
{ cap: "network", bytes: 512 }
|
|
344
|
+
],
|
|
345
|
+
artifacts: [
|
|
346
|
+
{
|
|
347
|
+
type: "wasm",
|
|
348
|
+
target: "node",
|
|
349
|
+
path: "/artifacts/sample-wallet/1.0.0/app.wasm",
|
|
350
|
+
size: 1024e3,
|
|
351
|
+
sha256: "abc123def456"
|
|
352
|
+
}
|
|
353
|
+
],
|
|
354
|
+
metadata: {
|
|
355
|
+
description: "A sample wallet application for testing",
|
|
356
|
+
author: "Sample Developer"
|
|
357
|
+
},
|
|
358
|
+
distribution: "local",
|
|
359
|
+
signature: {
|
|
360
|
+
alg: "ed25519",
|
|
361
|
+
sig: "sample-signature",
|
|
362
|
+
signed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
];
|
|
366
|
+
sampleApps.forEach((app) => {
|
|
367
|
+
const appKey = `${app.developer_pubkey}/${app.name}`;
|
|
368
|
+
this.setApp(appKey, app);
|
|
369
|
+
});
|
|
370
|
+
sampleManifests.forEach((manifest) => {
|
|
371
|
+
const manifestKey = `${manifest.app.app_id}/${manifest.version.semver}`;
|
|
372
|
+
this.setManifest(manifestKey, manifest);
|
|
373
|
+
});
|
|
374
|
+
this.saveData();
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
var LocalArtifactServer = class {
|
|
378
|
+
constructor(config, dataStore) {
|
|
379
|
+
this.config = config;
|
|
380
|
+
this.dataStore = dataStore;
|
|
381
|
+
this.artifactsDir = config.getArtifactsDir();
|
|
382
|
+
this.ensureArtifactsDir();
|
|
383
|
+
}
|
|
384
|
+
ensureArtifactsDir() {
|
|
385
|
+
if (!fs3.existsSync(this.artifactsDir)) {
|
|
386
|
+
fs3.mkdirSync(this.artifactsDir, { recursive: true });
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
// Copy artifact to local storage
|
|
390
|
+
async copyArtifactToLocal(sourcePath, appId, version, filename) {
|
|
391
|
+
if (!fs3.existsSync(sourcePath)) {
|
|
392
|
+
throw new Error(`Source file not found: ${sourcePath}`);
|
|
393
|
+
}
|
|
394
|
+
const appVersionDir = path.join(this.artifactsDir, appId, version);
|
|
395
|
+
if (!fs3.existsSync(appVersionDir)) {
|
|
396
|
+
fs3.mkdirSync(appVersionDir, { recursive: true });
|
|
397
|
+
}
|
|
398
|
+
const targetPath = path.join(appVersionDir, filename);
|
|
399
|
+
fs3.copyFileSync(sourcePath, targetPath);
|
|
400
|
+
const fileHash = await this.calculateFileHash(targetPath);
|
|
401
|
+
this.dataStore.setArtifactPath(fileHash, targetPath);
|
|
402
|
+
return targetPath;
|
|
403
|
+
}
|
|
404
|
+
// Serve artifact by app ID, version, and filename
|
|
405
|
+
async serveArtifact(appId, version, filename) {
|
|
406
|
+
const artifactPath = path.join(this.artifactsDir, appId, version, filename);
|
|
407
|
+
if (!fs3.existsSync(artifactPath)) {
|
|
408
|
+
throw new Error(`Artifact not found: ${artifactPath}`);
|
|
409
|
+
}
|
|
410
|
+
return fs3.readFileSync(artifactPath);
|
|
411
|
+
}
|
|
412
|
+
// Serve artifact by hash
|
|
413
|
+
async serveByHash(hash) {
|
|
414
|
+
const artifactPath = this.dataStore.getArtifactPath(hash);
|
|
415
|
+
if (!artifactPath || !fs3.existsSync(artifactPath)) {
|
|
416
|
+
throw new Error(`Artifact not found for hash: ${hash}`);
|
|
417
|
+
}
|
|
418
|
+
return fs3.readFileSync(artifactPath);
|
|
419
|
+
}
|
|
420
|
+
// Validate artifact file
|
|
421
|
+
async validateArtifact(filePath) {
|
|
422
|
+
if (!fs3.existsSync(filePath)) {
|
|
423
|
+
throw new Error(`File not found: ${filePath}`);
|
|
424
|
+
}
|
|
425
|
+
const stats = fs3.statSync(filePath);
|
|
426
|
+
const fileBuffer = fs3.readFileSync(filePath);
|
|
427
|
+
const sha256 = crypto.createHash("sha256").update(fileBuffer).digest("hex");
|
|
428
|
+
return {
|
|
429
|
+
size: stats.size,
|
|
430
|
+
sha256
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
// Calculate file hash
|
|
434
|
+
async calculateFileHash(filePath) {
|
|
435
|
+
const fileBuffer = fs3.readFileSync(filePath);
|
|
436
|
+
return crypto.createHash("sha256").update(fileBuffer).digest("hex");
|
|
437
|
+
}
|
|
438
|
+
// Get artifact URL for local serving
|
|
439
|
+
getArtifactUrl(appId, version, filename) {
|
|
440
|
+
const baseUrl = `http://${this.config.getHost()}:${this.config.getPort()}`;
|
|
441
|
+
return `${baseUrl}/artifacts/${appId}/${version}/${filename}`;
|
|
442
|
+
}
|
|
443
|
+
// Get artifact URL by hash
|
|
444
|
+
getArtifactUrlByHash(hash) {
|
|
445
|
+
const baseUrl = `http://${this.config.getHost()}:${this.config.getPort()}`;
|
|
446
|
+
return `${baseUrl}/artifacts/${hash}`;
|
|
447
|
+
}
|
|
448
|
+
// Update manifest artifacts to use local URLs
|
|
449
|
+
updateManifestArtifacts(manifest) {
|
|
450
|
+
const updatedManifest = { ...manifest };
|
|
451
|
+
if (updatedManifest.artifacts) {
|
|
452
|
+
updatedManifest.artifacts = updatedManifest.artifacts.map(
|
|
453
|
+
(artifact) => {
|
|
454
|
+
const updatedArtifact = { ...artifact };
|
|
455
|
+
if (artifact.path) {
|
|
456
|
+
const filename = path.basename(artifact.path);
|
|
457
|
+
updatedArtifact.mirrors = [
|
|
458
|
+
this.getArtifactUrl(
|
|
459
|
+
manifest.app.id || manifest.app.name?.replace(/\s+/g, "-").toLowerCase(),
|
|
460
|
+
manifest.version.semver,
|
|
461
|
+
filename
|
|
462
|
+
)
|
|
463
|
+
];
|
|
464
|
+
delete updatedArtifact.path;
|
|
465
|
+
}
|
|
466
|
+
return updatedArtifact;
|
|
467
|
+
}
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
return updatedManifest;
|
|
471
|
+
}
|
|
472
|
+
// Clean up old artifacts
|
|
473
|
+
async cleanupOldArtifacts(maxAge = 30 * 24 * 60 * 60 * 1e3) {
|
|
474
|
+
const now = Date.now();
|
|
475
|
+
const getAllFiles = (dir) => {
|
|
476
|
+
let files = [];
|
|
477
|
+
const items = fs3.readdirSync(dir);
|
|
478
|
+
for (const item of items) {
|
|
479
|
+
const fullPath = path.join(dir, item);
|
|
480
|
+
const stat = fs3.statSync(fullPath);
|
|
481
|
+
if (stat.isDirectory()) {
|
|
482
|
+
files = files.concat(getAllFiles(fullPath));
|
|
483
|
+
} else {
|
|
484
|
+
files.push(fullPath);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return files;
|
|
488
|
+
};
|
|
489
|
+
const allFiles = getAllFiles(this.artifactsDir);
|
|
490
|
+
for (const file of allFiles) {
|
|
491
|
+
const stats = fs3.statSync(file);
|
|
492
|
+
const age = now - stats.mtime.getTime();
|
|
493
|
+
if (age > maxAge) {
|
|
494
|
+
fs3.unlinkSync(file);
|
|
495
|
+
console.log(`Cleaned up old artifact: ${file}`);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
// Get artifact statistics
|
|
500
|
+
getArtifactStats() {
|
|
501
|
+
let totalFiles = 0;
|
|
502
|
+
let totalSize = 0;
|
|
503
|
+
let oldestFile = null;
|
|
504
|
+
const getAllFiles = (dir) => {
|
|
505
|
+
let files = [];
|
|
506
|
+
const items = fs3.readdirSync(dir);
|
|
507
|
+
for (const item of items) {
|
|
508
|
+
const fullPath = path.join(dir, item);
|
|
509
|
+
const stat = fs3.statSync(fullPath);
|
|
510
|
+
if (stat.isDirectory()) {
|
|
511
|
+
files = files.concat(getAllFiles(fullPath));
|
|
512
|
+
} else {
|
|
513
|
+
files.push(fullPath);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return files;
|
|
517
|
+
};
|
|
518
|
+
if (fs3.existsSync(this.artifactsDir)) {
|
|
519
|
+
const allFiles = getAllFiles(this.artifactsDir);
|
|
520
|
+
for (const file of allFiles) {
|
|
521
|
+
const stats = fs3.statSync(file);
|
|
522
|
+
totalFiles++;
|
|
523
|
+
totalSize += stats.size;
|
|
524
|
+
if (!oldestFile || stats.mtime < oldestFile) {
|
|
525
|
+
oldestFile = stats.mtime;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return {
|
|
530
|
+
totalFiles,
|
|
531
|
+
totalSize,
|
|
532
|
+
oldestFile
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
var RemoteRegistryClient = class {
|
|
537
|
+
constructor(baseURL, timeout) {
|
|
538
|
+
this.client = new SSAppRegistryClient({
|
|
539
|
+
baseURL,
|
|
540
|
+
timeout
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
async getApps(filters) {
|
|
544
|
+
return await this.client.getApps(filters);
|
|
545
|
+
}
|
|
546
|
+
async getAppVersions(appId) {
|
|
547
|
+
return await this.client.getAppVersions(appId);
|
|
548
|
+
}
|
|
549
|
+
async getAppManifest(appId, semver) {
|
|
550
|
+
return await this.client.getAppManifest(appId, semver);
|
|
551
|
+
}
|
|
552
|
+
async submitAppManifest(manifest) {
|
|
553
|
+
return await this.client.submitAppManifest(manifest);
|
|
554
|
+
}
|
|
555
|
+
async healthCheck() {
|
|
556
|
+
return await this.client.healthCheck();
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
var LocalRegistryClient = class {
|
|
560
|
+
constructor() {
|
|
561
|
+
this.config = new LocalConfig();
|
|
562
|
+
this.dataStore = new LocalDataStore(this.config);
|
|
563
|
+
this.artifactServer = new LocalArtifactServer(this.config, this.dataStore);
|
|
564
|
+
}
|
|
565
|
+
async getApps(filters) {
|
|
566
|
+
return this.dataStore.getApps(filters);
|
|
567
|
+
}
|
|
568
|
+
async getAppVersions(appId) {
|
|
569
|
+
return this.dataStore.getAppVersions(appId);
|
|
570
|
+
}
|
|
571
|
+
async getAppManifest(appId, semver) {
|
|
572
|
+
const manifest = this.dataStore.getManifest(appId, semver);
|
|
573
|
+
if (!manifest) {
|
|
574
|
+
throw new Error("Manifest not found");
|
|
575
|
+
}
|
|
576
|
+
return this.artifactServer.updateManifestArtifacts(manifest);
|
|
577
|
+
}
|
|
578
|
+
async submitAppManifest(manifest) {
|
|
579
|
+
if (!this.validateManifest(manifest)) {
|
|
580
|
+
throw new Error("Invalid manifest structure");
|
|
581
|
+
}
|
|
582
|
+
const processedManifest = await this.processManifestArtifacts(manifest);
|
|
583
|
+
const manifestKey = `${manifest.app.app_id}/${manifest.version.semver}`;
|
|
584
|
+
this.dataStore.setManifest(manifestKey, processedManifest);
|
|
585
|
+
const appKey = manifest.app.app_id;
|
|
586
|
+
const appSummary = {
|
|
587
|
+
name: manifest.app.name,
|
|
588
|
+
developer_pubkey: manifest.app.developer_pubkey,
|
|
589
|
+
latest_version: manifest.version.semver,
|
|
590
|
+
latest_cid: manifest.artifacts[0]?.cid || "",
|
|
591
|
+
alias: manifest.app.alias
|
|
592
|
+
};
|
|
593
|
+
this.dataStore.setApp(appKey, appSummary);
|
|
594
|
+
return {
|
|
595
|
+
success: true,
|
|
596
|
+
message: "App version registered successfully"
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
async healthCheck() {
|
|
600
|
+
return { status: "ok" };
|
|
601
|
+
}
|
|
602
|
+
validateManifest(manifest) {
|
|
603
|
+
return !!(manifest.manifest_version && manifest.app && manifest.app.name && manifest.app.developer_pubkey && manifest.version && manifest.version.semver && manifest.artifacts && manifest.artifacts.length > 0);
|
|
604
|
+
}
|
|
605
|
+
async processManifestArtifacts(manifest) {
|
|
606
|
+
const processedManifest = { ...manifest };
|
|
607
|
+
if (processedManifest.artifacts) {
|
|
608
|
+
processedManifest.artifacts = await Promise.all(
|
|
609
|
+
processedManifest.artifacts.map(async (artifact) => {
|
|
610
|
+
const processedArtifact = { ...artifact };
|
|
611
|
+
if (artifact.path && fs3.existsSync(artifact.path)) {
|
|
612
|
+
const filename = path.basename(artifact.path);
|
|
613
|
+
const appId = manifest.app.id || manifest.app.name?.replace(/\s+/g, "-").toLowerCase();
|
|
614
|
+
try {
|
|
615
|
+
await this.artifactServer.copyArtifactToLocal(
|
|
616
|
+
artifact.path,
|
|
617
|
+
appId,
|
|
618
|
+
manifest.version.semver,
|
|
619
|
+
filename
|
|
620
|
+
);
|
|
621
|
+
processedArtifact.mirrors = [
|
|
622
|
+
this.artifactServer.getArtifactUrl(
|
|
623
|
+
appId,
|
|
624
|
+
manifest.version.semver,
|
|
625
|
+
filename
|
|
626
|
+
)
|
|
627
|
+
];
|
|
628
|
+
delete processedArtifact.path;
|
|
629
|
+
} catch (error) {
|
|
630
|
+
console.warn(`Failed to copy artifact ${artifact.path}:`, error);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
return processedArtifact;
|
|
634
|
+
})
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
return processedManifest;
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
function createRegistryClient(useLocal, baseURL, timeout) {
|
|
641
|
+
if (useLocal) {
|
|
642
|
+
return new LocalRegistryClient();
|
|
643
|
+
} else {
|
|
644
|
+
return new RemoteRegistryClient(
|
|
645
|
+
baseURL || "http://localhost:8082",
|
|
646
|
+
timeout || 1e4
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
10
650
|
var appsCommand = new Command("apps").description("Manage SSApp applications").addCommand(
|
|
11
651
|
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
652
|
const globalOpts = command.parent?.parent?.opts();
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
653
|
+
const useLocal = globalOpts?.local || false;
|
|
654
|
+
const client = createRegistryClient(
|
|
655
|
+
useLocal,
|
|
656
|
+
globalOpts?.url,
|
|
657
|
+
parseInt(globalOpts?.timeout || "10000")
|
|
658
|
+
);
|
|
659
|
+
const spinner2 = ora6("Fetching applications...").start();
|
|
18
660
|
try {
|
|
19
661
|
const apps = await client.getApps({
|
|
20
662
|
dev: options.dev,
|
|
@@ -22,7 +664,7 @@ var appsCommand = new Command("apps").description("Manage SSApp applications").a
|
|
|
22
664
|
});
|
|
23
665
|
spinner2.succeed(`Found ${apps.length} application(s)`);
|
|
24
666
|
if (apps.length === 0) {
|
|
25
|
-
console.log(
|
|
667
|
+
console.log(chalk5.yellow("No applications found"));
|
|
26
668
|
return;
|
|
27
669
|
}
|
|
28
670
|
const tableData = [
|
|
@@ -39,7 +681,7 @@ var appsCommand = new Command("apps").description("Manage SSApp applications").a
|
|
|
39
681
|
} catch (error) {
|
|
40
682
|
spinner2.fail("Failed to fetch applications");
|
|
41
683
|
if (error instanceof Error) {
|
|
42
|
-
console.error(
|
|
684
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
43
685
|
}
|
|
44
686
|
process.exit(1);
|
|
45
687
|
}
|
|
@@ -47,16 +689,18 @@ var appsCommand = new Command("apps").description("Manage SSApp applications").a
|
|
|
47
689
|
).addCommand(
|
|
48
690
|
new Command("versions").description("List versions of a specific application").argument("<appId>", "Application ID").action(async (appId, options, command) => {
|
|
49
691
|
const globalOpts = command.parent?.parent?.opts();
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
692
|
+
const useLocal = globalOpts?.local || false;
|
|
693
|
+
const client = createRegistryClient(
|
|
694
|
+
useLocal,
|
|
695
|
+
globalOpts?.url,
|
|
696
|
+
parseInt(globalOpts?.timeout || "10000")
|
|
697
|
+
);
|
|
698
|
+
const spinner2 = ora6(`Fetching versions for ${appId}...`).start();
|
|
55
699
|
try {
|
|
56
700
|
const versions = await client.getAppVersions(appId);
|
|
57
701
|
spinner2.succeed(`Found ${versions.length} version(s)`);
|
|
58
702
|
if (versions.length === 0) {
|
|
59
|
-
console.log(
|
|
703
|
+
console.log(chalk5.yellow("No versions found"));
|
|
60
704
|
return;
|
|
61
705
|
}
|
|
62
706
|
const tableData = [
|
|
@@ -64,14 +708,14 @@ var appsCommand = new Command("apps").description("Manage SSApp applications").a
|
|
|
64
708
|
...versions.map((version) => [
|
|
65
709
|
version.semver,
|
|
66
710
|
version.cid.substring(0, 12) + "...",
|
|
67
|
-
version.yanked ?
|
|
711
|
+
version.yanked ? chalk5.red("Yes") : chalk5.green("No")
|
|
68
712
|
])
|
|
69
713
|
];
|
|
70
714
|
console.log(table(tableData));
|
|
71
715
|
} catch (error) {
|
|
72
716
|
spinner2.fail("Failed to fetch versions");
|
|
73
717
|
if (error instanceof Error) {
|
|
74
|
-
console.error(
|
|
718
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
75
719
|
}
|
|
76
720
|
process.exit(1);
|
|
77
721
|
}
|
|
@@ -79,22 +723,24 @@ var appsCommand = new Command("apps").description("Manage SSApp applications").a
|
|
|
79
723
|
).addCommand(
|
|
80
724
|
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
725
|
const globalOpts = command.parent?.parent?.opts();
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
726
|
+
const useLocal = globalOpts?.local || false;
|
|
727
|
+
const client = createRegistryClient(
|
|
728
|
+
useLocal,
|
|
729
|
+
globalOpts?.url,
|
|
730
|
+
parseInt(globalOpts?.timeout || "10000")
|
|
731
|
+
);
|
|
732
|
+
const spinner2 = ora6(
|
|
87
733
|
`Fetching manifest for ${appId}@${version}...`
|
|
88
734
|
).start();
|
|
89
735
|
try {
|
|
90
736
|
const manifest = await client.getAppManifest(appId, version);
|
|
91
737
|
spinner2.succeed("Manifest fetched successfully");
|
|
92
|
-
console.log(
|
|
738
|
+
console.log(chalk5.blue("\nApplication Manifest:"));
|
|
93
739
|
console.log(JSON.stringify(manifest, null, 2));
|
|
94
740
|
} catch (error) {
|
|
95
741
|
spinner2.fail("Failed to fetch manifest");
|
|
96
742
|
if (error instanceof Error) {
|
|
97
|
-
console.error(
|
|
743
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
98
744
|
}
|
|
99
745
|
process.exit(1);
|
|
100
746
|
}
|
|
@@ -102,37 +748,39 @@ var appsCommand = new Command("apps").description("Manage SSApp applications").a
|
|
|
102
748
|
).addCommand(
|
|
103
749
|
new Command("submit").description("Submit a new application manifest").argument("<manifest-file>", "Path to the manifest JSON file").action(async (manifestFile, options, command) => {
|
|
104
750
|
const globalOpts = command.parent?.parent?.opts();
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
751
|
+
const useLocal = globalOpts?.local || false;
|
|
752
|
+
const client = createRegistryClient(
|
|
753
|
+
useLocal,
|
|
754
|
+
globalOpts?.url,
|
|
755
|
+
parseInt(globalOpts?.timeout || "10000")
|
|
756
|
+
);
|
|
757
|
+
const spinner2 = ora6("Reading manifest file...").start();
|
|
110
758
|
try {
|
|
111
759
|
const manifestPath = path.resolve(manifestFile);
|
|
112
|
-
if (!
|
|
760
|
+
if (!fs3.existsSync(manifestPath)) {
|
|
113
761
|
spinner2.fail("Manifest file not found");
|
|
114
|
-
console.error(
|
|
762
|
+
console.error(chalk5.red(`File not found: ${manifestFile}`));
|
|
115
763
|
process.exit(1);
|
|
116
764
|
}
|
|
117
|
-
const manifestContent =
|
|
765
|
+
const manifestContent = fs3.readFileSync(manifestPath, "utf8");
|
|
118
766
|
const manifest = JSON.parse(manifestContent);
|
|
119
767
|
spinner2.text = "Submitting application manifest...";
|
|
120
768
|
const result = await client.submitAppManifest(manifest);
|
|
121
769
|
spinner2.succeed("Application submitted successfully");
|
|
122
|
-
console.log(
|
|
770
|
+
console.log(chalk5.green(`
|
|
123
771
|
\u2705 ${result.message}`));
|
|
124
772
|
if (manifest.app?.name) {
|
|
125
|
-
console.log(
|
|
773
|
+
console.log(chalk5.blue(`
|
|
126
774
|
\u{1F4F1} App: ${manifest.app.name}`));
|
|
127
775
|
console.log(
|
|
128
|
-
|
|
776
|
+
chalk5.blue(`\u{1F464} Developer: ${manifest.app.developer_pubkey}`)
|
|
129
777
|
);
|
|
130
|
-
console.log(
|
|
778
|
+
console.log(chalk5.blue(`\u{1F4E6} Version: ${manifest.version?.semver}`));
|
|
131
779
|
}
|
|
132
780
|
} catch (error) {
|
|
133
781
|
spinner2.fail("Failed to submit application");
|
|
134
782
|
if (error instanceof Error) {
|
|
135
|
-
console.error(
|
|
783
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
136
784
|
}
|
|
137
785
|
process.exit(1);
|
|
138
786
|
}
|
|
@@ -145,33 +793,33 @@ var developersCommand = new Command("developers").description("Manage developer
|
|
|
145
793
|
baseURL: globalOpts?.url || "http://localhost:8082",
|
|
146
794
|
timeout: parseInt(globalOpts?.timeout || "10000")
|
|
147
795
|
});
|
|
148
|
-
const spinner2 =
|
|
796
|
+
const spinner2 = ora6("Fetching developer profile...").start();
|
|
149
797
|
try {
|
|
150
798
|
const profile = await client.getDeveloper(pubkey);
|
|
151
799
|
spinner2.succeed("Developer profile fetched successfully");
|
|
152
|
-
console.log(
|
|
153
|
-
console.log(
|
|
800
|
+
console.log(chalk5.blue("\nDeveloper Profile:"));
|
|
801
|
+
console.log(chalk5.green("Display Name:"), profile.display_name);
|
|
154
802
|
if (profile.website) {
|
|
155
|
-
console.log(
|
|
803
|
+
console.log(chalk5.green("Website:"), profile.website);
|
|
156
804
|
}
|
|
157
805
|
if (profile.proofs.length > 0) {
|
|
158
|
-
console.log(
|
|
806
|
+
console.log(chalk5.green("\nProofs:"));
|
|
159
807
|
const tableData = [
|
|
160
808
|
["Type", "Value", "Verified"],
|
|
161
809
|
...profile.proofs.map((proof) => [
|
|
162
810
|
proof.type,
|
|
163
811
|
proof.value.substring(0, 20) + "...",
|
|
164
|
-
proof.verified ?
|
|
812
|
+
proof.verified ? chalk5.green("Yes") : chalk5.red("No")
|
|
165
813
|
])
|
|
166
814
|
];
|
|
167
815
|
console.log(table(tableData));
|
|
168
816
|
} else {
|
|
169
|
-
console.log(
|
|
817
|
+
console.log(chalk5.yellow("\nNo proofs found"));
|
|
170
818
|
}
|
|
171
819
|
} catch (error) {
|
|
172
820
|
spinner2.fail("Failed to fetch developer profile");
|
|
173
821
|
if (error instanceof Error) {
|
|
174
|
-
console.error(
|
|
822
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
175
823
|
}
|
|
176
824
|
process.exit(1);
|
|
177
825
|
}
|
|
@@ -183,7 +831,7 @@ var developersCommand = new Command("developers").description("Manage developer
|
|
|
183
831
|
baseURL: globalOpts?.url || "http://localhost:8082",
|
|
184
832
|
timeout: parseInt(globalOpts?.timeout || "10000")
|
|
185
833
|
});
|
|
186
|
-
const spinner2 =
|
|
834
|
+
const spinner2 = ora6("Creating developer profile...").start();
|
|
187
835
|
try {
|
|
188
836
|
let proofs = [];
|
|
189
837
|
if (options.proofs) {
|
|
@@ -191,7 +839,7 @@ var developersCommand = new Command("developers").description("Manage developer
|
|
|
191
839
|
proofs = JSON.parse(options.proofs);
|
|
192
840
|
} catch {
|
|
193
841
|
spinner2.fail("Invalid proofs JSON format");
|
|
194
|
-
console.error(
|
|
842
|
+
console.error(chalk5.red("Proofs must be a valid JSON array"));
|
|
195
843
|
process.exit(1);
|
|
196
844
|
}
|
|
197
845
|
}
|
|
@@ -203,18 +851,18 @@ var developersCommand = new Command("developers").description("Manage developer
|
|
|
203
851
|
};
|
|
204
852
|
const result = await client.submitDeveloperProfile(pubkey, profile);
|
|
205
853
|
spinner2.succeed("Developer profile created successfully");
|
|
206
|
-
console.log(
|
|
854
|
+
console.log(chalk5.green(`
|
|
207
855
|
\u2705 ${result.message}`));
|
|
208
|
-
console.log(
|
|
856
|
+
console.log(chalk5.blue(`
|
|
209
857
|
\u{1F464} Developer: ${displayName}`));
|
|
210
|
-
console.log(
|
|
858
|
+
console.log(chalk5.blue(`\u{1F511} Public Key: ${pubkey}`));
|
|
211
859
|
if (options.website) {
|
|
212
|
-
console.log(
|
|
860
|
+
console.log(chalk5.blue(`\u{1F310} Website: ${options.website}`));
|
|
213
861
|
}
|
|
214
862
|
} catch (error) {
|
|
215
863
|
spinner2.fail("Failed to create developer profile");
|
|
216
864
|
if (error instanceof Error) {
|
|
217
|
-
console.error(
|
|
865
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
218
866
|
}
|
|
219
867
|
process.exit(1);
|
|
220
868
|
}
|
|
@@ -227,7 +875,7 @@ var attestationsCommand = new Command("attestations").description("Manage applic
|
|
|
227
875
|
baseURL: globalOpts?.url || "http://localhost:8082",
|
|
228
876
|
timeout: parseInt(globalOpts?.timeout || "10000")
|
|
229
877
|
});
|
|
230
|
-
const spinner2 =
|
|
878
|
+
const spinner2 = ora6(
|
|
231
879
|
`Fetching attestation for ${name}@${version}...`
|
|
232
880
|
).start();
|
|
233
881
|
try {
|
|
@@ -237,16 +885,16 @@ var attestationsCommand = new Command("attestations").description("Manage applic
|
|
|
237
885
|
version
|
|
238
886
|
);
|
|
239
887
|
spinner2.succeed("Attestation fetched successfully");
|
|
240
|
-
console.log(
|
|
241
|
-
console.log(
|
|
242
|
-
console.log(
|
|
888
|
+
console.log(chalk5.blue("\nAttestation:"));
|
|
889
|
+
console.log(chalk5.green("Status:"), attestation.status);
|
|
890
|
+
console.log(chalk5.green("Timestamp:"), attestation.timestamp);
|
|
243
891
|
if (attestation.comment) {
|
|
244
|
-
console.log(
|
|
892
|
+
console.log(chalk5.green("Comment:"), attestation.comment);
|
|
245
893
|
}
|
|
246
894
|
} catch (error) {
|
|
247
895
|
spinner2.fail("Failed to fetch attestation");
|
|
248
896
|
if (error instanceof Error) {
|
|
249
|
-
console.error(
|
|
897
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
250
898
|
}
|
|
251
899
|
process.exit(1);
|
|
252
900
|
}
|
|
@@ -254,19 +902,21 @@ var attestationsCommand = new Command("attestations").description("Manage applic
|
|
|
254
902
|
);
|
|
255
903
|
var healthCommand = new Command("health").description("Check the health of the SSApp Registry API").action(async (options, command) => {
|
|
256
904
|
const globalOpts = command.parent?.opts();
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
905
|
+
const useLocal = globalOpts?.local || false;
|
|
906
|
+
const client = createRegistryClient(
|
|
907
|
+
useLocal,
|
|
908
|
+
globalOpts?.url,
|
|
909
|
+
parseInt(globalOpts?.timeout || "10000")
|
|
910
|
+
);
|
|
911
|
+
const spinner2 = ora6("Checking API health...").start();
|
|
262
912
|
try {
|
|
263
913
|
const health = await client.healthCheck();
|
|
264
914
|
spinner2.succeed("API is healthy");
|
|
265
|
-
console.log(
|
|
915
|
+
console.log(chalk5.green(`Status: ${health.status}`));
|
|
266
916
|
} catch (error) {
|
|
267
917
|
spinner2.fail("API health check failed");
|
|
268
918
|
if (error instanceof Error) {
|
|
269
|
-
console.error(
|
|
919
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
270
920
|
}
|
|
271
921
|
process.exit(1);
|
|
272
922
|
}
|
|
@@ -301,7 +951,7 @@ This is a demo file that would normally contain the actual application data.`;
|
|
|
301
951
|
);
|
|
302
952
|
}
|
|
303
953
|
}
|
|
304
|
-
var spinner = (text) =>
|
|
954
|
+
var spinner = (text) => ora6(text);
|
|
305
955
|
|
|
306
956
|
// src/commands/ipfs.ts
|
|
307
957
|
var ipfsCommand = new Command("ipfs").description("IPFS operations").addCommand(
|
|
@@ -339,6 +989,785 @@ var ipfsCommand = new Command("ipfs").description("IPFS operations").addCommand(
|
|
|
339
989
|
}
|
|
340
990
|
})
|
|
341
991
|
);
|
|
992
|
+
var LocalRegistryServer = class {
|
|
993
|
+
constructor(config) {
|
|
994
|
+
this.server = null;
|
|
995
|
+
this.isRunning = false;
|
|
996
|
+
this.config = config;
|
|
997
|
+
this.dataStore = new LocalDataStore(config);
|
|
998
|
+
this.artifactServer = new LocalArtifactServer(config, this.dataStore);
|
|
999
|
+
}
|
|
1000
|
+
async start(port) {
|
|
1001
|
+
if (this.isRunning) {
|
|
1002
|
+
throw new Error("Local registry is already running");
|
|
1003
|
+
}
|
|
1004
|
+
const serverPort = port || this.config.getPort();
|
|
1005
|
+
const host = this.config.getHost();
|
|
1006
|
+
this.config.ensureDirectories();
|
|
1007
|
+
this.server = fastify({
|
|
1008
|
+
logger: {
|
|
1009
|
+
level: "info"
|
|
1010
|
+
}
|
|
1011
|
+
});
|
|
1012
|
+
await this.server.register(cors, {
|
|
1013
|
+
origin: true,
|
|
1014
|
+
credentials: true
|
|
1015
|
+
});
|
|
1016
|
+
await this.registerRoutes();
|
|
1017
|
+
await this.server.listen({ port: serverPort, host });
|
|
1018
|
+
this.isRunning = true;
|
|
1019
|
+
console.log(
|
|
1020
|
+
`Local registry server started on http://${host}:${serverPort}`
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
async stop() {
|
|
1024
|
+
if (!this.server || !this.isRunning) {
|
|
1025
|
+
throw new Error("Local registry is not running");
|
|
1026
|
+
}
|
|
1027
|
+
await this.server.close();
|
|
1028
|
+
this.isRunning = false;
|
|
1029
|
+
this.server = null;
|
|
1030
|
+
}
|
|
1031
|
+
async getStatus() {
|
|
1032
|
+
const stats = this.dataStore.getStats();
|
|
1033
|
+
const artifactStats = this.artifactServer.getArtifactStats();
|
|
1034
|
+
return {
|
|
1035
|
+
running: this.isRunning,
|
|
1036
|
+
url: `http://${this.config.getHost()}:${this.config.getPort()}`,
|
|
1037
|
+
dataDir: this.config.getDataDir(),
|
|
1038
|
+
appsCount: stats.publishedApps,
|
|
1039
|
+
artifactsCount: artifactStats.totalFiles
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
async reset() {
|
|
1043
|
+
this.dataStore.reset();
|
|
1044
|
+
const artifactsDir = this.config.getArtifactsDir();
|
|
1045
|
+
if (fs3.existsSync(artifactsDir)) {
|
|
1046
|
+
fs3.rmSync(artifactsDir, { recursive: true, force: true });
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
async backup(outputPath) {
|
|
1050
|
+
return await this.dataStore.backup(outputPath);
|
|
1051
|
+
}
|
|
1052
|
+
async restore(backupPath) {
|
|
1053
|
+
await this.dataStore.restore(backupPath);
|
|
1054
|
+
}
|
|
1055
|
+
async seed() {
|
|
1056
|
+
await this.dataStore.seed();
|
|
1057
|
+
}
|
|
1058
|
+
async registerRoutes() {
|
|
1059
|
+
if (!this.server) {
|
|
1060
|
+
throw new Error("Server not initialized");
|
|
1061
|
+
}
|
|
1062
|
+
this.server.get("/healthz", async () => {
|
|
1063
|
+
return { status: "ok" };
|
|
1064
|
+
});
|
|
1065
|
+
this.server.get("/stats", async () => {
|
|
1066
|
+
const stats = this.dataStore.getStats();
|
|
1067
|
+
const artifactStats = this.artifactServer.getArtifactStats();
|
|
1068
|
+
return {
|
|
1069
|
+
publishedApps: stats.publishedApps,
|
|
1070
|
+
totalVersions: stats.totalVersions,
|
|
1071
|
+
totalArtifacts: artifactStats.totalFiles,
|
|
1072
|
+
totalSize: artifactStats.totalSize
|
|
1073
|
+
};
|
|
1074
|
+
});
|
|
1075
|
+
this.server.get("/apps", async (request) => {
|
|
1076
|
+
const query = request.query;
|
|
1077
|
+
return this.dataStore.getApps(query);
|
|
1078
|
+
});
|
|
1079
|
+
this.server.get("/apps/:appId", async (request) => {
|
|
1080
|
+
const { appId } = request.params;
|
|
1081
|
+
return this.dataStore.getAppVersions(appId);
|
|
1082
|
+
});
|
|
1083
|
+
this.server.get("/apps/:appId/:semver", async (request) => {
|
|
1084
|
+
const { appId, semver } = request.params;
|
|
1085
|
+
const manifest = this.dataStore.getManifest(appId, semver);
|
|
1086
|
+
if (!manifest) {
|
|
1087
|
+
return {
|
|
1088
|
+
statusCode: 404,
|
|
1089
|
+
error: "Not Found",
|
|
1090
|
+
message: "Manifest not found"
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
return this.artifactServer.updateManifestArtifacts(manifest);
|
|
1094
|
+
});
|
|
1095
|
+
this.server.post("/apps", async (request) => {
|
|
1096
|
+
const manifest = request.body;
|
|
1097
|
+
if (!this.validateManifest(manifest)) {
|
|
1098
|
+
return {
|
|
1099
|
+
statusCode: 400,
|
|
1100
|
+
error: "Bad Request",
|
|
1101
|
+
message: "Invalid manifest structure"
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
const processedManifest = await this.processManifestArtifacts(manifest);
|
|
1105
|
+
const manifestKey = `${manifest.app.app_id}/${manifest.version.semver}`;
|
|
1106
|
+
this.dataStore.setManifest(manifestKey, processedManifest);
|
|
1107
|
+
const appKey = manifest.app.app_id;
|
|
1108
|
+
const appSummary = {
|
|
1109
|
+
name: manifest.app.name,
|
|
1110
|
+
developer_pubkey: manifest.app.developer_pubkey,
|
|
1111
|
+
latest_version: manifest.version.semver,
|
|
1112
|
+
latest_cid: manifest.artifacts[0]?.cid || "",
|
|
1113
|
+
alias: manifest.app.name
|
|
1114
|
+
// Use name as alias
|
|
1115
|
+
};
|
|
1116
|
+
this.dataStore.setApp(appKey, appSummary);
|
|
1117
|
+
return {
|
|
1118
|
+
success: true,
|
|
1119
|
+
message: "App version registered successfully",
|
|
1120
|
+
manifest_key: manifestKey
|
|
1121
|
+
};
|
|
1122
|
+
});
|
|
1123
|
+
this.server.get(
|
|
1124
|
+
"/artifacts/:appId/:version/:filename",
|
|
1125
|
+
async (request, reply) => {
|
|
1126
|
+
const { appId, version, filename } = request.params;
|
|
1127
|
+
try {
|
|
1128
|
+
const artifactData = await this.artifactServer.serveArtifact(
|
|
1129
|
+
appId,
|
|
1130
|
+
version,
|
|
1131
|
+
filename
|
|
1132
|
+
);
|
|
1133
|
+
reply.header("Content-Type", "application/octet-stream");
|
|
1134
|
+
reply.header(
|
|
1135
|
+
"Content-Disposition",
|
|
1136
|
+
`attachment; filename="${filename}"`
|
|
1137
|
+
);
|
|
1138
|
+
return artifactData;
|
|
1139
|
+
} catch {
|
|
1140
|
+
return {
|
|
1141
|
+
statusCode: 404,
|
|
1142
|
+
error: "Not Found",
|
|
1143
|
+
message: "Artifact not found"
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
);
|
|
1148
|
+
this.server.get("/artifacts/:hash", async (request, reply) => {
|
|
1149
|
+
const { hash } = request.params;
|
|
1150
|
+
try {
|
|
1151
|
+
const artifactData = await this.artifactServer.serveByHash(hash);
|
|
1152
|
+
reply.header("Content-Type", "application/octet-stream");
|
|
1153
|
+
return artifactData;
|
|
1154
|
+
} catch {
|
|
1155
|
+
return {
|
|
1156
|
+
statusCode: 404,
|
|
1157
|
+
error: "Not Found",
|
|
1158
|
+
message: "Artifact not found"
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
});
|
|
1162
|
+
this.server.get("/local/status", async () => {
|
|
1163
|
+
return await this.getStatus();
|
|
1164
|
+
});
|
|
1165
|
+
this.server.post("/local/reset", async () => {
|
|
1166
|
+
await this.reset();
|
|
1167
|
+
return { message: "Local registry data reset successfully" };
|
|
1168
|
+
});
|
|
1169
|
+
this.server.get("/local/backup", async (request) => {
|
|
1170
|
+
const query = request.query;
|
|
1171
|
+
const backupPath = await this.backup(query.output);
|
|
1172
|
+
return { backupPath };
|
|
1173
|
+
});
|
|
1174
|
+
this.server.post("/local/restore", async (request) => {
|
|
1175
|
+
const { backupPath } = request.body;
|
|
1176
|
+
await this.restore(backupPath);
|
|
1177
|
+
return { message: "Data restored successfully" };
|
|
1178
|
+
});
|
|
1179
|
+
this.server.post("/local/seed", async () => {
|
|
1180
|
+
await this.seed();
|
|
1181
|
+
return { message: "Sample data seeded successfully" };
|
|
1182
|
+
});
|
|
1183
|
+
this.server.post("/v1/apps", async (request, reply) => {
|
|
1184
|
+
const manifest = request.body;
|
|
1185
|
+
if (!manifest.manifest_version || !manifest.id || !manifest.name || !manifest.version) {
|
|
1186
|
+
return reply.code(400).send({
|
|
1187
|
+
error: "invalid_schema",
|
|
1188
|
+
details: "Missing required fields"
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
try {
|
|
1192
|
+
const processedManifest = await this.processManifestArtifacts(manifest);
|
|
1193
|
+
const manifestKey = `${manifest.id}/${manifest.version}`;
|
|
1194
|
+
this.dataStore.setManifest(manifestKey, processedManifest);
|
|
1195
|
+
const appKey = manifest.id;
|
|
1196
|
+
const appSummary = {
|
|
1197
|
+
id: manifest.id,
|
|
1198
|
+
name: manifest.name,
|
|
1199
|
+
developer_pubkey: "local-dev-key",
|
|
1200
|
+
latest_version: manifest.version,
|
|
1201
|
+
latest_cid: manifest.artifact?.digest || ""
|
|
1202
|
+
};
|
|
1203
|
+
this.dataStore.setApp(appKey, appSummary);
|
|
1204
|
+
return reply.code(201).send({
|
|
1205
|
+
id: manifest.id,
|
|
1206
|
+
version: manifest.version,
|
|
1207
|
+
canonical_uri: `/v1/apps/${manifest.id}/${manifest.version}`
|
|
1208
|
+
});
|
|
1209
|
+
} catch {
|
|
1210
|
+
return reply.code(409).send({
|
|
1211
|
+
error: "already_exists",
|
|
1212
|
+
details: `${manifest.id}@${manifest.version}`
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
});
|
|
1216
|
+
this.server.get("/v1/apps/:id", async (request, reply) => {
|
|
1217
|
+
const { id } = request.params;
|
|
1218
|
+
const versions = this.dataStore.getAppVersions(id);
|
|
1219
|
+
if (!versions || versions.length === 0) {
|
|
1220
|
+
return reply.code(404).send({ error: "not_found", message: "App not found" });
|
|
1221
|
+
}
|
|
1222
|
+
return {
|
|
1223
|
+
id,
|
|
1224
|
+
versions: versions.map((v) => v.semver)
|
|
1225
|
+
};
|
|
1226
|
+
});
|
|
1227
|
+
this.server.get("/v1/apps/:id/:version", async (request, reply) => {
|
|
1228
|
+
const { id, version } = request.params;
|
|
1229
|
+
const { canonical } = request.query;
|
|
1230
|
+
const oldManifest = this.dataStore.getManifest(id, version);
|
|
1231
|
+
if (!oldManifest) {
|
|
1232
|
+
return reply.code(404).send({ error: "not_found", message: "Manifest not found" });
|
|
1233
|
+
}
|
|
1234
|
+
const v1Manifest = {
|
|
1235
|
+
manifest_version: oldManifest.manifest_version,
|
|
1236
|
+
id: oldManifest.app.app_id,
|
|
1237
|
+
name: oldManifest.app.name,
|
|
1238
|
+
version: oldManifest.version.semver,
|
|
1239
|
+
chains: oldManifest.supported_chains,
|
|
1240
|
+
artifact: oldManifest.artifacts[0] ? {
|
|
1241
|
+
type: oldManifest.artifacts[0].type,
|
|
1242
|
+
target: oldManifest.artifacts[0].target,
|
|
1243
|
+
digest: oldManifest.artifacts[0].cid || `sha256:${"0".repeat(64)}`,
|
|
1244
|
+
uri: oldManifest.artifacts[0].path || oldManifest.artifacts[0].mirrors?.[0] || "https://example.com/artifact"
|
|
1245
|
+
} : {
|
|
1246
|
+
type: "wasm",
|
|
1247
|
+
target: "node",
|
|
1248
|
+
digest: `sha256:${"0".repeat(64)}`,
|
|
1249
|
+
uri: "https://example.com/artifact"
|
|
1250
|
+
},
|
|
1251
|
+
_warnings: []
|
|
1252
|
+
};
|
|
1253
|
+
if (canonical === "true") {
|
|
1254
|
+
const canonicalJCS = JSON.stringify(
|
|
1255
|
+
v1Manifest,
|
|
1256
|
+
Object.keys(v1Manifest).sort()
|
|
1257
|
+
);
|
|
1258
|
+
return {
|
|
1259
|
+
canonical_jcs: Buffer.from(canonicalJCS, "utf8").toString("base64")
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
return v1Manifest;
|
|
1263
|
+
});
|
|
1264
|
+
this.server.get("/v1/search", async (request, reply) => {
|
|
1265
|
+
const { q } = request.query;
|
|
1266
|
+
if (!q) {
|
|
1267
|
+
return reply.code(400).send({
|
|
1268
|
+
error: "bad_request",
|
|
1269
|
+
message: 'Query parameter "q" is required'
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
const apps = this.dataStore.getApps({});
|
|
1273
|
+
const results = apps.filter(
|
|
1274
|
+
(app) => app.name.toLowerCase().includes(q.toLowerCase()) || app.id?.toLowerCase().includes(q.toLowerCase())
|
|
1275
|
+
);
|
|
1276
|
+
return results.map((app) => ({
|
|
1277
|
+
id: app.id || app.name,
|
|
1278
|
+
versions: [app.latest_version]
|
|
1279
|
+
}));
|
|
1280
|
+
});
|
|
1281
|
+
this.server.post("/v1/resolve", async (request, reply) => {
|
|
1282
|
+
const { root } = request.body;
|
|
1283
|
+
if (!root || !root.id || !root.version) {
|
|
1284
|
+
return reply.code(400).send({
|
|
1285
|
+
error: "bad_request",
|
|
1286
|
+
message: "Root app ID and version are required"
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
return {
|
|
1290
|
+
plan: [{ action: "install", id: root.id, version: root.version }],
|
|
1291
|
+
satisfies: [],
|
|
1292
|
+
missing: []
|
|
1293
|
+
};
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
validateManifest(manifest) {
|
|
1297
|
+
return !!(manifest.manifest_version && manifest.app && manifest.app.app_id && manifest.app.name && manifest.app.developer_pubkey && manifest.version && manifest.version.semver && manifest.artifacts && manifest.artifacts.length > 0);
|
|
1298
|
+
}
|
|
1299
|
+
async processManifestArtifacts(manifest) {
|
|
1300
|
+
const processedManifest = { ...manifest };
|
|
1301
|
+
if (manifest.artifact && manifest.artifact.uri) {
|
|
1302
|
+
const artifact = manifest.artifact;
|
|
1303
|
+
if (artifact.uri.startsWith("file://")) {
|
|
1304
|
+
const filePath = artifact.uri.replace("file://", "");
|
|
1305
|
+
if (fs3.existsSync(filePath)) {
|
|
1306
|
+
const filename = path.basename(filePath);
|
|
1307
|
+
const appId = manifest.id;
|
|
1308
|
+
try {
|
|
1309
|
+
await this.artifactServer.copyArtifactToLocal(
|
|
1310
|
+
filePath,
|
|
1311
|
+
appId,
|
|
1312
|
+
manifest.version,
|
|
1313
|
+
filename
|
|
1314
|
+
);
|
|
1315
|
+
processedManifest.artifact = {
|
|
1316
|
+
...artifact,
|
|
1317
|
+
uri: this.artifactServer.getArtifactUrl(
|
|
1318
|
+
appId,
|
|
1319
|
+
manifest.version,
|
|
1320
|
+
filename
|
|
1321
|
+
)
|
|
1322
|
+
};
|
|
1323
|
+
} catch (error) {
|
|
1324
|
+
console.warn(`Failed to copy artifact ${filePath}:`, error);
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
return processedManifest;
|
|
1330
|
+
}
|
|
1331
|
+
};
|
|
1332
|
+
var localCommand = new Command("local").description(
|
|
1333
|
+
"Manage local registry for development"
|
|
1334
|
+
);
|
|
1335
|
+
localCommand.addCommand(
|
|
1336
|
+
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) => {
|
|
1337
|
+
const spinner2 = ora6("Starting local registry...").start();
|
|
1338
|
+
try {
|
|
1339
|
+
const config = new LocalConfig();
|
|
1340
|
+
const server = new LocalRegistryServer(config);
|
|
1341
|
+
await server.start(parseInt(options.port));
|
|
1342
|
+
spinner2.succeed(
|
|
1343
|
+
`Local registry started on http://${options.host}:${options.port}`
|
|
1344
|
+
);
|
|
1345
|
+
console.log(chalk5.blue("\n\u{1F4F1} Local Registry Status:"));
|
|
1346
|
+
console.log(
|
|
1347
|
+
chalk5.green(`\u2705 Server: http://${options.host}:${options.port}`)
|
|
1348
|
+
);
|
|
1349
|
+
console.log(chalk5.green(`\u{1F4C1} Data: ${config.getDataDir()}`));
|
|
1350
|
+
console.log(
|
|
1351
|
+
chalk5.green(
|
|
1352
|
+
`\u{1F4CB} Health: http://${options.host}:${options.port}/healthz`
|
|
1353
|
+
)
|
|
1354
|
+
);
|
|
1355
|
+
console.log(
|
|
1356
|
+
chalk5.green(`\u{1F4CA} Stats: http://${options.host}:${options.port}/stats`)
|
|
1357
|
+
);
|
|
1358
|
+
console.log(
|
|
1359
|
+
chalk5.blue(
|
|
1360
|
+
"\n\u{1F4A1} Use --local flag with other commands to use local registry"
|
|
1361
|
+
)
|
|
1362
|
+
);
|
|
1363
|
+
} catch (error) {
|
|
1364
|
+
spinner2.fail("Failed to start local registry");
|
|
1365
|
+
if (error instanceof Error) {
|
|
1366
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
1367
|
+
}
|
|
1368
|
+
process.exit(1);
|
|
1369
|
+
}
|
|
1370
|
+
})
|
|
1371
|
+
);
|
|
1372
|
+
localCommand.addCommand(
|
|
1373
|
+
new Command("stop").description("Stop local registry server").action(async () => {
|
|
1374
|
+
const spinner2 = ora6("Stopping local registry...").start();
|
|
1375
|
+
try {
|
|
1376
|
+
const config = new LocalConfig();
|
|
1377
|
+
const server = new LocalRegistryServer(config);
|
|
1378
|
+
await server.stop();
|
|
1379
|
+
spinner2.succeed("Local registry stopped");
|
|
1380
|
+
} catch (error) {
|
|
1381
|
+
spinner2.fail("Failed to stop local registry");
|
|
1382
|
+
if (error instanceof Error) {
|
|
1383
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
1384
|
+
}
|
|
1385
|
+
process.exit(1);
|
|
1386
|
+
}
|
|
1387
|
+
})
|
|
1388
|
+
);
|
|
1389
|
+
localCommand.addCommand(
|
|
1390
|
+
new Command("status").description("Check local registry status").action(async () => {
|
|
1391
|
+
const spinner2 = ora6("Checking local registry status...").start();
|
|
1392
|
+
try {
|
|
1393
|
+
const config = new LocalConfig();
|
|
1394
|
+
const server = new LocalRegistryServer(config);
|
|
1395
|
+
const status = await server.getStatus();
|
|
1396
|
+
if (status.running) {
|
|
1397
|
+
spinner2.succeed("Local registry is running");
|
|
1398
|
+
console.log(chalk5.green(`\u2705 Server: ${status.url}`));
|
|
1399
|
+
console.log(chalk5.green(`\u{1F4C1} Data: ${status.dataDir}`));
|
|
1400
|
+
console.log(chalk5.green(`\u{1F4CA} Apps: ${status.appsCount} applications`));
|
|
1401
|
+
console.log(
|
|
1402
|
+
chalk5.green(`\u{1F4E6} Artifacts: ${status.artifactsCount} artifacts`)
|
|
1403
|
+
);
|
|
1404
|
+
} else {
|
|
1405
|
+
spinner2.warn("Local registry is not running");
|
|
1406
|
+
console.log(
|
|
1407
|
+
chalk5.yellow(
|
|
1408
|
+
'\u{1F4A1} Run "calimero-registry local start" to start the local registry'
|
|
1409
|
+
)
|
|
1410
|
+
);
|
|
1411
|
+
}
|
|
1412
|
+
} catch (error) {
|
|
1413
|
+
spinner2.fail("Failed to check local registry status");
|
|
1414
|
+
if (error instanceof Error) {
|
|
1415
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
1416
|
+
}
|
|
1417
|
+
process.exit(1);
|
|
1418
|
+
}
|
|
1419
|
+
})
|
|
1420
|
+
);
|
|
1421
|
+
localCommand.addCommand(
|
|
1422
|
+
new Command("reset").description("Reset local registry data").option("-f, --force", "Force reset without confirmation").action(async (options) => {
|
|
1423
|
+
if (!options.force) {
|
|
1424
|
+
console.log(
|
|
1425
|
+
chalk5.yellow("\u26A0\uFE0F This will delete all local registry data!")
|
|
1426
|
+
);
|
|
1427
|
+
console.log(chalk5.yellow(" Use --force flag to confirm"));
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
const spinner2 = ora6("Resetting local registry data...").start();
|
|
1431
|
+
try {
|
|
1432
|
+
const config = new LocalConfig();
|
|
1433
|
+
const server = new LocalRegistryServer(config);
|
|
1434
|
+
await server.reset();
|
|
1435
|
+
spinner2.succeed("Local registry data reset");
|
|
1436
|
+
console.log(chalk5.green("\u2705 All local data has been cleared"));
|
|
1437
|
+
} catch (error) {
|
|
1438
|
+
spinner2.fail("Failed to reset local registry data");
|
|
1439
|
+
if (error instanceof Error) {
|
|
1440
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
1441
|
+
}
|
|
1442
|
+
process.exit(1);
|
|
1443
|
+
}
|
|
1444
|
+
})
|
|
1445
|
+
);
|
|
1446
|
+
localCommand.addCommand(
|
|
1447
|
+
new Command("backup").description("Backup local registry data").option("-o, --output <file>", "Output file path").action(async (options) => {
|
|
1448
|
+
const spinner2 = ora6("Creating backup...").start();
|
|
1449
|
+
try {
|
|
1450
|
+
const config = new LocalConfig();
|
|
1451
|
+
const server = new LocalRegistryServer(config);
|
|
1452
|
+
const backupPath = await server.backup(options.output);
|
|
1453
|
+
spinner2.succeed("Backup created successfully");
|
|
1454
|
+
console.log(chalk5.green(`\u{1F4E6} Backup saved to: ${backupPath}`));
|
|
1455
|
+
} catch (error) {
|
|
1456
|
+
spinner2.fail("Failed to create backup");
|
|
1457
|
+
if (error instanceof Error) {
|
|
1458
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
1459
|
+
}
|
|
1460
|
+
process.exit(1);
|
|
1461
|
+
}
|
|
1462
|
+
})
|
|
1463
|
+
);
|
|
1464
|
+
localCommand.addCommand(
|
|
1465
|
+
new Command("restore").description("Restore local registry data from backup").argument("<backup-file>", "Path to backup file").action(async (backupFile) => {
|
|
1466
|
+
const spinner2 = ora6("Restoring from backup...").start();
|
|
1467
|
+
try {
|
|
1468
|
+
if (!fs3.existsSync(backupFile)) {
|
|
1469
|
+
spinner2.fail("Backup file not found");
|
|
1470
|
+
console.error(chalk5.red(`File not found: ${backupFile}`));
|
|
1471
|
+
process.exit(1);
|
|
1472
|
+
}
|
|
1473
|
+
const config = new LocalConfig();
|
|
1474
|
+
const server = new LocalRegistryServer(config);
|
|
1475
|
+
await server.restore(backupFile);
|
|
1476
|
+
spinner2.succeed("Data restored successfully");
|
|
1477
|
+
console.log(chalk5.green(`\u2705 Restored from: ${backupFile}`));
|
|
1478
|
+
} catch (error) {
|
|
1479
|
+
spinner2.fail("Failed to restore from backup");
|
|
1480
|
+
if (error instanceof Error) {
|
|
1481
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
1482
|
+
}
|
|
1483
|
+
process.exit(1);
|
|
1484
|
+
}
|
|
1485
|
+
})
|
|
1486
|
+
);
|
|
1487
|
+
localCommand.addCommand(
|
|
1488
|
+
new Command("seed").description("Seed local registry with sample data").action(async () => {
|
|
1489
|
+
const spinner2 = ora6("Seeding local registry with sample data...").start();
|
|
1490
|
+
try {
|
|
1491
|
+
const config = new LocalConfig();
|
|
1492
|
+
const server = new LocalRegistryServer(config);
|
|
1493
|
+
await server.seed();
|
|
1494
|
+
spinner2.succeed("Sample data seeded successfully");
|
|
1495
|
+
console.log(
|
|
1496
|
+
chalk5.green("\u2705 Local registry populated with sample applications")
|
|
1497
|
+
);
|
|
1498
|
+
console.log(
|
|
1499
|
+
chalk5.blue(
|
|
1500
|
+
'\u{1F4A1} Run "calimero-registry apps list --local" to see the sample apps'
|
|
1501
|
+
)
|
|
1502
|
+
);
|
|
1503
|
+
} catch (error) {
|
|
1504
|
+
spinner2.fail("Failed to seed sample data");
|
|
1505
|
+
if (error instanceof Error) {
|
|
1506
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
1507
|
+
}
|
|
1508
|
+
process.exit(1);
|
|
1509
|
+
}
|
|
1510
|
+
})
|
|
1511
|
+
);
|
|
1512
|
+
var v1Command = new Command("v1").description("V1 API commands for Calimero SSApp Registry").addCommand(createPushCommand()).addCommand(createGetCommand()).addCommand(createListCommand()).addCommand(createResolveCommand()).addCommand(createVerifyCommand());
|
|
1513
|
+
function createPushCommand() {
|
|
1514
|
+
return new Command("push").description("Submit a v1 manifest to the registry").argument("<manifest-file>", "Path to manifest JSON file").option("--local", "Use local registry").action(
|
|
1515
|
+
async (manifestFile, options = {}, command) => {
|
|
1516
|
+
try {
|
|
1517
|
+
const globalOpts = command.parent?.parent?.opts();
|
|
1518
|
+
const useLocal = globalOpts?.local || options.local || false;
|
|
1519
|
+
if (!fs3.existsSync(manifestFile)) {
|
|
1520
|
+
console.error(`\u274C Manifest file not found: ${manifestFile}`);
|
|
1521
|
+
process.exit(1);
|
|
1522
|
+
}
|
|
1523
|
+
const manifestContent = fs3.readFileSync(manifestFile, "utf8");
|
|
1524
|
+
const manifest = JSON.parse(manifestContent);
|
|
1525
|
+
if (manifest.manifest_version !== "1.0") {
|
|
1526
|
+
console.error('\u274C Invalid manifest version. Must be "1.0"');
|
|
1527
|
+
process.exit(1);
|
|
1528
|
+
}
|
|
1529
|
+
if (!manifest.id || !manifest.name || !manifest.version) {
|
|
1530
|
+
console.error("\u274C Missing required fields: id, name, version");
|
|
1531
|
+
process.exit(1);
|
|
1532
|
+
}
|
|
1533
|
+
const baseUrl = useLocal ? "http://localhost:8082" : "http://localhost:8080";
|
|
1534
|
+
console.log(
|
|
1535
|
+
`\u{1F4E4} Submitting manifest: ${manifest.id}@${manifest.version}`
|
|
1536
|
+
);
|
|
1537
|
+
console.log(` Name: ${manifest.name}`);
|
|
1538
|
+
console.log(` Chains: ${manifest.chains?.join(", ")}`);
|
|
1539
|
+
console.log(
|
|
1540
|
+
` Provides: ${manifest.provides?.join(", ") || "none"}`
|
|
1541
|
+
);
|
|
1542
|
+
console.log(
|
|
1543
|
+
` Requires: ${manifest.requires?.join(", ") || "none"}`
|
|
1544
|
+
);
|
|
1545
|
+
const response = await fetch(`${baseUrl}/v1/apps`, {
|
|
1546
|
+
method: "POST",
|
|
1547
|
+
headers: { "Content-Type": "application/json" },
|
|
1548
|
+
body: JSON.stringify(manifest)
|
|
1549
|
+
});
|
|
1550
|
+
if (!response.ok) {
|
|
1551
|
+
const error = await response.json();
|
|
1552
|
+
throw new Error(`${error.error}: ${error.details}`);
|
|
1553
|
+
}
|
|
1554
|
+
const result = await response.json();
|
|
1555
|
+
console.log("\u2705 Manifest submitted successfully!");
|
|
1556
|
+
console.log(` ID: ${result.id}`);
|
|
1557
|
+
console.log(` Version: ${result.version}`);
|
|
1558
|
+
console.log(` URI: ${result.canonical_uri}`);
|
|
1559
|
+
} catch (error) {
|
|
1560
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1561
|
+
console.error("\u274C Failed to submit manifest:", errorMessage);
|
|
1562
|
+
process.exit(1);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
);
|
|
1566
|
+
}
|
|
1567
|
+
function createGetCommand() {
|
|
1568
|
+
return new Command("get").description("Get manifest from registry").argument("<app-id>", "Application ID").argument("[version]", "Specific version (optional)").option("--local", "Use local registry").option("--canonical", "Return canonical JCS format").action(
|
|
1569
|
+
async (appId, version, options = {}, command) => {
|
|
1570
|
+
try {
|
|
1571
|
+
const globalOpts = command.parent?.parent?.opts();
|
|
1572
|
+
const useLocal = globalOpts?.local || options.local || false;
|
|
1573
|
+
const baseUrl = useLocal ? "http://localhost:8082" : "http://localhost:8080";
|
|
1574
|
+
if (version) {
|
|
1575
|
+
console.log(`\u{1F4E5} Getting manifest: ${appId}@${version}`);
|
|
1576
|
+
const url = options.canonical ? `/v1/apps/${appId}/${version}?canonical=true` : `/v1/apps/${appId}/${version}`;
|
|
1577
|
+
const response = await fetch(`${baseUrl}${url}`);
|
|
1578
|
+
if (!response.ok) {
|
|
1579
|
+
if (response.status === 404) {
|
|
1580
|
+
console.error(`\u274C Manifest not found: ${appId}@${version}`);
|
|
1581
|
+
} else {
|
|
1582
|
+
console.error(
|
|
1583
|
+
`\u274C Error: ${response.status} ${response.statusText}`
|
|
1584
|
+
);
|
|
1585
|
+
}
|
|
1586
|
+
process.exit(1);
|
|
1587
|
+
}
|
|
1588
|
+
const manifest = await response.json();
|
|
1589
|
+
console.log(JSON.stringify(manifest, null, 2));
|
|
1590
|
+
} else {
|
|
1591
|
+
console.log(`\u{1F4E5} Getting versions for: ${appId}`);
|
|
1592
|
+
const response = await fetch(`${baseUrl}/v1/apps/${appId}`);
|
|
1593
|
+
if (!response.ok) {
|
|
1594
|
+
if (response.status === 404) {
|
|
1595
|
+
console.error(`\u274C App not found: ${appId}`);
|
|
1596
|
+
} else {
|
|
1597
|
+
console.error(
|
|
1598
|
+
`\u274C Error: ${response.status} ${response.statusText}`
|
|
1599
|
+
);
|
|
1600
|
+
}
|
|
1601
|
+
process.exit(1);
|
|
1602
|
+
}
|
|
1603
|
+
const result = await response.json();
|
|
1604
|
+
console.log(`\u{1F4F1} App: ${result.id}`);
|
|
1605
|
+
console.log(`\u{1F4CB} Versions: ${result.versions.join(", ")}`);
|
|
1606
|
+
}
|
|
1607
|
+
} catch (error) {
|
|
1608
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1609
|
+
console.error("\u274C Failed to get manifest:", errorMessage);
|
|
1610
|
+
process.exit(1);
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
);
|
|
1614
|
+
}
|
|
1615
|
+
function createListCommand() {
|
|
1616
|
+
return new Command("ls").description("List applications in registry").option("--local", "Use local registry").option("--search <query>", "Search query").action(
|
|
1617
|
+
async (options = {}, command) => {
|
|
1618
|
+
try {
|
|
1619
|
+
const globalOpts = command.parent?.parent?.opts();
|
|
1620
|
+
const useLocal = globalOpts?.local || options.local || false;
|
|
1621
|
+
const baseUrl = useLocal ? "http://localhost:8082" : "http://localhost:8080";
|
|
1622
|
+
if (options.search) {
|
|
1623
|
+
console.log(`\u{1F50D} Searching for: ${options.search}`);
|
|
1624
|
+
const response = await fetch(
|
|
1625
|
+
`${baseUrl}/v1/search?q=${encodeURIComponent(options.search)}`
|
|
1626
|
+
);
|
|
1627
|
+
if (!response.ok) {
|
|
1628
|
+
console.error(
|
|
1629
|
+
`\u274C Error: ${response.status} ${response.statusText}`
|
|
1630
|
+
);
|
|
1631
|
+
process.exit(1);
|
|
1632
|
+
}
|
|
1633
|
+
const results = await response.json();
|
|
1634
|
+
if (results.length === 0) {
|
|
1635
|
+
console.log("No results found");
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1638
|
+
console.log(`Found ${results.length} result(s):`);
|
|
1639
|
+
results.forEach((result) => {
|
|
1640
|
+
console.log(
|
|
1641
|
+
` \u{1F4F1} ${result.id}@${result.versions?.[0] || "unknown"}`
|
|
1642
|
+
);
|
|
1643
|
+
if (result.provides?.length > 0) {
|
|
1644
|
+
console.log(` Provides: ${result.provides.join(", ")}`);
|
|
1645
|
+
}
|
|
1646
|
+
if (result.requires?.length > 0) {
|
|
1647
|
+
console.log(` Requires: ${result.requires.join(", ")}`);
|
|
1648
|
+
}
|
|
1649
|
+
});
|
|
1650
|
+
} else {
|
|
1651
|
+
console.log("\u{1F4CB} Listing all applications...");
|
|
1652
|
+
console.log(
|
|
1653
|
+
"Note: Use --search <query> to search for specific apps"
|
|
1654
|
+
);
|
|
1655
|
+
}
|
|
1656
|
+
} catch (error) {
|
|
1657
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1658
|
+
console.error("\u274C Failed to list applications:", errorMessage);
|
|
1659
|
+
process.exit(1);
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
);
|
|
1663
|
+
}
|
|
1664
|
+
function createResolveCommand() {
|
|
1665
|
+
return new Command("resolve").description("Resolve dependencies for an application").argument("<app-id>", "Application ID").argument("<version>", "Application version").option("--local", "Use local registry").action(
|
|
1666
|
+
async (appId, version, options = {}, command) => {
|
|
1667
|
+
try {
|
|
1668
|
+
const globalOpts = command.parent?.parent?.opts();
|
|
1669
|
+
const useLocal = globalOpts?.local || options.local || false;
|
|
1670
|
+
const baseUrl = useLocal ? "http://localhost:8082" : "http://localhost:8080";
|
|
1671
|
+
console.log(`\u{1F50D} Resolving dependencies for: ${appId}@${version}`);
|
|
1672
|
+
const resolveRequest = {
|
|
1673
|
+
root: { id: appId, version },
|
|
1674
|
+
installed: []
|
|
1675
|
+
// Could be extended to support pre-installed apps
|
|
1676
|
+
};
|
|
1677
|
+
const response = await fetch(`${baseUrl}/v1/resolve`, {
|
|
1678
|
+
method: "POST",
|
|
1679
|
+
headers: { "Content-Type": "application/json" },
|
|
1680
|
+
body: JSON.stringify(resolveRequest)
|
|
1681
|
+
});
|
|
1682
|
+
if (!response.ok) {
|
|
1683
|
+
const error = await response.json();
|
|
1684
|
+
console.error(`\u274C Resolution failed: ${error.error}`);
|
|
1685
|
+
console.error(` Details: ${error.details}`);
|
|
1686
|
+
process.exit(1);
|
|
1687
|
+
}
|
|
1688
|
+
const result = await response.json();
|
|
1689
|
+
console.log("\u2705 Dependencies resolved successfully!");
|
|
1690
|
+
console.log(`\u{1F4CB} Installation plan:`);
|
|
1691
|
+
result.plan.forEach(
|
|
1692
|
+
(item) => {
|
|
1693
|
+
console.log(` ${item.action}: ${item.id}@${item.version}`);
|
|
1694
|
+
}
|
|
1695
|
+
);
|
|
1696
|
+
if (result.satisfies?.length > 0) {
|
|
1697
|
+
console.log(`\u2705 Satisfies: ${result.satisfies.join(", ")}`);
|
|
1698
|
+
}
|
|
1699
|
+
if (result.missing?.length > 0) {
|
|
1700
|
+
console.log(`\u274C Missing: ${result.missing.join(", ")}`);
|
|
1701
|
+
}
|
|
1702
|
+
} catch (error) {
|
|
1703
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1704
|
+
console.error("\u274C Failed to resolve dependencies:", errorMessage);
|
|
1705
|
+
process.exit(1);
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
);
|
|
1709
|
+
}
|
|
1710
|
+
function createVerifyCommand() {
|
|
1711
|
+
return new Command("verify").description("Verify manifest signature and integrity").argument("<manifest-file>", "Path to manifest JSON file").action(async (manifestFile) => {
|
|
1712
|
+
try {
|
|
1713
|
+
if (!fs3.existsSync(manifestFile)) {
|
|
1714
|
+
console.error(`\u274C Manifest file not found: ${manifestFile}`);
|
|
1715
|
+
process.exit(1);
|
|
1716
|
+
}
|
|
1717
|
+
const manifestContent = fs3.readFileSync(manifestFile, "utf8");
|
|
1718
|
+
const manifest = JSON.parse(manifestContent);
|
|
1719
|
+
console.log(
|
|
1720
|
+
`\u{1F50D} Verifying manifest: ${manifest.id}@${manifest.version}`
|
|
1721
|
+
);
|
|
1722
|
+
if (manifest.manifest_version !== "1.0") {
|
|
1723
|
+
console.error("\u274C Invalid manifest version");
|
|
1724
|
+
process.exit(1);
|
|
1725
|
+
}
|
|
1726
|
+
const requiredFields = ["id", "name", "version", "chains", "artifact"];
|
|
1727
|
+
const missingFields = requiredFields.filter(
|
|
1728
|
+
(field) => !manifest[field]
|
|
1729
|
+
);
|
|
1730
|
+
if (missingFields.length > 0) {
|
|
1731
|
+
console.error(
|
|
1732
|
+
`\u274C Missing required fields: ${missingFields.join(", ")}`
|
|
1733
|
+
);
|
|
1734
|
+
process.exit(1);
|
|
1735
|
+
}
|
|
1736
|
+
if (manifest.artifact.type !== "wasm" || manifest.artifact.target !== "node") {
|
|
1737
|
+
console.error("\u274C Invalid artifact type or target");
|
|
1738
|
+
process.exit(1);
|
|
1739
|
+
}
|
|
1740
|
+
if (!manifest.artifact.digest.match(/^sha256:[0-9a-f]{64}$/)) {
|
|
1741
|
+
console.error("\u274C Invalid artifact digest format");
|
|
1742
|
+
process.exit(1);
|
|
1743
|
+
}
|
|
1744
|
+
if (!manifest.artifact.uri.match(/^(https:\/\/|ipfs:\/\/)/)) {
|
|
1745
|
+
console.error("\u274C Invalid artifact URI format");
|
|
1746
|
+
process.exit(1);
|
|
1747
|
+
}
|
|
1748
|
+
if (manifest.signature) {
|
|
1749
|
+
console.log("\u{1F510} Verifying signature...");
|
|
1750
|
+
console.log("\u26A0\uFE0F Signature verification not implemented in CLI yet");
|
|
1751
|
+
}
|
|
1752
|
+
console.log("\u2705 Manifest verification passed!");
|
|
1753
|
+
console.log(` ID: ${manifest.id}`);
|
|
1754
|
+
console.log(` Name: ${manifest.name}`);
|
|
1755
|
+
console.log(` Version: ${manifest.version}`);
|
|
1756
|
+
console.log(` Chains: ${manifest.chains.join(", ")}`);
|
|
1757
|
+
console.log(` Artifact: ${manifest.artifact.uri}`);
|
|
1758
|
+
if (manifest.provides?.length > 0) {
|
|
1759
|
+
console.log(` Provides: ${manifest.provides.join(", ")}`);
|
|
1760
|
+
}
|
|
1761
|
+
if (manifest.requires?.length > 0) {
|
|
1762
|
+
console.log(` Requires: ${manifest.requires.join(", ")}`);
|
|
1763
|
+
}
|
|
1764
|
+
} catch (error) {
|
|
1765
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1766
|
+
console.error("\u274C Failed to verify manifest:", errorMessage);
|
|
1767
|
+
process.exit(1);
|
|
1768
|
+
}
|
|
1769
|
+
});
|
|
1770
|
+
}
|
|
342
1771
|
|
|
343
1772
|
// src/index.ts
|
|
344
1773
|
var program = new Command();
|
|
@@ -351,19 +1780,22 @@ program.option(
|
|
|
351
1780
|
"Request timeout in milliseconds",
|
|
352
1781
|
"10000"
|
|
353
1782
|
);
|
|
1783
|
+
program.option("--local", "Use local registry instead of remote API");
|
|
354
1784
|
program.addCommand(appsCommand);
|
|
355
1785
|
program.addCommand(developersCommand);
|
|
356
1786
|
program.addCommand(attestationsCommand);
|
|
357
1787
|
program.addCommand(healthCommand);
|
|
358
1788
|
program.addCommand(ipfsCommand);
|
|
1789
|
+
program.addCommand(localCommand);
|
|
1790
|
+
program.addCommand(v1Command);
|
|
359
1791
|
program.exitOverride();
|
|
360
1792
|
try {
|
|
361
1793
|
program.parse();
|
|
362
1794
|
} catch (err) {
|
|
363
1795
|
if (err instanceof Error) {
|
|
364
|
-
console.error(
|
|
1796
|
+
console.error(chalk5.red("Error:"), err.message);
|
|
365
1797
|
} else {
|
|
366
|
-
console.error(
|
|
1798
|
+
console.error(chalk5.red("Unknown error occurred"));
|
|
367
1799
|
}
|
|
368
1800
|
process.exit(1);
|
|
369
1801
|
}
|