@calimero-network/registry-cli 1.22.4 → 1.24.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 +3 -3
- package/dist/index.js +97 -25
- package/dist/index.js.map +1 -1
- package/package.json +1 -3
package/README.md
CHANGED
|
@@ -48,9 +48,9 @@ calimero-registry org update <orgId> [-n <name>] [-m <json>]
|
|
|
48
48
|
calimero-registry org delete <orgId> [-y] # irreversible
|
|
49
49
|
|
|
50
50
|
calimero-registry org members list <orgId>
|
|
51
|
-
calimero-registry org members add <orgId> <
|
|
52
|
-
calimero-registry org members update <orgId> <
|
|
53
|
-
calimero-registry org members remove <orgId> <
|
|
51
|
+
calimero-registry org members add <orgId> <username> [-r admin|member]
|
|
52
|
+
calimero-registry org members update <orgId> <username> -r admin|member
|
|
53
|
+
calimero-registry org members remove <orgId> <username>
|
|
54
54
|
|
|
55
55
|
calimero-registry org packages link <orgId> <package>
|
|
56
56
|
calimero-registry org packages unlink <orgId> <package>
|
package/dist/index.js
CHANGED
|
@@ -98,10 +98,24 @@ var RemoteConfig = class _RemoteConfig {
|
|
|
98
98
|
return false;
|
|
99
99
|
}
|
|
100
100
|
if (parsed.protocol !== "http:") return false;
|
|
101
|
-
const host = parsed.hostname.toLowerCase();
|
|
101
|
+
const host = parsed.hostname.toLowerCase().replace(/^\[(.*)\]$/, "$1");
|
|
102
102
|
const isLocal = host === "localhost" || host === "127.0.0.1" || host === "::1" || host.endsWith(".localhost");
|
|
103
103
|
return !isLocal;
|
|
104
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Throws when an API key would be sent to `url` over plain http:// to a
|
|
107
|
+
* non-loopback host. Called wherever the effective registry URL (flag, env
|
|
108
|
+
* var or config file) is about to receive the Bearer key. https:// and
|
|
109
|
+
* http://localhost / 127.0.0.1 / [::1] are always allowed.
|
|
110
|
+
*/
|
|
111
|
+
static assertApiKeyTransport(url, apiKey) {
|
|
112
|
+
if (!apiKey) return;
|
|
113
|
+
if (_RemoteConfig.isInsecureRemoteUrl(url)) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`Refusing to send the API key to ${url}: plain http:// is only allowed for localhost / 127.0.0.1 / [::1]. Use an https:// registry URL (check --url, CALIMERO_REGISTRY_URL and \`calimero-registry config get registry-url\`).`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
105
119
|
/**
|
|
106
120
|
* Get API key with priority:
|
|
107
121
|
* 1. Environment variable CALIMERO_API_KEY
|
|
@@ -776,6 +790,18 @@ var LocalArtifactServer = class {
|
|
|
776
790
|
};
|
|
777
791
|
}
|
|
778
792
|
};
|
|
793
|
+
function isAllowedOrigin(origin, allowedHosts) {
|
|
794
|
+
let parsed;
|
|
795
|
+
try {
|
|
796
|
+
parsed = new URL(origin);
|
|
797
|
+
} catch {
|
|
798
|
+
return false;
|
|
799
|
+
}
|
|
800
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
803
|
+
return allowedHosts.has(parsed.hostname.toLowerCase());
|
|
804
|
+
}
|
|
779
805
|
var LocalRegistryServer = class {
|
|
780
806
|
config;
|
|
781
807
|
dataStore;
|
|
@@ -821,6 +847,43 @@ var LocalRegistryServer = class {
|
|
|
821
847
|
});
|
|
822
848
|
}
|
|
823
849
|
});
|
|
850
|
+
const allowedOriginHosts = /* @__PURE__ */ new Set();
|
|
851
|
+
for (const name of allowedHosts) {
|
|
852
|
+
allowedOriginHosts.add(name.replace(/:\d+$/, ""));
|
|
853
|
+
}
|
|
854
|
+
this.server.addHook("onRequest", async (request, reply) => {
|
|
855
|
+
const method = request.method.toUpperCase();
|
|
856
|
+
if (method === "GET" || method === "HEAD" || method === "OPTIONS") {
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
const origin = request.headers.origin;
|
|
860
|
+
if (origin !== void 0 && !isAllowedOrigin(origin, allowedOriginHosts)) {
|
|
861
|
+
return reply.code(403).send({
|
|
862
|
+
statusCode: 403,
|
|
863
|
+
error: "Forbidden",
|
|
864
|
+
message: "Cross-origin request not allowed"
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
const fetchSite = request.headers["sec-fetch-site"];
|
|
868
|
+
if (fetchSite !== void 0 && fetchSite !== "same-origin" && fetchSite !== "none") {
|
|
869
|
+
return reply.code(403).send({
|
|
870
|
+
statusCode: 403,
|
|
871
|
+
error: "Forbidden",
|
|
872
|
+
message: "Cross-site request not allowed"
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
const pathname = request.url.split("?")[0];
|
|
876
|
+
if (pathname.startsWith("/local/")) {
|
|
877
|
+
const contentType = String(request.headers["content-type"] ?? "").split(";")[0].trim().toLowerCase();
|
|
878
|
+
if (contentType !== "application/json") {
|
|
879
|
+
return reply.code(415).send({
|
|
880
|
+
statusCode: 415,
|
|
881
|
+
error: "Unsupported Media Type",
|
|
882
|
+
message: "Content-Type must be application/json"
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
});
|
|
824
887
|
await this.registerRoutes();
|
|
825
888
|
await this.server.listen({ port: serverPort, host });
|
|
826
889
|
this.isRunning = true;
|
|
@@ -1755,11 +1818,12 @@ Note:
|
|
|
1755
1818
|
- Config file values are used unless overridden by flags or environment variables
|
|
1756
1819
|
- Priority: flag > environment variable > config file > default
|
|
1757
1820
|
`
|
|
1758
|
-
).action(async (bundleFile, options) => {
|
|
1821
|
+
).action(async (bundleFile, options, command) => {
|
|
1822
|
+
const urlFlag = command.optsWithGlobals().url;
|
|
1759
1823
|
let mpkPath;
|
|
1760
1824
|
let tempMpk = null;
|
|
1761
1825
|
try {
|
|
1762
|
-
if (!options.remote && (
|
|
1826
|
+
if (!options.remote && (urlFlag || options.apiKey)) {
|
|
1763
1827
|
console.warn(
|
|
1764
1828
|
"\u26A0\uFE0F Warning: --url and --api-key are only used with --remote flag"
|
|
1765
1829
|
);
|
|
@@ -1889,8 +1953,9 @@ Note:
|
|
|
1889
1953
|
);
|
|
1890
1954
|
} else if (useRemote) {
|
|
1891
1955
|
const remoteConfig = new RemoteConfig();
|
|
1892
|
-
const registryUrl =
|
|
1956
|
+
const registryUrl = urlFlag || process.env.CALIMERO_REGISTRY_URL || remoteConfig.getRegistryUrl();
|
|
1893
1957
|
const apiKey = options.apiKey || process.env.CALIMERO_API_KEY || remoteConfig.getApiKey();
|
|
1958
|
+
RemoteConfig.assertApiKeyTransport(registryUrl, apiKey);
|
|
1894
1959
|
await pushToRemote(mpkPath, manifest, registryUrl, apiKey);
|
|
1895
1960
|
}
|
|
1896
1961
|
} catch (error) {
|
|
@@ -1949,7 +2014,8 @@ Examples:
|
|
|
1949
2014
|
# 3) With custom registry URL:
|
|
1950
2015
|
$ calimero-registry bundle edit com.calimero.myapp 1.0.0 --remote --url https://apps.calimero.network --manifest signed-manifest.json
|
|
1951
2016
|
`
|
|
1952
|
-
).action(async (pkg, version2, options) => {
|
|
2017
|
+
).action(async (pkg, version2, options, command) => {
|
|
2018
|
+
const urlFlag = command.optsWithGlobals().url;
|
|
1953
2019
|
try {
|
|
1954
2020
|
if (!options.remote) {
|
|
1955
2021
|
console.error(
|
|
@@ -1958,8 +2024,9 @@ Examples:
|
|
|
1958
2024
|
process.exit(1);
|
|
1959
2025
|
}
|
|
1960
2026
|
const remoteConfig = new RemoteConfig();
|
|
1961
|
-
const registryUrl =
|
|
2027
|
+
const registryUrl = urlFlag || process.env.CALIMERO_REGISTRY_URL || remoteConfig.getRegistryUrl();
|
|
1962
2028
|
const apiKey = options.apiKey || process.env.CALIMERO_API_KEY || remoteConfig.getApiKey();
|
|
2029
|
+
RemoteConfig.assertApiKeyTransport(registryUrl, apiKey);
|
|
1963
2030
|
const baseUrl = registryUrl.replace(/\/$/, "");
|
|
1964
2031
|
const bundleUrl = `${baseUrl}/api/v2/bundles/${encodeURIComponent(pkg)}/${encodeURIComponent(version2)}`;
|
|
1965
2032
|
const headers = {
|
|
@@ -2426,7 +2493,7 @@ function getGlobalOpts(command) {
|
|
|
2426
2493
|
timeout: parseInt(String(opts.timeout ?? DEFAULT_TIMEOUT), 10) || DEFAULT_TIMEOUT
|
|
2427
2494
|
};
|
|
2428
2495
|
}
|
|
2429
|
-
function getAuthHeaders() {
|
|
2496
|
+
function getAuthHeaders(url) {
|
|
2430
2497
|
const remoteConfig = new RemoteConfig();
|
|
2431
2498
|
const apiKey = remoteConfig.getApiKey();
|
|
2432
2499
|
if (!apiKey) {
|
|
@@ -2434,6 +2501,7 @@ function getAuthHeaders() {
|
|
|
2434
2501
|
"API token required for org write operations.\nGet a token from the Organizations page in the web UI, then run:\n calimero-registry config set api-key <token>"
|
|
2435
2502
|
);
|
|
2436
2503
|
}
|
|
2504
|
+
RemoteConfig.assertApiKeyTransport(url, apiKey);
|
|
2437
2505
|
return { Authorization: `Bearer ${apiKey}` };
|
|
2438
2506
|
}
|
|
2439
2507
|
async function fetchJson(url, options = {}) {
|
|
@@ -2461,9 +2529,9 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
|
|
|
2461
2529
|
const spinner2 = ora4("Resolving identity...").start();
|
|
2462
2530
|
let authHeaders;
|
|
2463
2531
|
try {
|
|
2464
|
-
authHeaders = getAuthHeaders();
|
|
2532
|
+
authHeaders = getAuthHeaders(url);
|
|
2465
2533
|
} catch (e) {
|
|
2466
|
-
spinner2.fail("API token
|
|
2534
|
+
spinner2.fail("Cannot use API token");
|
|
2467
2535
|
console.error(chalk4.red(e instanceof Error ? e.message : String(e)));
|
|
2468
2536
|
process.exit(1);
|
|
2469
2537
|
}
|
|
@@ -2538,7 +2606,7 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
|
|
|
2538
2606
|
const { url, timeout } = getGlobalOpts(command);
|
|
2539
2607
|
const spinner2 = ora4("Creating organization...").start();
|
|
2540
2608
|
try {
|
|
2541
|
-
const authHeaders = getAuthHeaders();
|
|
2609
|
+
const authHeaders = getAuthHeaders(url);
|
|
2542
2610
|
const pathname = "/api/v2/orgs";
|
|
2543
2611
|
const body = {
|
|
2544
2612
|
name: options.name.trim(),
|
|
@@ -2599,7 +2667,7 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
|
|
|
2599
2667
|
const { url, timeout } = getGlobalOpts(command);
|
|
2600
2668
|
const spinner2 = ora4("Updating organization...").start();
|
|
2601
2669
|
try {
|
|
2602
|
-
const authHeaders = getAuthHeaders();
|
|
2670
|
+
const authHeaders = getAuthHeaders(url);
|
|
2603
2671
|
const pathname = `/api/v2/orgs/${encodeURIComponent(orgId)}`;
|
|
2604
2672
|
const body = {};
|
|
2605
2673
|
if (options.name !== void 0) body.name = options.name;
|
|
@@ -2664,7 +2732,7 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
|
|
|
2664
2732
|
}
|
|
2665
2733
|
const spinner2 = ora4("Deleting organization...").start();
|
|
2666
2734
|
try {
|
|
2667
|
-
const authHeaders = getAuthHeaders();
|
|
2735
|
+
const authHeaders = getAuthHeaders(url);
|
|
2668
2736
|
const pathname = `/api/v2/orgs/${encodeURIComponent(orgId)}`;
|
|
2669
2737
|
const base = url.replace(/\/$/, "");
|
|
2670
2738
|
const { data, status } = await fetchJson(`${base}${pathname}`, {
|
|
@@ -2690,7 +2758,7 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
|
|
|
2690
2758
|
}
|
|
2691
2759
|
)
|
|
2692
2760
|
).addCommand(
|
|
2693
|
-
new Command("members").description("List,
|
|
2761
|
+
new Command("members").description("List, invite, or remove organization members").addCommand(
|
|
2694
2762
|
new Command("list").description("List members of an org").argument("<orgId>", "Organization id or slug").action(async (orgId, _options, command) => {
|
|
2695
2763
|
const { url, timeout } = getGlobalOpts(command);
|
|
2696
2764
|
const base = url.replace(/\/$/, "");
|
|
@@ -2715,15 +2783,17 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
|
|
|
2715
2783
|
}
|
|
2716
2784
|
})
|
|
2717
2785
|
).addCommand(
|
|
2718
|
-
new Command("add").description(
|
|
2719
|
-
|
|
2786
|
+
new Command("add").description(
|
|
2787
|
+
"Invite a member by username; they join once they accept"
|
|
2788
|
+
).argument("<orgId>", "Organization id or slug").argument("<username>", "Member username").option("-r, --role <role>", "Role: member or admin", "member").action(
|
|
2789
|
+
async (orgId, username, options, command) => {
|
|
2720
2790
|
const { url, timeout } = getGlobalOpts(command);
|
|
2721
|
-
const spinner2 = ora4("
|
|
2791
|
+
const spinner2 = ora4("Inviting member...").start();
|
|
2722
2792
|
try {
|
|
2723
|
-
const authHeaders = getAuthHeaders();
|
|
2793
|
+
const authHeaders = getAuthHeaders(url);
|
|
2724
2794
|
const pathname = `/api/v2/orgs/${encodeURIComponent(orgId)}/members`;
|
|
2725
2795
|
const body = {
|
|
2726
|
-
|
|
2796
|
+
username: username.trim().replace(/^@+/, ""),
|
|
2727
2797
|
role: options.role === "admin" ? "admin" : "member"
|
|
2728
2798
|
};
|
|
2729
2799
|
const base = url.replace(/\/$/, "");
|
|
@@ -2740,7 +2810,9 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
|
|
|
2740
2810
|
console.error(chalk4.red(JSON.stringify(data)));
|
|
2741
2811
|
process.exit(1);
|
|
2742
2812
|
}
|
|
2743
|
-
spinner2.succeed(
|
|
2813
|
+
spinner2.succeed(
|
|
2814
|
+
status === 202 ? "Invitation sent; the user joins once they accept it" : "Member added"
|
|
2815
|
+
);
|
|
2744
2816
|
} catch (e) {
|
|
2745
2817
|
spinner2.fail("Failed");
|
|
2746
2818
|
console.error(
|
|
@@ -2751,13 +2823,13 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
|
|
|
2751
2823
|
}
|
|
2752
2824
|
)
|
|
2753
2825
|
).addCommand(
|
|
2754
|
-
new Command("update").description("Update a member role (admin or member)").argument("<orgId>", "Organization id or slug").argument("<
|
|
2826
|
+
new Command("update").description("Update a member role (admin or member)").argument("<orgId>", "Organization id or slug").argument("<member>", "Member username or email address").requiredOption("-r, --role <role>", "New role: admin or member").action(
|
|
2755
2827
|
async (orgId, email, options, command) => {
|
|
2756
2828
|
const { url, timeout } = getGlobalOpts(command);
|
|
2757
2829
|
const role = options.role === "admin" ? "admin" : "member";
|
|
2758
2830
|
const spinner2 = ora4("Updating member role...").start();
|
|
2759
2831
|
try {
|
|
2760
|
-
const authHeaders = getAuthHeaders();
|
|
2832
|
+
const authHeaders = getAuthHeaders(url);
|
|
2761
2833
|
const pathname = `/api/v2/orgs/${encodeURIComponent(orgId)}/members/${encodeURIComponent(email.trim())}`;
|
|
2762
2834
|
const body = { role };
|
|
2763
2835
|
const base = url.replace(/\/$/, "");
|
|
@@ -2785,12 +2857,12 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
|
|
|
2785
2857
|
}
|
|
2786
2858
|
)
|
|
2787
2859
|
).addCommand(
|
|
2788
|
-
new Command("remove").description("Remove a member by email").argument("<orgId>", "Organization id or slug").argument("<
|
|
2860
|
+
new Command("remove").description("Remove a member by username or email").argument("<orgId>", "Organization id or slug").argument("<member>", "Member username or email address").action(
|
|
2789
2861
|
async (orgId, email, _options, command) => {
|
|
2790
2862
|
const { url, timeout } = getGlobalOpts(command);
|
|
2791
2863
|
const spinner2 = ora4("Removing member...").start();
|
|
2792
2864
|
try {
|
|
2793
|
-
const authHeaders = getAuthHeaders();
|
|
2865
|
+
const authHeaders = getAuthHeaders(url);
|
|
2794
2866
|
const pathname = `/api/v2/orgs/${encodeURIComponent(orgId)}/members/${encodeURIComponent(email.trim())}`;
|
|
2795
2867
|
const base = url.replace(/\/$/, "");
|
|
2796
2868
|
const { data, status } = await fetchJson(`${base}${pathname}`, {
|
|
@@ -2823,7 +2895,7 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
|
|
|
2823
2895
|
const { url, timeout } = getGlobalOpts(command);
|
|
2824
2896
|
const spinner2 = ora4("Linking package...").start();
|
|
2825
2897
|
try {
|
|
2826
|
-
const authHeaders = getAuthHeaders();
|
|
2898
|
+
const authHeaders = getAuthHeaders(url);
|
|
2827
2899
|
const pathname = `/api/v2/orgs/${encodeURIComponent(orgId)}/packages`;
|
|
2828
2900
|
const body = { package: pkg.trim() };
|
|
2829
2901
|
const base = url.replace(/\/$/, "");
|
|
@@ -2856,7 +2928,7 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
|
|
|
2856
2928
|
const { url, timeout } = getGlobalOpts(command);
|
|
2857
2929
|
const spinner2 = ora4("Unlinking package...").start();
|
|
2858
2930
|
try {
|
|
2859
|
-
const authHeaders = getAuthHeaders();
|
|
2931
|
+
const authHeaders = getAuthHeaders(url);
|
|
2860
2932
|
const pathname = `/api/v2/orgs/${encodeURIComponent(orgId)}/packages/${encodeURIComponent(pkg.trim())}`;
|
|
2861
2933
|
const base = url.replace(/\/$/, "");
|
|
2862
2934
|
const { data, status } = await fetchJson(`${base}${pathname}`, {
|