@supacloud/cli 0.6.1 → 0.7.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 +302 -1
  3. package/package.json +3 -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
@@ -16613,6 +16613,300 @@ ${formatQueueSettings(res.data)}` : `❌ Failed (${res.status})`;
16613
16613
  });
16614
16614
  }
16615
16615
 
16616
+ // src/shared/tools/gateway-tools.ts
16617
+ var stringArray = exports_external.preprocess((value) => {
16618
+ if (value === undefined || value === null)
16619
+ return;
16620
+ if (Array.isArray(value))
16621
+ return value;
16622
+ const text = String(value).trim();
16623
+ if (!text)
16624
+ return [];
16625
+ if (text.startsWith("[")) {
16626
+ try {
16627
+ return JSON.parse(text);
16628
+ } catch {
16629
+ return [text];
16630
+ }
16631
+ }
16632
+ return text.split(",").map((item) => item.trim()).filter(Boolean);
16633
+ }, exports_external.array(exports_external.string()).optional());
16634
+ var headersRecord = exports_external.preprocess((value) => {
16635
+ if (value === undefined || value === null)
16636
+ return;
16637
+ if (typeof value === "object" && !Array.isArray(value))
16638
+ return value;
16639
+ const text = String(value).trim();
16640
+ if (!text)
16641
+ return;
16642
+ try {
16643
+ const parsed = JSON.parse(text);
16644
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
16645
+ return parsed;
16646
+ } catch {}
16647
+ const out = {};
16648
+ for (const part of text.split(",")) {
16649
+ const idx = part.indexOf(":");
16650
+ if (idx > 0)
16651
+ out[part.slice(0, idx).trim()] = part.slice(idx + 1).trim();
16652
+ }
16653
+ return Object.keys(out).length > 0 ? out : undefined;
16654
+ }, exports_external.record(exports_external.string(), exports_external.string()).optional());
16655
+ var ok2 = (res) => res.ok ? JSON.stringify(res.data, null, 2) : `❌ Failed (${res.status}): ${JSON.stringify(res.data)}`;
16656
+ var simple = (res, msg) => res.ok ? `✅ ${msg}` : `❌ Failed (${res.status}): ${JSON.stringify(res.data)}`;
16657
+ function registerGatewayTools(server, http, options = {}) {
16658
+ const { projectRef } = options;
16659
+ server.tool("gateway", `Gateway / Caddy 配置(通过 JSON Admin API 注入)。要求 admin 权限。
16660
+ 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`, {
16661
+ action: exports_external.enum([
16662
+ "routes",
16663
+ "upsert_route",
16664
+ "update_route",
16665
+ "delete_route",
16666
+ "config",
16667
+ "get_certificate",
16668
+ "update_certificate",
16669
+ "issue_certificate",
16670
+ "deploy_certificate",
16671
+ "rebuild",
16672
+ "custom_hostname",
16673
+ "set_custom_hostname",
16674
+ "delete_custom_hostname",
16675
+ "verify_custom_hostname"
16676
+ ]).describe("Action"),
16677
+ ref: exports_external.string().optional().describe(projectRef ? "可选:覆盖自动关联的项目 ref" : "项目 ref"),
16678
+ route_id: exports_external.string().optional().describe("[upsert_route/update_route/delete_route] 路由 ID(字母/数字/_/-,1-64)"),
16679
+ hosts: stringArray.describe("[upsert_route/update_route] 主机名列表,逗号分隔或 JSON 数组(1-20)"),
16680
+ paths: stringArray.describe("[upsert_route/update_route] 路径列表,逗号分隔或 JSON 数组(1-20)"),
16681
+ upstream: exports_external.string().optional().describe("[upsert_route/update_route] 反代上游 host:port 或 http(s)://host[:port]"),
16682
+ upstream_tls_insecure_skip_verify: exports_external.boolean().optional().describe("[upsert_route/update_route] 上游 TLS 跳过校验"),
16683
+ static_root: exports_external.string().optional().describe("[upsert_route/update_route] 静态站点根目录(与 upstream 二选一)"),
16684
+ rewrite_uri: exports_external.string().optional().describe("[upsert_route/update_route] 重写 URI(以 / 开头)"),
16685
+ strip_prefix: exports_external.string().optional().describe("[upsert_route/update_route] 去除前缀"),
16686
+ headers: headersRecord.describe("[upsert_route/update_route] 自定义请求头,JSON 或 K:V,K2:V2"),
16687
+ cors: stringArray.describe("[upsert_route/update_route] 额外 CORS 源,逗号分隔"),
16688
+ priority: exports_external.number().optional().describe("[upsert_route/update_route] 路由优先级"),
16689
+ enabled: exports_external.boolean().optional().describe("[upsert_route/update_route] 是否启用"),
16690
+ rate_limit_tier: exports_external.enum(["free", "pro", "enterprise"]).optional().describe("[config] 限流档位"),
16691
+ cors_origins: exports_external.string().optional().describe("[config] CORS 源(逗号分隔)"),
16692
+ jwt_enabled: exports_external.boolean().optional().describe("[config] 是否启用 JWT"),
16693
+ jwt_secret: exports_external.string().optional().describe("[config] JWT 密钥"),
16694
+ cert_mode: exports_external.enum(["lego", "manual"]).optional().describe("[update_certificate] 证书模式"),
16695
+ challenge: exports_external.enum(["dns-01", "http-01"]).optional().describe("[update_certificate/issue_certificate] ACME challenge"),
16696
+ email: exports_external.string().optional().describe("[update_certificate/issue_certificate] ACME 邮箱"),
16697
+ dns_provider: exports_external.string().optional().describe("[update_certificate/issue_certificate] DNS 提供商"),
16698
+ dns_env: stringArray.describe("[update_certificate/issue_certificate] DNS 环境变量 KEY=VALUE 列表"),
16699
+ domains: stringArray.describe("[update_certificate/issue_certificate/deploy_certificate] 域名列表"),
16700
+ auto_renew: exports_external.boolean().optional().describe("[update_certificate/issue_certificate] 自动续期"),
16701
+ renew: exports_external.boolean().optional().describe("[issue_certificate] 仅续期已有证书"),
16702
+ cert: exports_external.string().optional().describe("[deploy_certificate] PEM 证书内容"),
16703
+ key: exports_external.string().optional().describe("[deploy_certificate] PEM 私钥内容"),
16704
+ clean: exports_external.boolean().optional().describe("[rebuild] 清理后全量重建"),
16705
+ custom_hostname: exports_external.string().optional().describe("[set_custom_hostname] 自定义域名")
16706
+ }, async (args) => {
16707
+ const resolveRef4 = (override) => {
16708
+ const ref2 = projectRef || override;
16709
+ if (!ref2)
16710
+ throw new Error("'ref' is required for this action");
16711
+ return ref2;
16712
+ };
16713
+ const need = (field, value) => {
16714
+ if (value === undefined || value === null || value === "")
16715
+ throw new Error(`'${field}' is required for '${args.action}'`);
16716
+ };
16717
+ const {
16718
+ action,
16719
+ ref,
16720
+ route_id,
16721
+ hosts,
16722
+ paths,
16723
+ upstream,
16724
+ upstream_tls_insecure_skip_verify,
16725
+ static_root,
16726
+ rewrite_uri,
16727
+ strip_prefix,
16728
+ headers,
16729
+ cors,
16730
+ priority,
16731
+ enabled,
16732
+ rate_limit_tier,
16733
+ cors_origins,
16734
+ jwt_enabled,
16735
+ jwt_secret,
16736
+ cert_mode,
16737
+ challenge,
16738
+ email: email3,
16739
+ dns_provider,
16740
+ dns_env,
16741
+ domains,
16742
+ auto_renew,
16743
+ renew,
16744
+ cert,
16745
+ key,
16746
+ clean,
16747
+ custom_hostname
16748
+ } = args;
16749
+ const projectRefValue = resolveRef4(ref);
16750
+ let text;
16751
+ switch (action) {
16752
+ case "routes":
16753
+ text = ok2(await http.get(`/v1/projects/${projectRefValue}/gateway/routes`));
16754
+ break;
16755
+ case "upsert_route": {
16756
+ need("route_id", route_id);
16757
+ need("hosts", hosts);
16758
+ need("paths", paths);
16759
+ const body = {
16760
+ id: route_id,
16761
+ hosts,
16762
+ path: paths.length === 1 ? paths[0] : paths
16763
+ };
16764
+ if (upstream !== undefined)
16765
+ body.upstream = upstream;
16766
+ if (upstream_tls_insecure_skip_verify !== undefined)
16767
+ body.upstream_tls_insecure_skip_verify = upstream_tls_insecure_skip_verify;
16768
+ if (static_root !== undefined)
16769
+ body.static_root = static_root;
16770
+ if (rewrite_uri !== undefined)
16771
+ body.rewrite_uri = rewrite_uri;
16772
+ if (strip_prefix !== undefined)
16773
+ body.strip_prefix = strip_prefix;
16774
+ if (headers !== undefined)
16775
+ body.headers = headers;
16776
+ if (cors !== undefined)
16777
+ body.cors = cors;
16778
+ if (priority !== undefined)
16779
+ body.priority = priority;
16780
+ if (enabled !== undefined)
16781
+ body.enabled = enabled;
16782
+ text = ok2(await http.post(`/v1/projects/${projectRefValue}/gateway/routes`, body));
16783
+ break;
16784
+ }
16785
+ case "update_route": {
16786
+ need("route_id", route_id);
16787
+ need("hosts", hosts);
16788
+ need("paths", paths);
16789
+ const body = {
16790
+ hosts,
16791
+ path: paths.length === 1 ? paths[0] : paths
16792
+ };
16793
+ if (upstream !== undefined)
16794
+ body.upstream = upstream;
16795
+ if (upstream_tls_insecure_skip_verify !== undefined)
16796
+ body.upstream_tls_insecure_skip_verify = upstream_tls_insecure_skip_verify;
16797
+ if (static_root !== undefined)
16798
+ body.static_root = static_root;
16799
+ if (rewrite_uri !== undefined)
16800
+ body.rewrite_uri = rewrite_uri;
16801
+ if (strip_prefix !== undefined)
16802
+ body.strip_prefix = strip_prefix;
16803
+ if (headers !== undefined)
16804
+ body.headers = headers;
16805
+ if (cors !== undefined)
16806
+ body.cors = cors;
16807
+ if (priority !== undefined)
16808
+ body.priority = priority;
16809
+ if (enabled !== undefined)
16810
+ body.enabled = enabled;
16811
+ text = ok2(await http.put(`/v1/projects/${projectRefValue}/gateway/routes/${route_id}`, body));
16812
+ break;
16813
+ }
16814
+ case "delete_route": {
16815
+ need("route_id", route_id);
16816
+ text = ok2(await http.delete(`/v1/projects/${projectRefValue}/gateway/routes/${route_id}`));
16817
+ break;
16818
+ }
16819
+ case "config": {
16820
+ const body = {};
16821
+ if (rate_limit_tier !== undefined)
16822
+ body.rate_limit_tier = rate_limit_tier;
16823
+ if (cors_origins !== undefined)
16824
+ body.cors_origins = cors_origins;
16825
+ if (jwt_enabled !== undefined)
16826
+ body.jwt_enabled = jwt_enabled;
16827
+ if (jwt_secret !== undefined)
16828
+ body.jwt_secret = jwt_secret;
16829
+ if (Object.keys(body).length === 0)
16830
+ throw new Error("At least one of rate_limit_tier, cors_origins, jwt_enabled, jwt_secret is required");
16831
+ text = ok2(await http.post(`/v1/projects/${projectRefValue}/gateway/config`, body));
16832
+ break;
16833
+ }
16834
+ case "get_certificate":
16835
+ text = ok2(await http.get(`/v1/projects/${projectRefValue}/gateway/certificate`));
16836
+ break;
16837
+ case "update_certificate": {
16838
+ const body = {};
16839
+ if (cert_mode !== undefined)
16840
+ body.mode = cert_mode;
16841
+ if (challenge !== undefined)
16842
+ body.challenge = challenge;
16843
+ if (email3 !== undefined)
16844
+ body.email = email3;
16845
+ if (dns_provider !== undefined)
16846
+ body.dns_provider = dns_provider;
16847
+ if (dns_env !== undefined)
16848
+ body.dns_env = dns_env;
16849
+ if (domains !== undefined)
16850
+ body.domains = domains;
16851
+ if (auto_renew !== undefined)
16852
+ body.auto_renew = auto_renew;
16853
+ text = ok2(await http.put(`/v1/projects/${projectRefValue}/gateway/certificate`, body));
16854
+ break;
16855
+ }
16856
+ case "issue_certificate": {
16857
+ const body = {};
16858
+ if (challenge !== undefined)
16859
+ body.challenge = challenge;
16860
+ if (email3 !== undefined)
16861
+ body.email = email3;
16862
+ if (dns_provider !== undefined)
16863
+ body.dns_provider = dns_provider;
16864
+ if (dns_env !== undefined)
16865
+ body.dns_env = dns_env;
16866
+ if (domains !== undefined)
16867
+ body.domains = domains;
16868
+ if (auto_renew !== undefined)
16869
+ body.auto_renew = auto_renew;
16870
+ if (renew !== undefined)
16871
+ body.renew = renew;
16872
+ text = ok2(await http.post(`/v1/projects/${projectRefValue}/gateway/certificate/issue`, body));
16873
+ break;
16874
+ }
16875
+ case "deploy_certificate": {
16876
+ need("cert", cert);
16877
+ need("key", key);
16878
+ const body = { cert, key };
16879
+ if (domains !== undefined)
16880
+ body.domains = domains;
16881
+ text = ok2(await http.post(`/v1/projects/${projectRefValue}/gateway/certificate/deploy`, body));
16882
+ break;
16883
+ }
16884
+ case "rebuild": {
16885
+ const query = clean ? "?clean=true" : "";
16886
+ text = ok2(await http.post(`/v1/projects/${projectRefValue}/gateway/rebuild-all${query}`));
16887
+ break;
16888
+ }
16889
+ case "custom_hostname":
16890
+ text = ok2(await http.get(`/v1/projects/${projectRefValue}/custom-hostname`));
16891
+ break;
16892
+ case "set_custom_hostname": {
16893
+ need("custom_hostname", custom_hostname);
16894
+ text = simple(await http.post(`/v1/projects/${projectRefValue}/custom-hostname`, { custom_hostname }), `Custom hostname ${custom_hostname} requested`);
16895
+ break;
16896
+ }
16897
+ case "delete_custom_hostname":
16898
+ text = simple(await http.delete(`/v1/projects/${projectRefValue}/custom-hostname`), "Custom hostname removed");
16899
+ break;
16900
+ case "verify_custom_hostname":
16901
+ text = ok2(await http.post(`/v1/projects/${projectRefValue}/custom-hostname/verify`));
16902
+ break;
16903
+ default:
16904
+ text = `❌ Unknown action: ${action}`;
16905
+ }
16906
+ return { content: [{ type: "text", text }] };
16907
+ });
16908
+ }
16909
+
16616
16910
  // src/index.ts
16617
16911
  var invokedCommand = path.basename(process.argv[1] || "supacloud-cli");
16618
16912
  var commandName = invokedCommand === "supacloud" ? "supacloud" : "supacloud-cli";
@@ -16694,6 +16988,10 @@ EXAMPLES
16694
16988
  ${preferredCommand} database push_migrations --ref abc123 --dir supabase/migrations --dry_run
16695
16989
  ${preferredCommand} edge_functions deploy --ref abc123 --slug hello --path ./supabase/functions/hello
16696
16990
  ${preferredCommand} edge_functions config --ref abc123 --slug hello --verify_jwt false --background_routes "/queue/*,/render/*"
16991
+ ${preferredCommand} gateway routes --ref abc123
16992
+ ${preferredCommand} gateway upsert_route --ref abc123 --route_id webhook --hosts "api.example.com" --paths "/webhook/*" --upstream 10.0.0.5:8080
16993
+ ${preferredCommand} gateway config --ref abc123 --rate_limit_tier pro
16994
+ ${preferredCommand} gateway rebuild --ref abc123 --clean
16697
16995
 
16698
16996
  SEPARATE ADMIN CLI
16699
16997
 
@@ -16748,7 +17046,7 @@ function createCliTools() {
16748
17046
  ]
16749
17047
  })
16750
17048
  };
16751
- for (const name of ["database", "auth", "storage", "edge_functions", "secrets", "frontend", "queue", "task_events", "diagnostics"]) {
17049
+ for (const name of ["database", "auth", "storage", "edge_functions", "secrets", "frontend", "queue", "task_events", "diagnostics", "gateway"]) {
16752
17050
  tools[name] = {
16753
17051
  schema: { action: genericActionSchema },
16754
17052
  callback: async () => ({
@@ -16811,6 +17109,9 @@ function createCliTools() {
16811
17109
  assign(captureTools((server) => registerStorageTools(server, http)));
16812
17110
  assign(captureTools((server) => registerAdvancedTools(server, http)));
16813
17111
  assign(captureTools((server) => registerFrontendTools(server, http)));
17112
+ assign(captureTools((server) => registerGatewayTools(server, http, {
17113
+ projectRef: context.projectRef || undefined
17114
+ })));
16814
17115
  assign(captureTools((server) => registerQueueTools(server, http, {
16815
17116
  projectRef: context.projectRef || undefined
16816
17117
  })));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/cli",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "Project-scoped CLI for SupaCloud users",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -34,7 +34,7 @@
34
34
  "zod": "^4.4.3"
35
35
  },
36
36
  "devDependencies": {
37
- "@types/bun": "^1.3.9",
38
- "typescript": "^6.0.0"
37
+ "@types/bun": "^1.3.14",
38
+ "typescript": "^6.0.3"
39
39
  }
40
40
  }