@cloudbase/cloudbase-mcp 2.25.10 → 2.26.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/cli.cjs +5 -5
- package/dist/index.cjs +547 -79
- package/dist/index.js +547 -79
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -41,6 +41,7 @@ const auth_js_1 = __webpack_require__(7291);
|
|
|
41
41
|
const cloudbase_manager_js_1 = __webpack_require__(3431);
|
|
42
42
|
const logger_js_1 = __webpack_require__(3039);
|
|
43
43
|
const tool_result_js_1 = __webpack_require__(9835);
|
|
44
|
+
const gateway_access_urls_js_1 = __webpack_require__(2435);
|
|
44
45
|
const env_setup_js_1 = __webpack_require__(6978);
|
|
45
46
|
/**
|
|
46
47
|
* Resources accepted by CreateEnv. Matches Cloud API / Manager SDK contract.
|
|
@@ -793,6 +794,89 @@ async function enrichEnvInfoWithMissingFields(manager, result, envId) {
|
|
|
793
794
|
return result;
|
|
794
795
|
}
|
|
795
796
|
}
|
|
797
|
+
/**
|
|
798
|
+
* Project gateway Route.Enable onto envQuery(info) without mutating StaticDomain.
|
|
799
|
+
*
|
|
800
|
+
* StaticStorages[].StaticDomain stays the cloud-API nominal hostname.
|
|
801
|
+
* Sibling field staticDomainRouteEnabled (and EnvInfo-level mirrors for the
|
|
802
|
+
* primary store) tell callers whether the default hosting domain route is
|
|
803
|
+
* actually reachable (Enable !== false). Matches queryHosting websiteConfig.
|
|
804
|
+
*/
|
|
805
|
+
async function enrichEnvInfoWithStaticDomainRouteEnabled(manager, result, envId) {
|
|
806
|
+
const envInfo = result?.EnvInfo;
|
|
807
|
+
if (!envInfo || typeof envInfo !== "object") {
|
|
808
|
+
return result;
|
|
809
|
+
}
|
|
810
|
+
const staticStorages = Array.isArray(envInfo.StaticStorages)
|
|
811
|
+
? envInfo.StaticStorages
|
|
812
|
+
: null;
|
|
813
|
+
if (!staticStorages || staticStorages.length === 0) {
|
|
814
|
+
return result;
|
|
815
|
+
}
|
|
816
|
+
if (typeof manager?.env?.describeHttpServiceRoute !== "function") {
|
|
817
|
+
return result;
|
|
818
|
+
}
|
|
819
|
+
const resolvedEnvId = (typeof envId === "string" && envId.trim()) ||
|
|
820
|
+
(typeof envInfo.EnvId === "string" && envInfo.EnvId.trim()) ||
|
|
821
|
+
undefined;
|
|
822
|
+
if (!resolvedEnvId) {
|
|
823
|
+
return result;
|
|
824
|
+
}
|
|
825
|
+
try {
|
|
826
|
+
const routeResult = await manager.env.describeHttpServiceRoute({
|
|
827
|
+
EnvId: resolvedEnvId,
|
|
828
|
+
Limit: 1000,
|
|
829
|
+
});
|
|
830
|
+
const routes = (0, gateway_access_urls_js_1.flattenHttpServiceRoutes)(routeResult);
|
|
831
|
+
let primaryRouteEnabled = null;
|
|
832
|
+
const enrichedStorages = staticStorages.map((store, index) => {
|
|
833
|
+
if (!store || typeof store !== "object" || Array.isArray(store)) {
|
|
834
|
+
return store;
|
|
835
|
+
}
|
|
836
|
+
const record = store;
|
|
837
|
+
const domain = typeof record.StaticDomain === "string" ? record.StaticDomain : "";
|
|
838
|
+
if (!domain) {
|
|
839
|
+
return store;
|
|
840
|
+
}
|
|
841
|
+
const reachable = (0, gateway_access_urls_js_1.isDomainPathReachableViaGateway)(routes, domain, "/");
|
|
842
|
+
if (index === 0) {
|
|
843
|
+
primaryRouteEnabled = reachable;
|
|
844
|
+
}
|
|
845
|
+
return {
|
|
846
|
+
...record,
|
|
847
|
+
staticDomainRouteEnabled: reachable,
|
|
848
|
+
};
|
|
849
|
+
});
|
|
850
|
+
const enrichment = {
|
|
851
|
+
StaticStorages: enrichedStorages,
|
|
852
|
+
};
|
|
853
|
+
if (primaryRouteEnabled !== null) {
|
|
854
|
+
enrichment.staticDomainRouteEnabled = primaryRouteEnabled;
|
|
855
|
+
enrichment.accessUrlReachable = primaryRouteEnabled !== false;
|
|
856
|
+
if (primaryRouteEnabled === false) {
|
|
857
|
+
const primaryDomain = typeof enrichedStorages[0]
|
|
858
|
+
?.StaticDomain === "string"
|
|
859
|
+
? String(enrichedStorages[0].StaticDomain)
|
|
860
|
+
: "";
|
|
861
|
+
enrichment.routeDisabled = true;
|
|
862
|
+
if (primaryDomain) {
|
|
863
|
+
enrichment.disabledAccessUrls = [`https://${primaryDomain}/`];
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
return {
|
|
868
|
+
...result,
|
|
869
|
+
EnvInfo: {
|
|
870
|
+
...envInfo,
|
|
871
|
+
...enrichment,
|
|
872
|
+
},
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
catch {
|
|
876
|
+
// Gateway route lookup is best-effort enrichment only.
|
|
877
|
+
return result;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
796
880
|
function normalizeOptionalToolString(value) {
|
|
797
881
|
return typeof value === "string" && value.trim().length > 0
|
|
798
882
|
? value.trim()
|
|
@@ -1274,8 +1358,9 @@ function registerEnvTools(server) {
|
|
|
1274
1358
|
if (action === "status") {
|
|
1275
1359
|
const loginState = await (0, auth_js_1.peekLoginState)();
|
|
1276
1360
|
const authFlowState = await (0, auth_js_1.getAuthProgressState)();
|
|
1277
|
-
//
|
|
1278
|
-
const
|
|
1361
|
+
// Detect API Key mode (CLOUDBASE_API_KEY preferred, CLOUDBASE_APIKEY fallback)
|
|
1362
|
+
const apiKeyFromEnv = (0, auth_js_1.getCloudBaseApiKeyFromEnv)();
|
|
1363
|
+
const isApiKeyMode = !!(apiKeyFromEnv && process.env.CLOUDBASE_ENV_ID);
|
|
1279
1364
|
const authStatus = loginState
|
|
1280
1365
|
? "READY"
|
|
1281
1366
|
: authFlowState.status === "PENDING"
|
|
@@ -1323,8 +1408,9 @@ function registerEnvTools(server) {
|
|
|
1323
1408
|
});
|
|
1324
1409
|
}
|
|
1325
1410
|
if (action === "start_auth") {
|
|
1326
|
-
// API Key
|
|
1327
|
-
|
|
1411
|
+
// API Key mode: exchange for temporary credentials
|
|
1412
|
+
const apiKeyFromEnv = (0, auth_js_1.getCloudBaseApiKeyFromEnv)();
|
|
1413
|
+
if (apiKeyFromEnv && process.env.CLOUDBASE_ENV_ID) {
|
|
1328
1414
|
try {
|
|
1329
1415
|
const existingLoginState = await (0, auth_js_1.peekLoginState)();
|
|
1330
1416
|
if (existingLoginState) {
|
|
@@ -1338,22 +1424,21 @@ function registerEnvTools(server) {
|
|
|
1338
1424
|
}
|
|
1339
1425
|
}
|
|
1340
1426
|
catch (e) {
|
|
1341
|
-
// peekLoginState
|
|
1427
|
+
// peekLoginState threw; log and fall through to diagnostic
|
|
1342
1428
|
(0, logger_js_1.debug)("start_auth: peekLoginState threw", { error: e instanceof Error ? e.message : String(e) });
|
|
1343
1429
|
}
|
|
1344
|
-
// API Key
|
|
1345
|
-
// 尝试获取更详细的错误信息
|
|
1430
|
+
// API Key exchange failed: return diagnostic details
|
|
1346
1431
|
let diagMessage = "当前配置了 API Key 认证模式,但换取临时密钥失败。";
|
|
1347
1432
|
const endpoint = process.env.CLOUDBASE_API_ENDPOINT || `https://${process.env.CLOUDBASE_ENV_ID}.ap-shanghai.tcb-api.tencentcloudapi.com`;
|
|
1348
1433
|
diagMessage += `\n\n诊断信息:`;
|
|
1349
1434
|
diagMessage += `\n- CLOUDBASE_ENV_ID: ${process.env.CLOUDBASE_ENV_ID}`;
|
|
1350
|
-
diagMessage += `\n- CLOUDBASE_API_KEY: ${
|
|
1435
|
+
diagMessage += `\n- CLOUDBASE_API_KEY: ${apiKeyFromEnv.slice(0, 20)}...(已截断)`;
|
|
1351
1436
|
diagMessage += `\n- Endpoint: ${endpoint}`;
|
|
1352
1437
|
diagMessage += `\n\n可能原因:`;
|
|
1353
1438
|
diagMessage += `\n1. API Key 已过期或被删除`;
|
|
1354
1439
|
diagMessage += `\n2. Endpoint 不可达(网络/DNS 问题)`;
|
|
1355
1440
|
diagMessage += `\n3. CLOUDBASE_ENV_ID 与 API Key 所属环境不匹配`;
|
|
1356
|
-
diagMessage += `\n\n建议:检查 MCP 配置中的 CLOUDBASE_API_KEY
|
|
1441
|
+
diagMessage += `\n\n建议:检查 MCP 配置中的 CLOUDBASE_API_KEY(或兼容的 CLOUDBASE_APIKEY)和 CLOUDBASE_ENV_ID 环境变量是否正确。`;
|
|
1357
1442
|
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1358
1443
|
ok: false,
|
|
1359
1444
|
code: "API_KEY_AUTH_FAILED",
|
|
@@ -1639,12 +1724,12 @@ function registerEnvTools(server) {
|
|
|
1639
1724
|
});
|
|
1640
1725
|
}
|
|
1641
1726
|
if (action === "logout") {
|
|
1642
|
-
// API Key
|
|
1643
|
-
if (
|
|
1727
|
+
// Disallow logout in API Key mode
|
|
1728
|
+
if ((0, auth_js_1.getCloudBaseApiKeyFromEnv)() && process.env.CLOUDBASE_ENV_ID) {
|
|
1644
1729
|
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
1645
1730
|
ok: false,
|
|
1646
1731
|
code: "LOGOUT_NOT_ALLOWED",
|
|
1647
|
-
message: "当前使用 API Key 认证模式,不支持退出登录。如需切换认证方式,请移除 CLOUDBASE_API_KEY
|
|
1732
|
+
message: "当前使用 API Key 认证模式,不支持退出登录。如需切换认证方式,请移除 CLOUDBASE_API_KEY(或兼容的 CLOUDBASE_APIKEY)环境变量后重启。",
|
|
1648
1733
|
auth_mode: "api_key",
|
|
1649
1734
|
});
|
|
1650
1735
|
}
|
|
@@ -1866,6 +1951,8 @@ function registerEnvTools(server) {
|
|
|
1866
1951
|
if (envId) {
|
|
1867
1952
|
result = await enrichEnvInfoWithMissingFields(cloudbaseInfo, result, envId);
|
|
1868
1953
|
}
|
|
1954
|
+
// Project gateway Route.Enable next to StaticDomain (do not rewrite StaticDomain).
|
|
1955
|
+
result = await enrichEnvInfoWithStaticDomainRouteEnabled(cloudbaseInfo, result, envId);
|
|
1869
1956
|
result = await enrichEnvInfoWithRuntimeMode(result, cloudbaseInfo);
|
|
1870
1957
|
break;
|
|
1871
1958
|
case "domains":
|
|
@@ -1934,7 +2021,7 @@ function registerEnvTools(server) {
|
|
|
1934
2021
|
// Register primary tool name (queryEnv)
|
|
1935
2022
|
const queryEnvToolSchema = {
|
|
1936
2023
|
title: "CloudBase 环境查询",
|
|
1937
|
-
description: "查询 CloudBase 环境相关信息,支持查询环境列表、指定环境详情和安全域名。(曾用名:envQuery、listEnvs、getEnvInfo、getEnvAuthDomains)当 action=list 时,会按 DescribeEnvs 语义做列表/筛选,标准返回字段为 EnvId、Alias、Status、EnvType、Region、PackageId、PackageName、IsDefault,并支持通过 fields 白名单裁剪这些字段;aliasExact=true 时会按别名精确筛选,避免把前缀相近的环境误当作候选;即使传入 envId,action=list 也只返回摘要,不会返回完整资源明细或 expiry。如需查询某个已知 EnvId 对应环境的详细信息(包括资源字段和计费信息),必须使用 action=info 并传入目标环境的 envId 参数。action=info 会在可用时补充 BillingInfo(如 ExpireTime、PayMode、IsAutoRenew 等计费字段)。\n\n🔍 action=info 还会派生三个用于后端选型的字段:\n- `EnvInfo.RuntimeMode`:'postgresql' 或 'nosql',表示新业务建议默认使用的后端(PG 已开通时为 postgresql,否则为 nosql)。\n- `EnvInfo.RuntimeBackends`:`{postgresql, nosql, mysql}` 三个布尔值,描述当前环境实际并存的后端。\n- `EnvInfo.RuntimeModeHints`:每个后端对应的 API/工具/skill 提示。\n\nAI 在写业务/权限/存储代码前必须先看这三项:PG 模式下新业务推荐 `app.rdb()` + RLS(`managePgDatabase action=execute` 跑 `CREATE POLICY`)+ pgstore;已存在的 NoSQL 集合 / 旧 storage / `managePermissions(resourceType=\"noSqlDatabase\")` 在 PG 环境下仍然有效。真正不适用的是 MySQL:当 `RuntimeBackends.mysql === false` 时,`manageMysqlDatabase` / `queryMysqlDatabase` / `relational-database-mcp-cloudbase` skill 都不该使用。",
|
|
2024
|
+
description: "查询 CloudBase 环境相关信息,支持查询环境列表、指定环境详情和安全域名。(曾用名:envQuery、listEnvs、getEnvInfo、getEnvAuthDomains)当 action=list 时,会按 DescribeEnvs 语义做列表/筛选,标准返回字段为 EnvId、Alias、Status、EnvType、Region、PackageId、PackageName、IsDefault,并支持通过 fields 白名单裁剪这些字段;aliasExact=true 时会按别名精确筛选,避免把前缀相近的环境误当作候选;即使传入 envId,action=list 也只返回摘要,不会返回完整资源明细或 expiry。如需查询某个已知 EnvId 对应环境的详细信息(包括资源字段和计费信息),必须使用 action=info 并传入目标环境的 envId 参数。action=info 会在可用时补充 BillingInfo(如 ExpireTime、PayMode、IsAutoRenew 等计费字段)。\n\n🔍 action=info 还会派生三个用于后端选型的字段:\n- `EnvInfo.RuntimeMode`:'postgresql' 或 'nosql',表示新业务建议默认使用的后端(PG 已开通时为 postgresql,否则为 nosql)。\n- `EnvInfo.RuntimeBackends`:`{postgresql, nosql, mysql}` 三个布尔值,描述当前环境实际并存的后端。\n- `EnvInfo.RuntimeModeHints`:每个后端对应的 API/工具/skill 提示。\n\n🌐 action=info 还会在不改写 `StaticStorages[].StaticDomain`(云 API 名义域名)的前提下,投影网关路由 Enable 状态:`StaticStorages[].staticDomainRouteEnabled` 与 `EnvInfo.staticDomainRouteEnabled`(与 queryHosting websiteConfig 同源)。`false` 表示默认静态域名根路由已禁用(访问会返回 GATEWAY_ROUTE_DISABLED),勿把名义域名当成可达 URL。\n\nAI 在写业务/权限/存储代码前必须先看这三项:PG 模式下新业务推荐 `app.rdb()` + RLS(`managePgDatabase action=execute` 跑 `CREATE POLICY`)+ pgstore;已存在的 NoSQL 集合 / 旧 storage / `managePermissions(resourceType=\"noSqlDatabase\")` 在 PG 环境下仍然有效。真正不适用的是 MySQL:当 `RuntimeBackends.mysql === false` 时,`manageMysqlDatabase` / `queryMysqlDatabase` / `relational-database-mcp-cloudbase` skill 都不该使用。",
|
|
1938
2025
|
inputSchema: {
|
|
1939
2026
|
action: zod_1.z
|
|
1940
2027
|
.enum(["list", "info", "domains"])
|
|
@@ -2816,7 +2903,7 @@ ${envIdSection}
|
|
|
2816
2903
|
## 环境信息
|
|
2817
2904
|
- 操作系统: ${os_1.default.type()} ${os_1.default.release()}
|
|
2818
2905
|
- Node.js版本: ${process.version}
|
|
2819
|
-
- MCP 版本:${process.env.npm_package_version || ( true ? "2.
|
|
2906
|
+
- MCP 版本:${process.env.npm_package_version || ( true ? "2.26.0" : 0)}
|
|
2820
2907
|
- 系统架构: ${os_1.default.arch()}
|
|
2821
2908
|
- 时间: ${new Date().toISOString()}
|
|
2822
2909
|
- 请求ID: ${requestId}
|
|
@@ -2897,7 +2984,7 @@ function createWrappedHandler(name, handler, server) {
|
|
|
2897
2984
|
requestId: (typeof error === 'object' && error && 'requestId' in error) ? error.requestId : '',
|
|
2898
2985
|
ide: server.ide || process.env.INTEGRATION_IDE || ''
|
|
2899
2986
|
});
|
|
2900
|
-
const mcpVersion = true ? "2.
|
|
2987
|
+
const mcpVersion = true ? "2.26.0" : 0;
|
|
2901
2988
|
const requestIdSuffix = requestId ? `\n🆔 RequestId: ${requestId}` : '';
|
|
2902
2989
|
const enhancedErrorMessage = `${errorMessage}${requestIdSuffix}\n\n📦 CloudBase MCP v${mcpVersion}\n🔗 遇到问题?请复制以下链接到浏览器打开\n即可自动携带错误详情快速创建 GitHub Issue:\n${issueLink}`;
|
|
2903
2990
|
// 创建新的错误对象,保持原有的错误类型但更新消息
|
|
@@ -3499,12 +3586,19 @@ module.exports = __WEBPACK_EXTERNAL_MODULE__modelcontextprotocol_sdk_types_js_70
|
|
|
3499
3586
|
|
|
3500
3587
|
|
|
3501
3588
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
3589
|
+
exports.isGatewayRouteEnabled = isGatewayRouteEnabled;
|
|
3590
|
+
exports.isDomainPathReachableViaGateway = isDomainPathReachableViaGateway;
|
|
3502
3591
|
exports.rankGatewayAccessUrls = rankGatewayAccessUrls;
|
|
3503
3592
|
exports.flattenHttpServiceRoutes = flattenHttpServiceRoutes;
|
|
3504
3593
|
exports.filterRoutesByUpstream = filterRoutesByUpstream;
|
|
3505
3594
|
exports.toAccessUrlEnvelope = toAccessUrlEnvelope;
|
|
3506
3595
|
exports.resolveGatewayAccessUrls = resolveGatewayAccessUrls;
|
|
3596
|
+
exports.resolveAllGatewayRoutes = resolveAllGatewayRoutes;
|
|
3507
3597
|
exports.preferGatewayOrFallback = preferGatewayOrFallback;
|
|
3598
|
+
/** Route is reachable unless Enable is explicitly false. */
|
|
3599
|
+
function isGatewayRouteEnabled(route) {
|
|
3600
|
+
return route.Enable !== false;
|
|
3601
|
+
}
|
|
3508
3602
|
function normalizeAccessPath(path) {
|
|
3509
3603
|
if (!path) {
|
|
3510
3604
|
return "/";
|
|
@@ -3514,13 +3608,63 @@ function normalizeAccessPath(path) {
|
|
|
3514
3608
|
function buildHttpsUrl(domain, path) {
|
|
3515
3609
|
return `https://${domain}${normalizeAccessPath(path)}`;
|
|
3516
3610
|
}
|
|
3611
|
+
function extractHostname(urlOrHost) {
|
|
3612
|
+
const trimmed = urlOrHost.trim();
|
|
3613
|
+
if (!trimmed) {
|
|
3614
|
+
return "";
|
|
3615
|
+
}
|
|
3616
|
+
try {
|
|
3617
|
+
if (trimmed.includes("://")) {
|
|
3618
|
+
return new URL(trimmed).hostname.toLowerCase();
|
|
3619
|
+
}
|
|
3620
|
+
}
|
|
3621
|
+
catch {
|
|
3622
|
+
// Fall through to treat as bare hostname.
|
|
3623
|
+
}
|
|
3624
|
+
return trimmed.replace(/\/.*$/, "").toLowerCase();
|
|
3625
|
+
}
|
|
3626
|
+
/**
|
|
3627
|
+
* Whether a hostname has an enabled covering gateway route for the given path.
|
|
3628
|
+
* Longest matching Path prefix wins. Missing route info → unknown (null).
|
|
3629
|
+
*/
|
|
3630
|
+
function isDomainPathReachableViaGateway(routes, domainOrUrl, requestPath) {
|
|
3631
|
+
const hostname = extractHostname(domainOrUrl);
|
|
3632
|
+
if (!hostname) {
|
|
3633
|
+
return null;
|
|
3634
|
+
}
|
|
3635
|
+
const normalizedRequest = normalizeAccessPath(requestPath);
|
|
3636
|
+
const domainRoutes = routes.filter((route) => Boolean(route.Domain) &&
|
|
3637
|
+
route.Domain.toLowerCase() === hostname);
|
|
3638
|
+
if (domainRoutes.length === 0) {
|
|
3639
|
+
return null;
|
|
3640
|
+
}
|
|
3641
|
+
const covering = domainRoutes
|
|
3642
|
+
.map((route) => ({
|
|
3643
|
+
route,
|
|
3644
|
+
path: normalizeAccessPath(route.Path),
|
|
3645
|
+
}))
|
|
3646
|
+
.filter(({ path }) => {
|
|
3647
|
+
if (path === "/") {
|
|
3648
|
+
return true;
|
|
3649
|
+
}
|
|
3650
|
+
return (normalizedRequest === path ||
|
|
3651
|
+
normalizedRequest.startsWith(path.endsWith("/") ? path : `${path}/`));
|
|
3652
|
+
})
|
|
3653
|
+
.sort((a, b) => b.path.length - a.path.length);
|
|
3654
|
+
if (covering.length === 0) {
|
|
3655
|
+
return null;
|
|
3656
|
+
}
|
|
3657
|
+
return isGatewayRouteEnabled(covering[0].route);
|
|
3658
|
+
}
|
|
3517
3659
|
/**
|
|
3518
3660
|
* Rank gateway route URLs: custom domains (IsDefault !== true) before default domain.
|
|
3519
|
-
* Stable within each group; dedupe by full URL.
|
|
3661
|
+
* By default skips Enable === false routes. Stable within each group; dedupe by full URL.
|
|
3520
3662
|
*/
|
|
3521
|
-
function rankGatewayAccessUrls(routes) {
|
|
3663
|
+
function rankGatewayAccessUrls(routes, options) {
|
|
3664
|
+
const includeDisabled = options?.includeDisabled === true;
|
|
3522
3665
|
const mapped = routes
|
|
3523
3666
|
.filter((route) => Boolean(route.Domain))
|
|
3667
|
+
.filter((route) => includeDisabled || isGatewayRouteEnabled(route))
|
|
3524
3668
|
.map((route) => {
|
|
3525
3669
|
const path = normalizeAccessPath(route.Path);
|
|
3526
3670
|
const isDefault = route.IsDefault === true;
|
|
@@ -3529,6 +3673,7 @@ function rankGatewayAccessUrls(routes) {
|
|
|
3529
3673
|
domain: route.Domain,
|
|
3530
3674
|
path,
|
|
3531
3675
|
isDefault,
|
|
3676
|
+
enabled: isGatewayRouteEnabled(route),
|
|
3532
3677
|
source: (isDefault
|
|
3533
3678
|
? "gateway.default"
|
|
3534
3679
|
: "gateway.custom"),
|
|
@@ -3556,6 +3701,7 @@ function flattenHttpServiceRoutes(result) {
|
|
|
3556
3701
|
Domain: domainItem.Domain ?? "",
|
|
3557
3702
|
IsDefault: domainItem.IsDefault,
|
|
3558
3703
|
Path: typeof route.Path === "string" ? route.Path : undefined,
|
|
3704
|
+
Enable: typeof route.Enable === "boolean" ? route.Enable : undefined,
|
|
3559
3705
|
UpstreamResourceType: typeof route.UpstreamResourceType === "string"
|
|
3560
3706
|
? route.UpstreamResourceType
|
|
3561
3707
|
: undefined,
|
|
@@ -3578,64 +3724,116 @@ function filterRoutesByUpstream(routes, input) {
|
|
|
3578
3724
|
types.includes(String(route.UpstreamResourceType)));
|
|
3579
3725
|
});
|
|
3580
3726
|
}
|
|
3581
|
-
function toAccessUrlEnvelope(ranked) {
|
|
3582
|
-
|
|
3583
|
-
|
|
3727
|
+
function toAccessUrlEnvelope(ranked, disabledAccessUrls = []) {
|
|
3728
|
+
const enabled = ranked.filter((item) => item.enabled);
|
|
3729
|
+
if (enabled.length === 0) {
|
|
3730
|
+
return {
|
|
3731
|
+
accessUrls: [],
|
|
3732
|
+
routes: [],
|
|
3733
|
+
disabledAccessUrls,
|
|
3734
|
+
};
|
|
3584
3735
|
}
|
|
3585
3736
|
return {
|
|
3586
|
-
accessUrl:
|
|
3587
|
-
accessUrls:
|
|
3588
|
-
accessUrlSource:
|
|
3589
|
-
routes:
|
|
3737
|
+
accessUrl: enabled[0].url,
|
|
3738
|
+
accessUrls: enabled.map((item) => item.url),
|
|
3739
|
+
accessUrlSource: enabled[0].source,
|
|
3740
|
+
routes: enabled,
|
|
3741
|
+
disabledAccessUrls,
|
|
3590
3742
|
};
|
|
3591
3743
|
}
|
|
3744
|
+
async function loadHttpServiceRoutes(getManager, envId) {
|
|
3745
|
+
const manager = await getManager();
|
|
3746
|
+
const result = await manager.env.describeHttpServiceRoute({
|
|
3747
|
+
EnvId: envId,
|
|
3748
|
+
Limit: 1000,
|
|
3749
|
+
});
|
|
3750
|
+
return flattenHttpServiceRoutes(result);
|
|
3751
|
+
}
|
|
3592
3752
|
/**
|
|
3593
3753
|
* Best-effort lookup of gateway access URLs for an upstream resource.
|
|
3594
3754
|
* Never throws — returns empty lists on failure.
|
|
3755
|
+
* Disabled routes (Enable === false) are excluded from accessUrl(s).
|
|
3595
3756
|
*/
|
|
3596
3757
|
async function resolveGatewayAccessUrls(input) {
|
|
3597
3758
|
try {
|
|
3598
|
-
const
|
|
3599
|
-
const result = await manager.env.describeHttpServiceRoute({
|
|
3600
|
-
EnvId: input.envId,
|
|
3601
|
-
Limit: 1000,
|
|
3602
|
-
});
|
|
3603
|
-
const flattened = flattenHttpServiceRoutes(result);
|
|
3759
|
+
const flattened = await loadHttpServiceRoutes(input.getManager, input.envId);
|
|
3604
3760
|
const matched = filterRoutesByUpstream(flattened, {
|
|
3605
3761
|
upstreamResourceName: input.upstreamResourceName,
|
|
3606
3762
|
upstreamResourceTypes: input.upstreamResourceTypes,
|
|
3607
3763
|
});
|
|
3608
|
-
|
|
3764
|
+
const disabledAccessUrls = rankGatewayAccessUrls(matched, {
|
|
3765
|
+
includeDisabled: true,
|
|
3766
|
+
})
|
|
3767
|
+
.filter((item) => !item.enabled)
|
|
3768
|
+
.map((item) => item.url);
|
|
3769
|
+
return toAccessUrlEnvelope(rankGatewayAccessUrls(matched), disabledAccessUrls);
|
|
3770
|
+
}
|
|
3771
|
+
catch {
|
|
3772
|
+
return { accessUrls: [], routes: [], disabledAccessUrls: [] };
|
|
3773
|
+
}
|
|
3774
|
+
}
|
|
3775
|
+
/**
|
|
3776
|
+
* Load all flattened HTTP service routes (best-effort). Used by hosting to
|
|
3777
|
+
* judge whether the static-domain fallback itself is gateway-disabled.
|
|
3778
|
+
*/
|
|
3779
|
+
async function resolveAllGatewayRoutes(input) {
|
|
3780
|
+
try {
|
|
3781
|
+
return await loadHttpServiceRoutes(input.getManager, input.envId);
|
|
3609
3782
|
}
|
|
3610
3783
|
catch {
|
|
3611
|
-
return
|
|
3784
|
+
return [];
|
|
3612
3785
|
}
|
|
3613
3786
|
}
|
|
3614
3787
|
/**
|
|
3615
3788
|
* Merge ranked gateway URLs with a resource-native fallback URL.
|
|
3616
3789
|
* Gateway custom/default always win over the fallback when present.
|
|
3790
|
+
* Unreachable fallbacks are omitted from accessUrl(s) and listed under
|
|
3791
|
+
* disabledAccessUrls instead.
|
|
3617
3792
|
*/
|
|
3618
3793
|
function preferGatewayOrFallback(input) {
|
|
3794
|
+
const disabledAccessUrls = [
|
|
3795
|
+
...(input.gateway.disabledAccessUrls ?? []),
|
|
3796
|
+
];
|
|
3797
|
+
const fallbackReachable = input.fallbackReachable !== false;
|
|
3619
3798
|
if (input.gateway.accessUrl) {
|
|
3620
3799
|
const urls = [...input.gateway.accessUrls];
|
|
3621
3800
|
if (input.fallbackUrl &&
|
|
3801
|
+
fallbackReachable &&
|
|
3622
3802
|
!urls.includes(input.fallbackUrl)) {
|
|
3623
3803
|
urls.push(input.fallbackUrl);
|
|
3624
3804
|
}
|
|
3805
|
+
else if (input.fallbackUrl &&
|
|
3806
|
+
!fallbackReachable &&
|
|
3807
|
+
!disabledAccessUrls.includes(input.fallbackUrl)) {
|
|
3808
|
+
disabledAccessUrls.push(input.fallbackUrl);
|
|
3809
|
+
}
|
|
3625
3810
|
return {
|
|
3626
3811
|
accessUrl: input.gateway.accessUrl,
|
|
3627
3812
|
accessUrls: urls,
|
|
3628
3813
|
accessUrlSource: input.gateway.accessUrlSource,
|
|
3814
|
+
accessUrlReachable: true,
|
|
3815
|
+
disabledAccessUrls,
|
|
3629
3816
|
};
|
|
3630
3817
|
}
|
|
3631
|
-
if (input.fallbackUrl) {
|
|
3818
|
+
if (input.fallbackUrl && fallbackReachable) {
|
|
3632
3819
|
return {
|
|
3633
3820
|
accessUrl: input.fallbackUrl,
|
|
3634
3821
|
accessUrls: [input.fallbackUrl],
|
|
3635
3822
|
accessUrlSource: input.fallbackSource,
|
|
3823
|
+
accessUrlReachable: true,
|
|
3824
|
+
disabledAccessUrls,
|
|
3636
3825
|
};
|
|
3637
3826
|
}
|
|
3638
|
-
|
|
3827
|
+
if (input.fallbackUrl && !fallbackReachable) {
|
|
3828
|
+
if (!disabledAccessUrls.includes(input.fallbackUrl)) {
|
|
3829
|
+
disabledAccessUrls.push(input.fallbackUrl);
|
|
3830
|
+
}
|
|
3831
|
+
}
|
|
3832
|
+
return {
|
|
3833
|
+
accessUrls: [],
|
|
3834
|
+
accessUrlReachable: false,
|
|
3835
|
+
disabledAccessUrls,
|
|
3836
|
+
};
|
|
3639
3837
|
}
|
|
3640
3838
|
|
|
3641
3839
|
|
|
@@ -7892,6 +8090,8 @@ const MANAGE_GATEWAY_ACTIONS = [
|
|
|
7892
8090
|
"createRoute",
|
|
7893
8091
|
"updateRoute",
|
|
7894
8092
|
"deleteRoute",
|
|
8093
|
+
"enableRoute",
|
|
8094
|
+
"disableRoute",
|
|
7895
8095
|
"bindCustomDomain",
|
|
7896
8096
|
"deleteCustomDomain",
|
|
7897
8097
|
"enableService",
|
|
@@ -7964,6 +8164,7 @@ function registerGatewayTools(server) {
|
|
|
7964
8164
|
Domain: route.Domain,
|
|
7965
8165
|
Path: route.Path,
|
|
7966
8166
|
IsDefault: route.IsDefault,
|
|
8167
|
+
Enable: typeof route.Enable === "boolean" ? route.Enable : undefined,
|
|
7967
8168
|
UpstreamResourceType: route.UpstreamResourceType,
|
|
7968
8169
|
UpstreamResourceName: route.UpstreamResourceName,
|
|
7969
8170
|
}));
|
|
@@ -7974,16 +8175,26 @@ function registerGatewayTools(server) {
|
|
|
7974
8175
|
Domain: route.Domain,
|
|
7975
8176
|
Path: route.Path,
|
|
7976
8177
|
IsDefault: route.IsDefault,
|
|
8178
|
+
Enable: typeof route.Enable === "boolean" ? route.Enable : undefined,
|
|
7977
8179
|
UpstreamResourceType: route.UpstreamResourceType,
|
|
7978
8180
|
UpstreamResourceName: route.UpstreamResourceName,
|
|
7979
8181
|
}));
|
|
7980
|
-
const
|
|
8182
|
+
const disabledAccessUrls = (0, gateway_access_urls_js_1.rankGatewayAccessUrls)(candidates, {
|
|
8183
|
+
includeDisabled: true,
|
|
8184
|
+
})
|
|
8185
|
+
.filter((item) => !item.enabled)
|
|
8186
|
+
.map((item) => item.url);
|
|
8187
|
+
const envelope = (0, gateway_access_urls_js_1.toAccessUrlEnvelope)((0, gateway_access_urls_js_1.rankGatewayAccessUrls)(candidates), disabledAccessUrls);
|
|
7981
8188
|
return {
|
|
7982
8189
|
...(envelope.accessUrl ? { accessUrl: envelope.accessUrl } : {}),
|
|
7983
8190
|
accessUrls: envelope.accessUrls,
|
|
7984
8191
|
...(envelope.accessUrlSource
|
|
7985
8192
|
? { accessUrlSource: envelope.accessUrlSource }
|
|
7986
8193
|
: {}),
|
|
8194
|
+
accessUrlReachable: Boolean(envelope.accessUrl),
|
|
8195
|
+
...(disabledAccessUrls.length > 0
|
|
8196
|
+
? { disabledAccessUrls, routeDisabled: true }
|
|
8197
|
+
: {}),
|
|
7987
8198
|
};
|
|
7988
8199
|
};
|
|
7989
8200
|
const pickUsableDefaultDomain = (domains) => {
|
|
@@ -8073,6 +8284,15 @@ function registerGatewayTools(server) {
|
|
|
8073
8284
|
: input.enablePathTransmission !== undefined
|
|
8074
8285
|
? input.enablePathTransmission
|
|
8075
8286
|
: undefined;
|
|
8287
|
+
// Route enable: prefer route.enable. For updateRoute only, top-level
|
|
8288
|
+
// enable also maps to Route.Enable (enableService/authSwitch use the
|
|
8289
|
+
// same field name but different actions).
|
|
8290
|
+
const routeEnable = input.route?.enable !== undefined
|
|
8291
|
+
? input.route.enable
|
|
8292
|
+
: input.action === "updateRoute" &&
|
|
8293
|
+
typeof input.enable === "boolean"
|
|
8294
|
+
? input.enable
|
|
8295
|
+
: undefined;
|
|
8076
8296
|
const route = {
|
|
8077
8297
|
Path: path,
|
|
8078
8298
|
UpstreamResourceType: upstreamResourceType,
|
|
@@ -8082,6 +8302,9 @@ function registerGatewayTools(server) {
|
|
|
8082
8302
|
if (enablePathTransmission !== undefined) {
|
|
8083
8303
|
route.EnablePathTransmission = enablePathTransmission;
|
|
8084
8304
|
}
|
|
8305
|
+
if (routeEnable !== undefined) {
|
|
8306
|
+
route.Enable = routeEnable;
|
|
8307
|
+
}
|
|
8085
8308
|
return {
|
|
8086
8309
|
EnvId: await resolveEnvId(),
|
|
8087
8310
|
Domain: {
|
|
@@ -8095,9 +8318,46 @@ function registerGatewayTools(server) {
|
|
|
8095
8318
|
upstreamResourceName,
|
|
8096
8319
|
enableAuth,
|
|
8097
8320
|
enablePathTransmission,
|
|
8321
|
+
enable: routeEnable,
|
|
8098
8322
|
},
|
|
8099
8323
|
};
|
|
8100
8324
|
};
|
|
8325
|
+
const resolveExistingRouteForToggle = async (input) => {
|
|
8326
|
+
const routePath = input.routePath ?? input.path;
|
|
8327
|
+
if (!routePath) {
|
|
8328
|
+
throw new Error("action=enableRoute/disableRoute 时必须提供 path 或 route.path(例如 \"/\" 或 \"/api\")");
|
|
8329
|
+
}
|
|
8330
|
+
const normalizedPath = normalizeAccessPath(routePath);
|
|
8331
|
+
const preferredDomain = input.domain;
|
|
8332
|
+
const result = await listHttpServiceRoutes(preferredDomain ? { domain: preferredDomain } : undefined);
|
|
8333
|
+
const upstreamName = input.routeServiceName ?? input.targetName;
|
|
8334
|
+
const matches = flattenRoutes(result).filter((item) => {
|
|
8335
|
+
if (preferredDomain &&
|
|
8336
|
+
item.Domain !== preferredDomain) {
|
|
8337
|
+
return false;
|
|
8338
|
+
}
|
|
8339
|
+
if (normalizeAccessPath(String(item.Path ?? "")) !== normalizedPath) {
|
|
8340
|
+
return false;
|
|
8341
|
+
}
|
|
8342
|
+
if (upstreamName && item.UpstreamResourceName !== upstreamName) {
|
|
8343
|
+
return false;
|
|
8344
|
+
}
|
|
8345
|
+
return true;
|
|
8346
|
+
});
|
|
8347
|
+
if (matches.length === 0) {
|
|
8348
|
+
throw new Error(`未找到路径 ${normalizedPath} 的路由` +
|
|
8349
|
+
(preferredDomain ? `(域名 ${preferredDomain})` : "") +
|
|
8350
|
+
(upstreamName ? `(上游 ${upstreamName})` : "") +
|
|
8351
|
+
"。请先用 queryGateway(action=\"listRoutes\") 确认 Domain / Path,再调用 enableRoute/disableRoute。" +
|
|
8352
|
+
"关闭静态托管默认域名(*.tcloudbaseapp.com)时,请显式传 domain=该 STATIC_STORE IsDefault 域名,并通常 path=\"/\"。");
|
|
8353
|
+
}
|
|
8354
|
+
if (matches.length > 1) {
|
|
8355
|
+
const domains = [...new Set(matches.map((item) => item.Domain))];
|
|
8356
|
+
throw new Error(`路径 ${normalizedPath} 匹配到 ${matches.length} 条路由(域名:${domains.join(", ")})。` +
|
|
8357
|
+
"请补充 domain(必要时再加 targetName/route.serviceName)精确定位后再 enableRoute/disableRoute。");
|
|
8358
|
+
}
|
|
8359
|
+
return matches[0];
|
|
8360
|
+
};
|
|
8101
8361
|
const routeMutationNextActions = (targetName) => [
|
|
8102
8362
|
{
|
|
8103
8363
|
tool: "queryGateway",
|
|
@@ -8301,6 +8561,10 @@ function registerGatewayTools(server) {
|
|
|
8301
8561
|
Domain: payload.Domain,
|
|
8302
8562
|
});
|
|
8303
8563
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
8564
|
+
const routeEnabled = payload.resolved.enable !== false;
|
|
8565
|
+
const accessUrl = routeEnabled
|
|
8566
|
+
? `https://${payload.resolved.domain}${payload.resolved.path}`
|
|
8567
|
+
: undefined;
|
|
8304
8568
|
return buildEnvelope({
|
|
8305
8569
|
action: input.action,
|
|
8306
8570
|
model: "httpServiceRoute",
|
|
@@ -8310,13 +8574,31 @@ function registerGatewayTools(server) {
|
|
|
8310
8574
|
upstreamResourceName: payload.resolved.upstreamResourceName,
|
|
8311
8575
|
auth: payload.resolved.enableAuth ?? null,
|
|
8312
8576
|
enablePathTransmission: payload.resolved.enablePathTransmission ?? null,
|
|
8313
|
-
|
|
8314
|
-
|
|
8577
|
+
enable: payload.resolved.enable ?? null,
|
|
8578
|
+
...(accessUrl
|
|
8579
|
+
? {
|
|
8580
|
+
accessUrl,
|
|
8581
|
+
accessUrls: [accessUrl],
|
|
8582
|
+
accessUrlReachable: true,
|
|
8583
|
+
}
|
|
8584
|
+
: {
|
|
8585
|
+
accessUrls: [],
|
|
8586
|
+
accessUrlReachable: false,
|
|
8587
|
+
routeDisabled: true,
|
|
8588
|
+
disabledAccessUrls: [
|
|
8589
|
+
`https://${payload.resolved.domain}${payload.resolved.path}`,
|
|
8590
|
+
],
|
|
8591
|
+
}),
|
|
8315
8592
|
accessUrlSource: input.domain && input.domain === payload.resolved.domain
|
|
8316
8593
|
? "gateway.custom"
|
|
8317
8594
|
: "gateway.default",
|
|
8318
8595
|
raw: result,
|
|
8319
8596
|
}, `HTTP 路由更新成功(${payload.resolved.domain}${payload.resolved.path}` +
|
|
8597
|
+
(payload.resolved.enable === false
|
|
8598
|
+
? ",路由已禁用 Enable=false"
|
|
8599
|
+
: payload.resolved.enable === true
|
|
8600
|
+
? ",路由已启用 Enable=true"
|
|
8601
|
+
: "") +
|
|
8320
8602
|
(payload.resolved.enablePathTransmission === true
|
|
8321
8603
|
? ",路径透传=开启"
|
|
8322
8604
|
: payload.resolved.enablePathTransmission === false
|
|
@@ -8324,6 +8606,102 @@ function registerGatewayTools(server) {
|
|
|
8324
8606
|
: "") +
|
|
8325
8607
|
`)`, routeMutationNextActions(payload.resolved.upstreamResourceName));
|
|
8326
8608
|
}
|
|
8609
|
+
case "enableRoute":
|
|
8610
|
+
case "disableRoute": {
|
|
8611
|
+
const enable = input.action === "enableRoute";
|
|
8612
|
+
const existing = await resolveExistingRouteForToggle({
|
|
8613
|
+
path: input.path,
|
|
8614
|
+
routePath: input.route?.path,
|
|
8615
|
+
domain: input.domain,
|
|
8616
|
+
targetName: input.targetName,
|
|
8617
|
+
routeServiceName: input.route?.serviceName,
|
|
8618
|
+
});
|
|
8619
|
+
const upstreamResourceType = resolveUpstreamResourceType({
|
|
8620
|
+
upstreamResourceType: input.upstreamResourceType ??
|
|
8621
|
+
existing.UpstreamResourceType,
|
|
8622
|
+
routeUpstreamResourceType: input.route?.upstreamResourceType,
|
|
8623
|
+
});
|
|
8624
|
+
const upstreamResourceName = input.route?.serviceName ??
|
|
8625
|
+
input.targetName ??
|
|
8626
|
+
String(existing.UpstreamResourceName ?? "");
|
|
8627
|
+
if (!upstreamResourceName) {
|
|
8628
|
+
throw new Error(`action=${input.action} 无法解析上游资源名;请用 queryGateway(listRoutes) 确认后传入 targetName 或 route.serviceName。`);
|
|
8629
|
+
}
|
|
8630
|
+
const normalizedPath = normalizeAccessPath(String(existing.Path ?? input.route?.path ?? input.path ?? "/"));
|
|
8631
|
+
const domain = existing.Domain;
|
|
8632
|
+
const enableAuth = input.route?.auth !== undefined
|
|
8633
|
+
? input.route.auth
|
|
8634
|
+
: input.auth !== undefined
|
|
8635
|
+
? input.auth
|
|
8636
|
+
: existing.EnableAuth;
|
|
8637
|
+
const enablePathTransmission = input.route?.enablePathTransmission !== undefined
|
|
8638
|
+
? input.route.enablePathTransmission
|
|
8639
|
+
: input.enablePathTransmission !== undefined
|
|
8640
|
+
? input.enablePathTransmission
|
|
8641
|
+
: existing.EnablePathTransmission;
|
|
8642
|
+
const route = {
|
|
8643
|
+
Path: normalizedPath,
|
|
8644
|
+
UpstreamResourceType: upstreamResourceType,
|
|
8645
|
+
UpstreamResourceName: upstreamResourceName,
|
|
8646
|
+
Enable: enable,
|
|
8647
|
+
};
|
|
8648
|
+
if (enableAuth !== undefined) {
|
|
8649
|
+
route.EnableAuth = enableAuth;
|
|
8650
|
+
}
|
|
8651
|
+
if (enablePathTransmission !== undefined) {
|
|
8652
|
+
route.EnablePathTransmission = enablePathTransmission;
|
|
8653
|
+
}
|
|
8654
|
+
const cloudbase = await getManager();
|
|
8655
|
+
const result = await cloudbase.env.modifyHttpServiceRoute({
|
|
8656
|
+
EnvId: await resolveEnvId(),
|
|
8657
|
+
Domain: {
|
|
8658
|
+
Domain: domain,
|
|
8659
|
+
Routes: [route],
|
|
8660
|
+
},
|
|
8661
|
+
});
|
|
8662
|
+
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
8663
|
+
const verb = enable ? "启用" : "禁用";
|
|
8664
|
+
const accessUrl = enable
|
|
8665
|
+
? `https://${domain}${normalizedPath}`
|
|
8666
|
+
: undefined;
|
|
8667
|
+
return buildEnvelope({
|
|
8668
|
+
action: input.action,
|
|
8669
|
+
model: "httpServiceRoute",
|
|
8670
|
+
domain,
|
|
8671
|
+
path: normalizedPath,
|
|
8672
|
+
upstreamResourceType,
|
|
8673
|
+
upstreamResourceName,
|
|
8674
|
+
enable,
|
|
8675
|
+
auth: enableAuth ?? null,
|
|
8676
|
+
enablePathTransmission: enablePathTransmission ?? null,
|
|
8677
|
+
...(accessUrl
|
|
8678
|
+
? {
|
|
8679
|
+
accessUrl,
|
|
8680
|
+
accessUrls: [accessUrl],
|
|
8681
|
+
accessUrlReachable: true,
|
|
8682
|
+
}
|
|
8683
|
+
: {
|
|
8684
|
+
accessUrls: [],
|
|
8685
|
+
accessUrlReachable: false,
|
|
8686
|
+
routeDisabled: true,
|
|
8687
|
+
disabledAccessUrls: [`https://${domain}${normalizedPath}`],
|
|
8688
|
+
}),
|
|
8689
|
+
accessUrlSource: existing.IsDefault
|
|
8690
|
+
? "gateway.default"
|
|
8691
|
+
: "gateway.custom",
|
|
8692
|
+
raw: result,
|
|
8693
|
+
}, `HTTP 路由已${verb}(${domain}${normalizedPath},Enable=${enable})。` +
|
|
8694
|
+
(enable
|
|
8695
|
+
? "启用后通常数秒到约 30 秒内可访问;请用 queryGateway(getRoute) 或探测 accessUrl 确认。"
|
|
8696
|
+
: "禁用后该路径将不可公网访问(GATEWAY_ROUTE_DISABLED);关闭静态托管默认域名(*.tcloudbaseapp.com)时,请确认 DomainType=STATIC_STORE 且 IsDefault=true。") +
|
|
8697
|
+
" 底层对应 tcb ModifyHTTPServiceRoute(不是 ModifyGatewayRoute)。", [
|
|
8698
|
+
{
|
|
8699
|
+
tool: "queryGateway",
|
|
8700
|
+
action: "getRoute",
|
|
8701
|
+
reason: `复核路由 Enable=${enable} 是否已生效`,
|
|
8702
|
+
},
|
|
8703
|
+
]);
|
|
8704
|
+
}
|
|
8327
8705
|
case "deleteRoute": {
|
|
8328
8706
|
const routePath = input.route?.path ?? input.path;
|
|
8329
8707
|
if (!routePath) {
|
|
@@ -8531,8 +8909,11 @@ function registerGatewayTools(server) {
|
|
|
8531
8909
|
}, async (input) => withEnvelope(() => handleQueryGateway(input)));
|
|
8532
8910
|
server.registerTool?.("manageGateway", {
|
|
8533
8911
|
title: "管理 CloudBase 网关",
|
|
8534
|
-
description: "CloudBase HTTP 网关统一写入口(Domain/Route)。createRoute/updateRoute/deleteRoute 把域名下的 path
|
|
8912
|
+
description: "CloudBase HTTP 网关统一写入口(Domain/Route)。createRoute/updateRoute/deleteRoute 把域名下的 path 转到上游;" +
|
|
8913
|
+
"enableRoute/disableRoute 启用或禁用已有路由(底层 ModifyHTTPServiceRoute 的 Routes[].Enable,不是 ModifyGatewayRoute)。" +
|
|
8914
|
+
"未传 domain 时用 DomainType=HTTPSERVICE 的 IsDefault 默认 HTTP 域名(形如 *.{region}.app.tcloudbase.com),不会使用静态托管 CDN 域名(*.tcloudbaseapp.com,DomainType=STATIC_STORE)。" +
|
|
8535
8915
|
"这是网关默认域上的路径路由,不是 STATIC_STORE 上游绑定;STATIC_STORE 上游必须显式传 upstreamResourceType=STATIC_STORE。" +
|
|
8916
|
+
"关闭静态托管默认域名(*.tcloudbaseapp.com):先 queryGateway(listRoutes) 找到 DomainType=STATIC_STORE 且 IsDefault=true 的 domain,再 manageGateway(action=\"disableRoute\", domain=该域名, path=\"/\");勿用 manageHosting。" +
|
|
8536
8917
|
"创建后可用 queryGateway(action=\"listRoutes\") 核对 Domain / DomainType / Path / UpstreamResourceType。" +
|
|
8537
8918
|
"上游类型只用一个参数 upstreamResourceType(也可写在 route.upstreamResourceType,route 优先):" +
|
|
8538
8919
|
"WEB_SCF=HTTP云函数,SCF=Event云函数,CBR=云托管,STATIC_STORE=静态托管,LH=轻量应用服务器;" +
|
|
@@ -8545,9 +8926,12 @@ function registerGatewayTools(server) {
|
|
|
8545
8926
|
inputSchema: {
|
|
8546
8927
|
action: zod_1.z
|
|
8547
8928
|
.enum(MANAGE_GATEWAY_ACTIONS)
|
|
8548
|
-
.describe("写操作:createRoute/updateRoute/deleteRoute 管理路由;
|
|
8929
|
+
.describe("写操作:createRoute/updateRoute/deleteRoute 管理路由;enableRoute/disableRoute 启用或禁用已有路由(需 path,建议显式传 domain);" +
|
|
8930
|
+
"bindCustomDomain/deleteCustomDomain 管理自定义域名;" +
|
|
8549
8931
|
"enableService/authSwitch 开关 HTTP 网关总开关与访问鉴权(需配合 enable 参数)。" +
|
|
8550
|
-
"createRoute/updateRoute 必须提供 upstreamResourceType
|
|
8932
|
+
"createRoute/updateRoute 必须提供 upstreamResourceType;enableRoute/disableRoute 会先 listRoutes 定位已有路由,通常不必重填上游。" +
|
|
8933
|
+
"updateRoute 也可传 enable/route.enable 直接改 Routes[].Enable。" +
|
|
8934
|
+
"关闭 *.tcloudbaseapp.com 默认静态托管域:disableRoute + domain=该 STATIC_STORE IsDefault 域名 + path=\"/\"。" +
|
|
8551
8935
|
"已有自定义域名时优先 createRoute(domain=已有域名) 实现访问,不必再次 bindCustomDomain / 传入 certificateId;" +
|
|
8552
8936
|
"bindCustomDomain 仅用于首次绑定新域名(需 certificateId;可选 accessType=DIRECT|CDN|CUSTOM,CUSTOM 需 customCname;普通场景用默认 DIRECT)。" +
|
|
8553
8937
|
"接入说明:https://docs.cloudbase.net/service/custom-domain"),
|
|
@@ -8596,16 +8980,25 @@ function registerGatewayTools(server) {
|
|
|
8596
8980
|
.boolean()
|
|
8597
8981
|
.optional()
|
|
8598
8982
|
.describe("同顶层 enablePathTransmission。route 内设置时优先于顶层。"),
|
|
8983
|
+
enable: zod_1.z
|
|
8984
|
+
.boolean()
|
|
8985
|
+
.optional()
|
|
8986
|
+
.describe("路由级开关(Routes[].Enable / Route.Enable)。createRoute/updateRoute 可用:" +
|
|
8987
|
+
"enable=false 禁用该 Domain+Path(访问返回 GATEWAY_ROUTE_DISABLED);" +
|
|
8988
|
+
"enable=true 重新启用。updateRoute 也可用顶层 enable 表达同一语义;" +
|
|
8989
|
+
"也可用专用 action enableRoute/disableRoute。route.enable 优先于顶层 enable。"),
|
|
8599
8990
|
})
|
|
8600
8991
|
.optional()
|
|
8601
8992
|
.describe("路由对象(可选写法)。例:云函数 {upstreamResourceType:\"WEB_SCF\",serviceName:\"fn\",path:\"/api\"};" +
|
|
8602
8993
|
"云托管 {upstreamResourceType:\"CBR\",serviceName:\"svc\",path:\"/api\"};" +
|
|
8603
|
-
"静态托管 {upstreamResourceType:\"STATIC_STORE\",serviceName:\"staticstore\",path:\"/\"}
|
|
8994
|
+
"静态托管 {upstreamResourceType:\"STATIC_STORE\",serviceName:\"staticstore\",path:\"/\"};" +
|
|
8995
|
+
"禁用路由 {path:\"/\",enable:false}(配合 updateRoute,或直接用 disableRoute)。"),
|
|
8604
8996
|
domain: zod_1.z
|
|
8605
8997
|
.string()
|
|
8606
8998
|
.optional()
|
|
8607
8999
|
.describe("域名。省略时自动使用环境 DomainType=HTTPSERVICE 的 IsDefault 默认 HTTP 域名(*.{region}.app.tcloudbase.com),不会回退到静态托管 CDN 域名(*.tcloudbaseapp.com,DomainType=STATIC_STORE);也不是 STATIC_STORE 上游绑定。" +
|
|
8608
9000
|
"可用 queryGateway(action=\"listRoutes\") 核对实际 Domain / DomainType。" +
|
|
9001
|
+
"enableRoute/disableRoute 操作 *.tcloudbaseapp.com 时必须显式传入该域名。" +
|
|
8609
9002
|
"已有自定义域名时请显式传入该域名并 createRoute/updateRoute/deleteRoute,即可实现自定义域名访问且无需证书 ID;" +
|
|
8610
9003
|
"仅 bindCustomDomain 时表示要新绑定的域名。"),
|
|
8611
9004
|
certificateId: zod_1.z
|
|
@@ -8628,9 +9021,11 @@ function registerGatewayTools(server) {
|
|
|
8628
9021
|
enable: zod_1.z
|
|
8629
9022
|
.boolean()
|
|
8630
9023
|
.optional()
|
|
8631
|
-
.describe("
|
|
8632
|
-
"bindCustomDomain 可选:enable=false
|
|
8633
|
-
"
|
|
9024
|
+
.describe("开关目标状态:enableService / authSwitch 必填(true 开启 / false 关闭);" +
|
|
9025
|
+
"bindCustomDomain 可选:enable=false 表示绑定后禁用域名(默认启用);" +
|
|
9026
|
+
"updateRoute 可选:映射到 Routes[].Enable(也可用 route.enable,route 优先;" +
|
|
9027
|
+
"也可用专用 action enableRoute/disableRoute)。" +
|
|
9028
|
+
"省略或非布尔值在 enableService/authSwitch 会返回参数错误。"),
|
|
8634
9029
|
},
|
|
8635
9030
|
annotations: {
|
|
8636
9031
|
readOnlyHint: false,
|
|
@@ -11255,7 +11650,7 @@ class TelemetryReporter {
|
|
|
11255
11650
|
const arch = os_1.default.arch(); // 系统架构
|
|
11256
11651
|
// Build-time injected version; guard for vitest / non-webpack runtimes.
|
|
11257
11652
|
const mcpVersion = process.env.npm_package_version ||
|
|
11258
|
-
( true ? "2.
|
|
11653
|
+
( true ? "2.26.0" : 0);
|
|
11259
11654
|
return {
|
|
11260
11655
|
userAgent: `${osType} ${osRelease} ${arch} ${nodeVersion} CloudBase-MCP/${mcpVersion}`,
|
|
11261
11656
|
deviceId: this.deviceId,
|
|
@@ -16790,7 +17185,7 @@ function registerSetupTools(server) {
|
|
|
16790
17185
|
server.registerTool("downloadTemplate", {
|
|
16791
17186
|
title: "下载项目模板",
|
|
16792
17187
|
description: `自动下载并部署CloudBase项目模板。
|
|
16793
|
-
**Note**: Call this tool when the user requests to create a new project using a CloudBase template.\n\n支持的模板:\n- react: React + CloudBase 全栈应用模板\n- vue: Vue + CloudBase 全栈应用模板\n- miniprogram: 微信小程序 + 云开发模板 \n- uniapp: UniApp + CloudBase 跨端应用模板\n- rules: 只包含AI编辑器配置文件(包含Cursor、WindSurf、CodeBuddy等所有主流编辑器配置),适合在已有项目中补充AI编辑器配置\n\n支持的IDE类型:\n- all: 下载所有IDE配置\n- cursor: Cursor AI编辑器\n- 其他IDE类型见下方列表\n\n注意:如果未传入 ide 参数且无法从环境变量检测到 IDE,将提示错误并要求传入 ide 参数\n- windsurf: WindSurf AI编辑器\n- codebuddy: CodeBuddy AI编辑器\n- claude-code: Claude Code AI编辑器\n- cline: Cline AI编辑器\n- gemini-cli: Gemini CLI\n- opencode: OpenCode AI编辑器\n- qwen-code: 通义灵码\n- baidu-comate: 百度Comate\n- openai-codex-cli: OpenAI Codex CLI\n- augment-code: Augment Code\n- github-copilot: GitHub Copilot\n- roocode: RooCode AI编辑器\n- tongyi-lingma: 通义灵码\n- trae: Trae AI编辑器\n- qoder: Qoder AI编辑器\n- antigravity: Google Antigravity AI编辑器\n- vscode: Visual Studio Code\n- kiro: Kiro AI编辑器\n- aider: Aider AI编辑器\n\n特别说明:\n- rules 模板会自动包含当前 mcp 版本号信息(版本号:${ true ? "2.
|
|
17188
|
+
**Note**: Call this tool when the user requests to create a new project using a CloudBase template.\n\n支持的模板:\n- react: React + CloudBase 全栈应用模板\n- vue: Vue + CloudBase 全栈应用模板\n- miniprogram: 微信小程序 + 云开发模板 \n- uniapp: UniApp + CloudBase 跨端应用模板\n- rules: 只包含AI编辑器配置文件(包含Cursor、WindSurf、CodeBuddy等所有主流编辑器配置),适合在已有项目中补充AI编辑器配置\n\n支持的IDE类型:\n- all: 下载所有IDE配置\n- cursor: Cursor AI编辑器\n- 其他IDE类型见下方列表\n\n注意:如果未传入 ide 参数且无法从环境变量检测到 IDE,将提示错误并要求传入 ide 参数\n- windsurf: WindSurf AI编辑器\n- codebuddy: CodeBuddy AI编辑器\n- claude-code: Claude Code AI编辑器\n- cline: Cline AI编辑器\n- gemini-cli: Gemini CLI\n- opencode: OpenCode AI编辑器\n- qwen-code: 通义灵码\n- baidu-comate: 百度Comate\n- openai-codex-cli: OpenAI Codex CLI\n- augment-code: Augment Code\n- github-copilot: GitHub Copilot\n- roocode: RooCode AI编辑器\n- tongyi-lingma: 通义灵码\n- trae: Trae AI编辑器\n- qoder: Qoder AI编辑器\n- antigravity: Google Antigravity AI编辑器\n- vscode: Visual Studio Code\n- kiro: Kiro AI编辑器\n- aider: Aider AI编辑器\n\n特别说明:\n- rules 模板会自动包含当前 mcp 版本号信息(版本号:${ true ? "2.26.0" : 0}),便于后续维护和版本追踪\n- 下载 rules 模板时,如果项目中已存在 README.md 文件,系统会自动保护该文件不被覆盖(除非设置 overwrite=true)`,
|
|
16794
17189
|
inputSchema: {
|
|
16795
17190
|
template: zod_1.z
|
|
16796
17191
|
.enum(["react", "vue", "miniprogram", "uniapp", "rules"])
|
|
@@ -18064,16 +18459,19 @@ function isDirectoryUploadTarget(localPath, cloudPath) {
|
|
|
18064
18459
|
return true;
|
|
18065
18460
|
return path_1.default.posix.extname(normalizedCloudPath) === '';
|
|
18066
18461
|
}
|
|
18067
|
-
function
|
|
18068
|
-
if (!staticDomain)
|
|
18069
|
-
return '';
|
|
18462
|
+
function hostingAccessPathname(cloudPath, localPath) {
|
|
18070
18463
|
const normalizedCloudPath = (cloudPath ?? '').trim().replace(/^\/+|\/+$/g, '');
|
|
18071
18464
|
const isDirectory = isDirectoryUploadTarget(localPath, cloudPath);
|
|
18072
18465
|
if (!normalizedCloudPath) {
|
|
18073
|
-
return
|
|
18466
|
+
return '/';
|
|
18074
18467
|
}
|
|
18075
18468
|
const pathname = isDirectory ? `${normalizedCloudPath}/` : normalizedCloudPath;
|
|
18076
|
-
return
|
|
18469
|
+
return `/${pathname}`;
|
|
18470
|
+
}
|
|
18471
|
+
function buildHostingAccessUrl(staticDomain, cloudPath, localPath) {
|
|
18472
|
+
if (!staticDomain)
|
|
18473
|
+
return '';
|
|
18474
|
+
return `https://${staticDomain}${hostingAccessPathname(cloudPath, localPath)}`;
|
|
18077
18475
|
}
|
|
18078
18476
|
function buildUploadErrorMessage(error, localPath) {
|
|
18079
18477
|
const baseMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -18254,7 +18652,7 @@ function buildDomainMutationResult(params) {
|
|
|
18254
18652
|
message: `静态托管域名${actionLabel}请求已提交。域名配置、证书校验和边缘侧传播通常需要 30 秒到 10 分钟,请继续调用 queryHosting(action="domainStatus") 确认最终结果。`,
|
|
18255
18653
|
};
|
|
18256
18654
|
}
|
|
18257
|
-
async function getHostingWebsiteConfig(cloudbase, logger) {
|
|
18655
|
+
async function getHostingWebsiteConfig(cloudbase, logger, cloudBaseOptions) {
|
|
18258
18656
|
const websiteConfig = await cloudbase.hosting.getWebsiteConfig();
|
|
18259
18657
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(logger, websiteConfig);
|
|
18260
18658
|
const hostingResult = {
|
|
@@ -18271,6 +18669,27 @@ async function getHostingWebsiteConfig(cloudbase, logger) {
|
|
|
18271
18669
|
catch {
|
|
18272
18670
|
// Ignore enrichment failures and return the website config as-is.
|
|
18273
18671
|
}
|
|
18672
|
+
try {
|
|
18673
|
+
const cdnDomain = typeof hostingResult.CdnDomain === 'string' ? hostingResult.CdnDomain : '';
|
|
18674
|
+
if (cdnDomain && typeof cloudbase.env?.describeHttpServiceRoute === 'function') {
|
|
18675
|
+
const envId = await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
|
|
18676
|
+
const routeResult = await cloudbase.env.describeHttpServiceRoute({
|
|
18677
|
+
EnvId: envId,
|
|
18678
|
+
Limit: 1000,
|
|
18679
|
+
});
|
|
18680
|
+
const routes = (0, gateway_access_urls_js_1.flattenHttpServiceRoutes)(routeResult);
|
|
18681
|
+
const reachable = (0, gateway_access_urls_js_1.isDomainPathReachableViaGateway)(routes, cdnDomain, '/');
|
|
18682
|
+
hostingResult.staticDomainRouteEnabled = reachable;
|
|
18683
|
+
hostingResult.accessUrlReachable = reachable !== false;
|
|
18684
|
+
if (reachable === false) {
|
|
18685
|
+
hostingResult.routeDisabled = true;
|
|
18686
|
+
hostingResult.disabledAccessUrls = [`https://${cdnDomain}/`];
|
|
18687
|
+
}
|
|
18688
|
+
}
|
|
18689
|
+
}
|
|
18690
|
+
catch {
|
|
18691
|
+
// Gateway route lookup is best-effort enrichment only.
|
|
18692
|
+
}
|
|
18274
18693
|
return hostingResult;
|
|
18275
18694
|
}
|
|
18276
18695
|
async function resolveHostingStaticDomain(cloudbase, logger) {
|
|
@@ -18354,7 +18773,7 @@ function registerHostingTools(server) {
|
|
|
18354
18773
|
const cloudbase = await getManager();
|
|
18355
18774
|
switch (input.action) {
|
|
18356
18775
|
case 'websiteConfig': {
|
|
18357
|
-
const websiteConfig = await getHostingWebsiteConfig(cloudbase, server.logger);
|
|
18776
|
+
const websiteConfig = await getHostingWebsiteConfig(cloudbase, server.logger, cloudBaseOptions);
|
|
18358
18777
|
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
18359
18778
|
success: true,
|
|
18360
18779
|
data: {
|
|
@@ -18480,7 +18899,7 @@ function registerHostingTools(server) {
|
|
|
18480
18899
|
});
|
|
18481
18900
|
server.registerTool('manageHosting', {
|
|
18482
18901
|
title: '管理 CloudBase 静态托管',
|
|
18483
|
-
description: '管理 CloudBase 静态托管的变更操作。action=upload 上传本地构建产物到共享域名(域名格式:<envId>-<appId>.tcloudbaseapp.com/<cloudPath>);action=delete 删除托管文件或目录(必须 confirm=true);action=setWebsiteDocument 设置首页/错误页/路由规则;action=enableService 开通静态托管;action=bindDomain / unbindDomain / updateDomain 管理自定义域名;action=downloadFile / downloadDirectory 下载托管内容到本地。⚠️ 新项目部署优先使用 manageApps(部署到独立子域名),本工具适合已有老项目继续使用或作为 manageApps 的 fallback。manageApps 与 manageHosting 域名不同,切换会导致老链接失效。若任务只是查看配置、文件或域名状态,请改用 queryHosting。',
|
|
18902
|
+
description: '管理 CloudBase 静态托管的变更操作。action=upload 上传本地构建产物到共享域名(域名格式:<envId>-<appId>.tcloudbaseapp.com/<cloudPath>);action=delete 删除托管文件或目录(必须 confirm=true);action=setWebsiteDocument 设置首页/错误页/路由规则;action=enableService 开通静态托管;action=bindDomain / unbindDomain / updateDomain 管理自定义域名;action=downloadFile / downloadDirectory 下载托管内容到本地。⚠️ 本工具没有关闭默认域名(*.tcloudbaseapp.com)的 action;要禁用该默认公网域名,请用 manageGateway(action="disableRoute", domain=该 STATIC_STORE IsDefault 域名, path="/")(底层 ModifyHTTPServiceRoute,不是 ModifyGatewayRoute)。⚠️ 新项目部署优先使用 manageApps(部署到独立子域名),本工具适合已有老项目继续使用或作为 manageApps 的 fallback。manageApps 与 manageHosting 域名不同,切换会导致老链接失效。若任务只是查看配置、文件或域名状态,请改用 queryHosting。',
|
|
18484
18903
|
inputSchema: manageHostingInputSchema,
|
|
18485
18904
|
annotations: {
|
|
18486
18905
|
readOnlyHint: false,
|
|
@@ -18520,7 +18939,24 @@ function registerHostingTools(server) {
|
|
|
18520
18939
|
const cdnFromStore = (store.CdnDomain ?? store.StaticDomain);
|
|
18521
18940
|
const staticDomain = cdnFromStore || await resolveHostingStaticDomain(cloudbase, server.logger);
|
|
18522
18941
|
const fallbackAccessUrl = buildHostingAccessUrl(staticDomain, input.cloudPath, input.localPath);
|
|
18942
|
+
const accessPathname = hostingAccessPathname(input.cloudPath, input.localPath);
|
|
18523
18943
|
const envId = await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
|
|
18944
|
+
const getGatewayManager = async () => {
|
|
18945
|
+
const manager = await getManager();
|
|
18946
|
+
if (!manager) {
|
|
18947
|
+
throw new Error("cloudbase manager unavailable");
|
|
18948
|
+
}
|
|
18949
|
+
return manager;
|
|
18950
|
+
};
|
|
18951
|
+
const allGatewayRoutes = await (0, gateway_access_urls_js_1.resolveAllGatewayRoutes)({
|
|
18952
|
+
envId,
|
|
18953
|
+
getManager: getGatewayManager,
|
|
18954
|
+
});
|
|
18955
|
+
const staticDomainRouteEnabled = staticDomain
|
|
18956
|
+
? (0, gateway_access_urls_js_1.isDomainPathReachableViaGateway)(allGatewayRoutes, staticDomain, accessPathname)
|
|
18957
|
+
: null;
|
|
18958
|
+
// null = no matching gateway route info → keep legacy fallback.
|
|
18959
|
+
const fallbackReachable = staticDomainRouteEnabled !== false;
|
|
18524
18960
|
const gatewayCandidates = Array.from(new Set([
|
|
18525
18961
|
store.StaticStoreName,
|
|
18526
18962
|
store.Name,
|
|
@@ -18530,34 +18966,38 @@ function registerHostingTools(server) {
|
|
|
18530
18966
|
]
|
|
18531
18967
|
.map((item) => (typeof item === "string" ? item.trim() : ""))
|
|
18532
18968
|
.filter(Boolean)));
|
|
18533
|
-
let accessUrl = fallbackAccessUrl;
|
|
18969
|
+
let accessUrl = fallbackReachable ? fallbackAccessUrl : "";
|
|
18534
18970
|
let accessUrls = accessUrl ? [accessUrl] : [];
|
|
18535
18971
|
let accessUrlSource = accessUrl
|
|
18536
18972
|
? "hosting.staticDomain"
|
|
18537
18973
|
: undefined;
|
|
18974
|
+
let accessUrlReachable = Boolean(accessUrl);
|
|
18975
|
+
let disabledAccessUrls = [];
|
|
18976
|
+
if (fallbackAccessUrl && !fallbackReachable) {
|
|
18977
|
+
disabledAccessUrls.push(fallbackAccessUrl);
|
|
18978
|
+
}
|
|
18538
18979
|
for (const upstreamName of gatewayCandidates) {
|
|
18539
18980
|
const gateway = await (0, gateway_access_urls_js_1.resolveGatewayAccessUrls)({
|
|
18540
18981
|
envId,
|
|
18541
18982
|
upstreamResourceName: upstreamName,
|
|
18542
18983
|
upstreamResourceTypes: ["STATIC_STORE"],
|
|
18543
|
-
getManager:
|
|
18544
|
-
const manager = await getManager();
|
|
18545
|
-
if (!manager) {
|
|
18546
|
-
throw new Error("cloudbase manager unavailable");
|
|
18547
|
-
}
|
|
18548
|
-
return manager;
|
|
18549
|
-
},
|
|
18984
|
+
getManager: getGatewayManager,
|
|
18550
18985
|
});
|
|
18551
18986
|
const preferred = (0, gateway_access_urls_js_1.preferGatewayOrFallback)({
|
|
18552
18987
|
gateway,
|
|
18553
18988
|
fallbackUrl: fallbackAccessUrl || undefined,
|
|
18554
18989
|
fallbackSource: "hosting.staticDomain",
|
|
18990
|
+
fallbackReachable,
|
|
18555
18991
|
});
|
|
18556
|
-
if (preferred.accessUrl) {
|
|
18557
|
-
accessUrl = preferred.accessUrl;
|
|
18992
|
+
if (preferred.accessUrl || preferred.disabledAccessUrls.length > 0) {
|
|
18993
|
+
accessUrl = preferred.accessUrl ?? "";
|
|
18558
18994
|
accessUrls = preferred.accessUrls;
|
|
18559
18995
|
accessUrlSource = preferred.accessUrlSource;
|
|
18560
|
-
|
|
18996
|
+
accessUrlReachable = preferred.accessUrlReachable;
|
|
18997
|
+
disabledAccessUrls = preferred.disabledAccessUrls;
|
|
18998
|
+
if (preferred.accessUrl) {
|
|
18999
|
+
break;
|
|
19000
|
+
}
|
|
18561
19001
|
}
|
|
18562
19002
|
}
|
|
18563
19003
|
try {
|
|
@@ -18573,18 +19013,31 @@ function registerHostingTools(server) {
|
|
|
18573
19013
|
projectName = path_1.default.basename(input.localPath);
|
|
18574
19014
|
}
|
|
18575
19015
|
}
|
|
18576
|
-
|
|
18577
|
-
|
|
18578
|
-
|
|
18579
|
-
|
|
18580
|
-
|
|
18581
|
-
|
|
18582
|
-
|
|
19016
|
+
// sendDeployNotification requires a concrete url; skip when no reachable accessUrl
|
|
19017
|
+
if (accessUrl) {
|
|
19018
|
+
await (0, notification_js_1.sendDeployNotification)(server, {
|
|
19019
|
+
deployType: 'hosting',
|
|
19020
|
+
url: accessUrl,
|
|
19021
|
+
projectId: envId,
|
|
19022
|
+
projectName,
|
|
19023
|
+
consoleUrl: `https://tcb.cloud.tencent.com/dev?envId=${envId}#/static-hosting`,
|
|
19024
|
+
});
|
|
19025
|
+
}
|
|
18583
19026
|
}
|
|
18584
19027
|
catch {
|
|
18585
19028
|
// Notification failure should not block uploads.
|
|
18586
19029
|
}
|
|
18587
19030
|
const uploadPrefix = input.cloudPath ?? input.files?.[0]?.cloudPath ?? '';
|
|
19031
|
+
const routeDisabled = staticDomainRouteEnabled === false;
|
|
19032
|
+
let message = '静态托管文件上传成功。若需要校验上传结果,请继续调用 queryHosting(action="findFiles") 或 queryHosting(action="listFiles")。';
|
|
19033
|
+
if (routeDisabled && accessUrlReachable) {
|
|
19034
|
+
message =
|
|
19035
|
+
'静态托管文件上传成功。默认静态托管域名的网关路由已禁用(访问会返回 GATEWAY_ROUTE_DISABLED),已改用其他可达域名作为 accessUrl。可用 manageGateway(action="updateRoute", route.enable=true) 重新启用默认域路由。';
|
|
19036
|
+
}
|
|
19037
|
+
else if (routeDisabled && !accessUrlReachable) {
|
|
19038
|
+
message =
|
|
19039
|
+
'静态托管文件上传成功,但默认静态托管域名的网关路由已禁用(GATEWAY_ROUTE_DISABLED),当前没有可达的 accessUrl。请用 manageGateway(action="updateRoute", domain="<静态域名>", path="/", upstreamResourceType="STATIC_STORE", targetName="staticstore", route.enable=true) 启用路由,或改用已启用的自定义域名 / CloudBase Sites 域名访问。';
|
|
19040
|
+
}
|
|
18588
19041
|
return (0, tool_result_js_1.buildJsonToolResult)({
|
|
18589
19042
|
success: true,
|
|
18590
19043
|
data: {
|
|
@@ -18594,13 +19047,19 @@ function registerHostingTools(server) {
|
|
|
18594
19047
|
files: input.files ?? [],
|
|
18595
19048
|
ignore: input.ignore,
|
|
18596
19049
|
staticDomain,
|
|
18597
|
-
|
|
19050
|
+
staticDomainRouteEnabled,
|
|
19051
|
+
accessUrl: accessUrl || undefined,
|
|
18598
19052
|
accessUrls,
|
|
18599
19053
|
accessUrlSource,
|
|
19054
|
+
accessUrlReachable,
|
|
19055
|
+
...(disabledAccessUrls.length > 0
|
|
19056
|
+
? { disabledAccessUrls }
|
|
19057
|
+
: {}),
|
|
19058
|
+
...(routeDisabled ? { routeDisabled: true } : {}),
|
|
18600
19059
|
result,
|
|
18601
19060
|
nextActions: uploadPrefix ? [buildFindFilesNextStep(uploadPrefix.replace(/^\/+/, ''))] : undefined,
|
|
18602
19061
|
},
|
|
18603
|
-
message
|
|
19062
|
+
message,
|
|
18604
19063
|
});
|
|
18605
19064
|
}
|
|
18606
19065
|
case 'delete': {
|
|
@@ -18803,6 +19262,7 @@ function registerHostingTools(server) {
|
|
|
18803
19262
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
18804
19263
|
exports.buildVerificationUriComplete = buildVerificationUriComplete;
|
|
18805
19264
|
exports.buildDeviceAuthChallengePayload = buildDeviceAuthChallengePayload;
|
|
19265
|
+
exports.getCloudBaseApiKeyFromEnv = getCloudBaseApiKeyFromEnv;
|
|
18806
19266
|
exports.getAuthProgressStateSync = getAuthProgressStateSync;
|
|
18807
19267
|
exports.getAuthProgressState = getAuthProgressState;
|
|
18808
19268
|
exports.resolveAuthOptions = resolveAuthOptions;
|
|
@@ -18904,16 +19364,24 @@ function updateAuthProgressState(partial) {
|
|
|
18904
19364
|
});
|
|
18905
19365
|
return getAuthProgressStateSync();
|
|
18906
19366
|
}
|
|
19367
|
+
/**
|
|
19368
|
+
* Resolve CloudBase API Key from env.
|
|
19369
|
+
* Prefer CLOUDBASE_API_KEY; fall back to CLOUDBASE_APIKEY (js-sdk / node-sdk convention).
|
|
19370
|
+
*/
|
|
19371
|
+
function getCloudBaseApiKeyFromEnv() {
|
|
19372
|
+
return process.env.CLOUDBASE_API_KEY || process.env.CLOUDBASE_APIKEY || undefined;
|
|
19373
|
+
}
|
|
18907
19374
|
function normalizeLoginStateFromEnvVars(options) {
|
|
18908
19375
|
if (options?.ignoreEnvVars) {
|
|
18909
19376
|
return null;
|
|
18910
19377
|
}
|
|
18911
|
-
//
|
|
18912
|
-
const
|
|
18913
|
-
|
|
19378
|
+
// Prefer CloudBase API Key over TENCENTCLOUD_*
|
|
19379
|
+
const apiKey = getCloudBaseApiKeyFromEnv();
|
|
19380
|
+
const CLOUDBASE_ENV = process.env.CLOUDBASE_ENV_ID;
|
|
19381
|
+
if (apiKey && CLOUDBASE_ENV) {
|
|
18914
19382
|
return {
|
|
18915
19383
|
_type: 'api_key',
|
|
18916
|
-
apiKey
|
|
19384
|
+
apiKey,
|
|
18917
19385
|
envId: CLOUDBASE_ENV,
|
|
18918
19386
|
};
|
|
18919
19387
|
}
|
|
@@ -19085,7 +19553,7 @@ async function peekLoginState(options) {
|
|
|
19085
19553
|
}
|
|
19086
19554
|
async function ensureLogin(options) {
|
|
19087
19555
|
(0, logger_js_1.debug)("TENCENTCLOUD_SECRETID", { hasSecretId: !!process.env.TENCENTCLOUD_SECRETID });
|
|
19088
|
-
(0, logger_js_1.debug)("CLOUDBASE_API_KEY", { hasApiKey: !!
|
|
19556
|
+
(0, logger_js_1.debug)("CLOUDBASE_API_KEY", { hasApiKey: !!getCloudBaseApiKeyFromEnv() });
|
|
19089
19557
|
const loginState = await peekLoginState({
|
|
19090
19558
|
ignoreEnvVars: options?.ignoreEnvVars,
|
|
19091
19559
|
});
|