@supacloud/cli 0.6.2 → 0.8.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.
Files changed (3) hide show
  1. package/README.md +25 -0
  2. package/dist/index.js +304 -9
  3. package/package.json +2 -3
package/README.md CHANGED
@@ -96,4 +96,29 @@ Task event commands:
96
96
  - `task_events unregister_webhook`
97
97
  - `task_events inspect_webhook`
98
98
 
99
+ Gateway / Caddy commands (require admin privileges; config is injected via the Caddy JSON Admin API):
100
+
101
+ - `gateway routes` — list custom gateway routes (reverse_proxy / static sites)
102
+ - `gateway upsert_route` — create or replace a route
103
+ - `gateway update_route` — replace a route by id
104
+ - `gateway delete_route` — remove a route by id
105
+ - `gateway config` — update rate-limit tier, CORS origins, or JWT settings
106
+ - `gateway get_certificate` — read certificate automation settings
107
+ - `gateway update_certificate` — save certificate automation settings
108
+ - `gateway issue_certificate` — issue or renew a certificate with lego
109
+ - `gateway deploy_certificate` — deploy an existing PEM cert/key pair
110
+ - `gateway rebuild` — rebuild all tenant gateway configs (`--clean` for a full rebuild)
111
+ - `gateway custom_hostname` — read the bound custom hostname
112
+ - `gateway set_custom_hostname` — bind a custom hostname
113
+ - `gateway delete_custom_hostname` — remove the custom hostname
114
+ - `gateway verify_custom_hostname` — verify a custom hostname
115
+
116
+ ```bash
117
+ supacloud-cli gateway routes --ref abc123
118
+ supacloud-cli gateway upsert_route --ref abc123 --route_id webhook \
119
+ --hosts "api.example.com" --paths "/webhook/*" --upstream 10.0.0.5:8080
120
+ supacloud-cli gateway config --ref abc123 --rate_limit_tier pro
121
+ supacloud-cli gateway rebuild --ref abc123 --clean
122
+ ```
123
+
99
124
  For server installation, SSH diagnostics, and tenant administration, use `@supacloud/admin`.
package/dist/index.js CHANGED
@@ -14,9 +14,6 @@ var __export = (target, all) => {
14
14
  });
15
15
  };
16
16
 
17
- // src/index.ts
18
- import path from "node:path";
19
-
20
17
  // node_modules/zod/v4/classic/external.js
21
18
  var exports_external = {};
22
19
  __export(exports_external, {
@@ -16613,10 +16610,303 @@ ${formatQueueSettings(res.data)}` : `❌ Failed (${res.status})`;
16613
16610
  });
16614
16611
  }
16615
16612
 
16613
+ // src/shared/tools/gateway-tools.ts
16614
+ var stringArray = exports_external.preprocess((value) => {
16615
+ if (value === undefined || value === null)
16616
+ return;
16617
+ if (Array.isArray(value))
16618
+ return value;
16619
+ const text = String(value).trim();
16620
+ if (!text)
16621
+ return [];
16622
+ if (text.startsWith("[")) {
16623
+ try {
16624
+ return JSON.parse(text);
16625
+ } catch {
16626
+ return [text];
16627
+ }
16628
+ }
16629
+ return text.split(",").map((item) => item.trim()).filter(Boolean);
16630
+ }, exports_external.array(exports_external.string()).optional());
16631
+ var headersRecord = exports_external.preprocess((value) => {
16632
+ if (value === undefined || value === null)
16633
+ return;
16634
+ if (typeof value === "object" && !Array.isArray(value))
16635
+ return value;
16636
+ const text = String(value).trim();
16637
+ if (!text)
16638
+ return;
16639
+ try {
16640
+ const parsed = JSON.parse(text);
16641
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
16642
+ return parsed;
16643
+ } catch {}
16644
+ const out = {};
16645
+ for (const part of text.split(",")) {
16646
+ const idx = part.indexOf(":");
16647
+ if (idx > 0)
16648
+ out[part.slice(0, idx).trim()] = part.slice(idx + 1).trim();
16649
+ }
16650
+ return Object.keys(out).length > 0 ? out : undefined;
16651
+ }, exports_external.record(exports_external.string(), exports_external.string()).optional());
16652
+ var ok2 = (res) => res.ok ? JSON.stringify(res.data, null, 2) : `❌ Failed (${res.status}): ${JSON.stringify(res.data)}`;
16653
+ var simple = (res, msg) => res.ok ? `✅ ${msg}` : `❌ Failed (${res.status}): ${JSON.stringify(res.data)}`;
16654
+ function registerGatewayTools(server, http, options = {}) {
16655
+ const { projectRef } = options;
16656
+ server.tool("gateway", `Gateway / Caddy 配置(通过 JSON Admin API 注入)。要求 admin 权限。
16657
+ Actions: routes, upsert_route, update_route, delete_route, config, get_certificate, update_certificate, issue_certificate, deploy_certificate, rebuild, custom_hostname, set_custom_hostname, delete_custom_hostname, verify_custom_hostname`, {
16658
+ action: exports_external.enum([
16659
+ "routes",
16660
+ "upsert_route",
16661
+ "update_route",
16662
+ "delete_route",
16663
+ "config",
16664
+ "get_certificate",
16665
+ "update_certificate",
16666
+ "issue_certificate",
16667
+ "deploy_certificate",
16668
+ "rebuild",
16669
+ "custom_hostname",
16670
+ "set_custom_hostname",
16671
+ "delete_custom_hostname",
16672
+ "verify_custom_hostname"
16673
+ ]).describe("Action"),
16674
+ ref: exports_external.string().optional().describe(projectRef ? "可选:覆盖自动关联的项目 ref" : "项目 ref"),
16675
+ route_id: exports_external.string().optional().describe("[upsert_route/update_route/delete_route] 路由 ID(字母/数字/_/-,1-64)"),
16676
+ hosts: stringArray.describe("[upsert_route/update_route] 主机名列表,逗号分隔或 JSON 数组(1-20)"),
16677
+ paths: stringArray.describe("[upsert_route/update_route] 路径列表,逗号分隔或 JSON 数组(1-20)"),
16678
+ upstream: exports_external.string().optional().describe("[upsert_route/update_route] 反代上游 host:port 或 http(s)://host[:port]"),
16679
+ upstream_tls_insecure_skip_verify: exports_external.boolean().optional().describe("[upsert_route/update_route] 上游 TLS 跳过校验"),
16680
+ static_root: exports_external.string().optional().describe("[upsert_route/update_route] 静态站点根目录(与 upstream 二选一)"),
16681
+ rewrite_uri: exports_external.string().optional().describe("[upsert_route/update_route] 重写 URI(以 / 开头)"),
16682
+ strip_prefix: exports_external.string().optional().describe("[upsert_route/update_route] 去除前缀"),
16683
+ headers: headersRecord.describe("[upsert_route/update_route] 自定义请求头,JSON 或 K:V,K2:V2"),
16684
+ cors: stringArray.describe("[upsert_route/update_route] 额外 CORS 源,逗号分隔"),
16685
+ priority: exports_external.number().optional().describe("[upsert_route/update_route] 路由优先级"),
16686
+ enabled: exports_external.boolean().optional().describe("[upsert_route/update_route] 是否启用"),
16687
+ rate_limit_tier: exports_external.enum(["free", "pro", "enterprise"]).optional().describe("[config] 限流档位"),
16688
+ cors_origins: exports_external.string().optional().describe("[config] CORS 源(逗号分隔)"),
16689
+ jwt_enabled: exports_external.boolean().optional().describe("[config] 是否启用 JWT"),
16690
+ jwt_secret: exports_external.string().optional().describe("[config] JWT 密钥"),
16691
+ cert_mode: exports_external.enum(["lego", "manual"]).optional().describe("[update_certificate] 证书模式"),
16692
+ challenge: exports_external.enum(["dns-01", "http-01"]).optional().describe("[update_certificate/issue_certificate] ACME challenge"),
16693
+ email: exports_external.string().optional().describe("[update_certificate/issue_certificate] ACME 邮箱"),
16694
+ dns_provider: exports_external.string().optional().describe("[update_certificate/issue_certificate] DNS 提供商"),
16695
+ dns_env: stringArray.describe("[update_certificate/issue_certificate] DNS 环境变量 KEY=VALUE 列表"),
16696
+ domains: stringArray.describe("[update_certificate/issue_certificate/deploy_certificate] 域名列表"),
16697
+ auto_renew: exports_external.boolean().optional().describe("[update_certificate/issue_certificate] 自动续期"),
16698
+ renew: exports_external.boolean().optional().describe("[issue_certificate] 仅续期已有证书"),
16699
+ cert: exports_external.string().optional().describe("[deploy_certificate] PEM 证书内容"),
16700
+ key: exports_external.string().optional().describe("[deploy_certificate] PEM 私钥内容"),
16701
+ clean: exports_external.boolean().optional().describe("[rebuild] 清理后全量重建"),
16702
+ custom_hostname: exports_external.string().optional().describe("[set_custom_hostname] 自定义域名")
16703
+ }, async (args) => {
16704
+ const resolveRef4 = (override) => {
16705
+ const ref2 = projectRef || override;
16706
+ if (!ref2)
16707
+ throw new Error("'ref' is required for this action");
16708
+ return ref2;
16709
+ };
16710
+ const need = (field, value) => {
16711
+ if (value === undefined || value === null || value === "")
16712
+ throw new Error(`'${field}' is required for '${args.action}'`);
16713
+ };
16714
+ const {
16715
+ action,
16716
+ ref,
16717
+ route_id,
16718
+ hosts,
16719
+ paths,
16720
+ upstream,
16721
+ upstream_tls_insecure_skip_verify,
16722
+ static_root,
16723
+ rewrite_uri,
16724
+ strip_prefix,
16725
+ headers,
16726
+ cors,
16727
+ priority,
16728
+ enabled,
16729
+ rate_limit_tier,
16730
+ cors_origins,
16731
+ jwt_enabled,
16732
+ jwt_secret,
16733
+ cert_mode,
16734
+ challenge,
16735
+ email: email3,
16736
+ dns_provider,
16737
+ dns_env,
16738
+ domains,
16739
+ auto_renew,
16740
+ renew,
16741
+ cert,
16742
+ key,
16743
+ clean,
16744
+ custom_hostname
16745
+ } = args;
16746
+ const projectRefValue = resolveRef4(ref);
16747
+ let text;
16748
+ switch (action) {
16749
+ case "routes":
16750
+ text = ok2(await http.get(`/v1/projects/${projectRefValue}/gateway/routes`));
16751
+ break;
16752
+ case "upsert_route": {
16753
+ need("route_id", route_id);
16754
+ need("hosts", hosts);
16755
+ need("paths", paths);
16756
+ const body = {
16757
+ id: route_id,
16758
+ hosts,
16759
+ path: paths.length === 1 ? paths[0] : paths
16760
+ };
16761
+ if (upstream !== undefined)
16762
+ body.upstream = upstream;
16763
+ if (upstream_tls_insecure_skip_verify !== undefined)
16764
+ body.upstream_tls_insecure_skip_verify = upstream_tls_insecure_skip_verify;
16765
+ if (static_root !== undefined)
16766
+ body.static_root = static_root;
16767
+ if (rewrite_uri !== undefined)
16768
+ body.rewrite_uri = rewrite_uri;
16769
+ if (strip_prefix !== undefined)
16770
+ body.strip_prefix = strip_prefix;
16771
+ if (headers !== undefined)
16772
+ body.headers = headers;
16773
+ if (cors !== undefined)
16774
+ body.cors = cors;
16775
+ if (priority !== undefined)
16776
+ body.priority = priority;
16777
+ if (enabled !== undefined)
16778
+ body.enabled = enabled;
16779
+ text = ok2(await http.post(`/v1/projects/${projectRefValue}/gateway/routes`, body));
16780
+ break;
16781
+ }
16782
+ case "update_route": {
16783
+ need("route_id", route_id);
16784
+ need("hosts", hosts);
16785
+ need("paths", paths);
16786
+ const body = {
16787
+ hosts,
16788
+ path: paths.length === 1 ? paths[0] : paths
16789
+ };
16790
+ if (upstream !== undefined)
16791
+ body.upstream = upstream;
16792
+ if (upstream_tls_insecure_skip_verify !== undefined)
16793
+ body.upstream_tls_insecure_skip_verify = upstream_tls_insecure_skip_verify;
16794
+ if (static_root !== undefined)
16795
+ body.static_root = static_root;
16796
+ if (rewrite_uri !== undefined)
16797
+ body.rewrite_uri = rewrite_uri;
16798
+ if (strip_prefix !== undefined)
16799
+ body.strip_prefix = strip_prefix;
16800
+ if (headers !== undefined)
16801
+ body.headers = headers;
16802
+ if (cors !== undefined)
16803
+ body.cors = cors;
16804
+ if (priority !== undefined)
16805
+ body.priority = priority;
16806
+ if (enabled !== undefined)
16807
+ body.enabled = enabled;
16808
+ text = ok2(await http.put(`/v1/projects/${projectRefValue}/gateway/routes/${route_id}`, body));
16809
+ break;
16810
+ }
16811
+ case "delete_route": {
16812
+ need("route_id", route_id);
16813
+ text = ok2(await http.delete(`/v1/projects/${projectRefValue}/gateway/routes/${route_id}`));
16814
+ break;
16815
+ }
16816
+ case "config": {
16817
+ const body = {};
16818
+ if (rate_limit_tier !== undefined)
16819
+ body.rate_limit_tier = rate_limit_tier;
16820
+ if (cors_origins !== undefined)
16821
+ body.cors_origins = cors_origins;
16822
+ if (jwt_enabled !== undefined)
16823
+ body.jwt_enabled = jwt_enabled;
16824
+ if (jwt_secret !== undefined)
16825
+ body.jwt_secret = jwt_secret;
16826
+ if (Object.keys(body).length === 0)
16827
+ throw new Error("At least one of rate_limit_tier, cors_origins, jwt_enabled, jwt_secret is required");
16828
+ text = ok2(await http.post(`/v1/projects/${projectRefValue}/gateway/config`, body));
16829
+ break;
16830
+ }
16831
+ case "get_certificate":
16832
+ text = ok2(await http.get(`/v1/projects/${projectRefValue}/gateway/certificate`));
16833
+ break;
16834
+ case "update_certificate": {
16835
+ const body = {};
16836
+ if (cert_mode !== undefined)
16837
+ body.mode = cert_mode;
16838
+ if (challenge !== undefined)
16839
+ body.challenge = challenge;
16840
+ if (email3 !== undefined)
16841
+ body.email = email3;
16842
+ if (dns_provider !== undefined)
16843
+ body.dns_provider = dns_provider;
16844
+ if (dns_env !== undefined)
16845
+ body.dns_env = dns_env;
16846
+ if (domains !== undefined)
16847
+ body.domains = domains;
16848
+ if (auto_renew !== undefined)
16849
+ body.auto_renew = auto_renew;
16850
+ text = ok2(await http.put(`/v1/projects/${projectRefValue}/gateway/certificate`, body));
16851
+ break;
16852
+ }
16853
+ case "issue_certificate": {
16854
+ const body = {};
16855
+ if (challenge !== undefined)
16856
+ body.challenge = challenge;
16857
+ if (email3 !== undefined)
16858
+ body.email = email3;
16859
+ if (dns_provider !== undefined)
16860
+ body.dns_provider = dns_provider;
16861
+ if (dns_env !== undefined)
16862
+ body.dns_env = dns_env;
16863
+ if (domains !== undefined)
16864
+ body.domains = domains;
16865
+ if (auto_renew !== undefined)
16866
+ body.auto_renew = auto_renew;
16867
+ if (renew !== undefined)
16868
+ body.renew = renew;
16869
+ text = ok2(await http.post(`/v1/projects/${projectRefValue}/gateway/certificate/issue`, body));
16870
+ break;
16871
+ }
16872
+ case "deploy_certificate": {
16873
+ need("cert", cert);
16874
+ need("key", key);
16875
+ const body = { cert, key };
16876
+ if (domains !== undefined)
16877
+ body.domains = domains;
16878
+ text = ok2(await http.post(`/v1/projects/${projectRefValue}/gateway/certificate/deploy`, body));
16879
+ break;
16880
+ }
16881
+ case "rebuild": {
16882
+ const query = clean ? "?clean=true" : "";
16883
+ text = ok2(await http.post(`/v1/projects/${projectRefValue}/gateway/rebuild-all${query}`));
16884
+ break;
16885
+ }
16886
+ case "custom_hostname":
16887
+ text = ok2(await http.get(`/v1/projects/${projectRefValue}/custom-hostname`));
16888
+ break;
16889
+ case "set_custom_hostname": {
16890
+ need("custom_hostname", custom_hostname);
16891
+ text = simple(await http.post(`/v1/projects/${projectRefValue}/custom-hostname`, { custom_hostname }), `Custom hostname ${custom_hostname} requested`);
16892
+ break;
16893
+ }
16894
+ case "delete_custom_hostname":
16895
+ text = simple(await http.delete(`/v1/projects/${projectRefValue}/custom-hostname`), "Custom hostname removed");
16896
+ break;
16897
+ case "verify_custom_hostname":
16898
+ text = ok2(await http.post(`/v1/projects/${projectRefValue}/custom-hostname/verify`));
16899
+ break;
16900
+ default:
16901
+ text = `❌ Unknown action: ${action}`;
16902
+ }
16903
+ return { content: [{ type: "text", text }] };
16904
+ });
16905
+ }
16906
+
16616
16907
  // src/index.ts
16617
- var invokedCommand = path.basename(process.argv[1] || "supacloud-cli");
16618
- var commandName = invokedCommand === "supacloud" ? "supacloud" : "supacloud-cli";
16619
- var preferredCommand = "supacloud-cli";
16908
+ var commandName = "supacloud-cli";
16909
+ var preferredCommand = commandName;
16620
16910
  var projectActionSchema = exports_external.enum([
16621
16911
  "get",
16622
16912
  "health",
@@ -16663,8 +16953,6 @@ function printHelp(context = resolveSupaCloudContext()) {
16663
16953
  ║ Project CLI for SupaCloud users ║
16664
16954
  ╚═══════════════════════════════════════════════════════════╝
16665
16955
 
16666
- ${commandName === "supacloud" ? "NOTE\n\n `supacloud` is kept as a compatibility alias. Prefer `supacloud-cli`\n to avoid confusion with the server binary at /usr/local/bin/supacloud.\n" : ""}
16667
-
16668
16956
  USAGE
16669
16957
 
16670
16958
  ${preferredCommand} <module> <action> [--flags]
@@ -16694,6 +16982,10 @@ EXAMPLES
16694
16982
  ${preferredCommand} database push_migrations --ref abc123 --dir supabase/migrations --dry_run
16695
16983
  ${preferredCommand} edge_functions deploy --ref abc123 --slug hello --path ./supabase/functions/hello
16696
16984
  ${preferredCommand} edge_functions config --ref abc123 --slug hello --verify_jwt false --background_routes "/queue/*,/render/*"
16985
+ ${preferredCommand} gateway routes --ref abc123
16986
+ ${preferredCommand} gateway upsert_route --ref abc123 --route_id webhook --hosts "api.example.com" --paths "/webhook/*" --upstream 10.0.0.5:8080
16987
+ ${preferredCommand} gateway config --ref abc123 --rate_limit_tier pro
16988
+ ${preferredCommand} gateway rebuild --ref abc123 --clean
16697
16989
 
16698
16990
  SEPARATE ADMIN CLI
16699
16991
 
@@ -16748,7 +17040,7 @@ function createCliTools() {
16748
17040
  ]
16749
17041
  })
16750
17042
  };
16751
- for (const name of ["database", "auth", "storage", "edge_functions", "secrets", "frontend", "queue", "task_events", "diagnostics"]) {
17043
+ for (const name of ["database", "auth", "storage", "edge_functions", "secrets", "frontend", "queue", "task_events", "diagnostics", "gateway"]) {
16752
17044
  tools[name] = {
16753
17045
  schema: { action: genericActionSchema },
16754
17046
  callback: async () => ({
@@ -16811,6 +17103,9 @@ function createCliTools() {
16811
17103
  assign(captureTools((server) => registerStorageTools(server, http)));
16812
17104
  assign(captureTools((server) => registerAdvancedTools(server, http)));
16813
17105
  assign(captureTools((server) => registerFrontendTools(server, http)));
17106
+ assign(captureTools((server) => registerGatewayTools(server, http, {
17107
+ projectRef: context.projectRef || undefined
17108
+ })));
16814
17109
  assign(captureTools((server) => registerQueueTools(server, http, {
16815
17110
  projectRef: context.projectRef || undefined
16816
17111
  })));
package/package.json CHANGED
@@ -1,12 +1,11 @@
1
1
  {
2
2
  "name": "@supacloud/cli",
3
- "version": "0.6.2",
3
+ "version": "0.8.0",
4
4
  "description": "Project-scoped CLI for SupaCloud users",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "bin": {
8
- "supacloud-cli": "dist/index.js",
9
- "supacloud": "dist/index.js"
8
+ "supacloud-cli": "dist/index.js"
10
9
  },
11
10
  "files": [
12
11
  "dist",