@calimero-network/registry-cli 1.3.0 → 1.5.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 +998 -677
- package/dist/index.js.map +1 -1
- package/package.json +11 -6
package/dist/index.js
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
-
import
|
|
3
|
+
import chalk6 from 'chalk';
|
|
4
4
|
import ora6 from 'ora';
|
|
5
5
|
import { table } from 'table';
|
|
6
6
|
import { SSAppRegistryClient } from '@calimero-network/registry-client';
|
|
7
7
|
import fs3, { existsSync, writeFileSync } from 'fs';
|
|
8
|
-
import
|
|
8
|
+
import path7 from 'path';
|
|
9
9
|
import os from 'os';
|
|
10
10
|
import crypto from 'crypto';
|
|
11
11
|
import fastify from 'fastify';
|
|
12
12
|
import cors from '@fastify/cors';
|
|
13
|
-
import
|
|
14
|
-
import fetch from 'node-fetch';
|
|
13
|
+
import * as tar from 'tar';
|
|
15
14
|
|
|
16
15
|
var LocalConfig = class {
|
|
17
16
|
constructor() {
|
|
18
|
-
this.configPath =
|
|
17
|
+
this.configPath = path7.join(
|
|
19
18
|
os.homedir(),
|
|
20
19
|
".calimero-registry",
|
|
21
20
|
"config.json"
|
|
22
21
|
);
|
|
23
|
-
this.dataDir =
|
|
22
|
+
this.dataDir = path7.join(os.homedir(), ".calimero-registry");
|
|
24
23
|
this.config = this.loadConfig();
|
|
25
24
|
}
|
|
26
25
|
loadConfig() {
|
|
27
26
|
const defaultConfig = {
|
|
28
27
|
server: {
|
|
29
28
|
port: 8082,
|
|
30
|
-
host: "
|
|
29
|
+
host: "0.0.0.0",
|
|
30
|
+
publicHost: "host.docker.internal"
|
|
31
31
|
},
|
|
32
32
|
data: {
|
|
33
|
-
dir:
|
|
34
|
-
artifactsDir:
|
|
33
|
+
dir: path7.join(os.homedir(), ".calimero-registry", "data"),
|
|
34
|
+
artifactsDir: path7.join(
|
|
35
35
|
os.homedir(),
|
|
36
36
|
".calimero-registry",
|
|
37
37
|
"artifacts"
|
|
38
38
|
)
|
|
39
39
|
},
|
|
40
40
|
artifacts: {
|
|
41
|
-
storageDir:
|
|
41
|
+
storageDir: path7.join(os.homedir(), ".calimero-registry", "artifacts"),
|
|
42
42
|
serveLocal: true,
|
|
43
43
|
copyArtifacts: true,
|
|
44
44
|
maxFileSize: "100MB",
|
|
@@ -50,7 +50,20 @@ var LocalConfig = class {
|
|
|
50
50
|
const existingConfig = JSON.parse(
|
|
51
51
|
fs3.readFileSync(this.configPath, "utf8")
|
|
52
52
|
);
|
|
53
|
-
return {
|
|
53
|
+
return {
|
|
54
|
+
server: {
|
|
55
|
+
...defaultConfig.server,
|
|
56
|
+
...existingConfig.server || {}
|
|
57
|
+
},
|
|
58
|
+
data: {
|
|
59
|
+
...defaultConfig.data,
|
|
60
|
+
...existingConfig.data || {}
|
|
61
|
+
},
|
|
62
|
+
artifacts: {
|
|
63
|
+
...defaultConfig.artifacts,
|
|
64
|
+
...existingConfig.artifacts || {}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
54
67
|
} catch {
|
|
55
68
|
console.warn("Failed to load existing config, using defaults");
|
|
56
69
|
}
|
|
@@ -58,7 +71,7 @@ var LocalConfig = class {
|
|
|
58
71
|
return defaultConfig;
|
|
59
72
|
}
|
|
60
73
|
saveConfig() {
|
|
61
|
-
const configDir =
|
|
74
|
+
const configDir = path7.dirname(this.configPath);
|
|
62
75
|
if (!fs3.existsSync(configDir)) {
|
|
63
76
|
fs3.mkdirSync(configDir, { recursive: true });
|
|
64
77
|
}
|
|
@@ -79,13 +92,20 @@ var LocalConfig = class {
|
|
|
79
92
|
this.config.server.host = host;
|
|
80
93
|
this.saveConfig();
|
|
81
94
|
}
|
|
95
|
+
getPublicHost() {
|
|
96
|
+
return this.config.server.publicHost;
|
|
97
|
+
}
|
|
98
|
+
setPublicHost(host) {
|
|
99
|
+
this.config.server.publicHost = host;
|
|
100
|
+
this.saveConfig();
|
|
101
|
+
}
|
|
82
102
|
// Data directory configuration
|
|
83
103
|
getDataDir() {
|
|
84
104
|
return this.config.data.dir;
|
|
85
105
|
}
|
|
86
106
|
setDataDir(dir) {
|
|
87
107
|
this.config.data.dir = dir;
|
|
88
|
-
this.config.artifacts.storageDir =
|
|
108
|
+
this.config.artifacts.storageDir = path7.join(dir, "artifacts");
|
|
89
109
|
this.saveConfig();
|
|
90
110
|
}
|
|
91
111
|
getArtifactsDir() {
|
|
@@ -109,8 +129,8 @@ var LocalConfig = class {
|
|
|
109
129
|
const dirs = [
|
|
110
130
|
this.getDataDir(),
|
|
111
131
|
this.getArtifactsDir(),
|
|
112
|
-
|
|
113
|
-
|
|
132
|
+
path7.join(this.getDataDir(), "backups"),
|
|
133
|
+
path7.join(this.getDataDir(), "logs")
|
|
114
134
|
];
|
|
115
135
|
dirs.forEach((dir) => {
|
|
116
136
|
if (!fs3.existsSync(dir)) {
|
|
@@ -133,15 +153,15 @@ var LocalConfig = class {
|
|
|
133
153
|
host: "localhost"
|
|
134
154
|
},
|
|
135
155
|
data: {
|
|
136
|
-
dir:
|
|
137
|
-
artifactsDir:
|
|
156
|
+
dir: path7.join(os.homedir(), ".calimero-registry", "data"),
|
|
157
|
+
artifactsDir: path7.join(
|
|
138
158
|
os.homedir(),
|
|
139
159
|
".calimero-registry",
|
|
140
160
|
"artifacts"
|
|
141
161
|
)
|
|
142
162
|
},
|
|
143
163
|
artifacts: {
|
|
144
|
-
storageDir:
|
|
164
|
+
storageDir: path7.join(os.homedir(), ".calimero-registry", "artifacts"),
|
|
145
165
|
serveLocal: true,
|
|
146
166
|
copyArtifacts: true,
|
|
147
167
|
maxFileSize: "100MB",
|
|
@@ -154,12 +174,15 @@ var LocalConfig = class {
|
|
|
154
174
|
var LocalDataStore = class {
|
|
155
175
|
constructor(config) {
|
|
156
176
|
this.config = config;
|
|
157
|
-
this.appsFile =
|
|
158
|
-
this.
|
|
159
|
-
|
|
177
|
+
this.appsFile = path7.join(config.getDataDir(), "apps.json");
|
|
178
|
+
this.bundleManifestsFile = path7.join(
|
|
179
|
+
config.getDataDir(),
|
|
180
|
+
"bundle_manifests.json"
|
|
181
|
+
);
|
|
182
|
+
this.artifactsFile = path7.join(config.getDataDir(), "artifacts.json");
|
|
160
183
|
this.data = {
|
|
161
184
|
apps: /* @__PURE__ */ new Map(),
|
|
162
|
-
|
|
185
|
+
bundleManifests: /* @__PURE__ */ new Map(),
|
|
163
186
|
artifacts: /* @__PURE__ */ new Map()
|
|
164
187
|
};
|
|
165
188
|
this.loadData();
|
|
@@ -170,11 +193,13 @@ var LocalDataStore = class {
|
|
|
170
193
|
const appsData = JSON.parse(fs3.readFileSync(this.appsFile, "utf8"));
|
|
171
194
|
this.data.apps = new Map(Object.entries(appsData));
|
|
172
195
|
}
|
|
173
|
-
if (fs3.existsSync(this.
|
|
174
|
-
const
|
|
175
|
-
fs3.readFileSync(this.
|
|
196
|
+
if (fs3.existsSync(this.bundleManifestsFile)) {
|
|
197
|
+
const bundleManifestsData = JSON.parse(
|
|
198
|
+
fs3.readFileSync(this.bundleManifestsFile, "utf8")
|
|
199
|
+
);
|
|
200
|
+
this.data.bundleManifests = new Map(
|
|
201
|
+
Object.entries(bundleManifestsData)
|
|
176
202
|
);
|
|
177
|
-
this.data.manifests = new Map(Object.entries(manifestsData));
|
|
178
203
|
}
|
|
179
204
|
if (fs3.existsSync(this.artifactsFile)) {
|
|
180
205
|
const artifactsData = JSON.parse(
|
|
@@ -191,10 +216,10 @@ var LocalDataStore = class {
|
|
|
191
216
|
this.config.ensureDirectories();
|
|
192
217
|
const appsObj = Object.fromEntries(this.data.apps);
|
|
193
218
|
fs3.writeFileSync(this.appsFile, JSON.stringify(appsObj, null, 2));
|
|
194
|
-
const
|
|
219
|
+
const bundleManifestsObj = Object.fromEntries(this.data.bundleManifests);
|
|
195
220
|
fs3.writeFileSync(
|
|
196
|
-
this.
|
|
197
|
-
JSON.stringify(
|
|
221
|
+
this.bundleManifestsFile,
|
|
222
|
+
JSON.stringify(bundleManifestsObj, null, 2)
|
|
198
223
|
);
|
|
199
224
|
const artifactsObj = Object.fromEntries(this.data.artifacts);
|
|
200
225
|
fs3.writeFileSync(
|
|
@@ -210,12 +235,12 @@ var LocalDataStore = class {
|
|
|
210
235
|
// Apps management
|
|
211
236
|
getApps(filters) {
|
|
212
237
|
let apps = Array.from(this.data.apps.values());
|
|
213
|
-
if (filters?.dev) {
|
|
214
|
-
apps = apps.filter((app) => app.developer_pubkey === filters.dev);
|
|
215
|
-
}
|
|
216
238
|
if (filters?.name) {
|
|
217
239
|
apps = apps.filter((app) => app.name.includes(filters.name));
|
|
218
240
|
}
|
|
241
|
+
if (filters?.dev) {
|
|
242
|
+
apps = apps.filter((app) => app.developer_pubkey === filters.dev);
|
|
243
|
+
}
|
|
219
244
|
return apps;
|
|
220
245
|
}
|
|
221
246
|
getApp(appKey) {
|
|
@@ -225,17 +250,19 @@ var LocalDataStore = class {
|
|
|
225
250
|
this.data.apps.set(appKey, app);
|
|
226
251
|
this.saveData();
|
|
227
252
|
}
|
|
228
|
-
// Versions management
|
|
229
|
-
getAppVersions(
|
|
253
|
+
// Versions management (bundles only)
|
|
254
|
+
getAppVersions(packageId) {
|
|
230
255
|
const versions = [];
|
|
231
|
-
for (const [key, manifest] of this.data.
|
|
232
|
-
if (key.startsWith(`${
|
|
256
|
+
for (const [key, manifest] of this.data.bundleManifests.entries()) {
|
|
257
|
+
if (key.startsWith(`${packageId}/`)) {
|
|
233
258
|
const semver = key.split("/").pop();
|
|
259
|
+
const digest = manifest.wasm?.hash || "";
|
|
234
260
|
versions.push({
|
|
235
261
|
semver,
|
|
236
|
-
|
|
262
|
+
digest,
|
|
263
|
+
cid: digest,
|
|
264
|
+
// Compatibility
|
|
237
265
|
yanked: false
|
|
238
|
-
// TODO: Implement yanking
|
|
239
266
|
});
|
|
240
267
|
}
|
|
241
268
|
}
|
|
@@ -243,13 +270,14 @@ var LocalDataStore = class {
|
|
|
243
270
|
(a, b) => a.semver.localeCompare(b.semver, void 0, { numeric: true })
|
|
244
271
|
);
|
|
245
272
|
}
|
|
246
|
-
// Manifest management
|
|
247
|
-
|
|
248
|
-
const
|
|
249
|
-
return this.data.
|
|
273
|
+
// Manifest management (V2 / Bundles)
|
|
274
|
+
getBundleManifest(pkg, version) {
|
|
275
|
+
const key = `${pkg}/${version}`;
|
|
276
|
+
return this.data.bundleManifests.get(key);
|
|
250
277
|
}
|
|
251
|
-
|
|
252
|
-
|
|
278
|
+
setBundleManifest(pkg, version, manifest) {
|
|
279
|
+
const key = `${pkg}/${version}`;
|
|
280
|
+
this.data.bundleManifests.set(key, manifest);
|
|
253
281
|
this.saveData();
|
|
254
282
|
}
|
|
255
283
|
// Artifact management
|
|
@@ -264,26 +292,26 @@ var LocalDataStore = class {
|
|
|
264
292
|
getStats() {
|
|
265
293
|
return {
|
|
266
294
|
publishedApps: this.data.apps.size,
|
|
267
|
-
totalVersions: this.data.
|
|
295
|
+
totalVersions: this.data.bundleManifests.size,
|
|
268
296
|
totalArtifacts: this.data.artifacts.size
|
|
269
297
|
};
|
|
270
298
|
}
|
|
271
299
|
// Backup and restore
|
|
272
300
|
async backup(outputPath) {
|
|
273
301
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
274
|
-
const backupPath = outputPath ||
|
|
302
|
+
const backupPath = outputPath || path7.join(
|
|
275
303
|
this.config.getDataDir(),
|
|
276
304
|
"backups",
|
|
277
305
|
`backup-${timestamp}.json`
|
|
278
306
|
);
|
|
279
|
-
const backupDir =
|
|
307
|
+
const backupDir = path7.dirname(backupPath);
|
|
280
308
|
if (!fs3.existsSync(backupDir)) {
|
|
281
309
|
fs3.mkdirSync(backupDir, { recursive: true });
|
|
282
310
|
}
|
|
283
311
|
const backupData = {
|
|
284
312
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
285
313
|
apps: Object.fromEntries(this.data.apps),
|
|
286
|
-
|
|
314
|
+
bundleManifests: Object.fromEntries(this.data.bundleManifests),
|
|
287
315
|
artifacts: Object.fromEntries(this.data.artifacts)
|
|
288
316
|
};
|
|
289
317
|
fs3.writeFileSync(backupPath, JSON.stringify(backupData, null, 2));
|
|
@@ -295,83 +323,62 @@ var LocalDataStore = class {
|
|
|
295
323
|
}
|
|
296
324
|
const backupData = JSON.parse(fs3.readFileSync(backupPath, "utf8"));
|
|
297
325
|
this.data.apps = new Map(Object.entries(backupData.apps || {}));
|
|
298
|
-
this.data.
|
|
326
|
+
this.data.bundleManifests = new Map(
|
|
327
|
+
Object.entries(backupData.bundleManifests || {})
|
|
328
|
+
);
|
|
299
329
|
this.data.artifacts = new Map(Object.entries(backupData.artifacts || {}));
|
|
300
330
|
this.saveData();
|
|
301
331
|
}
|
|
302
332
|
// Reset data
|
|
303
333
|
reset() {
|
|
304
334
|
this.data.apps.clear();
|
|
305
|
-
this.data.
|
|
335
|
+
this.data.bundleManifests.clear();
|
|
306
336
|
this.data.artifacts.clear();
|
|
307
337
|
this.saveData();
|
|
308
338
|
}
|
|
339
|
+
/**
|
|
340
|
+
* Get all bundle manifests
|
|
341
|
+
*/
|
|
342
|
+
getAllBundles() {
|
|
343
|
+
return Array.from(this.data.bundleManifests.values());
|
|
344
|
+
}
|
|
309
345
|
// Seed with sample data
|
|
310
346
|
async seed() {
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
347
|
+
const bundleManifest = {
|
|
348
|
+
version: "1.0",
|
|
349
|
+
package: "com.calimero.sample-bundle",
|
|
350
|
+
appVersion: "1.0.0",
|
|
351
|
+
metadata: {
|
|
352
|
+
name: "Sample Bundle App",
|
|
353
|
+
description: "A sample application using V2 Bundle Manifest",
|
|
354
|
+
tags: ["sample", "v2"],
|
|
355
|
+
license: "MIT"
|
|
318
356
|
},
|
|
319
|
-
{
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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();
|
|
357
|
+
interfaces: {
|
|
358
|
+
exports: ["com.calimero.sample.v1"],
|
|
359
|
+
uses: []
|
|
360
|
+
},
|
|
361
|
+
links: {
|
|
362
|
+
frontend: "https://example.com/app",
|
|
363
|
+
github: "https://github.com/example/app"
|
|
364
|
+
},
|
|
365
|
+
wasm: {
|
|
366
|
+
path: "app.wasm",
|
|
367
|
+
size: 1024,
|
|
368
|
+
hash: null
|
|
369
|
+
},
|
|
370
|
+
abi: {
|
|
371
|
+
path: "abi.json",
|
|
372
|
+
size: 512,
|
|
373
|
+
hash: null
|
|
374
|
+
},
|
|
375
|
+
migrations: []
|
|
376
|
+
};
|
|
377
|
+
this.setBundleManifest(
|
|
378
|
+
bundleManifest.package,
|
|
379
|
+
bundleManifest.appVersion,
|
|
380
|
+
bundleManifest
|
|
381
|
+
);
|
|
375
382
|
}
|
|
376
383
|
};
|
|
377
384
|
var LocalArtifactServer = class {
|
|
@@ -391,11 +398,11 @@ var LocalArtifactServer = class {
|
|
|
391
398
|
if (!fs3.existsSync(sourcePath)) {
|
|
392
399
|
throw new Error(`Source file not found: ${sourcePath}`);
|
|
393
400
|
}
|
|
394
|
-
const appVersionDir =
|
|
401
|
+
const appVersionDir = path7.join(this.artifactsDir, appId, version);
|
|
395
402
|
if (!fs3.existsSync(appVersionDir)) {
|
|
396
403
|
fs3.mkdirSync(appVersionDir, { recursive: true });
|
|
397
404
|
}
|
|
398
|
-
const targetPath =
|
|
405
|
+
const targetPath = path7.join(appVersionDir, filename);
|
|
399
406
|
fs3.copyFileSync(sourcePath, targetPath);
|
|
400
407
|
const fileHash = await this.calculateFileHash(targetPath);
|
|
401
408
|
this.dataStore.setArtifactPath(fileHash, targetPath);
|
|
@@ -403,7 +410,7 @@ var LocalArtifactServer = class {
|
|
|
403
410
|
}
|
|
404
411
|
// Serve artifact by app ID, version, and filename
|
|
405
412
|
async serveArtifact(appId, version, filename) {
|
|
406
|
-
const artifactPath =
|
|
413
|
+
const artifactPath = path7.join(this.artifactsDir, appId, version, filename);
|
|
407
414
|
if (!fs3.existsSync(artifactPath)) {
|
|
408
415
|
throw new Error(`Artifact not found: ${artifactPath}`);
|
|
409
416
|
}
|
|
@@ -437,35 +444,33 @@ var LocalArtifactServer = class {
|
|
|
437
444
|
}
|
|
438
445
|
// Get artifact URL for local serving
|
|
439
446
|
getArtifactUrl(appId, version, filename) {
|
|
440
|
-
const baseUrl = `http://${this.config.
|
|
447
|
+
const baseUrl = `http://${this.config.getPublicHost()}:${this.config.getPort()}`;
|
|
441
448
|
return `${baseUrl}/artifacts/${appId}/${version}/${filename}`;
|
|
442
449
|
}
|
|
443
450
|
// Get artifact URL by hash
|
|
444
451
|
getArtifactUrlByHash(hash) {
|
|
445
|
-
const baseUrl = `http://${this.config.
|
|
452
|
+
const baseUrl = `http://${this.config.getPublicHost()}:${this.config.getPort()}`;
|
|
446
453
|
return `${baseUrl}/artifacts/${hash}`;
|
|
447
454
|
}
|
|
448
|
-
// Update manifest
|
|
449
|
-
|
|
455
|
+
// Update manifest artifact to use local URLs (and preserve HTTP access)
|
|
456
|
+
updateManifestArtifact(manifest) {
|
|
450
457
|
const updatedManifest = { ...manifest };
|
|
451
|
-
if (
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
}
|
|
468
|
-
);
|
|
458
|
+
if (!manifest.artifact?.uri) {
|
|
459
|
+
return updatedManifest;
|
|
460
|
+
}
|
|
461
|
+
const uri = manifest.artifact.uri;
|
|
462
|
+
if (uri.startsWith("/")) {
|
|
463
|
+
updatedManifest.artifact = {
|
|
464
|
+
...manifest.artifact,
|
|
465
|
+
uri: `${this.getArtifactUrl(manifest.id, manifest.version, path7.basename(manifest.artifact.uri))}`
|
|
466
|
+
};
|
|
467
|
+
} else if (uri.startsWith("file://")) {
|
|
468
|
+
const filePath = uri.replace("file://", "");
|
|
469
|
+
const filename = path7.basename(filePath);
|
|
470
|
+
updatedManifest.artifact = {
|
|
471
|
+
...manifest.artifact,
|
|
472
|
+
uri: this.getArtifactUrl(manifest.id, manifest.version, filename)
|
|
473
|
+
};
|
|
469
474
|
}
|
|
470
475
|
return updatedManifest;
|
|
471
476
|
}
|
|
@@ -476,7 +481,7 @@ var LocalArtifactServer = class {
|
|
|
476
481
|
let files = [];
|
|
477
482
|
const items = fs3.readdirSync(dir);
|
|
478
483
|
for (const item of items) {
|
|
479
|
-
const fullPath =
|
|
484
|
+
const fullPath = path7.join(dir, item);
|
|
480
485
|
const stat = fs3.statSync(fullPath);
|
|
481
486
|
if (stat.isDirectory()) {
|
|
482
487
|
files = files.concat(getAllFiles(fullPath));
|
|
@@ -505,7 +510,7 @@ var LocalArtifactServer = class {
|
|
|
505
510
|
let files = [];
|
|
506
511
|
const items = fs3.readdirSync(dir);
|
|
507
512
|
for (const item of items) {
|
|
508
|
-
const fullPath =
|
|
513
|
+
const fullPath = path7.join(dir, item);
|
|
509
514
|
const stat = fs3.statSync(fullPath);
|
|
510
515
|
if (stat.isDirectory()) {
|
|
511
516
|
files = files.concat(getAllFiles(fullPath));
|
|
@@ -573,24 +578,22 @@ var LocalRegistryClient = class {
|
|
|
573
578
|
if (!manifest) {
|
|
574
579
|
throw new Error("Manifest not found");
|
|
575
580
|
}
|
|
576
|
-
return this.artifactServer.
|
|
581
|
+
return this.artifactServer.updateManifestArtifact(manifest);
|
|
577
582
|
}
|
|
578
583
|
async submitAppManifest(manifest) {
|
|
579
584
|
if (!this.validateManifest(manifest)) {
|
|
580
585
|
throw new Error("Invalid manifest structure");
|
|
581
586
|
}
|
|
582
587
|
const processedManifest = await this.processManifestArtifacts(manifest);
|
|
583
|
-
const manifestKey = `${
|
|
588
|
+
const manifestKey = `${processedManifest.id}/${processedManifest.version}`;
|
|
584
589
|
this.dataStore.setManifest(manifestKey, processedManifest);
|
|
585
|
-
const appKey = manifest.app.app_id;
|
|
586
590
|
const appSummary = {
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
latest_version:
|
|
590
|
-
|
|
591
|
-
alias: manifest.app.alias
|
|
591
|
+
id: processedManifest.id,
|
|
592
|
+
name: processedManifest.name,
|
|
593
|
+
latest_version: processedManifest.version,
|
|
594
|
+
latest_digest: processedManifest.artifact.digest
|
|
592
595
|
};
|
|
593
|
-
this.dataStore.setApp(
|
|
596
|
+
this.dataStore.setApp(processedManifest.id, appSummary);
|
|
594
597
|
return {
|
|
595
598
|
success: true,
|
|
596
599
|
message: "App version registered successfully"
|
|
@@ -600,39 +603,33 @@ var LocalRegistryClient = class {
|
|
|
600
603
|
return { status: "ok" };
|
|
601
604
|
}
|
|
602
605
|
validateManifest(manifest) {
|
|
603
|
-
return !!(manifest.manifest_version && manifest.
|
|
606
|
+
return !!(manifest.manifest_version && manifest.id && manifest.name && manifest.version && manifest.artifact && manifest.artifact.type && manifest.artifact.target && manifest.artifact.digest && manifest.artifact.uri);
|
|
604
607
|
}
|
|
605
608
|
async processManifestArtifacts(manifest) {
|
|
606
609
|
const processedManifest = { ...manifest };
|
|
607
|
-
if (
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
console.warn(`Failed to copy artifact ${artifact.path}:`, error);
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
return processedArtifact;
|
|
634
|
-
})
|
|
635
|
-
);
|
|
610
|
+
if (manifest.artifact?.uri?.startsWith("file://")) {
|
|
611
|
+
const filePath = manifest.artifact.uri.replace("file://", "");
|
|
612
|
+
if (fs3.existsSync(filePath)) {
|
|
613
|
+
const filename = path7.basename(filePath);
|
|
614
|
+
try {
|
|
615
|
+
await this.artifactServer.copyArtifactToLocal(
|
|
616
|
+
filePath,
|
|
617
|
+
manifest.id,
|
|
618
|
+
manifest.version,
|
|
619
|
+
filename
|
|
620
|
+
);
|
|
621
|
+
processedManifest.artifact = {
|
|
622
|
+
...manifest.artifact,
|
|
623
|
+
uri: this.artifactServer.getArtifactUrl(
|
|
624
|
+
manifest.id,
|
|
625
|
+
manifest.version,
|
|
626
|
+
filename
|
|
627
|
+
)
|
|
628
|
+
};
|
|
629
|
+
} catch (error) {
|
|
630
|
+
console.warn(`Failed to copy artifact ${filePath}:`, error);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
636
633
|
}
|
|
637
634
|
return processedManifest;
|
|
638
635
|
}
|
|
@@ -648,7 +645,7 @@ function createRegistryClient(useLocal, baseURL, timeout) {
|
|
|
648
645
|
}
|
|
649
646
|
}
|
|
650
647
|
var appsCommand = new Command("apps").description("Manage SSApp applications").addCommand(
|
|
651
|
-
new Command("list").description("List all applications").option("-
|
|
648
|
+
new Command("list").description("List all applications").option("-n, --name <name>", "Filter by application name").action(async (options, command) => {
|
|
652
649
|
const globalOpts = command.parent?.parent?.opts();
|
|
653
650
|
const useLocal = globalOpts?.local || false;
|
|
654
651
|
const client = createRegistryClient(
|
|
@@ -659,29 +656,28 @@ var appsCommand = new Command("apps").description("Manage SSApp applications").a
|
|
|
659
656
|
const spinner2 = ora6("Fetching applications...").start();
|
|
660
657
|
try {
|
|
661
658
|
const apps = await client.getApps({
|
|
662
|
-
dev: options.dev,
|
|
663
659
|
name: options.name
|
|
664
660
|
});
|
|
665
661
|
spinner2.succeed(`Found ${apps.length} application(s)`);
|
|
666
662
|
if (apps.length === 0) {
|
|
667
|
-
console.log(
|
|
663
|
+
console.log(chalk6.yellow("No applications found"));
|
|
668
664
|
return;
|
|
669
665
|
}
|
|
670
666
|
const tableData = [
|
|
671
|
-
["
|
|
667
|
+
["ID", "Name", "Latest Version", "Digest"],
|
|
672
668
|
...apps.map((app) => [
|
|
669
|
+
app.id,
|
|
673
670
|
app.name,
|
|
674
|
-
app.
|
|
675
|
-
|
|
676
|
-
app.latest_cid
|
|
677
|
-
app.alias || "-"
|
|
671
|
+
app.latest_version,
|
|
672
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
673
|
+
(app.latest_digest || app.latest_cid || "N/A").toString().substring(0, 12) + "..."
|
|
678
674
|
])
|
|
679
675
|
];
|
|
680
676
|
console.log(table(tableData));
|
|
681
677
|
} catch (error) {
|
|
682
678
|
spinner2.fail("Failed to fetch applications");
|
|
683
679
|
if (error instanceof Error) {
|
|
684
|
-
console.error(
|
|
680
|
+
console.error(chalk6.red(`Error: ${error.message}`));
|
|
685
681
|
}
|
|
686
682
|
process.exit(1);
|
|
687
683
|
}
|
|
@@ -700,7 +696,7 @@ var appsCommand = new Command("apps").description("Manage SSApp applications").a
|
|
|
700
696
|
const versions = await client.getAppVersions(appId);
|
|
701
697
|
spinner2.succeed(`Found ${versions.length} version(s)`);
|
|
702
698
|
if (versions.length === 0) {
|
|
703
|
-
console.log(
|
|
699
|
+
console.log(chalk6.yellow("No versions found"));
|
|
704
700
|
return;
|
|
705
701
|
}
|
|
706
702
|
const tableData = [
|
|
@@ -708,14 +704,14 @@ var appsCommand = new Command("apps").description("Manage SSApp applications").a
|
|
|
708
704
|
...versions.map((version) => [
|
|
709
705
|
version.semver,
|
|
710
706
|
version.cid.substring(0, 12) + "...",
|
|
711
|
-
version.yanked ?
|
|
707
|
+
version.yanked ? chalk6.red("Yes") : chalk6.green("No")
|
|
712
708
|
])
|
|
713
709
|
];
|
|
714
710
|
console.log(table(tableData));
|
|
715
711
|
} catch (error) {
|
|
716
712
|
spinner2.fail("Failed to fetch versions");
|
|
717
713
|
if (error instanceof Error) {
|
|
718
|
-
console.error(
|
|
714
|
+
console.error(chalk6.red(`Error: ${error.message}`));
|
|
719
715
|
}
|
|
720
716
|
process.exit(1);
|
|
721
717
|
}
|
|
@@ -735,12 +731,12 @@ var appsCommand = new Command("apps").description("Manage SSApp applications").a
|
|
|
735
731
|
try {
|
|
736
732
|
const manifest = await client.getAppManifest(appId, version);
|
|
737
733
|
spinner2.succeed("Manifest fetched successfully");
|
|
738
|
-
console.log(
|
|
734
|
+
console.log(chalk6.blue("\nApplication Manifest:"));
|
|
739
735
|
console.log(JSON.stringify(manifest, null, 2));
|
|
740
736
|
} catch (error) {
|
|
741
737
|
spinner2.fail("Failed to fetch manifest");
|
|
742
738
|
if (error instanceof Error) {
|
|
743
|
-
console.error(
|
|
739
|
+
console.error(chalk6.red(`Error: ${error.message}`));
|
|
744
740
|
}
|
|
745
741
|
process.exit(1);
|
|
746
742
|
}
|
|
@@ -756,10 +752,10 @@ var appsCommand = new Command("apps").description("Manage SSApp applications").a
|
|
|
756
752
|
);
|
|
757
753
|
const spinner2 = ora6("Reading manifest file...").start();
|
|
758
754
|
try {
|
|
759
|
-
const manifestPath =
|
|
755
|
+
const manifestPath = path7.resolve(manifestFile);
|
|
760
756
|
if (!fs3.existsSync(manifestPath)) {
|
|
761
757
|
spinner2.fail("Manifest file not found");
|
|
762
|
-
console.error(
|
|
758
|
+
console.error(chalk6.red(`File not found: ${manifestFile}`));
|
|
763
759
|
process.exit(1);
|
|
764
760
|
}
|
|
765
761
|
const manifestContent = fs3.readFileSync(manifestPath, "utf8");
|
|
@@ -767,20 +763,20 @@ var appsCommand = new Command("apps").description("Manage SSApp applications").a
|
|
|
767
763
|
spinner2.text = "Submitting application manifest...";
|
|
768
764
|
const result = await client.submitAppManifest(manifest);
|
|
769
765
|
spinner2.succeed("Application submitted successfully");
|
|
770
|
-
console.log(
|
|
766
|
+
console.log(chalk6.green(`
|
|
771
767
|
\u2705 ${result.message}`));
|
|
772
768
|
if (manifest.app?.name) {
|
|
773
|
-
console.log(
|
|
769
|
+
console.log(chalk6.blue(`
|
|
774
770
|
\u{1F4F1} App: ${manifest.app.name}`));
|
|
775
771
|
console.log(
|
|
776
|
-
|
|
772
|
+
chalk6.blue(`\u{1F464} Developer: ${manifest.app.developer_pubkey}`)
|
|
777
773
|
);
|
|
778
|
-
console.log(
|
|
774
|
+
console.log(chalk6.blue(`\u{1F4E6} Version: ${manifest.version?.semver}`));
|
|
779
775
|
}
|
|
780
776
|
} catch (error) {
|
|
781
777
|
spinner2.fail("Failed to submit application");
|
|
782
778
|
if (error instanceof Error) {
|
|
783
|
-
console.error(
|
|
779
|
+
console.error(chalk6.red(`Error: ${error.message}`));
|
|
784
780
|
}
|
|
785
781
|
process.exit(1);
|
|
786
782
|
}
|
|
@@ -797,29 +793,29 @@ var developersCommand = new Command("developers").description("Manage developer
|
|
|
797
793
|
try {
|
|
798
794
|
const profile = await client.getDeveloper(pubkey);
|
|
799
795
|
spinner2.succeed("Developer profile fetched successfully");
|
|
800
|
-
console.log(
|
|
801
|
-
console.log(
|
|
796
|
+
console.log(chalk6.blue("\nDeveloper Profile:"));
|
|
797
|
+
console.log(chalk6.green("Display Name:"), profile.display_name);
|
|
802
798
|
if (profile.website) {
|
|
803
|
-
console.log(
|
|
799
|
+
console.log(chalk6.green("Website:"), profile.website);
|
|
804
800
|
}
|
|
805
801
|
if (profile.proofs.length > 0) {
|
|
806
|
-
console.log(
|
|
802
|
+
console.log(chalk6.green("\nProofs:"));
|
|
807
803
|
const tableData = [
|
|
808
804
|
["Type", "Value", "Verified"],
|
|
809
805
|
...profile.proofs.map((proof) => [
|
|
810
806
|
proof.type,
|
|
811
807
|
proof.value.substring(0, 20) + "...",
|
|
812
|
-
proof.verified ?
|
|
808
|
+
proof.verified ? chalk6.green("Yes") : chalk6.red("No")
|
|
813
809
|
])
|
|
814
810
|
];
|
|
815
811
|
console.log(table(tableData));
|
|
816
812
|
} else {
|
|
817
|
-
console.log(
|
|
813
|
+
console.log(chalk6.yellow("\nNo proofs found"));
|
|
818
814
|
}
|
|
819
815
|
} catch (error) {
|
|
820
816
|
spinner2.fail("Failed to fetch developer profile");
|
|
821
817
|
if (error instanceof Error) {
|
|
822
|
-
console.error(
|
|
818
|
+
console.error(chalk6.red(`Error: ${error.message}`));
|
|
823
819
|
}
|
|
824
820
|
process.exit(1);
|
|
825
821
|
}
|
|
@@ -839,7 +835,7 @@ var developersCommand = new Command("developers").description("Manage developer
|
|
|
839
835
|
proofs = JSON.parse(options.proofs);
|
|
840
836
|
} catch {
|
|
841
837
|
spinner2.fail("Invalid proofs JSON format");
|
|
842
|
-
console.error(
|
|
838
|
+
console.error(chalk6.red("Proofs must be a valid JSON array"));
|
|
843
839
|
process.exit(1);
|
|
844
840
|
}
|
|
845
841
|
}
|
|
@@ -851,18 +847,18 @@ var developersCommand = new Command("developers").description("Manage developer
|
|
|
851
847
|
};
|
|
852
848
|
const result = await client.submitDeveloperProfile(pubkey, profile);
|
|
853
849
|
spinner2.succeed("Developer profile created successfully");
|
|
854
|
-
console.log(
|
|
850
|
+
console.log(chalk6.green(`
|
|
855
851
|
\u2705 ${result.message}`));
|
|
856
|
-
console.log(
|
|
852
|
+
console.log(chalk6.blue(`
|
|
857
853
|
\u{1F464} Developer: ${displayName}`));
|
|
858
|
-
console.log(
|
|
854
|
+
console.log(chalk6.blue(`\u{1F511} Public Key: ${pubkey}`));
|
|
859
855
|
if (options.website) {
|
|
860
|
-
console.log(
|
|
856
|
+
console.log(chalk6.blue(`\u{1F310} Website: ${options.website}`));
|
|
861
857
|
}
|
|
862
858
|
} catch (error) {
|
|
863
859
|
spinner2.fail("Failed to create developer profile");
|
|
864
860
|
if (error instanceof Error) {
|
|
865
|
-
console.error(
|
|
861
|
+
console.error(chalk6.red(`Error: ${error.message}`));
|
|
866
862
|
}
|
|
867
863
|
process.exit(1);
|
|
868
864
|
}
|
|
@@ -885,16 +881,16 @@ var attestationsCommand = new Command("attestations").description("Manage applic
|
|
|
885
881
|
version
|
|
886
882
|
);
|
|
887
883
|
spinner2.succeed("Attestation fetched successfully");
|
|
888
|
-
console.log(
|
|
889
|
-
console.log(
|
|
890
|
-
console.log(
|
|
884
|
+
console.log(chalk6.blue("\nAttestation:"));
|
|
885
|
+
console.log(chalk6.green("Status:"), attestation.status);
|
|
886
|
+
console.log(chalk6.green("Timestamp:"), attestation.timestamp);
|
|
891
887
|
if (attestation.comment) {
|
|
892
|
-
console.log(
|
|
888
|
+
console.log(chalk6.green("Comment:"), attestation.comment);
|
|
893
889
|
}
|
|
894
890
|
} catch (error) {
|
|
895
891
|
spinner2.fail("Failed to fetch attestation");
|
|
896
892
|
if (error instanceof Error) {
|
|
897
|
-
console.error(
|
|
893
|
+
console.error(chalk6.red(`Error: ${error.message}`));
|
|
898
894
|
}
|
|
899
895
|
process.exit(1);
|
|
900
896
|
}
|
|
@@ -912,11 +908,11 @@ var healthCommand = new Command("health").description("Check the health of the S
|
|
|
912
908
|
try {
|
|
913
909
|
const health = await client.healthCheck();
|
|
914
910
|
spinner2.succeed("API is healthy");
|
|
915
|
-
console.log(
|
|
911
|
+
console.log(chalk6.green(`Status: ${health.status}`));
|
|
916
912
|
} catch (error) {
|
|
917
913
|
spinner2.fail("API health check failed");
|
|
918
914
|
if (error instanceof Error) {
|
|
919
|
-
console.error(
|
|
915
|
+
console.error(chalk6.red(`Error: ${error.message}`));
|
|
920
916
|
}
|
|
921
917
|
process.exit(1);
|
|
922
918
|
}
|
|
@@ -1072,75 +1068,6 @@ var LocalRegistryServer = class {
|
|
|
1072
1068
|
totalSize: artifactStats.totalSize
|
|
1073
1069
|
};
|
|
1074
1070
|
});
|
|
1075
|
-
this.server.get("/apps", async (request) => {
|
|
1076
|
-
const query = request.query;
|
|
1077
|
-
if (query.id && query.versions === "true") {
|
|
1078
|
-
return {
|
|
1079
|
-
id: query.id,
|
|
1080
|
-
versions: this.dataStore.getAppVersions(query.id)
|
|
1081
|
-
};
|
|
1082
|
-
}
|
|
1083
|
-
if (query.id && query.version) {
|
|
1084
|
-
const manifest = this.dataStore.getManifest(query.id, query.version);
|
|
1085
|
-
if (!manifest) {
|
|
1086
|
-
return {
|
|
1087
|
-
statusCode: 404,
|
|
1088
|
-
error: "Not Found",
|
|
1089
|
-
message: "Manifest not found"
|
|
1090
|
-
};
|
|
1091
|
-
}
|
|
1092
|
-
return this.artifactServer.updateManifestArtifacts(manifest);
|
|
1093
|
-
}
|
|
1094
|
-
return this.dataStore.getApps(query);
|
|
1095
|
-
});
|
|
1096
|
-
this.server.get("/apps/:appId", async (request) => {
|
|
1097
|
-
const { appId } = request.params;
|
|
1098
|
-
const versions = this.dataStore.getAppVersions(appId);
|
|
1099
|
-
return {
|
|
1100
|
-
id: appId,
|
|
1101
|
-
versions
|
|
1102
|
-
};
|
|
1103
|
-
});
|
|
1104
|
-
this.server.get("/apps/:appId/:semver", async (request) => {
|
|
1105
|
-
const { appId, semver } = request.params;
|
|
1106
|
-
const manifest = this.dataStore.getManifest(appId, semver);
|
|
1107
|
-
if (!manifest) {
|
|
1108
|
-
return {
|
|
1109
|
-
statusCode: 404,
|
|
1110
|
-
error: "Not Found",
|
|
1111
|
-
message: "Manifest not found"
|
|
1112
|
-
};
|
|
1113
|
-
}
|
|
1114
|
-
return this.artifactServer.updateManifestArtifacts(manifest);
|
|
1115
|
-
});
|
|
1116
|
-
this.server.post("/apps", async (request) => {
|
|
1117
|
-
const manifest = request.body;
|
|
1118
|
-
if (!this.validateManifest(manifest)) {
|
|
1119
|
-
return {
|
|
1120
|
-
statusCode: 400,
|
|
1121
|
-
error: "Bad Request",
|
|
1122
|
-
message: "Invalid manifest structure"
|
|
1123
|
-
};
|
|
1124
|
-
}
|
|
1125
|
-
const processedManifest = await this.processManifestArtifacts(manifest);
|
|
1126
|
-
const manifestKey = `${manifest.app.app_id}/${manifest.version.semver}`;
|
|
1127
|
-
this.dataStore.setManifest(manifestKey, processedManifest);
|
|
1128
|
-
const appKey = manifest.app.app_id;
|
|
1129
|
-
const appSummary = {
|
|
1130
|
-
name: manifest.app.name,
|
|
1131
|
-
developer_pubkey: manifest.app.developer_pubkey,
|
|
1132
|
-
latest_version: manifest.version.semver,
|
|
1133
|
-
latest_cid: manifest.artifacts[0]?.cid || "",
|
|
1134
|
-
alias: manifest.app.name
|
|
1135
|
-
// Use name as alias
|
|
1136
|
-
};
|
|
1137
|
-
this.dataStore.setApp(appKey, appSummary);
|
|
1138
|
-
return {
|
|
1139
|
-
success: true,
|
|
1140
|
-
message: "App version registered successfully",
|
|
1141
|
-
manifest_key: manifestKey
|
|
1142
|
-
};
|
|
1143
|
-
});
|
|
1144
1071
|
this.server.get(
|
|
1145
1072
|
"/artifacts/:appId/:version/:filename",
|
|
1146
1073
|
async (request, reply) => {
|
|
@@ -1201,190 +1128,119 @@ var LocalRegistryServer = class {
|
|
|
1201
1128
|
await this.seed();
|
|
1202
1129
|
return { message: "Sample data seeded successfully" };
|
|
1203
1130
|
});
|
|
1204
|
-
this.server.
|
|
1205
|
-
const
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
const appKey = manifest.id;
|
|
1217
|
-
const appSummary = {
|
|
1218
|
-
id: manifest.id,
|
|
1219
|
-
name: manifest.name,
|
|
1220
|
-
developer_pubkey: "local-dev-key",
|
|
1221
|
-
latest_version: manifest.version,
|
|
1222
|
-
latest_cid: manifest.artifact?.digest || ""
|
|
1223
|
-
};
|
|
1224
|
-
this.dataStore.setApp(appKey, appSummary);
|
|
1225
|
-
return reply.code(201).send({
|
|
1226
|
-
id: manifest.id,
|
|
1227
|
-
version: manifest.version,
|
|
1228
|
-
canonical_uri: `/v1/apps/${manifest.id}/${manifest.version}`
|
|
1229
|
-
});
|
|
1230
|
-
} catch {
|
|
1231
|
-
return reply.code(409).send({
|
|
1232
|
-
error: "already_exists",
|
|
1233
|
-
details: `${manifest.id}@${manifest.version}`
|
|
1234
|
-
});
|
|
1235
|
-
}
|
|
1236
|
-
});
|
|
1237
|
-
this.server.get("/v1/apps/:id", async (request, reply) => {
|
|
1238
|
-
const { id } = request.params;
|
|
1239
|
-
const versions = this.dataStore.getAppVersions(id);
|
|
1240
|
-
if (!versions || versions.length === 0) {
|
|
1241
|
-
return reply.code(404).send({ error: "not_found", message: "App not found" });
|
|
1242
|
-
}
|
|
1243
|
-
return {
|
|
1244
|
-
id,
|
|
1245
|
-
versions: versions.map((v) => v.semver)
|
|
1246
|
-
};
|
|
1247
|
-
});
|
|
1248
|
-
this.server.get("/v1/apps/:id/:version", async (request, reply) => {
|
|
1249
|
-
const { id, version } = request.params;
|
|
1250
|
-
const { canonical } = request.query;
|
|
1251
|
-
const oldManifest = this.dataStore.getManifest(id, version);
|
|
1252
|
-
if (!oldManifest) {
|
|
1253
|
-
return reply.code(404).send({ error: "not_found", message: "Manifest not found" });
|
|
1254
|
-
}
|
|
1255
|
-
const v1Manifest = {
|
|
1256
|
-
manifest_version: oldManifest.manifest_version,
|
|
1257
|
-
id: oldManifest.app.app_id,
|
|
1258
|
-
name: oldManifest.app.name,
|
|
1259
|
-
version: oldManifest.version.semver,
|
|
1260
|
-
chains: oldManifest.supported_chains,
|
|
1261
|
-
artifact: oldManifest.artifacts[0] ? {
|
|
1262
|
-
type: oldManifest.artifacts[0].type,
|
|
1263
|
-
target: oldManifest.artifacts[0].target,
|
|
1264
|
-
digest: oldManifest.artifacts[0].cid || `sha256:${"0".repeat(64)}`,
|
|
1265
|
-
uri: oldManifest.artifacts[0].path || oldManifest.artifacts[0].mirrors?.[0] || "https://example.com/artifact"
|
|
1266
|
-
} : {
|
|
1267
|
-
type: "wasm",
|
|
1268
|
-
target: "node",
|
|
1269
|
-
digest: `sha256:${"0".repeat(64)}`,
|
|
1270
|
-
uri: "https://example.com/artifact"
|
|
1271
|
-
},
|
|
1272
|
-
_warnings: []
|
|
1273
|
-
};
|
|
1274
|
-
if (canonical === "true") {
|
|
1275
|
-
const canonicalJCS = JSON.stringify(
|
|
1276
|
-
v1Manifest,
|
|
1277
|
-
Object.keys(v1Manifest).sort()
|
|
1278
|
-
);
|
|
1279
|
-
return {
|
|
1280
|
-
canonical_jcs: Buffer.from(canonicalJCS, "utf8").toString("base64")
|
|
1281
|
-
};
|
|
1131
|
+
this.server.get("/api/v2/bundles", async (request, reply) => {
|
|
1132
|
+
const query = request.query;
|
|
1133
|
+
const { package: pkg, version, developer } = query;
|
|
1134
|
+
if (pkg && version) {
|
|
1135
|
+
const manifest = this.dataStore.getBundleManifest(pkg, version);
|
|
1136
|
+
if (!manifest) {
|
|
1137
|
+
return reply.code(404).send({
|
|
1138
|
+
error: "bundle_not_found",
|
|
1139
|
+
message: `Bundle ${pkg}@${version} not found`
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
return [manifest];
|
|
1282
1143
|
}
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1144
|
+
const allBundles = this.dataStore.getAllBundles();
|
|
1145
|
+
const bundles = [];
|
|
1146
|
+
for (const bundle of allBundles) {
|
|
1147
|
+
if (pkg && bundle.package !== pkg) {
|
|
1148
|
+
continue;
|
|
1149
|
+
}
|
|
1150
|
+
if (developer) {
|
|
1151
|
+
const bundlePubkey = bundle.signature?.pubkey;
|
|
1152
|
+
if (!bundlePubkey || bundlePubkey !== developer) {
|
|
1153
|
+
continue;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
bundles.push(bundle);
|
|
1292
1157
|
}
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
(
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
id: app.id || app.name,
|
|
1299
|
-
versions: [app.latest_version]
|
|
1300
|
-
}));
|
|
1301
|
-
});
|
|
1302
|
-
this.server.post("/v1/resolve", async (request, reply) => {
|
|
1303
|
-
const { root } = request.body;
|
|
1304
|
-
if (!root || !root.id || !root.version) {
|
|
1305
|
-
return reply.code(400).send({
|
|
1306
|
-
error: "bad_request",
|
|
1307
|
-
message: "Root app ID and version are required"
|
|
1158
|
+
bundles.sort((a, b) => {
|
|
1159
|
+
const pkgCompare = a.package.localeCompare(b.package);
|
|
1160
|
+
if (pkgCompare !== 0) return pkgCompare;
|
|
1161
|
+
return b.appVersion.localeCompare(a.appVersion, void 0, {
|
|
1162
|
+
numeric: true
|
|
1308
1163
|
});
|
|
1164
|
+
});
|
|
1165
|
+
if (pkg) {
|
|
1166
|
+
return bundles;
|
|
1167
|
+
}
|
|
1168
|
+
const latestByPackage = /* @__PURE__ */ new Map();
|
|
1169
|
+
for (const bundle of bundles) {
|
|
1170
|
+
const existing = latestByPackage.get(bundle.package);
|
|
1171
|
+
if (!existing || bundle.appVersion > existing.appVersion) {
|
|
1172
|
+
latestByPackage.set(bundle.package, bundle);
|
|
1173
|
+
}
|
|
1309
1174
|
}
|
|
1310
|
-
return
|
|
1311
|
-
plan: [{ action: "install", id: root.id, version: root.version }],
|
|
1312
|
-
satisfies: [],
|
|
1313
|
-
missing: []
|
|
1314
|
-
};
|
|
1175
|
+
return Array.from(latestByPackage.values());
|
|
1315
1176
|
});
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
if (fs3.existsSync(filePath)) {
|
|
1327
|
-
const filename = path.basename(filePath);
|
|
1328
|
-
const appId = manifest.id;
|
|
1329
|
-
try {
|
|
1330
|
-
await this.artifactServer.copyArtifactToLocal(
|
|
1331
|
-
filePath,
|
|
1332
|
-
appId,
|
|
1333
|
-
manifest.version,
|
|
1334
|
-
filename
|
|
1335
|
-
);
|
|
1336
|
-
processedManifest.artifact = {
|
|
1337
|
-
...artifact,
|
|
1338
|
-
uri: this.artifactServer.getArtifactUrl(
|
|
1339
|
-
appId,
|
|
1340
|
-
manifest.version,
|
|
1341
|
-
filename
|
|
1342
|
-
)
|
|
1343
|
-
};
|
|
1344
|
-
} catch (error) {
|
|
1345
|
-
console.warn(`Failed to copy artifact ${filePath}:`, error);
|
|
1346
|
-
}
|
|
1177
|
+
this.server.get(
|
|
1178
|
+
"/api/v2/bundles/:package/:version",
|
|
1179
|
+
async (request, reply) => {
|
|
1180
|
+
const { package: pkg, version } = request.params;
|
|
1181
|
+
const manifest = this.dataStore.getBundleManifest(pkg, version);
|
|
1182
|
+
if (!manifest) {
|
|
1183
|
+
return reply.code(404).send({
|
|
1184
|
+
error: "manifest_not_found",
|
|
1185
|
+
message: `Manifest not found for ${pkg}@${version}`
|
|
1186
|
+
});
|
|
1347
1187
|
}
|
|
1188
|
+
return manifest;
|
|
1348
1189
|
}
|
|
1349
|
-
|
|
1350
|
-
return processedManifest;
|
|
1190
|
+
);
|
|
1351
1191
|
}
|
|
1352
1192
|
};
|
|
1353
1193
|
var localCommand = new Command("local").description(
|
|
1354
1194
|
"Manage local registry for development"
|
|
1355
1195
|
);
|
|
1356
1196
|
localCommand.addCommand(
|
|
1357
|
-
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", "
|
|
1197
|
+
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", "0.0.0.0").option(
|
|
1198
|
+
"--public-host <host>",
|
|
1199
|
+
"Public host exposed in manifest artifact URLs",
|
|
1200
|
+
"host.docker.internal"
|
|
1201
|
+
).action(async (options) => {
|
|
1358
1202
|
const spinner2 = ora6("Starting local registry...").start();
|
|
1359
1203
|
try {
|
|
1360
1204
|
const config = new LocalConfig();
|
|
1205
|
+
config.setHost(options.host);
|
|
1206
|
+
config.setPublicHost(options.publicHost);
|
|
1207
|
+
config.setPort(parseInt(options.port, 10));
|
|
1361
1208
|
const server = new LocalRegistryServer(config);
|
|
1362
1209
|
await server.start(parseInt(options.port));
|
|
1363
1210
|
spinner2.succeed(
|
|
1364
|
-
`Local registry started on http://${
|
|
1211
|
+
`Local registry started on http://${config.getHost()}:${config.getPort()}`
|
|
1365
1212
|
);
|
|
1366
|
-
console.log(
|
|
1213
|
+
console.log(chalk6.blue("\n\u{1F4F1} Local Registry Status:"));
|
|
1367
1214
|
console.log(
|
|
1368
|
-
|
|
1215
|
+
chalk6.green(
|
|
1216
|
+
`\u2705 Server: http://${config.getHost()}:${config.getPort()}`
|
|
1217
|
+
)
|
|
1369
1218
|
);
|
|
1370
|
-
console.log(chalk5.green(`\u{1F4C1} Data: ${config.getDataDir()}`));
|
|
1371
1219
|
console.log(
|
|
1372
|
-
|
|
1373
|
-
`\u{
|
|
1220
|
+
chalk6.green(
|
|
1221
|
+
`\u{1F310} Public URL: http://${config.getPublicHost()}:${config.getPort()}`
|
|
1374
1222
|
)
|
|
1375
1223
|
);
|
|
1224
|
+
console.log(chalk6.green(`\u{1F4C1} Data: ${config.getDataDir()}`));
|
|
1376
1225
|
console.log(
|
|
1377
|
-
|
|
1226
|
+
chalk6.green(
|
|
1227
|
+
`\u{1F4CB} Health: http://${config.getHost()}:${config.getPort()}/healthz`
|
|
1228
|
+
)
|
|
1378
1229
|
);
|
|
1379
1230
|
console.log(
|
|
1380
|
-
|
|
1231
|
+
chalk6.green(
|
|
1232
|
+
`\u{1F4CA} Stats: http://${config.getHost()}:${config.getPort()}/stats`
|
|
1233
|
+
)
|
|
1234
|
+
);
|
|
1235
|
+
console.log(
|
|
1236
|
+
chalk6.blue(
|
|
1381
1237
|
"\n\u{1F4A1} Use --local flag with other commands to use local registry"
|
|
1382
1238
|
)
|
|
1383
1239
|
);
|
|
1384
1240
|
} catch (error) {
|
|
1385
1241
|
spinner2.fail("Failed to start local registry");
|
|
1386
1242
|
if (error instanceof Error) {
|
|
1387
|
-
console.error(
|
|
1243
|
+
console.error(chalk6.red(`Error: ${error.message}`));
|
|
1388
1244
|
}
|
|
1389
1245
|
process.exit(1);
|
|
1390
1246
|
}
|
|
@@ -1401,7 +1257,7 @@ localCommand.addCommand(
|
|
|
1401
1257
|
} catch (error) {
|
|
1402
1258
|
spinner2.fail("Failed to stop local registry");
|
|
1403
1259
|
if (error instanceof Error) {
|
|
1404
|
-
console.error(
|
|
1260
|
+
console.error(chalk6.red(`Error: ${error.message}`));
|
|
1405
1261
|
}
|
|
1406
1262
|
process.exit(1);
|
|
1407
1263
|
}
|
|
@@ -1416,16 +1272,16 @@ localCommand.addCommand(
|
|
|
1416
1272
|
const status = await server.getStatus();
|
|
1417
1273
|
if (status.running) {
|
|
1418
1274
|
spinner2.succeed("Local registry is running");
|
|
1419
|
-
console.log(
|
|
1420
|
-
console.log(
|
|
1421
|
-
console.log(
|
|
1275
|
+
console.log(chalk6.green(`\u2705 Server: ${status.url}`));
|
|
1276
|
+
console.log(chalk6.green(`\u{1F4C1} Data: ${status.dataDir}`));
|
|
1277
|
+
console.log(chalk6.green(`\u{1F4CA} Apps: ${status.appsCount} applications`));
|
|
1422
1278
|
console.log(
|
|
1423
|
-
|
|
1279
|
+
chalk6.green(`\u{1F4E6} Artifacts: ${status.artifactsCount} artifacts`)
|
|
1424
1280
|
);
|
|
1425
1281
|
} else {
|
|
1426
1282
|
spinner2.warn("Local registry is not running");
|
|
1427
1283
|
console.log(
|
|
1428
|
-
|
|
1284
|
+
chalk6.yellow(
|
|
1429
1285
|
'\u{1F4A1} Run "calimero-registry local start" to start the local registry'
|
|
1430
1286
|
)
|
|
1431
1287
|
);
|
|
@@ -1433,7 +1289,7 @@ localCommand.addCommand(
|
|
|
1433
1289
|
} catch (error) {
|
|
1434
1290
|
spinner2.fail("Failed to check local registry status");
|
|
1435
1291
|
if (error instanceof Error) {
|
|
1436
|
-
console.error(
|
|
1292
|
+
console.error(chalk6.red(`Error: ${error.message}`));
|
|
1437
1293
|
}
|
|
1438
1294
|
process.exit(1);
|
|
1439
1295
|
}
|
|
@@ -1443,9 +1299,9 @@ localCommand.addCommand(
|
|
|
1443
1299
|
new Command("reset").description("Reset local registry data").option("-f, --force", "Force reset without confirmation").action(async (options) => {
|
|
1444
1300
|
if (!options.force) {
|
|
1445
1301
|
console.log(
|
|
1446
|
-
|
|
1302
|
+
chalk6.yellow("\u26A0\uFE0F This will delete all local registry data!")
|
|
1447
1303
|
);
|
|
1448
|
-
console.log(
|
|
1304
|
+
console.log(chalk6.yellow(" Use --force flag to confirm"));
|
|
1449
1305
|
return;
|
|
1450
1306
|
}
|
|
1451
1307
|
const spinner2 = ora6("Resetting local registry data...").start();
|
|
@@ -1454,11 +1310,11 @@ localCommand.addCommand(
|
|
|
1454
1310
|
const server = new LocalRegistryServer(config);
|
|
1455
1311
|
await server.reset();
|
|
1456
1312
|
spinner2.succeed("Local registry data reset");
|
|
1457
|
-
console.log(
|
|
1313
|
+
console.log(chalk6.green("\u2705 All local data has been cleared"));
|
|
1458
1314
|
} catch (error) {
|
|
1459
1315
|
spinner2.fail("Failed to reset local registry data");
|
|
1460
1316
|
if (error instanceof Error) {
|
|
1461
|
-
console.error(
|
|
1317
|
+
console.error(chalk6.red(`Error: ${error.message}`));
|
|
1462
1318
|
}
|
|
1463
1319
|
process.exit(1);
|
|
1464
1320
|
}
|
|
@@ -1472,11 +1328,11 @@ localCommand.addCommand(
|
|
|
1472
1328
|
const server = new LocalRegistryServer(config);
|
|
1473
1329
|
const backupPath = await server.backup(options.output);
|
|
1474
1330
|
spinner2.succeed("Backup created successfully");
|
|
1475
|
-
console.log(
|
|
1331
|
+
console.log(chalk6.green(`\u{1F4E6} Backup saved to: ${backupPath}`));
|
|
1476
1332
|
} catch (error) {
|
|
1477
1333
|
spinner2.fail("Failed to create backup");
|
|
1478
1334
|
if (error instanceof Error) {
|
|
1479
|
-
console.error(
|
|
1335
|
+
console.error(chalk6.red(`Error: ${error.message}`));
|
|
1480
1336
|
}
|
|
1481
1337
|
process.exit(1);
|
|
1482
1338
|
}
|
|
@@ -1488,18 +1344,18 @@ localCommand.addCommand(
|
|
|
1488
1344
|
try {
|
|
1489
1345
|
if (!fs3.existsSync(backupFile)) {
|
|
1490
1346
|
spinner2.fail("Backup file not found");
|
|
1491
|
-
console.error(
|
|
1347
|
+
console.error(chalk6.red(`File not found: ${backupFile}`));
|
|
1492
1348
|
process.exit(1);
|
|
1493
1349
|
}
|
|
1494
1350
|
const config = new LocalConfig();
|
|
1495
1351
|
const server = new LocalRegistryServer(config);
|
|
1496
1352
|
await server.restore(backupFile);
|
|
1497
1353
|
spinner2.succeed("Data restored successfully");
|
|
1498
|
-
console.log(
|
|
1354
|
+
console.log(chalk6.green(`\u2705 Restored from: ${backupFile}`));
|
|
1499
1355
|
} catch (error) {
|
|
1500
1356
|
spinner2.fail("Failed to restore from backup");
|
|
1501
1357
|
if (error instanceof Error) {
|
|
1502
|
-
console.error(
|
|
1358
|
+
console.error(chalk6.red(`Error: ${error.message}`));
|
|
1503
1359
|
}
|
|
1504
1360
|
process.exit(1);
|
|
1505
1361
|
}
|
|
@@ -1514,277 +1370,729 @@ localCommand.addCommand(
|
|
|
1514
1370
|
await server.seed();
|
|
1515
1371
|
spinner2.succeed("Sample data seeded successfully");
|
|
1516
1372
|
console.log(
|
|
1517
|
-
|
|
1373
|
+
chalk6.green("\u2705 Local registry populated with sample applications")
|
|
1518
1374
|
);
|
|
1519
1375
|
console.log(
|
|
1520
|
-
|
|
1376
|
+
chalk6.blue(
|
|
1521
1377
|
'\u{1F4A1} Run "calimero-registry apps list --local" to see the sample apps'
|
|
1522
1378
|
)
|
|
1523
1379
|
);
|
|
1524
1380
|
} catch (error) {
|
|
1525
1381
|
spinner2.fail("Failed to seed sample data");
|
|
1526
1382
|
if (error instanceof Error) {
|
|
1527
|
-
console.error(
|
|
1383
|
+
console.error(chalk6.red(`Error: ${error.message}`));
|
|
1528
1384
|
}
|
|
1529
1385
|
process.exit(1);
|
|
1530
1386
|
}
|
|
1531
1387
|
})
|
|
1532
1388
|
);
|
|
1533
|
-
var
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1389
|
+
var RemoteConfig = class {
|
|
1390
|
+
constructor() {
|
|
1391
|
+
this.configPath = path7.join(
|
|
1392
|
+
os.homedir(),
|
|
1393
|
+
".calimero-registry",
|
|
1394
|
+
"remote-config.json"
|
|
1395
|
+
);
|
|
1396
|
+
this.config = this.loadConfig();
|
|
1397
|
+
}
|
|
1398
|
+
loadConfig() {
|
|
1399
|
+
const defaultConfig = {
|
|
1400
|
+
registry: {
|
|
1401
|
+
url: "https://apps.calimero.network"
|
|
1402
|
+
}
|
|
1403
|
+
};
|
|
1404
|
+
if (fs3.existsSync(this.configPath)) {
|
|
1537
1405
|
try {
|
|
1538
|
-
const
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1406
|
+
const existingConfig = JSON.parse(
|
|
1407
|
+
fs3.readFileSync(this.configPath, "utf8")
|
|
1408
|
+
);
|
|
1409
|
+
return {
|
|
1410
|
+
registry: {
|
|
1411
|
+
...defaultConfig.registry,
|
|
1412
|
+
...existingConfig.registry || {}
|
|
1413
|
+
}
|
|
1414
|
+
};
|
|
1415
|
+
} catch {
|
|
1416
|
+
console.warn("Failed to load existing config, using defaults");
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
return defaultConfig;
|
|
1420
|
+
}
|
|
1421
|
+
saveConfig() {
|
|
1422
|
+
const configDir = path7.dirname(this.configPath);
|
|
1423
|
+
if (!fs3.existsSync(configDir)) {
|
|
1424
|
+
fs3.mkdirSync(configDir, { recursive: true });
|
|
1425
|
+
}
|
|
1426
|
+
const configToSave = {
|
|
1427
|
+
registry: {
|
|
1428
|
+
url: this.config.registry.url,
|
|
1429
|
+
// Save API key if it exists in config (regardless of env var)
|
|
1430
|
+
...this.config.registry.apiKey ? { apiKey: this.config.registry.apiKey } : {}
|
|
1431
|
+
}
|
|
1432
|
+
};
|
|
1433
|
+
fs3.writeFileSync(this.configPath, JSON.stringify(configToSave, null, 2));
|
|
1434
|
+
}
|
|
1435
|
+
/**
|
|
1436
|
+
* Get registry URL with priority:
|
|
1437
|
+
* 1. Environment variable CALIMERO_REGISTRY_URL
|
|
1438
|
+
* 2. Config file value
|
|
1439
|
+
* 3. Default value
|
|
1440
|
+
*/
|
|
1441
|
+
getRegistryUrl() {
|
|
1442
|
+
return process.env.CALIMERO_REGISTRY_URL || this.config.registry.url || "https://apps.calimero.network";
|
|
1443
|
+
}
|
|
1444
|
+
/**
|
|
1445
|
+
* Set registry URL
|
|
1446
|
+
*/
|
|
1447
|
+
setRegistryUrl(url) {
|
|
1448
|
+
this.config.registry.url = url;
|
|
1449
|
+
this.saveConfig();
|
|
1450
|
+
}
|
|
1451
|
+
/**
|
|
1452
|
+
* Get API key with priority:
|
|
1453
|
+
* 1. Environment variable CALIMERO_API_KEY
|
|
1454
|
+
* 2. Config file value
|
|
1455
|
+
* 3. undefined
|
|
1456
|
+
*/
|
|
1457
|
+
getApiKey() {
|
|
1458
|
+
return process.env.CALIMERO_API_KEY || this.config.registry.apiKey;
|
|
1459
|
+
}
|
|
1460
|
+
/**
|
|
1461
|
+
* Set API key (stored in config file)
|
|
1462
|
+
*/
|
|
1463
|
+
setApiKey(apiKey) {
|
|
1464
|
+
this.config.registry.apiKey = apiKey;
|
|
1465
|
+
this.saveConfig();
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1468
|
+
* Remove API key from config
|
|
1469
|
+
*/
|
|
1470
|
+
removeApiKey() {
|
|
1471
|
+
delete this.config.registry.apiKey;
|
|
1472
|
+
this.saveConfig();
|
|
1473
|
+
}
|
|
1474
|
+
/**
|
|
1475
|
+
* Get full configuration
|
|
1476
|
+
*/
|
|
1477
|
+
getConfig() {
|
|
1478
|
+
return {
|
|
1479
|
+
registry: {
|
|
1480
|
+
url: this.getRegistryUrl(),
|
|
1481
|
+
// Don't expose API key in getConfig (security)
|
|
1482
|
+
apiKey: this.getApiKey() ? "***" : void 0
|
|
1483
|
+
}
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
/**
|
|
1487
|
+
* Get config file path
|
|
1488
|
+
*/
|
|
1489
|
+
getConfigPath() {
|
|
1490
|
+
return this.configPath;
|
|
1491
|
+
}
|
|
1492
|
+
/**
|
|
1493
|
+
* Reset to defaults
|
|
1494
|
+
*/
|
|
1495
|
+
reset() {
|
|
1496
|
+
this.config = {
|
|
1497
|
+
registry: {
|
|
1498
|
+
url: "https://apps.calimero.network"
|
|
1499
|
+
}
|
|
1500
|
+
};
|
|
1501
|
+
this.saveConfig();
|
|
1502
|
+
}
|
|
1503
|
+
};
|
|
1504
|
+
|
|
1505
|
+
// src/commands/bundle.ts
|
|
1506
|
+
var bundleCommand = new Command("bundle").description("Manage application bundles (V2)").addCommand(createCreateCommand()).addCommand(createPushCommand()).addCommand(createGetCommand());
|
|
1507
|
+
function createCreateCommand() {
|
|
1508
|
+
return new Command("create").description("Create an MPK bundle from a WASM file").argument("<wasm-file>", "Path to the WASM file").argument("<package>", "Package name (e.g. com.calimero.myapp)").argument("<version>", "Version (e.g. 1.0.0)").option("-o, --output <path>", "Output path for the MPK file").option("--name <name>", "Application name").option("--description <description>", "Application description").option("--author <author>", "Application author", "Calimero Team").option("--frontend <url>", "Frontend URL").option("--github <url>", "GitHub URL").option("--docs <url>", "Documentation URL").option(
|
|
1509
|
+
"--export <interface>",
|
|
1510
|
+
"Export interface (can be specified multiple times)",
|
|
1511
|
+
(value, prev) => {
|
|
1512
|
+
return [...prev || [], value];
|
|
1513
|
+
}
|
|
1514
|
+
).option(
|
|
1515
|
+
"--use <interface>",
|
|
1516
|
+
"Use interface (can be specified multiple times)",
|
|
1517
|
+
(value, prev) => {
|
|
1518
|
+
return [...prev || [], value];
|
|
1519
|
+
}
|
|
1520
|
+
).action(async (wasmFile, pkg, version, options) => {
|
|
1521
|
+
try {
|
|
1522
|
+
const wasmPath = path7.resolve(wasmFile);
|
|
1523
|
+
if (!fs3.existsSync(wasmPath)) {
|
|
1524
|
+
console.error(`\u274C WASM file not found: ${wasmFile}`);
|
|
1525
|
+
process.exit(1);
|
|
1526
|
+
}
|
|
1527
|
+
console.log(`\u{1F4E6} Creating MPK bundle from: ${path7.basename(wasmPath)}`);
|
|
1528
|
+
const wasmContent = fs3.readFileSync(wasmPath);
|
|
1529
|
+
const wasmSize = wasmContent.length;
|
|
1530
|
+
const hash = crypto.createHash("sha256").update(wasmContent).digest("hex");
|
|
1531
|
+
const metadata = {
|
|
1532
|
+
name: options.name || pkg,
|
|
1533
|
+
description: options.description || "",
|
|
1534
|
+
author: options.author || "Calimero Team"
|
|
1535
|
+
};
|
|
1536
|
+
const links = {};
|
|
1537
|
+
if (options.frontend) links.frontend = options.frontend;
|
|
1538
|
+
if (options.github) links.github = options.github;
|
|
1539
|
+
if (options.docs) links.docs = options.docs;
|
|
1540
|
+
const interfaces = {
|
|
1541
|
+
exports: options.export || [],
|
|
1542
|
+
uses: options.use || []
|
|
1543
|
+
};
|
|
1544
|
+
const manifest = {
|
|
1545
|
+
version: "1.0",
|
|
1546
|
+
package: pkg,
|
|
1547
|
+
appVersion: version,
|
|
1548
|
+
metadata: Object.keys(metadata).length > 0 ? metadata : void 0,
|
|
1549
|
+
interfaces: interfaces.exports.length > 0 || interfaces.uses.length > 0 ? interfaces : void 0,
|
|
1550
|
+
wasm: {
|
|
1551
|
+
path: "app.wasm",
|
|
1552
|
+
hash,
|
|
1553
|
+
size: wasmSize
|
|
1554
|
+
},
|
|
1555
|
+
abi: null,
|
|
1556
|
+
migrations: [],
|
|
1557
|
+
links: Object.keys(links).length > 0 ? links : void 0,
|
|
1558
|
+
signature: void 0
|
|
1559
|
+
};
|
|
1560
|
+
let outputPath = options.output;
|
|
1561
|
+
if (!outputPath) {
|
|
1562
|
+
const outputDir = path7.join(process.cwd(), pkg, version);
|
|
1563
|
+
if (!fs3.existsSync(outputDir)) {
|
|
1564
|
+
fs3.mkdirSync(outputDir, { recursive: true });
|
|
1543
1565
|
}
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1566
|
+
outputPath = path7.join(outputDir, `${pkg}-${version}.mpk`);
|
|
1567
|
+
} else {
|
|
1568
|
+
outputPath = path7.resolve(outputPath);
|
|
1569
|
+
const outputDir = path7.dirname(outputPath);
|
|
1570
|
+
if (!fs3.existsSync(outputDir)) {
|
|
1571
|
+
fs3.mkdirSync(outputDir, { recursive: true });
|
|
1549
1572
|
}
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1573
|
+
}
|
|
1574
|
+
const tempDir = path7.join(
|
|
1575
|
+
path7.dirname(outputPath),
|
|
1576
|
+
`.temp-bundle-${Date.now()}`
|
|
1577
|
+
);
|
|
1578
|
+
fs3.mkdirSync(tempDir, { recursive: true });
|
|
1579
|
+
try {
|
|
1580
|
+
fs3.writeFileSync(
|
|
1581
|
+
path7.join(tempDir, "manifest.json"),
|
|
1582
|
+
JSON.stringify(manifest, null, 2)
|
|
1583
|
+
);
|
|
1584
|
+
fs3.writeFileSync(path7.join(tempDir, "app.wasm"), wasmContent);
|
|
1585
|
+
await tar.create(
|
|
1586
|
+
{
|
|
1587
|
+
gzip: true,
|
|
1588
|
+
file: outputPath,
|
|
1589
|
+
cwd: tempDir
|
|
1590
|
+
},
|
|
1591
|
+
["manifest.json", "app.wasm"]
|
|
1592
|
+
);
|
|
1593
|
+
const outputSize = fs3.statSync(outputPath).size;
|
|
1594
|
+
console.log(`\u2705 Created MPK bundle: ${outputPath}`);
|
|
1595
|
+
console.log(` Package: ${pkg}`);
|
|
1596
|
+
console.log(` Version: ${version}`);
|
|
1597
|
+
console.log(` Size: ${outputSize} bytes`);
|
|
1598
|
+
console.log(` WASM Hash: ${hash}`);
|
|
1599
|
+
} finally {
|
|
1600
|
+
if (fs3.existsSync(tempDir)) {
|
|
1601
|
+
fs3.rmSync(tempDir, { recursive: true, force: true });
|
|
1553
1602
|
}
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1603
|
+
}
|
|
1604
|
+
} catch (error) {
|
|
1605
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1606
|
+
console.error("\u274C Failed to create bundle:", message);
|
|
1607
|
+
process.exit(1);
|
|
1608
|
+
}
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
function createPushCommand() {
|
|
1612
|
+
return new Command("push").description(
|
|
1613
|
+
"Push a bundle (.mpk) to the registry (local or remote). Defaults to local registry."
|
|
1614
|
+
).argument("<bundle-file>", "Path to the .mpk bundle file").option("--local", "Push to local registry instance (default)").option("--remote", "Push to remote registry instance").option(
|
|
1615
|
+
"--url <registry-url>",
|
|
1616
|
+
"Registry URL for remote push (overrides config file)"
|
|
1617
|
+
).option(
|
|
1618
|
+
"--api-key <key>",
|
|
1619
|
+
"API key for authentication (overrides config file and env var)"
|
|
1620
|
+
).addHelpText(
|
|
1621
|
+
"after",
|
|
1622
|
+
`
|
|
1623
|
+
Examples:
|
|
1624
|
+
$ calimero-registry bundle push bundle.mpk --local
|
|
1625
|
+
$ calimero-registry bundle push bundle.mpk --remote
|
|
1626
|
+
$ calimero-registry bundle push bundle.mpk --remote --url https://apps.calimero.network
|
|
1627
|
+
$ calimero-registry bundle push bundle.mpk --remote --api-key your-api-key
|
|
1628
|
+
|
|
1629
|
+
Configuration:
|
|
1630
|
+
Set defaults using the config command:
|
|
1631
|
+
$ calimero-registry config set registry-url https://apps.calimero.network
|
|
1632
|
+
$ calimero-registry config set api-key your-api-key
|
|
1633
|
+
|
|
1634
|
+
Or use environment variables:
|
|
1635
|
+
$ export CALIMERO_REGISTRY_URL=https://apps.calimero.network
|
|
1636
|
+
$ export CALIMERO_API_KEY=your-api-key
|
|
1637
|
+
|
|
1638
|
+
Note:
|
|
1639
|
+
- Use --local for development/testing with local registry
|
|
1640
|
+
- Use --remote for production deployments
|
|
1641
|
+
- Config file values are used unless overridden by flags or environment variables
|
|
1642
|
+
- Priority: flag > environment variable > config file > default
|
|
1643
|
+
`
|
|
1644
|
+
).action(async (bundleFile, options) => {
|
|
1645
|
+
try {
|
|
1646
|
+
if (!options.remote && (options.url || options.apiKey)) {
|
|
1647
|
+
console.warn(
|
|
1648
|
+
"\u26A0\uFE0F Warning: --url and --api-key are only used with --remote flag"
|
|
1557
1649
|
);
|
|
1558
|
-
console.
|
|
1559
|
-
|
|
1560
|
-
console.log(
|
|
1561
|
-
` Provides: ${manifest.provides?.join(", ") || "none"}`
|
|
1650
|
+
console.warn(
|
|
1651
|
+
" These options will be ignored. Use --remote to push to remote registry."
|
|
1562
1652
|
);
|
|
1563
|
-
|
|
1564
|
-
|
|
1653
|
+
}
|
|
1654
|
+
const useLocal = options.local === false ? false : options.local === true ? true : !options.remote;
|
|
1655
|
+
const useRemote = options.remote === true;
|
|
1656
|
+
if (options.local && options.remote) {
|
|
1657
|
+
console.error("\u274C Cannot use both --local and --remote flags");
|
|
1658
|
+
process.exit(1);
|
|
1659
|
+
}
|
|
1660
|
+
if (!useLocal && !useRemote) {
|
|
1661
|
+
console.error(
|
|
1662
|
+
"\u274C No push mode specified. Use --local or --remote flag"
|
|
1565
1663
|
);
|
|
1566
|
-
const response = await fetch(`${baseUrl}/v1/apps`, {
|
|
1567
|
-
method: "POST",
|
|
1568
|
-
headers: { "Content-Type": "application/json" },
|
|
1569
|
-
body: JSON.stringify(manifest)
|
|
1570
|
-
});
|
|
1571
|
-
if (!response.ok) {
|
|
1572
|
-
const error = await response.json();
|
|
1573
|
-
throw new Error(`${error.error}: ${error.details}`);
|
|
1574
|
-
}
|
|
1575
|
-
const result = await response.json();
|
|
1576
|
-
console.log("\u2705 Manifest submitted successfully!");
|
|
1577
|
-
console.log(` ID: ${result.id}`);
|
|
1578
|
-
console.log(` Version: ${result.version}`);
|
|
1579
|
-
console.log(` URI: ${result.canonical_uri}`);
|
|
1580
|
-
} catch (error) {
|
|
1581
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1582
|
-
console.error("\u274C Failed to submit manifest:", errorMessage);
|
|
1583
1664
|
process.exit(1);
|
|
1584
1665
|
}
|
|
1666
|
+
const fullPath = path7.resolve(bundleFile);
|
|
1667
|
+
if (!fs3.existsSync(fullPath)) {
|
|
1668
|
+
console.error(`\u274C File not found: ${bundleFile}`);
|
|
1669
|
+
process.exit(1);
|
|
1670
|
+
}
|
|
1671
|
+
console.log(`\u{1F4E6} Processing bundle: ${path7.basename(fullPath)}`);
|
|
1672
|
+
const manifest = await extractManifest(fullPath);
|
|
1673
|
+
if (!manifest) {
|
|
1674
|
+
console.error("\u274C manifest.json not found in bundle");
|
|
1675
|
+
process.exit(1);
|
|
1676
|
+
}
|
|
1677
|
+
if (!manifest.package || !manifest.appVersion) {
|
|
1678
|
+
console.error("\u274C Invalid manifest: missing package or appVersion");
|
|
1679
|
+
process.exit(1);
|
|
1680
|
+
}
|
|
1681
|
+
console.log(` Package: ${manifest.package}`);
|
|
1682
|
+
console.log(` Version: ${manifest.appVersion}`);
|
|
1683
|
+
if (manifest.metadata) {
|
|
1684
|
+
console.log(` Name: ${manifest.metadata.name}`);
|
|
1685
|
+
}
|
|
1686
|
+
if (useLocal) {
|
|
1687
|
+
const config = new LocalConfig();
|
|
1688
|
+
const store = new LocalDataStore(config);
|
|
1689
|
+
const artifactServer = new LocalArtifactServer(config, store);
|
|
1690
|
+
const bundleFilename = `${manifest.package}-${manifest.appVersion}.mpk`;
|
|
1691
|
+
const targetPath = await artifactServer.copyArtifactToLocal(
|
|
1692
|
+
fullPath,
|
|
1693
|
+
manifest.package,
|
|
1694
|
+
manifest.appVersion,
|
|
1695
|
+
bundleFilename
|
|
1696
|
+
);
|
|
1697
|
+
console.log(` Artifact stored: ${targetPath}`);
|
|
1698
|
+
store.setBundleManifest(
|
|
1699
|
+
manifest.package,
|
|
1700
|
+
manifest.appVersion,
|
|
1701
|
+
manifest
|
|
1702
|
+
);
|
|
1703
|
+
console.log("\u2705 Bundle manifest registered locally");
|
|
1704
|
+
console.log(
|
|
1705
|
+
` Run 'calimero-registry bundle get ${manifest.package} ${manifest.appVersion}' to verify`
|
|
1706
|
+
);
|
|
1707
|
+
} else if (useRemote) {
|
|
1708
|
+
const remoteConfig = new RemoteConfig();
|
|
1709
|
+
const registryUrl = options.url || process.env.CALIMERO_REGISTRY_URL || remoteConfig.getRegistryUrl();
|
|
1710
|
+
const apiKey = options.apiKey || process.env.CALIMERO_API_KEY || remoteConfig.getApiKey();
|
|
1711
|
+
await pushToRemote(fullPath, manifest, registryUrl, apiKey);
|
|
1712
|
+
}
|
|
1713
|
+
} catch (error) {
|
|
1714
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1715
|
+
console.error("\u274C Failed to push bundle:", message);
|
|
1716
|
+
process.exit(1);
|
|
1585
1717
|
}
|
|
1586
|
-
);
|
|
1718
|
+
});
|
|
1587
1719
|
}
|
|
1588
1720
|
function createGetCommand() {
|
|
1589
|
-
return new Command("get").description("Get manifest
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
const
|
|
1593
|
-
const
|
|
1594
|
-
const
|
|
1595
|
-
if (
|
|
1596
|
-
console.log(`\u{1F4E5} Getting manifest: ${appId}@${version}`);
|
|
1597
|
-
const url = options.canonical ? `/v1/apps/${appId}/${version}?canonical=true` : `/v1/apps/${appId}/${version}`;
|
|
1598
|
-
const response = await fetch(`${baseUrl}${url}`);
|
|
1599
|
-
if (!response.ok) {
|
|
1600
|
-
if (response.status === 404) {
|
|
1601
|
-
console.error(`\u274C Manifest not found: ${appId}@${version}`);
|
|
1602
|
-
} else {
|
|
1603
|
-
console.error(
|
|
1604
|
-
`\u274C Error: ${response.status} ${response.statusText}`
|
|
1605
|
-
);
|
|
1606
|
-
}
|
|
1607
|
-
process.exit(1);
|
|
1608
|
-
}
|
|
1609
|
-
const manifest = await response.json();
|
|
1721
|
+
return new Command("get").description("Get bundle manifest").argument("<package>", "Package name (e.g. com.calimero.kv)").argument("<version>", "Version (e.g. 1.0.0)").option("--local", "Use local registry", true).action(async (pkg, version, options) => {
|
|
1722
|
+
try {
|
|
1723
|
+
if (options.local) {
|
|
1724
|
+
const config = new LocalConfig();
|
|
1725
|
+
const store = new LocalDataStore(config);
|
|
1726
|
+
const manifest = store.getBundleManifest(pkg, version);
|
|
1727
|
+
if (manifest) {
|
|
1610
1728
|
console.log(JSON.stringify(manifest, null, 2));
|
|
1611
1729
|
} else {
|
|
1612
|
-
console.
|
|
1613
|
-
|
|
1614
|
-
if (!response.ok) {
|
|
1615
|
-
if (response.status === 404) {
|
|
1616
|
-
console.error(`\u274C App not found: ${appId}`);
|
|
1617
|
-
} else {
|
|
1618
|
-
console.error(
|
|
1619
|
-
`\u274C Error: ${response.status} ${response.statusText}`
|
|
1620
|
-
);
|
|
1621
|
-
}
|
|
1622
|
-
process.exit(1);
|
|
1623
|
-
}
|
|
1624
|
-
const result = await response.json();
|
|
1625
|
-
console.log(`\u{1F4F1} App: ${result.id}`);
|
|
1626
|
-
console.log(`\u{1F4CB} Versions: ${result.versions.join(", ")}`);
|
|
1730
|
+
console.error(`\u274C Manifest not found: ${pkg}@${version}`);
|
|
1731
|
+
process.exit(1);
|
|
1627
1732
|
}
|
|
1628
|
-
}
|
|
1629
|
-
|
|
1630
|
-
console.error("\u274C Failed to get manifest:", errorMessage);
|
|
1733
|
+
} else {
|
|
1734
|
+
console.error("\u274C Remote get not implemented yet");
|
|
1631
1735
|
process.exit(1);
|
|
1632
1736
|
}
|
|
1737
|
+
} catch (error) {
|
|
1738
|
+
console.error("\u274C Error:", error);
|
|
1739
|
+
process.exit(1);
|
|
1633
1740
|
}
|
|
1634
|
-
);
|
|
1741
|
+
});
|
|
1635
1742
|
}
|
|
1636
|
-
function
|
|
1637
|
-
|
|
1638
|
-
|
|
1743
|
+
async function pushToRemote(bundlePath, manifest, registryUrl, apiKey) {
|
|
1744
|
+
try {
|
|
1745
|
+
console.log(`\u{1F4E4} Pushing to remote registry: ${registryUrl}`);
|
|
1746
|
+
const bundleBuffer = fs3.readFileSync(bundlePath);
|
|
1747
|
+
const bundleSize = bundleBuffer.length;
|
|
1748
|
+
console.log(` Bundle size: ${bundleSize} bytes`);
|
|
1749
|
+
const bundleHex = bundleBuffer.toString("hex");
|
|
1750
|
+
console.log(` Converted to hex (${bundleHex.length} characters)`);
|
|
1751
|
+
const payload = {
|
|
1752
|
+
version: manifest.version || "1.0",
|
|
1753
|
+
package: manifest.package,
|
|
1754
|
+
appVersion: manifest.appVersion,
|
|
1755
|
+
_binary: bundleHex,
|
|
1756
|
+
_overwrite: true
|
|
1757
|
+
};
|
|
1758
|
+
if (manifest.metadata) {
|
|
1759
|
+
payload.metadata = manifest.metadata;
|
|
1760
|
+
}
|
|
1761
|
+
if (manifest.interfaces) {
|
|
1762
|
+
payload.interfaces = manifest.interfaces;
|
|
1763
|
+
}
|
|
1764
|
+
if (manifest.wasm) {
|
|
1765
|
+
payload.wasm = manifest.wasm;
|
|
1766
|
+
}
|
|
1767
|
+
if (manifest.abi) {
|
|
1768
|
+
payload.abi = manifest.abi;
|
|
1769
|
+
}
|
|
1770
|
+
if (manifest.migrations && manifest.migrations.length > 0) {
|
|
1771
|
+
payload.migrations = manifest.migrations;
|
|
1772
|
+
}
|
|
1773
|
+
if (manifest.links) {
|
|
1774
|
+
payload.links = manifest.links;
|
|
1775
|
+
}
|
|
1776
|
+
if (manifest.signature) {
|
|
1777
|
+
payload.signature = manifest.signature;
|
|
1778
|
+
}
|
|
1779
|
+
const headers = {
|
|
1780
|
+
"Content-Type": "application/json"
|
|
1781
|
+
};
|
|
1782
|
+
const finalApiKey = apiKey || process.env.CALIMERO_API_KEY;
|
|
1783
|
+
if (finalApiKey) {
|
|
1784
|
+
headers["Authorization"] = `Bearer ${finalApiKey}`;
|
|
1785
|
+
}
|
|
1786
|
+
const apiUrl = `${registryUrl.replace(/\/$/, "")}/api/v2/bundles/push`;
|
|
1787
|
+
console.log(` POST ${apiUrl}`);
|
|
1788
|
+
const controller = new AbortController();
|
|
1789
|
+
const timeoutId = setTimeout(() => controller.abort(), 6e4);
|
|
1790
|
+
try {
|
|
1791
|
+
const response = await fetch(apiUrl, {
|
|
1792
|
+
method: "POST",
|
|
1793
|
+
headers,
|
|
1794
|
+
body: JSON.stringify(payload),
|
|
1795
|
+
signal: controller.signal
|
|
1796
|
+
});
|
|
1797
|
+
clearTimeout(timeoutId);
|
|
1798
|
+
const responseText = await response.text();
|
|
1799
|
+
let responseBody;
|
|
1639
1800
|
try {
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1801
|
+
responseBody = JSON.parse(responseText);
|
|
1802
|
+
} catch {
|
|
1803
|
+
responseBody = { message: responseText };
|
|
1804
|
+
}
|
|
1805
|
+
if (response.status === 201) {
|
|
1806
|
+
console.log("\u2705 Bundle manifest uploaded successfully");
|
|
1807
|
+
console.log(` Package: ${responseBody.package || manifest.package}`);
|
|
1808
|
+
console.log(
|
|
1809
|
+
` Version: ${responseBody.version || manifest.appVersion}`
|
|
1810
|
+
);
|
|
1811
|
+
await verifyRemotePush(
|
|
1812
|
+
registryUrl,
|
|
1813
|
+
manifest.package,
|
|
1814
|
+
manifest.appVersion
|
|
1815
|
+
);
|
|
1816
|
+
} else {
|
|
1817
|
+
handlePushError(response.status, responseBody);
|
|
1818
|
+
}
|
|
1819
|
+
} catch (fetchError) {
|
|
1820
|
+
clearTimeout(timeoutId);
|
|
1821
|
+
if (fetchError instanceof Error && fetchError.name === "AbortError") {
|
|
1822
|
+
console.error("\u274C Request timed out after 60 seconds");
|
|
1823
|
+
console.error(" The bundle may be too large or the server is slow");
|
|
1824
|
+
process.exit(1);
|
|
1825
|
+
}
|
|
1826
|
+
throw fetchError;
|
|
1827
|
+
}
|
|
1828
|
+
} catch (error) {
|
|
1829
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1830
|
+
console.error("\u274C Failed to push to remote registry:", message);
|
|
1831
|
+
throw error;
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
async function verifyRemotePush(registryUrl, packageName, version) {
|
|
1835
|
+
try {
|
|
1836
|
+
console.log("\u{1F50D} Verifying upload to remote registry...");
|
|
1837
|
+
const verifyUrl = `${registryUrl.replace(/\/$/, "")}/api/v2/bundles/${packageName}/${version}`;
|
|
1838
|
+
const controller = new AbortController();
|
|
1839
|
+
const timeoutId = setTimeout(() => controller.abort(), 1e4);
|
|
1840
|
+
try {
|
|
1841
|
+
const response = await fetch(verifyUrl, {
|
|
1842
|
+
method: "GET",
|
|
1843
|
+
signal: controller.signal
|
|
1844
|
+
});
|
|
1845
|
+
clearTimeout(timeoutId);
|
|
1846
|
+
if (response.status === 200) {
|
|
1847
|
+
await response.json();
|
|
1848
|
+
console.log("\u2705 Bundle verified and accessible on remote registry");
|
|
1849
|
+
console.log(`\u{1F310} Registry: ${registryUrl}`);
|
|
1850
|
+
console.log(`\u{1F517} Endpoint: ${verifyUrl}`);
|
|
1851
|
+
} else {
|
|
1852
|
+
console.warn(
|
|
1853
|
+
`\u26A0\uFE0F Upload verification failed - bundle not found (HTTP ${response.status})`
|
|
1854
|
+
);
|
|
1855
|
+
console.warn(" The bundle may still be processing, check manually");
|
|
1856
|
+
}
|
|
1857
|
+
} catch (verifyError) {
|
|
1858
|
+
clearTimeout(timeoutId);
|
|
1859
|
+
if (verifyError instanceof Error && verifyError.name === "AbortError") {
|
|
1860
|
+
console.warn("\u26A0\uFE0F Verification request timed out");
|
|
1861
|
+
} else {
|
|
1862
|
+
const message = verifyError instanceof Error ? verifyError.message : String(verifyError);
|
|
1863
|
+
console.warn("\u26A0\uFE0F Verification request failed:", message);
|
|
1864
|
+
}
|
|
1865
|
+
console.warn(" The bundle may still be accessible, check manually");
|
|
1866
|
+
}
|
|
1867
|
+
} catch (error) {
|
|
1868
|
+
console.warn("\u26A0\uFE0F Could not verify bundle:", error);
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
function handlePushError(statusCode, responseBody) {
|
|
1872
|
+
const errorMessage = responseBody.message || responseBody.error || "Unknown error";
|
|
1873
|
+
switch (statusCode) {
|
|
1874
|
+
case 400:
|
|
1875
|
+
console.error("\u274C Bad Request: Invalid manifest");
|
|
1876
|
+
console.error(` ${errorMessage}`);
|
|
1877
|
+
console.error(" Check that your bundle manifest is valid");
|
|
1878
|
+
break;
|
|
1879
|
+
case 401:
|
|
1880
|
+
console.error("\u274C Unauthorized: Authentication required");
|
|
1881
|
+
console.error(` ${errorMessage}`);
|
|
1882
|
+
console.error(
|
|
1883
|
+
" Provide an API key with --api-key or CALIMERO_API_KEY env var"
|
|
1884
|
+
);
|
|
1885
|
+
break;
|
|
1886
|
+
case 403:
|
|
1887
|
+
console.error("\u274C Forbidden: Namespace ownership required");
|
|
1888
|
+
console.error(` ${errorMessage}`);
|
|
1889
|
+
console.error(
|
|
1890
|
+
" Ensure you have permission to publish to this package namespace"
|
|
1891
|
+
);
|
|
1892
|
+
break;
|
|
1893
|
+
case 409:
|
|
1894
|
+
console.error("\u274C Conflict: Version already exists");
|
|
1895
|
+
console.error(` ${errorMessage}`);
|
|
1896
|
+
console.error(
|
|
1897
|
+
" Increment the version number and try again, or contact registry admin"
|
|
1898
|
+
);
|
|
1899
|
+
break;
|
|
1900
|
+
case 500:
|
|
1901
|
+
console.error("\u274C Internal Server Error");
|
|
1902
|
+
console.error(` ${errorMessage}`);
|
|
1903
|
+
console.error(" The registry server encountered an error");
|
|
1904
|
+
break;
|
|
1905
|
+
default:
|
|
1906
|
+
console.error(`\u274C Upload failed with HTTP ${statusCode}`);
|
|
1907
|
+
console.error(` ${errorMessage}`);
|
|
1908
|
+
}
|
|
1909
|
+
process.exit(1);
|
|
1910
|
+
}
|
|
1911
|
+
async function extractManifest(bundlePath) {
|
|
1912
|
+
let manifestContent = null;
|
|
1913
|
+
await tar.t({
|
|
1914
|
+
file: bundlePath,
|
|
1915
|
+
onentry: (entry) => {
|
|
1916
|
+
if (entry.path === "manifest.json") ;
|
|
1917
|
+
}
|
|
1918
|
+
});
|
|
1919
|
+
const extractDir = path7.join(path7.dirname(bundlePath), `.temp-${Date.now()}`);
|
|
1920
|
+
if (!fs3.existsSync(extractDir)) {
|
|
1921
|
+
fs3.mkdirSync(extractDir);
|
|
1922
|
+
}
|
|
1923
|
+
try {
|
|
1924
|
+
await tar.x({
|
|
1925
|
+
file: bundlePath,
|
|
1926
|
+
cwd: extractDir,
|
|
1927
|
+
filter: (path8) => path8 === "manifest.json"
|
|
1928
|
+
});
|
|
1929
|
+
const manifestPath = path7.join(extractDir, "manifest.json");
|
|
1930
|
+
if (fs3.existsSync(manifestPath)) {
|
|
1931
|
+
const content = fs3.readFileSync(manifestPath, "utf8");
|
|
1932
|
+
manifestContent = content;
|
|
1933
|
+
}
|
|
1934
|
+
} catch {
|
|
1935
|
+
} finally {
|
|
1936
|
+
if (fs3.existsSync(extractDir)) {
|
|
1937
|
+
fs3.rmSync(extractDir, { recursive: true, force: true });
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
if (manifestContent) {
|
|
1941
|
+
try {
|
|
1942
|
+
return JSON.parse(manifestContent);
|
|
1943
|
+
} catch {
|
|
1944
|
+
console.error("Failed to parse manifest JSON");
|
|
1945
|
+
return null;
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
return null;
|
|
1949
|
+
}
|
|
1950
|
+
var configCommand = new Command("config").description("Manage CLI configuration for remote registry").addCommand(createConfigSetCommand()).addCommand(createConfigGetCommand()).addCommand(createConfigListCommand()).addCommand(createConfigResetCommand());
|
|
1951
|
+
function createConfigSetCommand() {
|
|
1952
|
+
return new Command("set").description("Set a configuration value").argument("<key>", "Configuration key (registry-url, api-key)").argument("<value>", "Configuration value").action((key, value) => {
|
|
1953
|
+
try {
|
|
1954
|
+
const config = new RemoteConfig();
|
|
1955
|
+
switch (key) {
|
|
1956
|
+
case "registry-url":
|
|
1957
|
+
case "registryUrl":
|
|
1958
|
+
case "url":
|
|
1959
|
+
config.setRegistryUrl(value);
|
|
1960
|
+
console.log(
|
|
1961
|
+
chalk6.green(`\u2705 Registry URL set to: ${chalk6.bold(value)}`)
|
|
1647
1962
|
);
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
}
|
|
1654
|
-
const results = await response.json();
|
|
1655
|
-
if (results.length === 0) {
|
|
1656
|
-
console.log("No results found");
|
|
1657
|
-
return;
|
|
1658
|
-
}
|
|
1659
|
-
console.log(`Found ${results.length} result(s):`);
|
|
1660
|
-
results.forEach((result) => {
|
|
1661
|
-
console.log(
|
|
1662
|
-
` \u{1F4F1} ${result.id}@${result.versions?.[0] || "unknown"}`
|
|
1663
|
-
);
|
|
1664
|
-
if (result.provides?.length > 0) {
|
|
1665
|
-
console.log(` Provides: ${result.provides.join(", ")}`);
|
|
1666
|
-
}
|
|
1667
|
-
if (result.requires?.length > 0) {
|
|
1668
|
-
console.log(` Requires: ${result.requires.join(", ")}`);
|
|
1669
|
-
}
|
|
1670
|
-
});
|
|
1671
|
-
} else {
|
|
1672
|
-
console.log("\u{1F4CB} Listing all applications...");
|
|
1963
|
+
break;
|
|
1964
|
+
case "api-key":
|
|
1965
|
+
case "apiKey":
|
|
1966
|
+
config.setApiKey(value);
|
|
1967
|
+
console.log(chalk6.green("\u2705 API key set successfully"));
|
|
1673
1968
|
console.log(
|
|
1674
|
-
|
|
1969
|
+
chalk6.yellow(
|
|
1970
|
+
"\u{1F4A1} Note: API key is stored in plain text. Consider using CALIMERO_API_KEY environment variable for better security."
|
|
1971
|
+
)
|
|
1675
1972
|
);
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1973
|
+
break;
|
|
1974
|
+
default:
|
|
1975
|
+
console.error(chalk6.red(`\u274C Unknown configuration key: ${key}`));
|
|
1976
|
+
console.log(chalk6.blue("\nAvailable keys:"));
|
|
1977
|
+
console.log(" registry-url - Default registry URL");
|
|
1978
|
+
console.log(" api-key - API key for authentication");
|
|
1979
|
+
process.exit(1);
|
|
1681
1980
|
}
|
|
1981
|
+
} catch (error) {
|
|
1982
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1983
|
+
console.error(chalk6.red("\u274C Failed to set configuration:"), message);
|
|
1984
|
+
process.exit(1);
|
|
1682
1985
|
}
|
|
1683
|
-
);
|
|
1986
|
+
});
|
|
1684
1987
|
}
|
|
1685
|
-
function
|
|
1686
|
-
return new Command("
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
}
|
|
1709
|
-
const result = await response.json();
|
|
1710
|
-
console.log("\u2705 Dependencies resolved successfully!");
|
|
1711
|
-
console.log(`\u{1F4CB} Installation plan:`);
|
|
1712
|
-
result.plan.forEach(
|
|
1713
|
-
(item) => {
|
|
1714
|
-
console.log(` ${item.action}: ${item.id}@${item.version}`);
|
|
1988
|
+
function createConfigGetCommand() {
|
|
1989
|
+
return new Command("get").description("Get a configuration value").argument("<key>", "Configuration key (registry-url, api-key)").action((key) => {
|
|
1990
|
+
try {
|
|
1991
|
+
const config = new RemoteConfig();
|
|
1992
|
+
switch (key) {
|
|
1993
|
+
case "registry-url":
|
|
1994
|
+
case "registryUrl":
|
|
1995
|
+
case "url":
|
|
1996
|
+
console.log(config.getRegistryUrl());
|
|
1997
|
+
break;
|
|
1998
|
+
case "api-key":
|
|
1999
|
+
case "apiKey": {
|
|
2000
|
+
const apiKey = config.getApiKey();
|
|
2001
|
+
if (apiKey) {
|
|
2002
|
+
const masked = apiKey.length > 8 ? `${apiKey.substring(0, 4)}...${apiKey.substring(apiKey.length - 4)}` : "***";
|
|
2003
|
+
console.log(masked);
|
|
2004
|
+
console.log(
|
|
2005
|
+
chalk6.yellow(
|
|
2006
|
+
'\u{1F4A1} Note: API key is masked. Use "config list" to see source.'
|
|
2007
|
+
)
|
|
2008
|
+
);
|
|
2009
|
+
} else {
|
|
2010
|
+
console.log("(not set)");
|
|
1715
2011
|
}
|
|
1716
|
-
|
|
1717
|
-
if (result.satisfies?.length > 0) {
|
|
1718
|
-
console.log(`\u2705 Satisfies: ${result.satisfies.join(", ")}`);
|
|
1719
|
-
}
|
|
1720
|
-
if (result.missing?.length > 0) {
|
|
1721
|
-
console.log(`\u274C Missing: ${result.missing.join(", ")}`);
|
|
2012
|
+
break;
|
|
1722
2013
|
}
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
2014
|
+
default:
|
|
2015
|
+
console.error(chalk6.red(`\u274C Unknown configuration key: ${key}`));
|
|
2016
|
+
console.log(chalk6.blue("\nAvailable keys:"));
|
|
2017
|
+
console.log(" registry-url - Default registry URL");
|
|
2018
|
+
console.log(" api-key - API key for authentication");
|
|
2019
|
+
process.exit(1);
|
|
1727
2020
|
}
|
|
2021
|
+
} catch (error) {
|
|
2022
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2023
|
+
console.error(chalk6.red("\u274C Failed to get configuration:"), message);
|
|
2024
|
+
process.exit(1);
|
|
1728
2025
|
}
|
|
1729
|
-
);
|
|
2026
|
+
});
|
|
1730
2027
|
}
|
|
1731
|
-
function
|
|
1732
|
-
return new Command("
|
|
2028
|
+
function createConfigListCommand() {
|
|
2029
|
+
return new Command("list").description("List all configuration values").alias("ls").action(() => {
|
|
1733
2030
|
try {
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
2031
|
+
const config = new RemoteConfig();
|
|
2032
|
+
console.log(chalk6.blue("\n\u{1F4CB} Remote Registry Configuration\n"));
|
|
2033
|
+
const configPath = config.getConfigPath();
|
|
2034
|
+
const configFileExists = fs3.existsSync(configPath);
|
|
2035
|
+
const url = config.getRegistryUrl();
|
|
2036
|
+
let urlSource;
|
|
2037
|
+
if (process.env.CALIMERO_REGISTRY_URL) {
|
|
2038
|
+
urlSource = chalk6.yellow("(from CALIMERO_REGISTRY_URL env var)");
|
|
2039
|
+
} else if (configFileExists) {
|
|
2040
|
+
urlSource = chalk6.gray("(from config file)");
|
|
2041
|
+
} else {
|
|
2042
|
+
urlSource = chalk6.gray("(default)");
|
|
2043
|
+
}
|
|
2044
|
+
console.log(` ${chalk6.bold("Registry URL:")} ${url} ${urlSource}`);
|
|
2045
|
+
const apiKey = config.getApiKey();
|
|
2046
|
+
if (apiKey) {
|
|
2047
|
+
let apiKeySource;
|
|
2048
|
+
if (process.env.CALIMERO_API_KEY) {
|
|
2049
|
+
apiKeySource = chalk6.yellow("(from CALIMERO_API_KEY env var)");
|
|
2050
|
+
} else if (configFileExists) {
|
|
2051
|
+
apiKeySource = chalk6.gray("(from config file)");
|
|
2052
|
+
} else {
|
|
2053
|
+
apiKeySource = chalk6.gray("(default)");
|
|
2054
|
+
}
|
|
2055
|
+
const masked = apiKey.length > 8 ? `${apiKey.substring(0, 4)}...${apiKey.substring(apiKey.length - 4)}` : "***";
|
|
2056
|
+
console.log(` ${chalk6.bold("API Key:")} ${masked} ${apiKeySource}`);
|
|
2057
|
+
} else {
|
|
2058
|
+
console.log(` ${chalk6.bold("API Key:")} ${chalk6.gray("(not set)")}`);
|
|
1737
2059
|
}
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
);
|
|
1743
|
-
if (manifest.manifest_version !== "1.0") {
|
|
1744
|
-
console.error("\u274C Invalid manifest version");
|
|
1745
|
-
process.exit(1);
|
|
2060
|
+
console.log(chalk6.blue(`
|
|
2061
|
+
\u{1F4C1} Config file: ${configPath}`));
|
|
2062
|
+
if (!configFileExists) {
|
|
2063
|
+
console.log(chalk6.gray(" (file does not exist, using defaults)"));
|
|
1746
2064
|
}
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
);
|
|
1751
|
-
|
|
2065
|
+
console.log();
|
|
2066
|
+
} catch (error) {
|
|
2067
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2068
|
+
console.error(chalk6.red("\u274C Failed to list configuration:"), message);
|
|
2069
|
+
process.exit(1);
|
|
2070
|
+
}
|
|
2071
|
+
});
|
|
2072
|
+
}
|
|
2073
|
+
function createConfigResetCommand() {
|
|
2074
|
+
return new Command("reset").description("Reset configuration to defaults").option("--force", "Skip confirmation prompt").action((options) => {
|
|
2075
|
+
try {
|
|
2076
|
+
if (!options.force) {
|
|
1752
2077
|
console.error(
|
|
1753
|
-
|
|
2078
|
+
chalk6.red("\u274C Configuration reset requires --force flag")
|
|
2079
|
+
);
|
|
2080
|
+
console.log(
|
|
2081
|
+
chalk6.yellow(
|
|
2082
|
+
"\u26A0\uFE0F This will reset all configuration to defaults. Use --force to confirm."
|
|
2083
|
+
)
|
|
1754
2084
|
);
|
|
1755
2085
|
process.exit(1);
|
|
1756
2086
|
}
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
process.exit(1);
|
|
1764
|
-
}
|
|
1765
|
-
if (!manifest.artifact.uri.match(/^(https:\/\/|ipfs:\/\/)/)) {
|
|
1766
|
-
console.error("\u274C Invalid artifact URI format");
|
|
1767
|
-
process.exit(1);
|
|
1768
|
-
}
|
|
1769
|
-
if (manifest.signature) {
|
|
1770
|
-
console.log("\u{1F510} Verifying signature...");
|
|
1771
|
-
console.log("\u26A0\uFE0F Signature verification not implemented in CLI yet");
|
|
1772
|
-
}
|
|
1773
|
-
console.log("\u2705 Manifest verification passed!");
|
|
1774
|
-
console.log(` ID: ${manifest.id}`);
|
|
1775
|
-
console.log(` Name: ${manifest.name}`);
|
|
1776
|
-
console.log(` Version: ${manifest.version}`);
|
|
1777
|
-
console.log(` Chains: ${manifest.chains.join(", ")}`);
|
|
1778
|
-
console.log(` Artifact: ${manifest.artifact.uri}`);
|
|
1779
|
-
if (manifest.provides?.length > 0) {
|
|
1780
|
-
console.log(` Provides: ${manifest.provides.join(", ")}`);
|
|
1781
|
-
}
|
|
1782
|
-
if (manifest.requires?.length > 0) {
|
|
1783
|
-
console.log(` Requires: ${manifest.requires.join(", ")}`);
|
|
1784
|
-
}
|
|
2087
|
+
const config = new RemoteConfig();
|
|
2088
|
+
config.reset();
|
|
2089
|
+
console.log(chalk6.green("\u2705 Configuration reset to defaults"));
|
|
2090
|
+
console.log(chalk6.blue("\nDefault values:"));
|
|
2091
|
+
console.log(" Registry URL: https://apps.calimero.network");
|
|
2092
|
+
console.log(" API Key: (not set)");
|
|
1785
2093
|
} catch (error) {
|
|
1786
|
-
const
|
|
1787
|
-
console.error("\u274C Failed to
|
|
2094
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2095
|
+
console.error(chalk6.red("\u274C Failed to reset configuration:"), message);
|
|
1788
2096
|
process.exit(1);
|
|
1789
2097
|
}
|
|
1790
2098
|
});
|
|
@@ -1795,6 +2103,18 @@ var program = new Command();
|
|
|
1795
2103
|
program.name("calimero-registry").description(
|
|
1796
2104
|
"Calimero Network App Registry CLI - Command-line interface for the App Registry"
|
|
1797
2105
|
).version("1.0.0");
|
|
2106
|
+
program.addHelpText(
|
|
2107
|
+
"after",
|
|
2108
|
+
`
|
|
2109
|
+
Examples:
|
|
2110
|
+
$ calimero-registry apps list
|
|
2111
|
+
$ calimero-registry apps create --file manifest.json
|
|
2112
|
+
$ calimero-registry local start
|
|
2113
|
+
$ calimero-registry health --local
|
|
2114
|
+
|
|
2115
|
+
For more information, visit: https://github.com/calimero-network/app-registry
|
|
2116
|
+
`
|
|
2117
|
+
);
|
|
1798
2118
|
program.option("-u, --url <url>", "Registry API URL", "http://localhost:8082");
|
|
1799
2119
|
program.option(
|
|
1800
2120
|
"-t, --timeout <timeout>",
|
|
@@ -1808,15 +2128,16 @@ program.addCommand(attestationsCommand);
|
|
|
1808
2128
|
program.addCommand(healthCommand);
|
|
1809
2129
|
program.addCommand(ipfsCommand);
|
|
1810
2130
|
program.addCommand(localCommand);
|
|
1811
|
-
program.addCommand(
|
|
2131
|
+
program.addCommand(bundleCommand);
|
|
2132
|
+
program.addCommand(configCommand);
|
|
1812
2133
|
program.exitOverride();
|
|
1813
2134
|
try {
|
|
1814
2135
|
program.parse();
|
|
1815
2136
|
} catch (err) {
|
|
1816
2137
|
if (err instanceof Error) {
|
|
1817
|
-
console.error(
|
|
2138
|
+
console.error(chalk6.red("Error:"), err.message);
|
|
1818
2139
|
} else {
|
|
1819
|
-
console.error(
|
|
2140
|
+
console.error(chalk6.red("Unknown error occurred"));
|
|
1820
2141
|
}
|
|
1821
2142
|
process.exit(1);
|
|
1822
2143
|
}
|