@cloudbase/cloudbase-mcp 2.24.1 → 2.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -515
- package/README.zh-CN.md +175 -0
- package/dist/cli.cjs +3 -3
- package/dist/index.cjs +1364 -455
- package/dist/index.js +382 -361
- package/package.json +6 -5
package/dist/index.cjs
CHANGED
|
@@ -340,7 +340,7 @@ class CloudRunService {
|
|
|
340
340
|
*/
|
|
341
341
|
async deploy(params) {
|
|
342
342
|
var _a;
|
|
343
|
-
const { serverName, targetPath = process.cwd(), serverConfig } = params;
|
|
343
|
+
const { serverName, targetPath = process.cwd(), serverConfig, vpcInfo, imageUrl } = params;
|
|
344
344
|
/**
|
|
345
345
|
* 参数校验和默认值设置
|
|
346
346
|
*/
|
|
@@ -350,30 +350,41 @@ class CloudRunService {
|
|
|
350
350
|
// 获取当前环境配置(包含EnvId)
|
|
351
351
|
const envConfig = this.environment.lazyEnvironmentConfig;
|
|
352
352
|
/**
|
|
353
|
-
*
|
|
353
|
+
* 根据部署类型构建 deployInfo
|
|
354
354
|
*/
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
355
|
+
let deployInfo;
|
|
356
|
+
if (imageUrl) {
|
|
357
|
+
// 镜像部署模式:直接使用镜像地址,无需上传代码包
|
|
358
|
+
deployInfo = {
|
|
359
|
+
DeployType: 'image',
|
|
360
|
+
ImageUrl: imageUrl
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
// 代码包部署模式:获取上传信息 → 打包 → 上传
|
|
365
|
+
const { UploadUrl: uploadUrl, UploadHeaders: uploadHeaders, PackageName: packageName, PackageVersion: packageVersion } = await this.tcbService.request('DescribeCloudBaseBuildService', {
|
|
366
|
+
EnvId: envConfig.EnvId,
|
|
367
|
+
ServiceName: serverName
|
|
368
|
+
});
|
|
369
|
+
deployInfo = {
|
|
370
|
+
DeployType: 'package',
|
|
371
|
+
PackageName: packageName,
|
|
372
|
+
PackageVersion: packageVersion
|
|
373
|
+
};
|
|
374
|
+
/**
|
|
375
|
+
* 上传部署包
|
|
376
|
+
*/
|
|
377
|
+
const zipFile = await codeToZip(targetPath, { installDependency: (serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.InstallDependency) !== undefined ? serverConfig.InstallDependency : true });
|
|
378
|
+
await (0, utils_1.upload)({
|
|
379
|
+
url: uploadUrl,
|
|
380
|
+
file: zipFile,
|
|
381
|
+
headers: (uploadHeaders || []).reduce((map, item) => {
|
|
382
|
+
map[item.Key] = item.Value;
|
|
383
|
+
return map;
|
|
384
|
+
}, {}) || {},
|
|
385
|
+
method: 'PUT'
|
|
386
|
+
});
|
|
387
|
+
}
|
|
377
388
|
/**
|
|
378
389
|
* 执行部署
|
|
379
390
|
*/
|
|
@@ -398,18 +409,18 @@ class CloudRunService {
|
|
|
398
409
|
else {
|
|
399
410
|
// 创建
|
|
400
411
|
/**
|
|
401
|
-
*
|
|
412
|
+
* 判断是容器还是函数型(镜像部署固定为 container)
|
|
402
413
|
*/
|
|
403
|
-
let type = 'function';
|
|
404
|
-
if (serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.Dockerfile) {
|
|
414
|
+
let type = imageUrl ? 'container' : 'function';
|
|
415
|
+
if (!imageUrl && (serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.Dockerfile)) {
|
|
405
416
|
type = 'container';
|
|
406
417
|
}
|
|
407
|
-
else {
|
|
418
|
+
else if (!imageUrl) {
|
|
408
419
|
if (await (0, fs_extra_1.pathExists)(path_1.default.join(targetPath, 'Dockerfile'))) {
|
|
409
420
|
type = 'container';
|
|
410
421
|
}
|
|
411
422
|
}
|
|
412
|
-
if (type === 'function') {
|
|
423
|
+
if (type === 'function' && !imageUrl) {
|
|
413
424
|
deployInfo.BuildPacks = {
|
|
414
425
|
LanguageVersion: '20.18',
|
|
415
426
|
RepoLanguage: 'Node.js'
|
|
@@ -425,7 +436,8 @@ class CloudRunService {
|
|
|
425
436
|
return this._upsertFunction(true, {
|
|
426
437
|
name: serverName,
|
|
427
438
|
deployInfo,
|
|
428
|
-
serverConfig: _serverConfig
|
|
439
|
+
serverConfig: _serverConfig,
|
|
440
|
+
vpcInfo
|
|
429
441
|
});
|
|
430
442
|
}
|
|
431
443
|
}
|
|
@@ -488,15 +500,20 @@ class CloudRunService {
|
|
|
488
500
|
}
|
|
489
501
|
}
|
|
490
502
|
_upsertFunction(isNew, data) {
|
|
491
|
-
const { name, deployInfo, serverConfig } = data;
|
|
503
|
+
const { name, deployInfo, serverConfig, vpcInfo } = data;
|
|
492
504
|
const envConfig = this.environment.lazyEnvironmentConfig;
|
|
493
505
|
const Items = parseObjectToDiffConfigItem(serverConfig);
|
|
494
|
-
|
|
506
|
+
const basePayload = {
|
|
495
507
|
EnvId: envConfig.EnvId,
|
|
496
508
|
ServerName: name,
|
|
497
509
|
DeployInfo: deployInfo,
|
|
498
510
|
Items,
|
|
499
|
-
}
|
|
511
|
+
};
|
|
512
|
+
// CreateCloudRunServer 支持传入 VpcInfo(仅创建时有效)
|
|
513
|
+
if (isNew && vpcInfo) {
|
|
514
|
+
basePayload.VpcInfo = vpcInfo;
|
|
515
|
+
}
|
|
516
|
+
return this.tcbrService.request(isNew ? 'CreateCloudRunServer' : 'UpdateCloudRunServer', basePayload);
|
|
500
517
|
}
|
|
501
518
|
}
|
|
502
519
|
exports.CloudRunService = CloudRunService;
|
|
@@ -640,6 +657,15 @@ function parseObjectToDiffConfigItem(data) {
|
|
|
640
657
|
else if (['TimerScale'].includes(k)) {
|
|
641
658
|
Items.push({ Key, TimerScale: v });
|
|
642
659
|
}
|
|
660
|
+
else if (['VpcConf'].includes(k)) {
|
|
661
|
+
Items.push({ Key, VpcConf: v });
|
|
662
|
+
}
|
|
663
|
+
else if (['VolumesConf'].includes(k)) {
|
|
664
|
+
Items.push({ Key, VolumesConf: v });
|
|
665
|
+
}
|
|
666
|
+
else if (['PublicNetConf'].includes(k)) {
|
|
667
|
+
Items.push({ Key, PublicNetConf: v });
|
|
668
|
+
}
|
|
643
669
|
});
|
|
644
670
|
return Items;
|
|
645
671
|
}
|
|
@@ -2415,8 +2441,8 @@ async function enrichEnvInfoWithRuntimeMode(result, manager) {
|
|
|
2415
2441
|
: "No legacy NoSQL Database/Storage observed in this env.",
|
|
2416
2442
|
MysqlNotAvailable: hasMysql
|
|
2417
2443
|
? "MySQL instance(s) detected — see EnvInfo.MysqlInstances."
|
|
2418
|
-
: "No MySQL instance is provisioned for this env. Do NOT use `manageMysqlDatabase` / `queryMysqlDatabase` (those are MySQL-specific) and do NOT read the `relational-database-
|
|
2419
|
-
RecommendedSkills: "Read `postgresql-development` first for new PG code. `
|
|
2444
|
+
: "No MySQL instance is provisioned for this env. Do NOT use `manageMysqlDatabase` / `queryMysqlDatabase` (those are MySQL-specific) and do NOT read the `relational-database-mcp-cloudbase` skill — that family targets MySQL, not CloudBase PG.",
|
|
2445
|
+
RecommendedSkills: "Read `postgresql-development-cloudbase` first for new PG code. `cloudbase-document-database-web-sdk` is still applicable for existing NoSQL collections in this env. Skip `relational-database-mcp-cloudbase` (MySQL-only) entirely.",
|
|
2420
2446
|
}
|
|
2421
2447
|
: {
|
|
2422
2448
|
PrimaryBackend: "PostgreSQL is NOT provisioned in this env — this is a legacy NoSQL CloudBase backend.",
|
|
@@ -2425,8 +2451,8 @@ async function enrichEnvInfoWithRuntimeMode(result, manager) {
|
|
|
2425
2451
|
Storage: "Browser uploads use `app.uploadFile()` against the bucket exposed in `EnvInfo.Storages[].Bucket`.",
|
|
2426
2452
|
MysqlNotAvailable: hasMysql
|
|
2427
2453
|
? "MySQL instance(s) detected — see EnvInfo.MysqlInstances."
|
|
2428
|
-
: "No MySQL instance in this env. `manageMysqlDatabase` / `queryMysqlDatabase` and the `relational-database-
|
|
2429
|
-
RecommendedSkills: "Read `
|
|
2454
|
+
: "No MySQL instance in this env. `manageMysqlDatabase` / `queryMysqlDatabase` and the `relational-database-mcp-cloudbase` skill are not applicable.",
|
|
2455
|
+
RecommendedSkills: "Read `cloudbase-document-database-web-sdk` (and `cloud-storage-web` for uploads). `postgresql-development-cloudbase` and `relational-database-mcp-cloudbase` are not applicable to this env.",
|
|
2430
2456
|
};
|
|
2431
2457
|
return {
|
|
2432
2458
|
...result,
|
|
@@ -3347,7 +3373,7 @@ function registerEnvTools(server) {
|
|
|
3347
3373
|
// Register primary tool name (queryEnv)
|
|
3348
3374
|
const queryEnvToolSchema = {
|
|
3349
3375
|
title: "CloudBase 环境查询",
|
|
3350
|
-
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-
|
|
3376
|
+
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 都不该使用。",
|
|
3351
3377
|
inputSchema: {
|
|
3352
3378
|
action: zod_1.z
|
|
3353
3379
|
.enum(["list", "info", "domains"])
|
|
@@ -80012,6 +80038,32 @@ function parallel(list, iterator, callback)
|
|
|
80012
80038
|
}
|
|
80013
80039
|
|
|
80014
80040
|
|
|
80041
|
+
/***/ }),
|
|
80042
|
+
|
|
80043
|
+
/***/ 18944:
|
|
80044
|
+
/***/ ((__unused_webpack_module, exports) => {
|
|
80045
|
+
|
|
80046
|
+
"use strict";
|
|
80047
|
+
|
|
80048
|
+
/**
|
|
80049
|
+
* 集成中心(UserKey)类型定义
|
|
80050
|
+
*
|
|
80051
|
+
* 字段与 dev-platform(weda-alternative/apps/dev-platform)对齐:
|
|
80052
|
+
* - 云 API 出入参为 PascalCase(EnvId / KeyID / AuthTypeCode / Ext / NeedPwd 等)
|
|
80053
|
+
* - Ext 为 JSON 字符串,承载动态配置字段(含加密后的敏感值)
|
|
80054
|
+
*/
|
|
80055
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
80056
|
+
exports.UserKeyBindServiceType = void 0;
|
|
80057
|
+
/** 密钥对绑定的服务类型 */
|
|
80058
|
+
var UserKeyBindServiceType;
|
|
80059
|
+
(function (UserKeyBindServiceType) {
|
|
80060
|
+
UserKeyBindServiceType["MCP"] = "Mcp";
|
|
80061
|
+
UserKeyBindServiceType["APIS"] = "Apis";
|
|
80062
|
+
UserKeyBindServiceType["OTHER"] = "Other";
|
|
80063
|
+
UserKeyBindServiceType["CLOUD_FUNCTION"] = "CloudFunction";
|
|
80064
|
+
})(UserKeyBindServiceType || (exports.UserKeyBindServiceType = UserKeyBindServiceType = {}));
|
|
80065
|
+
|
|
80066
|
+
|
|
80015
80067
|
/***/ }),
|
|
80016
80068
|
|
|
80017
80069
|
/***/ 18959:
|
|
@@ -84596,6 +84648,102 @@ exports.colors = {
|
|
|
84596
84648
|
})));
|
|
84597
84649
|
|
|
84598
84650
|
|
|
84651
|
+
/***/ }),
|
|
84652
|
+
|
|
84653
|
+
/***/ 22573:
|
|
84654
|
+
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
|
|
84655
|
+
|
|
84656
|
+
"use strict";
|
|
84657
|
+
|
|
84658
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
84659
|
+
exports.getAesKey = getAesKey;
|
|
84660
|
+
exports.__clearAesKeyCache = __clearAesKeyCache;
|
|
84661
|
+
/**
|
|
84662
|
+
* 集成中心 AES key 获取(七彩石)
|
|
84663
|
+
*
|
|
84664
|
+
* 通过 QueryRainbowConfig 拉取 group=tcb-config(国内)/
|
|
84665
|
+
* frontend.tcb-config(国际)下字段 dev-platform-apis 的值作为 AES-256-CBC 密钥。
|
|
84666
|
+
*
|
|
84667
|
+
* 安全约束:
|
|
84668
|
+
* - key 仅在进程内存使用,按 group 维度缓存;
|
|
84669
|
+
* - 绝不写入任何本地文件、绝不打印日志。
|
|
84670
|
+
*/
|
|
84671
|
+
const http_request_1 = __webpack_require__(72088);
|
|
84672
|
+
const error_1 = __webpack_require__(40430);
|
|
84673
|
+
/** 七彩石配置 */
|
|
84674
|
+
const RAINBOW_CONFIG = {
|
|
84675
|
+
ENV_TYPE: 'Default',
|
|
84676
|
+
GROUP_CN: 'tcb-config',
|
|
84677
|
+
GROUP_INTL: 'frontend.tcb-config',
|
|
84678
|
+
/** AES key 在 kvs 中的字段名 */
|
|
84679
|
+
CRYPTO_KEY_FIELD: 'dev-platform-apis'
|
|
84680
|
+
};
|
|
84681
|
+
const RAINBOW_HOST_CN = 'wedaapi.tcbautomation.cn';
|
|
84682
|
+
const RAINBOW_HOST_INTL = 'gray.decisiontcb.tencentcloud.com';
|
|
84683
|
+
/** 无 EnvId 时的占位 Uin */
|
|
84684
|
+
const FALLBACK_UIN = '3309033';
|
|
84685
|
+
/** 进程内缓存(按 group 维度),key 不落盘、不打印 */
|
|
84686
|
+
const keyCache = {};
|
|
84687
|
+
/**
|
|
84688
|
+
* 获取集成中心 AES key(进程内缓存)
|
|
84689
|
+
* @throws CloudBaseError 拉取或解析失败
|
|
84690
|
+
*/
|
|
84691
|
+
async function getAesKey(params = {}) {
|
|
84692
|
+
var _a, _b;
|
|
84693
|
+
const { envId, isIntl = false } = params;
|
|
84694
|
+
const group = isIntl ? RAINBOW_CONFIG.GROUP_INTL : RAINBOW_CONFIG.GROUP_CN;
|
|
84695
|
+
if (keyCache[group]) {
|
|
84696
|
+
return keyCache[group];
|
|
84697
|
+
}
|
|
84698
|
+
const host = isIntl ? RAINBOW_HOST_INTL : RAINBOW_HOST_CN;
|
|
84699
|
+
const url = `https://${host}/Graydecision/V1/QueryRainbowConfig?group=${group}`;
|
|
84700
|
+
const payload = {
|
|
84701
|
+
EnvType: RAINBOW_CONFIG.ENV_TYPE,
|
|
84702
|
+
Group: group
|
|
84703
|
+
};
|
|
84704
|
+
if (envId) {
|
|
84705
|
+
payload.EnvId = envId;
|
|
84706
|
+
}
|
|
84707
|
+
else {
|
|
84708
|
+
payload.Uin = FALLBACK_UIN;
|
|
84709
|
+
}
|
|
84710
|
+
let resJson;
|
|
84711
|
+
try {
|
|
84712
|
+
resJson = await (0, http_request_1.fetch)(url, {
|
|
84713
|
+
method: 'POST',
|
|
84714
|
+
headers: { 'content-type': 'application/json' },
|
|
84715
|
+
body: JSON.stringify(payload)
|
|
84716
|
+
});
|
|
84717
|
+
}
|
|
84718
|
+
catch (e) {
|
|
84719
|
+
throw new error_1.CloudBaseError('获取集成加解密密钥失败:七彩石配置拉取请求异常');
|
|
84720
|
+
}
|
|
84721
|
+
const data = (_a = resJson === null || resJson === void 0 ? void 0 : resJson.Response) === null || _a === void 0 ? void 0 : _a.Data;
|
|
84722
|
+
if (!data || data.ErrorCode !== '0') {
|
|
84723
|
+
throw new error_1.CloudBaseError(`获取集成加解密密钥失败:七彩石返回异常(ErrorCode=${data === null || data === void 0 ? void 0 : data.ErrorCode})`);
|
|
84724
|
+
}
|
|
84725
|
+
let config;
|
|
84726
|
+
try {
|
|
84727
|
+
config = JSON.parse(data.ConfigValue)[0];
|
|
84728
|
+
}
|
|
84729
|
+
catch (e) {
|
|
84730
|
+
throw new error_1.CloudBaseError('获取集成加解密密钥失败:七彩石配置内容解析失败');
|
|
84731
|
+
}
|
|
84732
|
+
const kvs = ((_b = config === null || config === void 0 ? void 0 : config.kvs) === null || _b === void 0 ? void 0 : _b.kvs) || [];
|
|
84733
|
+
const target = kvs.find(kv => kv.key === RAINBOW_CONFIG.CRYPTO_KEY_FIELD);
|
|
84734
|
+
const key = target === null || target === void 0 ? void 0 : target.value;
|
|
84735
|
+
if (!key) {
|
|
84736
|
+
throw new error_1.CloudBaseError(`获取集成加解密密钥失败:七彩石配置中缺少 ${RAINBOW_CONFIG.CRYPTO_KEY_FIELD} 字段`);
|
|
84737
|
+
}
|
|
84738
|
+
keyCache[group] = key;
|
|
84739
|
+
return key;
|
|
84740
|
+
}
|
|
84741
|
+
/** 清空进程内缓存(仅供测试使用) */
|
|
84742
|
+
function __clearAesKeyCache() {
|
|
84743
|
+
Object.keys(keyCache).forEach(k => delete keyCache[k]);
|
|
84744
|
+
}
|
|
84745
|
+
|
|
84746
|
+
|
|
84599
84747
|
/***/ }),
|
|
84600
84748
|
|
|
84601
84749
|
/***/ 22738:
|
|
@@ -87688,6 +87836,116 @@ __decorate([
|
|
|
87688
87836
|
], PermissionService.prototype, "modifyEnvAuthzConfig", null);
|
|
87689
87837
|
|
|
87690
87838
|
|
|
87839
|
+
/***/ }),
|
|
87840
|
+
|
|
87841
|
+
/***/ 25068:
|
|
87842
|
+
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
87843
|
+
|
|
87844
|
+
"use strict";
|
|
87845
|
+
|
|
87846
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
87847
|
+
if (k2 === undefined) k2 = k;
|
|
87848
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
87849
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
87850
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
87851
|
+
}
|
|
87852
|
+
Object.defineProperty(o, k2, desc);
|
|
87853
|
+
}) : (function(o, m, k, k2) {
|
|
87854
|
+
if (k2 === undefined) k2 = k;
|
|
87855
|
+
o[k2] = m[k];
|
|
87856
|
+
}));
|
|
87857
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
87858
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
87859
|
+
}) : function(o, v) {
|
|
87860
|
+
o["default"] = v;
|
|
87861
|
+
});
|
|
87862
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
87863
|
+
var ownKeys = function(o) {
|
|
87864
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
87865
|
+
var ar = [];
|
|
87866
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
87867
|
+
return ar;
|
|
87868
|
+
};
|
|
87869
|
+
return ownKeys(o);
|
|
87870
|
+
};
|
|
87871
|
+
return function (mod) {
|
|
87872
|
+
if (mod && mod.__esModule) return mod;
|
|
87873
|
+
var result = {};
|
|
87874
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
87875
|
+
__setModuleDefault(result, mod);
|
|
87876
|
+
return result;
|
|
87877
|
+
};
|
|
87878
|
+
})();
|
|
87879
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
87880
|
+
exports.AESCrypto = void 0;
|
|
87881
|
+
exports.aesEncryptWithIv = aesEncryptWithIv;
|
|
87882
|
+
/**
|
|
87883
|
+
* 集成中心 AES 加解密
|
|
87884
|
+
*
|
|
87885
|
+
* 与 dev-platform 控制台的 AESCrypto(crypto-js)完全对齐,保证密文可互相解出:
|
|
87886
|
+
* - 算法:AES-256-CBC
|
|
87887
|
+
* - key:32 字节(按 UTF-8 解析)
|
|
87888
|
+
* - IV:每次随机 16 字节
|
|
87889
|
+
* - padding:PKCS7
|
|
87890
|
+
* - 密文格式:base64(IV) + base64(ciphertext)
|
|
87891
|
+
* (IV 固定 16 字节 → base64 长度恒为 24,前 24 字符即 IV)
|
|
87892
|
+
*
|
|
87893
|
+
* 使用 Node 内置 crypto 复刻,无需引入额外依赖。
|
|
87894
|
+
*/
|
|
87895
|
+
const crypto = __importStar(__webpack_require__(55511));
|
|
87896
|
+
const ALGORITHM = 'aes-256-cbc';
|
|
87897
|
+
const KEY_BYTE_LENGTH = 32;
|
|
87898
|
+
const IV_BYTE_LENGTH = 16;
|
|
87899
|
+
/** base64(16 字节) 的固定长度 */
|
|
87900
|
+
const IV_BASE64_LENGTH = 24;
|
|
87901
|
+
function assertKey(key) {
|
|
87902
|
+
if (Buffer.byteLength(key, 'utf8') !== KEY_BYTE_LENGTH) {
|
|
87903
|
+
throw new Error('AES key 必须是 32 字节长度');
|
|
87904
|
+
}
|
|
87905
|
+
}
|
|
87906
|
+
/**
|
|
87907
|
+
* 使用指定 IV 加密(供测试与内部使用,保证可重现)
|
|
87908
|
+
*/
|
|
87909
|
+
function aesEncryptWithIv(plaintext, key, iv) {
|
|
87910
|
+
assertKey(key);
|
|
87911
|
+
if (iv.length !== IV_BYTE_LENGTH) {
|
|
87912
|
+
throw new Error('IV 必须是 16 字节长度');
|
|
87913
|
+
}
|
|
87914
|
+
const cipher = crypto.createCipheriv(ALGORITHM, Buffer.from(key, 'utf8'), iv);
|
|
87915
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
|
|
87916
|
+
return iv.toString('base64') + encrypted.toString('base64');
|
|
87917
|
+
}
|
|
87918
|
+
class AESCryptoImpl {
|
|
87919
|
+
/**
|
|
87920
|
+
* AES-256-CBC 加密,返回 base64(IV)+base64(密文)
|
|
87921
|
+
*/
|
|
87922
|
+
encrypt(plaintext, key) {
|
|
87923
|
+
assertKey(key);
|
|
87924
|
+
const iv = crypto.randomBytes(IV_BYTE_LENGTH);
|
|
87925
|
+
return aesEncryptWithIv(plaintext, key, iv);
|
|
87926
|
+
}
|
|
87927
|
+
/**
|
|
87928
|
+
* 解密 base64(IV)+base64(密文) 格式的密文
|
|
87929
|
+
*/
|
|
87930
|
+
decrypt(encryptedData, key) {
|
|
87931
|
+
assertKey(key);
|
|
87932
|
+
const ivBase64 = encryptedData.slice(0, IV_BASE64_LENGTH);
|
|
87933
|
+
const ciphertextBase64 = encryptedData.slice(IV_BASE64_LENGTH);
|
|
87934
|
+
const iv = Buffer.from(ivBase64, 'base64');
|
|
87935
|
+
if (iv.length !== IV_BYTE_LENGTH) {
|
|
87936
|
+
throw new Error('密文格式错误:IV 长度非法');
|
|
87937
|
+
}
|
|
87938
|
+
const decipher = crypto.createDecipheriv(ALGORITHM, Buffer.from(key, 'utf8'), iv);
|
|
87939
|
+
const decrypted = Buffer.concat([
|
|
87940
|
+
decipher.update(Buffer.from(ciphertextBase64, 'base64')),
|
|
87941
|
+
decipher.final()
|
|
87942
|
+
]);
|
|
87943
|
+
return decrypted.toString('utf8');
|
|
87944
|
+
}
|
|
87945
|
+
}
|
|
87946
|
+
exports.AESCrypto = new AESCryptoImpl();
|
|
87947
|
+
|
|
87948
|
+
|
|
87691
87949
|
/***/ }),
|
|
87692
87950
|
|
|
87693
87951
|
/***/ 25177:
|
|
@@ -89896,8 +90154,12 @@ exports.MANAGE_FUNCTION_ACTIONS = [
|
|
|
89896
90154
|
"incrementalDeployFunction", // 增量部署,需通过 pluginOptions.functions 注入实现
|
|
89897
90155
|
];
|
|
89898
90156
|
const VPC_SCHEMA = zod_1.z.object({
|
|
89899
|
-
vpcId: zod_1.z
|
|
89900
|
-
|
|
90157
|
+
vpcId: zod_1.z
|
|
90158
|
+
.string()
|
|
90159
|
+
.describe("VPC ID from the real database/network console (e.g. vpc-xxxxxxxx). Required for non-native TCP DB access. Do NOT invent or use placeholders."),
|
|
90160
|
+
subnetId: zod_1.z
|
|
90161
|
+
.string()
|
|
90162
|
+
.describe("Subnet ID in the same VPC as the private DB endpoint (e.g. subnet-xxxxxxxx). Do NOT invent or use placeholders."),
|
|
89901
90163
|
});
|
|
89902
90164
|
// 镜像部署值,对应 SCF Runtime=CustomImage
|
|
89903
90165
|
exports.CUSTOM_IMAGE_RUNTIME = "CustomImage";
|
|
@@ -89981,8 +90243,11 @@ const CREATE_FUNCTION_SCHEMA = zod_1.z.object({
|
|
|
89981
90243
|
})
|
|
89982
90244
|
.optional(),
|
|
89983
90245
|
timeout: zod_1.z.number().optional().describe("函数超时时间"),
|
|
89984
|
-
envVariables: zod_1.z
|
|
89985
|
-
|
|
90246
|
+
envVariables: zod_1.z
|
|
90247
|
+
.record(zod_1.z.string())
|
|
90248
|
+
.optional()
|
|
90249
|
+
.describe("环境变量。若包含 DATABASE_URL / MYSQL_* / POSTGRES_* / REDIS_* 等传统 TCP 连库变量,必须同时配置 vpc(vpcId+subnetId),且 ID 必须来自真实库/网络信息,禁止猜测。原生 app.rdb()/app.database() 不需要 VPC。"),
|
|
90250
|
+
vpc: VPC_SCHEMA.optional().describe("私有网络配置(出网)。非原生 SDK、用 TCP 访问 VPC 内 MySQL/PostgreSQL/Redis 时必填。vpcId/subnetId 必须与数据库内网 VPC 一致;未知时先查控制台或询问用户,禁止填占位符。"),
|
|
89986
90251
|
runtime: zod_1.z
|
|
89987
90252
|
.string()
|
|
89988
90253
|
.optional()
|
|
@@ -90236,7 +90501,7 @@ function registerFunctionTools(server) {
|
|
|
90236
90501
|
},
|
|
90237
90502
|
{
|
|
90238
90503
|
tool: "queryGateway",
|
|
90239
|
-
action: "
|
|
90504
|
+
action: "getRoute",
|
|
90240
90505
|
reason: "查看该函数是否已暴露网关访问入口",
|
|
90241
90506
|
},
|
|
90242
90507
|
]);
|
|
@@ -90546,7 +90811,7 @@ function registerFunctionTools(server) {
|
|
|
90546
90811
|
raw: imageResult,
|
|
90547
90812
|
}, `已基于镜像 ${createImageConfig.imageUri} 创建 HTTP 函数 ${functionName}。` +
|
|
90548
90813
|
`请确认 TCR、SCF 与构建管道处于同一地域;如需通过 URL 访问,请显式调用 ` +
|
|
90549
|
-
`manageGateway(action="
|
|
90814
|
+
`manageGateway(action="createRoute", upstreamResourceType="WEB_SCF") 并按需调整函数安全规则。` +
|
|
90550
90815
|
`部署后可用 queryFunctions(action="getFunctionDetail") 确认函数已就绪。`, [
|
|
90551
90816
|
{
|
|
90552
90817
|
tool: "queryFunctions",
|
|
@@ -90560,8 +90825,8 @@ function registerFunctionTools(server) {
|
|
|
90560
90825
|
},
|
|
90561
90826
|
{
|
|
90562
90827
|
tool: "manageGateway",
|
|
90563
|
-
action: "
|
|
90564
|
-
reason: "如需通过 URL 访问镜像 HTTP
|
|
90828
|
+
action: "createRoute",
|
|
90829
|
+
reason: "如需通过 URL 访问镜像 HTTP 函数,显式创建 Domain/Route 访问入口并传 type=\"HTTP\"(映射 WEB_SCF)",
|
|
90565
90830
|
},
|
|
90566
90831
|
]);
|
|
90567
90832
|
}
|
|
@@ -90616,12 +90881,12 @@ function registerFunctionTools(server) {
|
|
|
90616
90881
|
if (func.type === "HTTP") {
|
|
90617
90882
|
nextActions.push({
|
|
90618
90883
|
tool: "manageGateway",
|
|
90619
|
-
action: "
|
|
90620
|
-
reason: "如果需要通过 URL 访问 HTTP 函数,请调用 manageGateway(action=\"
|
|
90884
|
+
action: "createRoute",
|
|
90885
|
+
reason: "如果需要通过 URL 访问 HTTP 函数,请调用 manageGateway(action=\"createRoute\") 并显式传 upstreamResourceType=\"WEB_SCF\",再按实际路径和鉴权需求创建访问入口,不要默认假设 /函数名 已存在",
|
|
90621
90886
|
});
|
|
90622
90887
|
nextActions.push({
|
|
90623
90888
|
tool: "queryGateway",
|
|
90624
|
-
action: "
|
|
90889
|
+
action: "getRoute",
|
|
90625
90890
|
reason: "交付前确认 HTTP 访问路径是否已存在并已生效",
|
|
90626
90891
|
});
|
|
90627
90892
|
nextActions.push({
|
|
@@ -90636,7 +90901,7 @@ function registerFunctionTools(server) {
|
|
|
90636
90901
|
});
|
|
90637
90902
|
}
|
|
90638
90903
|
const message = func.type === "HTTP"
|
|
90639
|
-
? `已创建 HTTP 函数 ${functionName}。如果后续需要通过 URL 访问,请显式调用 manageGateway(action="
|
|
90904
|
+
? `已创建 HTTP 函数 ${functionName}。如果后续需要通过 URL 访问,请显式调用 manageGateway(action="createRoute"),并把 upstreamResourceType="WEB_SCF" 一起传入,再按实际路径和鉴权需求创建访问入口。评测或其他外部调用方可能会以匿名身份访问,而且失败后不一定会把 EXCEED_AUTHORITY 再反馈给 AI;交付前请主动确认访问路径和函数安全规则,若已出现 EXCEED_AUTHORITY,请先调用 queryPermissions(action="getResourcePermission", resourceType="function", resourceId="${functionName}") 查看当前规则,再按需要使用 managePermissions(action="updateResourcePermission") 调整权限。`
|
|
90640
90905
|
: `已创建函数 ${functionName}`;
|
|
90641
90906
|
return buildEnvelope({
|
|
90642
90907
|
action: input.action,
|
|
@@ -91124,8 +91389,11 @@ function registerFunctionTools(server) {
|
|
|
91124
91389
|
zipFile: zod_1.z.string().optional().describe("仅兼容特殊场景:预先准备好的代码包 base64 编码。普通 createFunction/updateFunctionCode 默认不要先压缩 zip,优先使用 functionRootPath。"),
|
|
91125
91390
|
handler: zod_1.z.string().optional().describe("函数入口"),
|
|
91126
91391
|
timeout: zod_1.z.number().optional().describe("配置更新时的超时时间"),
|
|
91127
|
-
envVariables: zod_1.z
|
|
91128
|
-
|
|
91392
|
+
envVariables: zod_1.z
|
|
91393
|
+
.record(zod_1.z.string())
|
|
91394
|
+
.optional()
|
|
91395
|
+
.describe("配置更新时要合并的环境变量。若含 DATABASE_URL / MYSQL_* / POSTGRES_* / REDIS_* 等 TCP 连库变量,必须同时提供真实 vpc(或函数已绑定完整 VPC)。禁止猜测 vpcId/subnetId。"),
|
|
91396
|
+
vpc: VPC_SCHEMA.optional().describe("配置更新时的 VPC 信息。非原生 TCP 连库场景必填真实 vpcId+subnetId;不要用占位符。"),
|
|
91129
91397
|
params: zod_1.z.record(zod_1.z.any()).optional().describe("invokeFunction 的调用参数"),
|
|
91130
91398
|
triggers: zod_1.z
|
|
91131
91399
|
.array(TRIGGER_SCHEMA)
|
|
@@ -93397,7 +93665,7 @@ class EnvService {
|
|
|
93397
93665
|
* @returns {Promise<IEnvInfoRes>}
|
|
93398
93666
|
*/
|
|
93399
93667
|
async getEnvInfo() {
|
|
93400
|
-
var _a;
|
|
93668
|
+
var _a, _b;
|
|
93401
93669
|
// 使用 DescribeEnvInfo 进行单环境查询
|
|
93402
93670
|
const params = {
|
|
93403
93671
|
EnvId: this.envId
|
|
@@ -93405,6 +93673,7 @@ class EnvService {
|
|
|
93405
93673
|
const response = await this.describeEnvInfo(params);
|
|
93406
93674
|
const envBaseInfo = (_a = response === null || response === void 0 ? void 0 : response.EnvInfo) === null || _a === void 0 ? void 0 : _a.EnvBaseInfo;
|
|
93407
93675
|
// 将 EnvBaseInfo 完整映射到 EnvInfo 类型,避免新增字段被丢弃
|
|
93676
|
+
// UserInfo 位于 response.EnvInfo 顶层(非 EnvBaseInfo),单独映射
|
|
93408
93677
|
const envInfo = {
|
|
93409
93678
|
EnvId: envBaseInfo.EnvId,
|
|
93410
93679
|
Alias: envBaseInfo.Alias,
|
|
@@ -93434,7 +93703,8 @@ class EnvService {
|
|
|
93434
93703
|
EnvPreferences: envBaseInfo.EnvPreferences,
|
|
93435
93704
|
Meta: envBaseInfo.Meta,
|
|
93436
93705
|
PostgreSQL: envBaseInfo.PostgreSQL,
|
|
93437
|
-
EnvStatus: envBaseInfo.EnvStatus
|
|
93706
|
+
EnvStatus: envBaseInfo.EnvStatus,
|
|
93707
|
+
UserInfo: (_b = response === null || response === void 0 ? void 0 : response.EnvInfo) === null || _b === void 0 ? void 0 : _b.UserInfo
|
|
93438
93708
|
};
|
|
93439
93709
|
return {
|
|
93440
93710
|
EnvInfo: envInfo,
|
|
@@ -103410,7 +103680,6 @@ const capi_js_1 = __webpack_require__(40738);
|
|
|
103410
103680
|
const cloudrun_js_1 = __webpack_require__(35023);
|
|
103411
103681
|
const dataModel_js_1 = __webpack_require__(34052);
|
|
103412
103682
|
const gateway_js_1 = __webpack_require__(84319);
|
|
103413
|
-
const invite_code_js_1 = __webpack_require__(44760);
|
|
103414
103683
|
const agents_js_1 = __webpack_require__(9527);
|
|
103415
103684
|
const app_auth_js_1 = __webpack_require__(5313);
|
|
103416
103685
|
const apps_js_1 = __webpack_require__(36817);
|
|
@@ -103441,7 +103710,6 @@ const DEFAULT_PLUGINS = [
|
|
|
103441
103710
|
"logs",
|
|
103442
103711
|
"agents",
|
|
103443
103712
|
"download",
|
|
103444
|
-
"invite-code",
|
|
103445
103713
|
"capi",
|
|
103446
103714
|
];
|
|
103447
103715
|
function registerDatabase(server) {
|
|
@@ -103483,7 +103751,6 @@ const AVAILABLE_PLUGINS = {
|
|
|
103483
103751
|
logs: { name: "logs", register: logs_js_1.registerLogTools },
|
|
103484
103752
|
agents: { name: "agents", register: agents_js_1.registerAgentTools },
|
|
103485
103753
|
apps: { name: "apps", register: apps_js_1.registerAppTools },
|
|
103486
|
-
"invite-code": { name: "invite-code", register: invite_code_js_1.registerInviteCodeTools },
|
|
103487
103754
|
cloudrun: { name: "cloudrun", register: cloudrun_js_1.registerCloudRunTools },
|
|
103488
103755
|
capi: { name: "capi", register: capi_js_1.registerCapiTools },
|
|
103489
103756
|
};
|
|
@@ -113963,6 +114230,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
113963
114230
|
};
|
|
113964
114231
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
113965
114232
|
exports.CLOUDRUN_ACCESS_TYPES = exports.CLOUDRUN_SERVICE_TYPES = void 0;
|
|
114233
|
+
exports.detectCloudRunDbNetworkRisk = detectCloudRunDbNetworkRisk;
|
|
113966
114234
|
exports.registerCloudRunTools = registerCloudRunTools;
|
|
113967
114235
|
const child_process_1 = __webpack_require__(79646);
|
|
113968
114236
|
const fs_1 = __importDefault(__webpack_require__(29021));
|
|
@@ -114005,7 +114273,7 @@ const ManageCloudRunInputSchema = {
|
|
|
114005
114273
|
})).optional().describe('扩缩容配置数组,用于配置服务的自动扩缩容策略。可配置多个扩缩容策略'),
|
|
114006
114274
|
CustomLogs: zod_1.z.string().optional().describe('自定义日志配置,用于配置服务的日志收集和存储策略'),
|
|
114007
114275
|
Port: zod_1.z.number().min(1).max(65535).optional().describe('服务监听端口配置。函数型服务固定为3000,容器型服务可自定义。服务代码必须监听此端口才能正常接收请求'),
|
|
114008
|
-
EnvParams: zod_1.z.string().optional().describe('环境变量配置,JSON字符串格式。用于传递配置信息给服务代码,如\'{"DATABASE_URL":"
|
|
114276
|
+
EnvParams: zod_1.z.string().optional().describe('环境变量配置,JSON字符串格式。用于传递配置信息给服务代码,如\'{"DATABASE_URL":"postgres://user:pass@10.x.x.x:5432/db","NODE_ENV":"production"}\'。SDK v5.6.1+ 会自动对传入的环境变量进行 AES-256-CBC 加密传输。⚠️ 若 EnvParams 含 DATABASE_URL / MYSQL_* / POSTGRES_* / REDIS_* 等传统 TCP 连库变量,必须同时配置 VpcConf,否则实例通常无法访问 VPC 内数据库'),
|
|
114009
114277
|
Dockerfile: zod_1.z.string().optional().describe('Dockerfile文件名配置,仅容器型服务需要。指定用于构建容器镜像的Dockerfile文件路径,默认为项目根目录下的Dockerfile'),
|
|
114010
114278
|
BuildDir: zod_1.z.string().optional().describe('构建目录配置,指定代码构建的目录路径。当代码结构与标准不同时使用,默认为项目根目录'),
|
|
114011
114279
|
InternalAccess: zod_1.z.string().optional().describe('内网访问开关配置,控制是否启用内网访问。true=启用内网访问(可通过云开发SDK直接调用),false=关闭内网访问(仅公网访问)'),
|
|
@@ -114029,9 +114297,9 @@ const ManageCloudRunInputSchema = {
|
|
|
114029
114297
|
ReplicaNum: zod_1.z.number().min(0).describe('定时扩缩容的目标副本数,最小值0(缩容到0)')
|
|
114030
114298
|
})).optional().describe('定时扩缩容配置数组,用于配置服务的定时自动扩缩容策略。可配置多个时间段的扩缩容计划,支持每日/每周/每月循环'),
|
|
114031
114299
|
VpcConf: zod_1.z.object({
|
|
114032
|
-
VpcId: zod_1.z.string().describe('VPC网络ID
|
|
114033
|
-
SubnetId: zod_1.z.string().describe('子网ID
|
|
114034
|
-
}).optional().describe('VPC
|
|
114300
|
+
VpcId: zod_1.z.string().describe('VPC网络ID,格式如 vpc-xxxxxxxx。必须与目标数据库/Redis 处于同一地域,并优先选择同一 VPC。禁止猜测或使用占位符;须来自数据库控制台、已有资源详情、callCloudApi 或用户确认。建议首次创建即配置;已存在服务也可在 deploy 时传入,部署后必须用 queryCloudRun detail 复核是否生效'),
|
|
114301
|
+
SubnetId: zod_1.z.string().describe('子网ID,格式如 subnet-xxxxxxxx。云托管实例将占用该子网 IP,需确保有足够可用 IP'),
|
|
114302
|
+
}).optional().describe('VPC网络配置(实例出网/私有网络)。用于让云托管实例接入指定 VPC,从而内网访问 MySQL/PostgreSQL/Redis/CVM 等资源。与 OpenAccessTypes(外部如何访问本服务)是不同概念。TCP 连库场景必须配置。禁止猜测 VpcId/SubnetId,须来自数据库控制台、已有资源详情、callCloudApi 或用户确认。MCP 在创建时会将 VpcConf 映射为 SDK vpcInfo(CreateType=2);更新后必须用 queryCloudRun detail 复核 ServerConfig.VpcConf,未生效再走控制台网络设置或删建兜底'),
|
|
114035
114303
|
VolumesConf: zod_1.z.array(zod_1.z.object({
|
|
114036
114304
|
VolumeName: zod_1.z.string().describe('存储卷名称'),
|
|
114037
114305
|
VolumeType: zod_1.z.string().describe('存储卷类型,如CFS表示云文件存储'),
|
|
@@ -114135,6 +114403,74 @@ function buildManageCloudRunErrorMessage(action, serverName, error) {
|
|
|
114135
114403
|
}
|
|
114136
114404
|
return `[manageCloudRun/${action}] ${baseMessage}\n建议:${suggestions.join(" ")}`;
|
|
114137
114405
|
}
|
|
114406
|
+
const CLOUDRUN_DB_ENV_KEY_PATTERN = /^(DATABASE_URL|DB_HOST|DB_PORT|DB_USER|DB_PASSWORD|DB_NAME|MYSQL_|POSTGRES_|PGHOST|PGPORT|PGUSER|PGPASSWORD|PGDATABASE|PG_|REDIS_|MONGO_|MONGODB_|SQLALCHEMY_DATABASE_URI|SPRING_DATASOURCE_)/i;
|
|
114407
|
+
const CLOUDRUN_DB_URL_PATTERN = /(mysql|mariadb|postgres|postgresql|mongodb(\+srv)?|redis|rediss):\/\//i;
|
|
114408
|
+
/**
|
|
114409
|
+
* Detect likely TCP database/cache env usage without VpcConf.
|
|
114410
|
+
* Soft signal for AI agents — does not block deploy.
|
|
114411
|
+
*/
|
|
114412
|
+
function detectCloudRunDbNetworkRisk(options) {
|
|
114413
|
+
const vpcId = options.vpcConf?.VpcId?.trim();
|
|
114414
|
+
const subnetId = options.vpcConf?.SubnetId?.trim();
|
|
114415
|
+
if (vpcId && subnetId) {
|
|
114416
|
+
return null;
|
|
114417
|
+
}
|
|
114418
|
+
const rawEnv = options.envParams?.trim();
|
|
114419
|
+
if (!rawEnv) {
|
|
114420
|
+
return null;
|
|
114421
|
+
}
|
|
114422
|
+
let parsed;
|
|
114423
|
+
try {
|
|
114424
|
+
const value = JSON.parse(rawEnv);
|
|
114425
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
114426
|
+
return null;
|
|
114427
|
+
}
|
|
114428
|
+
parsed = value;
|
|
114429
|
+
}
|
|
114430
|
+
catch {
|
|
114431
|
+
// Non-JSON EnvParams still may embed connection URLs as plain text.
|
|
114432
|
+
if (!CLOUDRUN_DB_URL_PATTERN.test(rawEnv)) {
|
|
114433
|
+
return null;
|
|
114434
|
+
}
|
|
114435
|
+
return {
|
|
114436
|
+
code: "MISSING_VPC_FOR_DB_ENV",
|
|
114437
|
+
message: "EnvParams appears to include a database/cache connection URL, but serverConfig.VpcConf is missing. CloudRun instances usually cannot reach VPC-private MySQL/PostgreSQL/Redis without VpcConf.",
|
|
114438
|
+
matchedKeys: ["<non-json-envParams>"],
|
|
114439
|
+
remediation: [
|
|
114440
|
+
"Set serverConfig.VpcConf to the same region/VPC (and a subnet with free IPs) as the database.",
|
|
114441
|
+
"Do NOT invent VpcId/SubnetId. Resolve real IDs from the DB console, resource detail, callCloudApi, or the user.",
|
|
114442
|
+
"Use the database private/intranet hostname in EnvParams, not localhost or docker-compose service names.",
|
|
114443
|
+
"Ensure the DB security group allows the CloudRun subnet on the DB port.",
|
|
114444
|
+
"Re-deploy after VpcConf is set. OpenAccessTypes alone does not provide VPC egress to databases.",
|
|
114445
|
+
],
|
|
114446
|
+
};
|
|
114447
|
+
}
|
|
114448
|
+
const matchedKeys = [];
|
|
114449
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
114450
|
+
if (CLOUDRUN_DB_ENV_KEY_PATTERN.test(key)) {
|
|
114451
|
+
matchedKeys.push(key);
|
|
114452
|
+
continue;
|
|
114453
|
+
}
|
|
114454
|
+
if (typeof value === "string" && CLOUDRUN_DB_URL_PATTERN.test(value)) {
|
|
114455
|
+
matchedKeys.push(key);
|
|
114456
|
+
}
|
|
114457
|
+
}
|
|
114458
|
+
if (matchedKeys.length === 0) {
|
|
114459
|
+
return null;
|
|
114460
|
+
}
|
|
114461
|
+
return {
|
|
114462
|
+
code: "MISSING_VPC_FOR_DB_ENV",
|
|
114463
|
+
message: "EnvParams includes database/cache connection settings, but serverConfig.VpcConf is missing. Deploy may succeed while runtime DB connections fail.",
|
|
114464
|
+
matchedKeys,
|
|
114465
|
+
remediation: [
|
|
114466
|
+
"Set serverConfig.VpcConf.VpcId and SubnetId to the database VPC/subnet (same region).",
|
|
114467
|
+
"Do NOT invent VpcId/SubnetId. Resolve real IDs from the DB console, resource detail, callCloudApi, or the user.",
|
|
114468
|
+
"Use the private DB endpoint in EnvParams.",
|
|
114469
|
+
"Confirm security group / allowlist permits CloudRun subnet access to the DB port.",
|
|
114470
|
+
"Do not confuse OpenAccessTypes (ingress) with VpcConf (egress to VPC resources).",
|
|
114471
|
+
],
|
|
114472
|
+
};
|
|
114473
|
+
}
|
|
114138
114474
|
function getCloudRunQueryServerName(input) {
|
|
114139
114475
|
return input.detailServerName || input.serverName;
|
|
114140
114476
|
}
|
|
@@ -114632,6 +114968,17 @@ for await (let x of res.textStream) {
|
|
|
114632
114968
|
if (input.serverConfig) {
|
|
114633
114969
|
deployParams.serverConfig = input.serverConfig;
|
|
114634
114970
|
}
|
|
114971
|
+
// Manager SDK create path prefers top-level vpcInfo (CreateCloudRunServer.VpcInfo).
|
|
114972
|
+
// Map serverConfig.VpcConf → vpcInfo so first-time create actually binds VPC.
|
|
114973
|
+
// Update path still sends Items.VpcConf; platform may ignore VPC changes on existing services.
|
|
114974
|
+
const vpcConf = input.serverConfig?.VpcConf;
|
|
114975
|
+
if (vpcConf?.VpcId?.trim() && vpcConf?.SubnetId?.trim()) {
|
|
114976
|
+
deployParams.vpcInfo = {
|
|
114977
|
+
VpcId: vpcConf.VpcId.trim(),
|
|
114978
|
+
CreateType: 2,
|
|
114979
|
+
SubnetIds: [vpcConf.SubnetId.trim()],
|
|
114980
|
+
};
|
|
114981
|
+
}
|
|
114635
114982
|
let result;
|
|
114636
114983
|
try {
|
|
114637
114984
|
result = await cloudrunService.deploy(deployParams);
|
|
@@ -114705,6 +115052,14 @@ for await (let x of res.textStream) {
|
|
|
114705
115052
|
// Notification failure should not affect deployment flow
|
|
114706
115053
|
// Error is already logged in sendDeployNotification
|
|
114707
115054
|
}
|
|
115055
|
+
const dbNetworkRisk = detectCloudRunDbNetworkRisk({
|
|
115056
|
+
envParams: input.serverConfig?.EnvParams,
|
|
115057
|
+
vpcConf: input.serverConfig?.VpcConf,
|
|
115058
|
+
});
|
|
115059
|
+
const warnings = dbNetworkRisk ? [dbNetworkRisk] : [];
|
|
115060
|
+
const warningSuffix = dbNetworkRisk
|
|
115061
|
+
? ` Warning: ${dbNetworkRisk.message} Set serverConfig.VpcConf before relying on DB connectivity.`
|
|
115062
|
+
: "";
|
|
114708
115063
|
return {
|
|
114709
115064
|
content: [
|
|
114710
115065
|
{
|
|
@@ -114717,9 +115072,10 @@ for await (let x of res.textStream) {
|
|
|
114717
115072
|
deployPath: targetPath,
|
|
114718
115073
|
serverType: serverType,
|
|
114719
115074
|
cloudbasercGenerated: true,
|
|
114720
|
-
consoleUrl
|
|
115075
|
+
consoleUrl,
|
|
115076
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
114721
115077
|
},
|
|
114722
|
-
message: `Triggered deployment for ${serverType} service '${input.serverName}' from ${targetPath}. You can follow the progress in ${consoleUrl}`
|
|
115078
|
+
message: `Triggered deployment for ${serverType} service '${input.serverName}' from ${targetPath}. You can follow the progress in ${consoleUrl}.${warningSuffix}`
|
|
114723
115079
|
}, null, 2)
|
|
114724
115080
|
}
|
|
114725
115081
|
]
|
|
@@ -132938,71 +133294,6 @@ util.inherits(ArchiverError, Error);
|
|
|
132938
133294
|
|
|
132939
133295
|
exports = module.exports = ArchiverError;
|
|
132940
133296
|
|
|
132941
|
-
/***/ }),
|
|
132942
|
-
|
|
132943
|
-
/***/ 44760:
|
|
132944
|
-
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
|
|
132945
|
-
|
|
132946
|
-
"use strict";
|
|
132947
|
-
|
|
132948
|
-
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
132949
|
-
exports.registerInviteCodeTools = registerInviteCodeTools;
|
|
132950
|
-
const zod_1 = __webpack_require__(21614);
|
|
132951
|
-
const cloudbase_manager_js_1 = __webpack_require__(3431);
|
|
132952
|
-
function registerInviteCodeTools(server) {
|
|
132953
|
-
const cloudBaseOptions = server.cloudBaseOptions;
|
|
132954
|
-
const getManager = () => (0, cloudbase_manager_js_1.getCloudBaseManager)({ cloudBaseOptions });
|
|
132955
|
-
server.registerTool?.("activateInviteCode", {
|
|
132956
|
-
title: "激活邀请码",
|
|
132957
|
-
description: "云开发 AI编程激励计划,通过邀请码激活用户激励。",
|
|
132958
|
-
inputSchema: {
|
|
132959
|
-
InviteCode: zod_1.z.string().describe("待激活的邀请码")
|
|
132960
|
-
},
|
|
132961
|
-
annotations: {
|
|
132962
|
-
readOnlyHint: false,
|
|
132963
|
-
destructiveHint: false,
|
|
132964
|
-
idempotentHint: false,
|
|
132965
|
-
openWorldHint: true,
|
|
132966
|
-
category: "invite-code"
|
|
132967
|
-
}
|
|
132968
|
-
}, async ({ InviteCode }) => {
|
|
132969
|
-
if (!InviteCode) {
|
|
132970
|
-
return {
|
|
132971
|
-
content: [
|
|
132972
|
-
{
|
|
132973
|
-
type: "text",
|
|
132974
|
-
text: JSON.stringify({
|
|
132975
|
-
ErrorCode: 'MissingParameter',
|
|
132976
|
-
ErrorMsg: '邀请码为必填项',
|
|
132977
|
-
RequestId: ''
|
|
132978
|
-
}, null, 2)
|
|
132979
|
-
}
|
|
132980
|
-
]
|
|
132981
|
-
};
|
|
132982
|
-
}
|
|
132983
|
-
const manager = await getManager();
|
|
132984
|
-
const EnvId = await (0, cloudbase_manager_js_1.getEnvId)(cloudBaseOptions);
|
|
132985
|
-
const result = await manager.commonService().call({
|
|
132986
|
-
Action: 'ActivateInviteCode',
|
|
132987
|
-
Param: { InviteCode, EnvId }
|
|
132988
|
-
});
|
|
132989
|
-
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
132990
|
-
return {
|
|
132991
|
-
content: [
|
|
132992
|
-
{
|
|
132993
|
-
type: "text",
|
|
132994
|
-
text: JSON.stringify({
|
|
132995
|
-
ErrorCode: result?.ErrorCode || '',
|
|
132996
|
-
ErrorMsg: result?.ErrorMsg || '',
|
|
132997
|
-
RequestId: result?.RequestId || ''
|
|
132998
|
-
}, null, 2)
|
|
132999
|
-
}
|
|
133000
|
-
]
|
|
133001
|
-
};
|
|
133002
|
-
});
|
|
133003
|
-
}
|
|
133004
|
-
|
|
133005
|
-
|
|
133006
133297
|
/***/ }),
|
|
133007
133298
|
|
|
133008
133299
|
/***/ 44763:
|
|
@@ -136914,7 +137205,7 @@ class TelemetryReporter {
|
|
|
136914
137205
|
const nodeVersion = process.version; // Node.js版本
|
|
136915
137206
|
const arch = os_1.default.arch(); // 系统架构
|
|
136916
137207
|
// 从构建时注入的版本号获取MCP版本信息
|
|
136917
|
-
const mcpVersion = process.env.npm_package_version || "2.
|
|
137208
|
+
const mcpVersion = process.env.npm_package_version || "2.25.1" || 0;
|
|
136918
137209
|
return {
|
|
136919
137210
|
userAgent: `${osType} ${osRelease} ${arch} ${nodeVersion} CloudBase-MCP/${mcpVersion}`,
|
|
136920
137211
|
deviceId: this.deviceId,
|
|
@@ -172843,6 +173134,7 @@ const permission_1 = __webpack_require__(24786);
|
|
|
172843
173134
|
const sandbox_1 = __webpack_require__(48488);
|
|
172844
173135
|
const aiModel_1 = __webpack_require__(34800);
|
|
172845
173136
|
const monitor_1 = __webpack_require__(8901);
|
|
173137
|
+
const integration_1 = __webpack_require__(72785);
|
|
172846
173138
|
class Environment {
|
|
172847
173139
|
constructor(context, envId) {
|
|
172848
173140
|
this.inited = false;
|
|
@@ -172869,6 +173161,7 @@ class Environment {
|
|
|
172869
173161
|
this.sandboxService = new sandbox_1.SandboxService(this);
|
|
172870
173162
|
this.aiModelService = new aiModel_1.AiModelService(this);
|
|
172871
173163
|
this.monitorService = new monitor_1.MonitorService(this);
|
|
173164
|
+
this.integrationService = new integration_1.IntegrationService(this);
|
|
172872
173165
|
}
|
|
172873
173166
|
async lazyInit() {
|
|
172874
173167
|
if (!this.inited) {
|
|
@@ -172946,6 +173239,9 @@ class Environment {
|
|
|
172946
173239
|
getMonitorService() {
|
|
172947
173240
|
return this.monitorService;
|
|
172948
173241
|
}
|
|
173242
|
+
getIntegrationService() {
|
|
173243
|
+
return this.integrationService;
|
|
173244
|
+
}
|
|
172949
173245
|
getCommonService(serviceType = 'tcb', serviceVersion) {
|
|
172950
173246
|
return new common_1.CommonService(this, serviceType, serviceVersion);
|
|
172951
173247
|
}
|
|
@@ -197005,7 +197301,7 @@ ${envIdSection}
|
|
|
197005
197301
|
## 环境信息
|
|
197006
197302
|
- 操作系统: ${os_1.default.type()} ${os_1.default.release()}
|
|
197007
197303
|
- Node.js版本: ${process.version}
|
|
197008
|
-
- MCP 版本:${process.env.npm_package_version || "2.
|
|
197304
|
+
- MCP 版本:${process.env.npm_package_version || "2.25.1" || 0}
|
|
197009
197305
|
- 系统架构: ${os_1.default.arch()}
|
|
197010
197306
|
- 时间: ${new Date().toISOString()}
|
|
197011
197307
|
- 请求ID: ${requestId}
|
|
@@ -197086,7 +197382,7 @@ function createWrappedHandler(name, handler, server) {
|
|
|
197086
197382
|
requestId: (typeof error === 'object' && error && 'requestId' in error) ? error.requestId : '',
|
|
197087
197383
|
ide: server.ide || process.env.INTEGRATION_IDE || ''
|
|
197088
197384
|
});
|
|
197089
|
-
const mcpVersion = true ? "2.
|
|
197385
|
+
const mcpVersion = true ? "2.25.1" : 0;
|
|
197090
197386
|
const requestIdSuffix = requestId ? `\n🆔 RequestId: ${requestId}` : '';
|
|
197091
197387
|
const enhancedErrorMessage = `${errorMessage}${requestIdSuffix}\n\n📦 CloudBase MCP v${mcpVersion}\n🔗 遇到问题?请复制以下链接到浏览器打开\n即可自动携带错误详情快速创建 GitHub Issue:\n${issueLink}`;
|
|
197092
197388
|
// 创建新的错误对象,保持原有的错误类型但更新消息
|
|
@@ -199117,6 +199413,7 @@ module.exports = cmp
|
|
|
199117
199413
|
|
|
199118
199414
|
"use strict";
|
|
199119
199415
|
|
|
199416
|
+
/* eslint-disable @typescript-eslint/member-ordering */
|
|
199120
199417
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
199121
199418
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
199122
199419
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -199359,27 +199656,30 @@ class StorageService {
|
|
|
199359
199656
|
getMetadataController.loadTasks(tasks);
|
|
199360
199657
|
const files = await getMetadataController.run();
|
|
199361
199658
|
// 对文件上传进行处理
|
|
199362
|
-
|
|
199363
|
-
const
|
|
199364
|
-
|
|
199365
|
-
|
|
199366
|
-
|
|
199659
|
+
// 构造 files 数组(格式:{ localPath, cloudPath }),传给 uploadFilesCustom
|
|
199660
|
+
const filesForUpload = fileStatsList
|
|
199661
|
+
.filter(stats => !stats.isDir)
|
|
199662
|
+
.map(stats => ({
|
|
199663
|
+
localPath: stats.filePath,
|
|
199664
|
+
cloudPath: stats.cloudFileKey
|
|
199665
|
+
}));
|
|
199666
|
+
// 调用 uploadFilesCustom(智能混合模式:并行→串行)
|
|
199667
|
+
// 不再直接调用 uploadFilesWithRetry(纯并行重试,容易 socket hang up)
|
|
199668
|
+
return this.uploadFilesCustom({
|
|
199669
|
+
files: filesForUpload,
|
|
199670
|
+
bucket,
|
|
199671
|
+
region,
|
|
199672
|
+
ignore: [],
|
|
199367
199673
|
onProgress,
|
|
199368
|
-
onFileFinish
|
|
199369
|
-
|
|
199370
|
-
|
|
199371
|
-
|
|
199372
|
-
|
|
199373
|
-
times: retryCount,
|
|
199374
|
-
interval: retryInterval,
|
|
199375
|
-
failedFiles: []
|
|
199674
|
+
onFileFinish,
|
|
199675
|
+
fileId,
|
|
199676
|
+
parallel,
|
|
199677
|
+
retryCount,
|
|
199678
|
+
retryInterval
|
|
199376
199679
|
});
|
|
199377
199680
|
}
|
|
199378
|
-
/**
|
|
199379
|
-
* 批量上传文件
|
|
199380
|
-
* @param options
|
|
199381
|
-
*/
|
|
199382
199681
|
async uploadFilesCustom(options) {
|
|
199682
|
+
var _a;
|
|
199383
199683
|
const { files, bucket, region, ignore, onProgress, onFileFinish, fileId = true, parallel = 20, retryCount = 0, retryInterval = 500 } = options;
|
|
199384
199684
|
if (!files || !files.length) {
|
|
199385
199685
|
return;
|
|
@@ -199393,6 +199693,146 @@ class StorageService {
|
|
|
199393
199693
|
};
|
|
199394
199694
|
})
|
|
199395
199695
|
.filter(item => ((ignore === null || ignore === void 0 ? void 0 : ignore.length) ? !micromatch_1.default.isMatch(item.filePath, ignore) : true));
|
|
199696
|
+
const totalFiles = fileList.length;
|
|
199697
|
+
console.log(`[uploadFiles] 准备上传 ${totalFiles} 个文件`);
|
|
199698
|
+
// 模式 1:完全串行模式(最稳定)
|
|
199699
|
+
if (process.env.COS_UPLOAD_SERIAL === 'true') {
|
|
199700
|
+
console.log(`[uploadFiles] 串行安全模式已启用`);
|
|
199701
|
+
const serialResult = await this.uploadFilesSerial({
|
|
199702
|
+
fileList,
|
|
199703
|
+
bucket,
|
|
199704
|
+
region,
|
|
199705
|
+
fileId,
|
|
199706
|
+
retryCount,
|
|
199707
|
+
retryInterval,
|
|
199708
|
+
onFileFinish
|
|
199709
|
+
});
|
|
199710
|
+
if (serialResult.failedFiles.length > 0) {
|
|
199711
|
+
throw new error_1.CloudBaseError(`[uploadFiles] 串行上传仍失败 (${serialResult.failedFiles.length} 个):\n` +
|
|
199712
|
+
serialResult.failedFiles.map(f => `- ${f.cloudFileKey}: ${f.error}`).join('\n'), { code: 'HOSTING_DEPLOY_FAILED' });
|
|
199713
|
+
}
|
|
199714
|
+
return {
|
|
199715
|
+
successCount: serialResult.successCount,
|
|
199716
|
+
totalFiles,
|
|
199717
|
+
failedFiles: []
|
|
199718
|
+
};
|
|
199719
|
+
}
|
|
199720
|
+
// 模式 2:智能混合模式(默认)
|
|
199721
|
+
// - 先并行快速上传
|
|
199722
|
+
// - 失败的文件自动走串行重试
|
|
199723
|
+
console.log(`[uploadFiles] 智能混合模式:先并行快速上传,失败文件自动串行重试`);
|
|
199724
|
+
// 第一步:并行上传(分批)
|
|
199725
|
+
const parallelFailedFiles = await this.uploadFilesParallel({
|
|
199726
|
+
fileList,
|
|
199727
|
+
bucket,
|
|
199728
|
+
region,
|
|
199729
|
+
fileId,
|
|
199730
|
+
parallel,
|
|
199731
|
+
retryCount: 0, // 并行阶段不重试,失败后交给串行阶段
|
|
199732
|
+
retryInterval,
|
|
199733
|
+
onFileFinish
|
|
199734
|
+
});
|
|
199735
|
+
// 第二步:串行重试失败的文件
|
|
199736
|
+
if (parallelFailedFiles && parallelFailedFiles.length > 0) {
|
|
199737
|
+
console.warn(`[uploadFiles] 并行阶段 ${parallelFailedFiles.length} 个文件失败,自动切换串行重试...`);
|
|
199738
|
+
const serialResult = await this.uploadFilesSerial({
|
|
199739
|
+
fileList: parallelFailedFiles,
|
|
199740
|
+
bucket,
|
|
199741
|
+
region,
|
|
199742
|
+
fileId: false, // 串行重试不需要再获取 fileId
|
|
199743
|
+
retryCount,
|
|
199744
|
+
retryInterval,
|
|
199745
|
+
onFileFinish
|
|
199746
|
+
});
|
|
199747
|
+
// 检查是否还有失败
|
|
199748
|
+
if (((_a = serialResult === null || serialResult === void 0 ? void 0 : serialResult.failedFiles) === null || _a === void 0 ? void 0 : _a.length) > 0) {
|
|
199749
|
+
throw new error_1.CloudBaseError(`[uploadFiles] 串行重试后仍失败 (${serialResult.failedFiles.length} 个):\n` +
|
|
199750
|
+
serialResult.failedFiles.map(f => `- ${f.cloudFileKey}: ${f.error}`).join('\n'), { code: 'HOSTING_DEPLOY_FAILED' });
|
|
199751
|
+
}
|
|
199752
|
+
}
|
|
199753
|
+
console.log(`[uploadFiles] 全部完成!共处理 ${totalFiles} 个文件`);
|
|
199754
|
+
return {
|
|
199755
|
+
successCount: totalFiles,
|
|
199756
|
+
totalFiles,
|
|
199757
|
+
failedFiles: []
|
|
199758
|
+
};
|
|
199759
|
+
}
|
|
199760
|
+
/**
|
|
199761
|
+
* 串行安全模式:逐文件上传(类似自定义脚本 deploy.js 的方式)
|
|
199762
|
+
* 特点:
|
|
199763
|
+
* - 每个文件独立 HTTP 连接,不会出现 socket hang up
|
|
199764
|
+
* - 单线程串行,稳定性最高
|
|
199765
|
+
* - 适合大规模目录(>1000 文件)或有网络问题的场景
|
|
199766
|
+
*/
|
|
199767
|
+
// eslint-disable-next-line @typescript-eslint/member-ordering
|
|
199768
|
+
async uploadFilesSerial(options) {
|
|
199769
|
+
const { fileList, bucket, region, fileId, retryCount, retryInterval, onFileFinish } = options;
|
|
199770
|
+
let uploadedCount = 0;
|
|
199771
|
+
let failedFiles = [];
|
|
199772
|
+
for (let i = 0; i < fileList.length; i++) {
|
|
199773
|
+
const file = fileList[i];
|
|
199774
|
+
let success = false;
|
|
199775
|
+
// 重试机制
|
|
199776
|
+
for (let attempt = 0; attempt <= retryCount; attempt++) {
|
|
199777
|
+
try {
|
|
199778
|
+
// 每个文件创建新的 COS 实例(确保连接隔离)
|
|
199779
|
+
const cos = this.getCos(1); // 严格单并发
|
|
199780
|
+
const putObject = util_1.default.promisify(cos.putObject).bind(cos);
|
|
199781
|
+
await putObject({
|
|
199782
|
+
Bucket: bucket,
|
|
199783
|
+
Region: region,
|
|
199784
|
+
Key: file.cloudFileKey,
|
|
199785
|
+
Body: fs_1.default.createReadStream(file.filePath),
|
|
199786
|
+
ContentLength: fs_1.default.statSync(file.filePath).size,
|
|
199787
|
+
});
|
|
199788
|
+
uploadedCount++;
|
|
199789
|
+
// 显示进度(每 100 个文件或最后 5 个)
|
|
199790
|
+
if (uploadedCount % 100 === 0 || fileList.length - i <= 5) {
|
|
199791
|
+
console.log(`[serial-upload] [${uploadedCount}/${fileList.length}] ${file.cloudFileKey}`);
|
|
199792
|
+
}
|
|
199793
|
+
success = true;
|
|
199794
|
+
// 回调成功
|
|
199795
|
+
if (onFileFinish) {
|
|
199796
|
+
onFileFinish(null, {}, file);
|
|
199797
|
+
}
|
|
199798
|
+
break;
|
|
199799
|
+
}
|
|
199800
|
+
catch (error) {
|
|
199801
|
+
const isLastAttempt = attempt === retryCount;
|
|
199802
|
+
if (isLastAttempt) {
|
|
199803
|
+
console.error(`[serial-upload] [${i + 1}/${fileList.length}] ${file.cloudFileKey}: ${error.message || error.code}`);
|
|
199804
|
+
// 记录失败的文件
|
|
199805
|
+
failedFiles.push(Object.assign(Object.assign({}, file), { error: error.message || String(error) }));
|
|
199806
|
+
// 回调失败
|
|
199807
|
+
if (onFileFinish) {
|
|
199808
|
+
onFileFinish(error, null, file);
|
|
199809
|
+
}
|
|
199810
|
+
}
|
|
199811
|
+
else {
|
|
199812
|
+
// 等待后重试
|
|
199813
|
+
await new Promise(resolve => { setTimeout(resolve, retryInterval); });
|
|
199814
|
+
}
|
|
199815
|
+
}
|
|
199816
|
+
}
|
|
199817
|
+
}
|
|
199818
|
+
console.log(`[serial-upload] 完成:成功 ${uploadedCount}/${fileList.length},失败 ${failedFiles.length}`);
|
|
199819
|
+
return {
|
|
199820
|
+
successCount: uploadedCount,
|
|
199821
|
+
failedFiles
|
|
199822
|
+
};
|
|
199823
|
+
}
|
|
199824
|
+
/**
|
|
199825
|
+
* 并发模式:使用 cos.uploadFiles() 批量上传
|
|
199826
|
+
* 特点:
|
|
199827
|
+
* - 速度快,利用 SDK 内部并行能力
|
|
199828
|
+
* - 分批上传 + 自动刷新凭证
|
|
199829
|
+
* - 适合小规模目录或网络良好的场景
|
|
199830
|
+
*
|
|
199831
|
+
* @returns 失败的文件列表(空的表示全部成功)
|
|
199832
|
+
*/
|
|
199833
|
+
// eslint-disable-next-line @typescript-eslint/member-ordering
|
|
199834
|
+
async uploadFilesParallel(options) {
|
|
199835
|
+
const { fileList, bucket, region, fileId, parallel, retryCount, retryInterval, onProgress, onFileFinish } = options;
|
|
199396
199836
|
// 生成上传文件属性
|
|
199397
199837
|
const tasks = fileList.map(stats => async () => {
|
|
199398
199838
|
let cosFileId;
|
|
@@ -199408,31 +199848,89 @@ class StorageService {
|
|
|
199408
199848
|
'x-cos-meta-fileid': cosFileId
|
|
199409
199849
|
};
|
|
199410
199850
|
});
|
|
199411
|
-
//
|
|
199851
|
+
// 控制请求并发(获取 metadata 阶段)
|
|
199412
199852
|
const asyncTaskController = new parallel_1.AsyncTaskParallelController(parallel, 50);
|
|
199413
199853
|
asyncTaskController.loadTasks(tasks);
|
|
199414
|
-
|
|
199415
|
-
|
|
199416
|
-
|
|
199417
|
-
const
|
|
199418
|
-
|
|
199419
|
-
|
|
199420
|
-
|
|
199421
|
-
|
|
199422
|
-
|
|
199423
|
-
|
|
199424
|
-
|
|
199425
|
-
|
|
199426
|
-
|
|
199427
|
-
|
|
199428
|
-
|
|
199429
|
-
|
|
199430
|
-
|
|
199431
|
-
|
|
199432
|
-
|
|
199433
|
-
|
|
199434
|
-
|
|
199435
|
-
|
|
199854
|
+
const processedFileList = await asyncTaskController.run();
|
|
199855
|
+
// 分批上传,避免单次上传时间过长导致 STS 临时凭证过期
|
|
199856
|
+
// 默认每批 500 个文件,可通过环境变量 COS_UPLOAD_BATCH_SIZE 自定义
|
|
199857
|
+
const BATCH_SIZE = process.env.COS_UPLOAD_BATCH_SIZE
|
|
199858
|
+
? parseInt(process.env.COS_UPLOAD_BATCH_SIZE, 10)
|
|
199859
|
+
: 500;
|
|
199860
|
+
// 收集所有批次失败的文件
|
|
199861
|
+
let allFailedFiles = [];
|
|
199862
|
+
for (let batchStart = 0; batchStart < processedFileList.length; batchStart += BATCH_SIZE) {
|
|
199863
|
+
const batchEnd = Math.min(batchStart + BATCH_SIZE, processedFileList.length);
|
|
199864
|
+
const batchFiles = processedFileList.slice(batchStart, batchEnd);
|
|
199865
|
+
console.log(`[parallel-upload] 正在上传第 ${Math.floor(batchStart / BATCH_SIZE) + 1} 批次 (文件 ${batchStart + 1}-${batchEnd}/${processedFileList.length})`);
|
|
199866
|
+
// 每批次重新获取 COS 实例(刷新 STS 临时凭证)
|
|
199867
|
+
const cos = this.getCos(parallel);
|
|
199868
|
+
const uploadFiles = util_1.default.promisify(cos.uploadFiles).bind(cos);
|
|
199869
|
+
// 跟踪本批次中真正失败的文件(通过 onFileFinish 回调)
|
|
199870
|
+
const batchFailedFiles = [];
|
|
199871
|
+
const batchSuccessFiles = new Set();
|
|
199872
|
+
const params = {
|
|
199873
|
+
files: batchFiles,
|
|
199874
|
+
SliceSize: BIG_FILE_SIZE,
|
|
199875
|
+
onProgress,
|
|
199876
|
+
onFileFinish: (error, result, file) => {
|
|
199877
|
+
// 空值检查:防止 file 为 null/undefined
|
|
199878
|
+
if (!file) {
|
|
199879
|
+
console.warn('[parallel-upload] onFileFinish received null/undefined file');
|
|
199880
|
+
return;
|
|
199881
|
+
}
|
|
199882
|
+
const fileKey = file.Key || file.cloudFileKey;
|
|
199883
|
+
if (error) {
|
|
199884
|
+
batchFailedFiles.push({
|
|
199885
|
+
filePath: file.FilePath || file.filePath,
|
|
199886
|
+
cloudFileKey: fileKey,
|
|
199887
|
+
error: error.message || String(error)
|
|
199888
|
+
});
|
|
199889
|
+
}
|
|
199890
|
+
else {
|
|
199891
|
+
batchSuccessFiles.add(fileKey);
|
|
199892
|
+
}
|
|
199893
|
+
// 继续调用用户传入的回调(转换参数格式)
|
|
199894
|
+
// 注意:cloudFileKey 和 filePath 是 CLI 期望的格式
|
|
199895
|
+
onFileFinish === null || onFileFinish === void 0 ? void 0 : onFileFinish(error, result, Object.assign(Object.assign({}, file), { cloudFileKey: fileKey, filePath: file.FilePath || file.filePath }));
|
|
199896
|
+
}
|
|
199897
|
+
};
|
|
199898
|
+
try {
|
|
199899
|
+
await this.uploadFilesWithRetry({
|
|
199900
|
+
uploadFiles,
|
|
199901
|
+
options: params,
|
|
199902
|
+
times: retryCount,
|
|
199903
|
+
interval: retryInterval,
|
|
199904
|
+
failedFiles: []
|
|
199905
|
+
});
|
|
199906
|
+
console.log(`[parallel-upload] 第 ${Math.floor(batchStart / BATCH_SIZE) + 1} 批次上传成功`);
|
|
199907
|
+
}
|
|
199908
|
+
catch (error) {
|
|
199909
|
+
console.error(`[parallel-upload] 第 ${Math.floor(batchStart / BATCH_SIZE) + 1} 批次上传失败:`, error.message || error.code || error);
|
|
199910
|
+
// 只记录真正失败的文件(通过 onFileFinish 回调跟踪)
|
|
199911
|
+
// 如果 onFileFinish 没有被调用(如 socket hang up),则标记整个批次为失败
|
|
199912
|
+
if (batchFailedFiles.length === 0 && batchSuccessFiles.size === 0) {
|
|
199913
|
+
// onFileFinish 未被调用,整个批次失败
|
|
199914
|
+
allFailedFiles.push(...batchFiles.map(f => ({
|
|
199915
|
+
filePath: f.FilePath || f.filePath,
|
|
199916
|
+
cloudFileKey: f.Key || f.cloudFileKey,
|
|
199917
|
+
error: error.message || String(error)
|
|
199918
|
+
})));
|
|
199919
|
+
}
|
|
199920
|
+
else {
|
|
199921
|
+
// onFileFinish 已被调用,只记录失败的文件
|
|
199922
|
+
allFailedFiles.push(...batchFailedFiles);
|
|
199923
|
+
}
|
|
199924
|
+
}
|
|
199925
|
+
}
|
|
199926
|
+
// 返回失败文件列表(不抛异常,让调用方决定是否走串行重试)
|
|
199927
|
+
if (allFailedFiles.length > 0) {
|
|
199928
|
+
console.warn(`[parallel-upload] 共 ${allFailedFiles.length}/${processedFileList.length} 个文件并行上传失败,等待串行重试...`);
|
|
199929
|
+
}
|
|
199930
|
+
else {
|
|
199931
|
+
console.log(`[parallel-upload] 全部 ${processedFileList.length} 个文件并行上传成功`);
|
|
199932
|
+
}
|
|
199933
|
+
return allFailedFiles;
|
|
199436
199934
|
}
|
|
199437
199935
|
/**
|
|
199438
199936
|
* 创建一个空的文件夹
|
|
@@ -201624,6 +202122,7 @@ class StorageService {
|
|
|
201624
202122
|
}
|
|
201625
202123
|
/**
|
|
201626
202124
|
* 获取 COS 配置
|
|
202125
|
+
* @param parallel 并发数量
|
|
201627
202126
|
*/
|
|
201628
202127
|
getCos(parallel = 20) {
|
|
201629
202128
|
const internalEndpoint = this.environment.cloudBaseContext.isInternalEndpoint();
|
|
@@ -201869,6 +202368,16 @@ __decorate([
|
|
|
201869
202368
|
], StorageService.prototype, "uploadDirectoryCustom", null);
|
|
201870
202369
|
__decorate([
|
|
201871
202370
|
(0, utils_1.preLazy)()
|
|
202371
|
+
/**
|
|
202372
|
+
* 上传文件(支持精准重试失败文件)
|
|
202373
|
+
*
|
|
202374
|
+
* 智能混合模式(默认):
|
|
202375
|
+
* 1. 先并行快速上传(速度快)
|
|
202376
|
+
* 2. 失败的文件自动走串行重试(稳定性高)
|
|
202377
|
+
*
|
|
202378
|
+
* 可选模式:
|
|
202379
|
+
* - COS_UPLOAD_SERIAL=true:完全串行模式(最稳定但慢)
|
|
202380
|
+
*/
|
|
201872
202381
|
], StorageService.prototype, "uploadFilesCustom", null);
|
|
201873
202382
|
__decorate([
|
|
201874
202383
|
(0, utils_1.preLazy)()
|
|
@@ -202679,6 +203188,367 @@ Promise.filter = function (promises, fn, options) {
|
|
|
202679
203188
|
};
|
|
202680
203189
|
|
|
202681
203190
|
|
|
203191
|
+
/***/ }),
|
|
203192
|
+
|
|
203193
|
+
/***/ 72785:
|
|
203194
|
+
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
203195
|
+
|
|
203196
|
+
"use strict";
|
|
203197
|
+
|
|
203198
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
203199
|
+
if (k2 === undefined) k2 = k;
|
|
203200
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
203201
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
203202
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
203203
|
+
}
|
|
203204
|
+
Object.defineProperty(o, k2, desc);
|
|
203205
|
+
}) : (function(o, m, k, k2) {
|
|
203206
|
+
if (k2 === undefined) k2 = k;
|
|
203207
|
+
o[k2] = m[k];
|
|
203208
|
+
}));
|
|
203209
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
203210
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
203211
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
203212
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
203213
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
203214
|
+
};
|
|
203215
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
203216
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
203217
|
+
};
|
|
203218
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
203219
|
+
exports.IntegrationService = void 0;
|
|
203220
|
+
const error_1 = __webpack_require__(40430);
|
|
203221
|
+
const utils_1 = __webpack_require__(62358);
|
|
203222
|
+
const constants_1 = __webpack_require__(79640);
|
|
203223
|
+
const crypto_1 = __webpack_require__(25068);
|
|
203224
|
+
const secretKey_1 = __webpack_require__(22573);
|
|
203225
|
+
__exportStar(__webpack_require__(18944), exports);
|
|
203226
|
+
/**
|
|
203227
|
+
* 集成中心(UserKey)服务
|
|
203228
|
+
*
|
|
203229
|
+
* 复用现有 tcb 云 API(version 2018-06-08),统一携带 Business='integration'。
|
|
203230
|
+
*
|
|
203231
|
+
* 提供两种使用模式:
|
|
203232
|
+
* 1. 透传模式:直接传 `ext`(JSON 字符串),SDK 原样传递给云 API(向后兼容)
|
|
203233
|
+
* 2. 自动加解密模式:传 `envVariables`(明文键值对),SDK 自动识别模板中的
|
|
203234
|
+
* password 字段、拉取七彩石密钥完成加密后拼装 ext
|
|
203235
|
+
*/
|
|
203236
|
+
class IntegrationService {
|
|
203237
|
+
constructor(environment) {
|
|
203238
|
+
this.environment = environment;
|
|
203239
|
+
this.tcbService = new utils_1.CloudService(environment.cloudBaseContext, 'tcb', '2018-06-08');
|
|
203240
|
+
}
|
|
203241
|
+
// =========== 只读 API ===========
|
|
203242
|
+
/** 列出集成模板(密钥对模版列表) */
|
|
203243
|
+
async listTemplates(params = {}) {
|
|
203244
|
+
var _a, _b, _c;
|
|
203245
|
+
const { envId } = this.getEnvInfo();
|
|
203246
|
+
return this.tcbService.request('GetUserKeyTemplateList', {
|
|
203247
|
+
EnvId: envId,
|
|
203248
|
+
Offset: (_a = params.offset) !== null && _a !== void 0 ? _a : 0,
|
|
203249
|
+
Limit: (_b = params.limit) !== null && _b !== void 0 ? _b : 100,
|
|
203250
|
+
AuthTypeCode: params.authTypeCode,
|
|
203251
|
+
AuthTypeName: params.authTypeName,
|
|
203252
|
+
Business: (_c = params.business) !== null && _c !== void 0 ? _c : constants_1.USER_KEY_BUSINESS // 默认 integration
|
|
203253
|
+
});
|
|
203254
|
+
}
|
|
203255
|
+
/** 列出集成实例(密钥对列表) */
|
|
203256
|
+
async listUserKeys(params = {}) {
|
|
203257
|
+
var _a, _b, _c;
|
|
203258
|
+
const { envId } = this.getEnvInfo();
|
|
203259
|
+
return this.tcbService.request('GetUserKeyList', {
|
|
203260
|
+
EnvId: envId,
|
|
203261
|
+
Offset: (_a = params.offset) !== null && _a !== void 0 ? _a : 0,
|
|
203262
|
+
Limit: (_b = params.limit) !== null && _b !== void 0 ? _b : 100,
|
|
203263
|
+
Name: params.name,
|
|
203264
|
+
AuthTypeCode: params.authTypeCode,
|
|
203265
|
+
Business: (_c = params.business) !== null && _c !== void 0 ? _c : constants_1.USER_KEY_BUSINESS // 默认 integration
|
|
203266
|
+
});
|
|
203267
|
+
}
|
|
203268
|
+
/**
|
|
203269
|
+
* 查询单个集成实例详情。
|
|
203270
|
+
*
|
|
203271
|
+
* 当 `decryptPwd=true` 时,SDK 自动拉取模板识别 password 字段并从七彩石
|
|
203272
|
+
* 获取 AES 密钥解密,Ext 中 password 字段返回明文。解密失败时保留密文原值,
|
|
203273
|
+
* 不中断流程。
|
|
203274
|
+
*/
|
|
203275
|
+
async getUserKey(params) {
|
|
203276
|
+
var _a, _b, _c;
|
|
203277
|
+
this.validateRequiredParams(params, ['keyId']);
|
|
203278
|
+
const { envId } = this.getEnvInfo();
|
|
203279
|
+
const result = await this.tcbService.request('GetOneUserKey', {
|
|
203280
|
+
EnvId: envId,
|
|
203281
|
+
KeyID: params.keyId,
|
|
203282
|
+
NeedPwd: (_a = params.needPwd) !== null && _a !== void 0 ? _a : false,
|
|
203283
|
+
Business: (_b = params.business) !== null && _b !== void 0 ? _b : constants_1.USER_KEY_BUSINESS // 默认 integration
|
|
203284
|
+
});
|
|
203285
|
+
// 自动解密 password 字段
|
|
203286
|
+
if (params.decryptPwd && ((_c = result.UserKeyInfo) === null || _c === void 0 ? void 0 : _c.Ext)) {
|
|
203287
|
+
result.UserKeyInfo = await this.decryptExtIfNeeded(result.UserKeyInfo, envId);
|
|
203288
|
+
}
|
|
203289
|
+
return result;
|
|
203290
|
+
}
|
|
203291
|
+
// =========== 写 API ===========
|
|
203292
|
+
/**
|
|
203293
|
+
* 创建集成实例。
|
|
203294
|
+
*
|
|
203295
|
+
* 支持两种传参方式:
|
|
203296
|
+
* - 传 `ext`:透传模式,原样传递给云 API(向后兼容,调用方自行加密)
|
|
203297
|
+
* - 传 `envVariables`:自动加密模式,SDK 识别模板 password 字段并加密后拼装 ext
|
|
203298
|
+
* - 两者都传时以 `ext` 优先
|
|
203299
|
+
*/
|
|
203300
|
+
async createUserKey(params) {
|
|
203301
|
+
var _a;
|
|
203302
|
+
this.validateRequiredParams(params, ['name', 'authTypeCode']);
|
|
203303
|
+
const { envId } = this.getEnvInfo();
|
|
203304
|
+
const ext = await this.buildExtFromParams(envId, params.authTypeCode, { ext: params.ext, envVariables: params.envVariables });
|
|
203305
|
+
return this.tcbService.request('CreateUserKey', {
|
|
203306
|
+
EnvId: envId,
|
|
203307
|
+
Name: params.name,
|
|
203308
|
+
KeyID: params.keyId,
|
|
203309
|
+
AuthTypeCode: params.authTypeCode,
|
|
203310
|
+
Description: params.description,
|
|
203311
|
+
Ext: ext,
|
|
203312
|
+
TicketID: params.ticketId,
|
|
203313
|
+
Business: (_a = params.business) !== null && _a !== void 0 ? _a : constants_1.USER_KEY_BUSINESS, // 默认 integration
|
|
203314
|
+
DemoCodeFunctionName: params.demoCodeFunctionName,
|
|
203315
|
+
RuntimeConfig: params.runtimeConfig
|
|
203316
|
+
});
|
|
203317
|
+
}
|
|
203318
|
+
/**
|
|
203319
|
+
* 更新集成实例。
|
|
203320
|
+
*
|
|
203321
|
+
* 支持两种传参方式:
|
|
203322
|
+
* - 传 `ext`:透传模式,原样传递给云 API(向后兼容)
|
|
203323
|
+
* - 传 `envVariables`:自动加密模式,与现有 Ext 合并后加密写入
|
|
203324
|
+
* - 两者都传时以 `ext` 优先
|
|
203325
|
+
* - 都未传时后端保留原有 Ext 不变
|
|
203326
|
+
*/
|
|
203327
|
+
async updateUserKey(params) {
|
|
203328
|
+
var _a;
|
|
203329
|
+
this.validateRequiredParams(params, ['keyId']);
|
|
203330
|
+
const { envId } = this.getEnvInfo();
|
|
203331
|
+
let ext = params.ext;
|
|
203332
|
+
// 传了 envVariables 且未传 ext:需要合并现有 Ext 后加密
|
|
203333
|
+
if (params.envVariables && !params.ext) {
|
|
203334
|
+
ext = await this.buildMergedExtForUpdate(envId, params.keyId, { authTypeCode: params.authTypeCode, envVariables: params.envVariables });
|
|
203335
|
+
}
|
|
203336
|
+
return this.tcbService.request('UpdateUserKey', {
|
|
203337
|
+
EnvId: envId,
|
|
203338
|
+
KeyID: params.keyId,
|
|
203339
|
+
Name: params.name,
|
|
203340
|
+
AuthTypeCode: params.authTypeCode,
|
|
203341
|
+
Description: params.description,
|
|
203342
|
+
Ext: ext,
|
|
203343
|
+
DemoCodeFunctionName: params.demoCodeFunctionName,
|
|
203344
|
+
Business: (_a = params.business) !== null && _a !== void 0 ? _a : constants_1.USER_KEY_BUSINESS, // 默认 integration
|
|
203345
|
+
RuntimeConfig: params.runtimeConfig
|
|
203346
|
+
});
|
|
203347
|
+
}
|
|
203348
|
+
/** 删除集成实例 */
|
|
203349
|
+
async deleteUserKey(params) {
|
|
203350
|
+
this.validateRequiredParams(params, ['keyId']);
|
|
203351
|
+
const { envId } = this.getEnvInfo();
|
|
203352
|
+
return this.tcbService.request('DeleteUserKey', {
|
|
203353
|
+
EnvId: envId,
|
|
203354
|
+
KeyID: params.keyId
|
|
203355
|
+
});
|
|
203356
|
+
}
|
|
203357
|
+
// =========== 辅助方法 ===========
|
|
203358
|
+
/** 获取环境信息 */
|
|
203359
|
+
getEnvInfo() {
|
|
203360
|
+
const envConfig = this.environment.lazyEnvironmentConfig;
|
|
203361
|
+
return {
|
|
203362
|
+
envId: envConfig.EnvId
|
|
203363
|
+
};
|
|
203364
|
+
}
|
|
203365
|
+
/** 校验必填参数 */
|
|
203366
|
+
validateRequiredParams(params, fields) {
|
|
203367
|
+
for (const field of fields) {
|
|
203368
|
+
if (!params[field]) {
|
|
203369
|
+
throw new error_1.CloudBaseError(`${field} 是必填参数`);
|
|
203370
|
+
}
|
|
203371
|
+
}
|
|
203372
|
+
}
|
|
203373
|
+
/**
|
|
203374
|
+
* 根据 ext / envVariables 参数构造最终的 Ext JSON 字符串。
|
|
203375
|
+
* ext 优先;envVariables 由 SDK 自动加密 password 字段。
|
|
203376
|
+
*/
|
|
203377
|
+
async buildExtFromParams(envId, authTypeCode, opts) {
|
|
203378
|
+
// ext 优先(透传模式)
|
|
203379
|
+
if (opts.ext !== undefined) {
|
|
203380
|
+
return opts.ext;
|
|
203381
|
+
}
|
|
203382
|
+
// 自动加密模式
|
|
203383
|
+
if (opts.envVariables && Object.keys(opts.envVariables).length > 0) {
|
|
203384
|
+
return this.encryptAndStringify(envId, authTypeCode, opts.envVariables);
|
|
203385
|
+
}
|
|
203386
|
+
return undefined;
|
|
203387
|
+
}
|
|
203388
|
+
/**
|
|
203389
|
+
* 更新场景:将 envVariables 与现有实例的 Ext 合并后加密。
|
|
203390
|
+
* 需要先查询现有实例获取当前的 authTypeCode 和 Ext。
|
|
203391
|
+
*/
|
|
203392
|
+
async buildMergedExtForUpdate(envId, keyId, opts) {
|
|
203393
|
+
var _a, _b;
|
|
203394
|
+
const { envVariables, authTypeCode } = opts;
|
|
203395
|
+
if (!envVariables || Object.keys(envVariables).length === 0) {
|
|
203396
|
+
return undefined;
|
|
203397
|
+
}
|
|
203398
|
+
// 查询现有实例以合并 Ext
|
|
203399
|
+
let existingExtObj = {};
|
|
203400
|
+
let resolvedAuthTypeCode = authTypeCode;
|
|
203401
|
+
try {
|
|
203402
|
+
const existing = await this.getUserKey({
|
|
203403
|
+
keyId,
|
|
203404
|
+
needPwd: true,
|
|
203405
|
+
decryptPwd: true, // 解密后方便合并
|
|
203406
|
+
business: constants_1.USER_KEY_BUSINESS
|
|
203407
|
+
});
|
|
203408
|
+
const extObj = this.parseExt((_a = existing.UserKeyInfo) === null || _a === void 0 ? void 0 : _a.Ext);
|
|
203409
|
+
existingExtObj = Object.assign({}, extObj);
|
|
203410
|
+
resolvedAuthTypeCode = resolvedAuthTypeCode || ((_b = existing.UserKeyInfo) === null || _b === void 0 ? void 0 : _b.AuthTypeCode);
|
|
203411
|
+
}
|
|
203412
|
+
catch (_c) {
|
|
203413
|
+
// 查询失败时仅使用传入的 envVariables
|
|
203414
|
+
}
|
|
203415
|
+
if (!resolvedAuthTypeCode) {
|
|
203416
|
+
// 无法获取 authTypeCode,无法识别 password 字段,直接 JSON 序列化
|
|
203417
|
+
return JSON.stringify(Object.assign(Object.assign({}, existingExtObj), envVariables));
|
|
203418
|
+
}
|
|
203419
|
+
// 合并:现有 Ext 为基础,envVariables 覆盖
|
|
203420
|
+
const merged = Object.assign(Object.assign({}, existingExtObj), envVariables);
|
|
203421
|
+
return this.encryptAndStringify(envId, resolvedAuthTypeCode, merged);
|
|
203422
|
+
}
|
|
203423
|
+
/**
|
|
203424
|
+
* 加密 envVariables 中的 password 字段并返回 JSON 字符串。
|
|
203425
|
+
* 无 password 字段时直接 JSON.stringify,不请求七彩石。
|
|
203426
|
+
*/
|
|
203427
|
+
async encryptAndStringify(envId, authTypeCode, envVariables) {
|
|
203428
|
+
// 1. 拉取模板,识别 password 字段
|
|
203429
|
+
const passwordFieldNames = await this.getPasswordFieldNames(authTypeCode);
|
|
203430
|
+
// 2. 判断是否有 password 字段需要加密
|
|
203431
|
+
const hasPasswordValue = passwordFieldNames.some(name => envVariables[name]);
|
|
203432
|
+
if (!hasPasswordValue) {
|
|
203433
|
+
return JSON.stringify(envVariables);
|
|
203434
|
+
}
|
|
203435
|
+
// 3. 拉取 AES key 并加密
|
|
203436
|
+
try {
|
|
203437
|
+
const aesKey = await (0, secretKey_1.getAesKey)({ envId });
|
|
203438
|
+
const encrypted = this.encryptPasswordFields(envVariables, passwordFieldNames, aesKey);
|
|
203439
|
+
return JSON.stringify(encrypted);
|
|
203440
|
+
}
|
|
203441
|
+
catch (_a) {
|
|
203442
|
+
// 获取密钥失败时以明文写入(与 CLI 行为一致)
|
|
203443
|
+
return JSON.stringify(envVariables);
|
|
203444
|
+
}
|
|
203445
|
+
}
|
|
203446
|
+
/**
|
|
203447
|
+
* 解密 Ext 中的 password 字段并返回新的 IUserKeyInfo。
|
|
203448
|
+
* 解密失败时保留原值,不中断流程。
|
|
203449
|
+
*/
|
|
203450
|
+
async decryptExtIfNeeded(userKeyInfo, envId) {
|
|
203451
|
+
if (!userKeyInfo.Ext || !userKeyInfo.AuthTypeCode)
|
|
203452
|
+
return userKeyInfo;
|
|
203453
|
+
try {
|
|
203454
|
+
const passwordFieldNames = await this.getPasswordFieldNames(userKeyInfo.AuthTypeCode);
|
|
203455
|
+
if (passwordFieldNames.length === 0)
|
|
203456
|
+
return userKeyInfo;
|
|
203457
|
+
const extObj = this.parseExt(userKeyInfo.Ext);
|
|
203458
|
+
if (!Object.keys(extObj).length)
|
|
203459
|
+
return userKeyInfo;
|
|
203460
|
+
const hasPasswordValue = passwordFieldNames.some(name => extObj[name]);
|
|
203461
|
+
if (!hasPasswordValue)
|
|
203462
|
+
return userKeyInfo;
|
|
203463
|
+
const aesKey = await (0, secretKey_1.getAesKey)({ envId });
|
|
203464
|
+
const decrypted = this.decryptPasswordFields(extObj, passwordFieldNames, aesKey);
|
|
203465
|
+
return Object.assign(Object.assign({}, userKeyInfo), { Ext: JSON.stringify(decrypted) });
|
|
203466
|
+
}
|
|
203467
|
+
catch (_a) {
|
|
203468
|
+
return userKeyInfo;
|
|
203469
|
+
}
|
|
203470
|
+
}
|
|
203471
|
+
/** 从模板中获取 password 类型字段名列表 */
|
|
203472
|
+
async getPasswordFieldNames(authTypeCode) {
|
|
203473
|
+
var _a, _b;
|
|
203474
|
+
try {
|
|
203475
|
+
const res = await this.listTemplates({ authTypeCode, limit: 1 });
|
|
203476
|
+
const templateExt = (_b = (_a = res.KeyTemplateList) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.Ext;
|
|
203477
|
+
if (!templateExt)
|
|
203478
|
+
return [];
|
|
203479
|
+
const parsed = JSON.parse(templateExt);
|
|
203480
|
+
const fields = Array.isArray(parsed === null || parsed === void 0 ? void 0 : parsed.fields) ? parsed.fields : [];
|
|
203481
|
+
return fields
|
|
203482
|
+
.filter((f) => f && f.type === 'password' && typeof f.name === 'string')
|
|
203483
|
+
.map((f) => f.name);
|
|
203484
|
+
}
|
|
203485
|
+
catch (_c) {
|
|
203486
|
+
return [];
|
|
203487
|
+
}
|
|
203488
|
+
}
|
|
203489
|
+
/** 解析 Ext JSON 字符串为对象,失败返回空对象 */
|
|
203490
|
+
parseExt(ext) {
|
|
203491
|
+
if (!ext)
|
|
203492
|
+
return {};
|
|
203493
|
+
try {
|
|
203494
|
+
const parsed = JSON.parse(ext);
|
|
203495
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
|
|
203496
|
+
}
|
|
203497
|
+
catch (_a) {
|
|
203498
|
+
return {};
|
|
203499
|
+
}
|
|
203500
|
+
}
|
|
203501
|
+
/** 加密 ext 对象中的 password 字段(仅加密有值的字段),返回新对象 */
|
|
203502
|
+
encryptPasswordFields(extObj, passwordFieldNames, aesKey) {
|
|
203503
|
+
const result = Object.assign({}, extObj);
|
|
203504
|
+
passwordFieldNames.forEach(name => {
|
|
203505
|
+
const value = result[name];
|
|
203506
|
+
if (name && value) {
|
|
203507
|
+
result[name] = crypto_1.AESCrypto.encrypt(value, aesKey);
|
|
203508
|
+
}
|
|
203509
|
+
});
|
|
203510
|
+
return result;
|
|
203511
|
+
}
|
|
203512
|
+
/** 解密 ext 对象中的 password 字段,解密失败保留原值,返回新对象 */
|
|
203513
|
+
decryptPasswordFields(extObj, passwordFieldNames, aesKey) {
|
|
203514
|
+
const result = Object.assign({}, extObj);
|
|
203515
|
+
const MIN_CIPHERTEXT_LEN = 24; // base64(16 字节 IV) = 24 字符
|
|
203516
|
+
passwordFieldNames.forEach(name => {
|
|
203517
|
+
const encrypted = result[name];
|
|
203518
|
+
if (typeof encrypted !== 'string' || encrypted.length <= MIN_CIPHERTEXT_LEN) {
|
|
203519
|
+
return;
|
|
203520
|
+
}
|
|
203521
|
+
try {
|
|
203522
|
+
result[name] = crypto_1.AESCrypto.decrypt(encrypted, aesKey);
|
|
203523
|
+
}
|
|
203524
|
+
catch (_a) {
|
|
203525
|
+
// 解密失败保留原值,不中断流程
|
|
203526
|
+
}
|
|
203527
|
+
});
|
|
203528
|
+
return result;
|
|
203529
|
+
}
|
|
203530
|
+
}
|
|
203531
|
+
exports.IntegrationService = IntegrationService;
|
|
203532
|
+
__decorate([
|
|
203533
|
+
(0, utils_1.preLazy)()
|
|
203534
|
+
], IntegrationService.prototype, "listTemplates", null);
|
|
203535
|
+
__decorate([
|
|
203536
|
+
(0, utils_1.preLazy)()
|
|
203537
|
+
], IntegrationService.prototype, "listUserKeys", null);
|
|
203538
|
+
__decorate([
|
|
203539
|
+
(0, utils_1.preLazy)()
|
|
203540
|
+
], IntegrationService.prototype, "getUserKey", null);
|
|
203541
|
+
__decorate([
|
|
203542
|
+
(0, utils_1.preLazy)()
|
|
203543
|
+
], IntegrationService.prototype, "createUserKey", null);
|
|
203544
|
+
__decorate([
|
|
203545
|
+
(0, utils_1.preLazy)()
|
|
203546
|
+
], IntegrationService.prototype, "updateUserKey", null);
|
|
203547
|
+
__decorate([
|
|
203548
|
+
(0, utils_1.preLazy)()
|
|
203549
|
+
], IntegrationService.prototype, "deleteUserKey", null);
|
|
203550
|
+
|
|
203551
|
+
|
|
202682
203552
|
/***/ }),
|
|
202683
203553
|
|
|
202684
203554
|
/***/ 72923:
|
|
@@ -213100,7 +213970,7 @@ function registerSetupTools(server) {
|
|
|
213100
213970
|
server.registerTool("downloadTemplate", {
|
|
213101
213971
|
title: "下载项目模板",
|
|
213102
213972
|
description: `自动下载并部署CloudBase项目模板。
|
|
213103
|
-
**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.
|
|
213973
|
+
**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.25.1" : 0}),便于后续维护和版本追踪\n- 下载 rules 模板时,如果项目中已存在 README.md 文件,系统会自动保护该文件不被覆盖(除非设置 overwrite=true)`,
|
|
213104
213974
|
inputSchema: {
|
|
213105
213975
|
template: zod_1.z
|
|
213106
213976
|
.enum(["react", "vue", "miniprogram", "uniapp", "rules"])
|
|
@@ -221481,6 +222351,22 @@ module.exports = typeof queueMicrotask === 'function'
|
|
|
221481
222351
|
.catch(err => setTimeout(() => { throw err }, 0))
|
|
221482
222352
|
|
|
221483
222353
|
|
|
222354
|
+
/***/ }),
|
|
222355
|
+
|
|
222356
|
+
/***/ 79640:
|
|
222357
|
+
/***/ ((__unused_webpack_module, exports) => {
|
|
222358
|
+
|
|
222359
|
+
"use strict";
|
|
222360
|
+
|
|
222361
|
+
/**
|
|
222362
|
+
* 集成中心常量定义(与 dev-platform 对齐)
|
|
222363
|
+
*/
|
|
222364
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
222365
|
+
exports.USER_KEY_BUSINESS = void 0;
|
|
222366
|
+
/** 业务类型标识:集成中心来源的密钥对 */
|
|
222367
|
+
exports.USER_KEY_BUSINESS = 'integration';
|
|
222368
|
+
|
|
222369
|
+
|
|
221484
222370
|
/***/ }),
|
|
221485
222371
|
|
|
221486
222372
|
/***/ 79646:
|
|
@@ -230184,21 +231070,23 @@ const zod_1 = __webpack_require__(21614);
|
|
|
230184
231070
|
const cloudbase_manager_js_1 = __webpack_require__(3431);
|
|
230185
231071
|
const json_content_js_1 = __webpack_require__(4365);
|
|
230186
231072
|
const QUERY_GATEWAY_ACTIONS = [
|
|
230187
|
-
"getAccess",
|
|
230188
|
-
"listDomains",
|
|
230189
231073
|
"listRoutes",
|
|
230190
231074
|
"getRoute",
|
|
230191
231075
|
"listCustomDomains",
|
|
230192
231076
|
];
|
|
230193
231077
|
const MANAGE_GATEWAY_ACTIONS = [
|
|
230194
|
-
"createAccess",
|
|
230195
231078
|
"createRoute",
|
|
230196
231079
|
"updateRoute",
|
|
230197
231080
|
"deleteRoute",
|
|
230198
231081
|
"bindCustomDomain",
|
|
230199
231082
|
"deleteCustomDomain",
|
|
230200
|
-
|
|
230201
|
-
|
|
231083
|
+
];
|
|
231084
|
+
const UPSTREAM_RESOURCE_TYPES = [
|
|
231085
|
+
"SCF",
|
|
231086
|
+
"WEB_SCF",
|
|
231087
|
+
"CBR",
|
|
231088
|
+
"STATIC_STORE",
|
|
231089
|
+
"LH",
|
|
230202
231090
|
];
|
|
230203
231091
|
function normalizeAccessPath(path) {
|
|
230204
231092
|
if (!path) {
|
|
@@ -230229,24 +231117,11 @@ function registerGatewayTools(server) {
|
|
|
230229
231117
|
return (0, json_content_js_1.jsonContent)(buildErrorEnvelope(error));
|
|
230230
231118
|
}
|
|
230231
231119
|
};
|
|
230232
|
-
const listGatewayDomains = async () => {
|
|
230233
|
-
const cloudbase = await getManager();
|
|
230234
|
-
const result = await cloudbase.access.getDomainList();
|
|
230235
|
-
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
230236
|
-
const domains = [
|
|
230237
|
-
result.DefaultDomain,
|
|
230238
|
-
...(result.ServiceSet || []).map((item) => item.Domain),
|
|
230239
|
-
].filter(Boolean);
|
|
230240
|
-
return {
|
|
230241
|
-
domains,
|
|
230242
|
-
enableService: result.EnableService,
|
|
230243
|
-
raw: result,
|
|
230244
|
-
};
|
|
230245
|
-
};
|
|
230246
231120
|
const listHttpServiceRoutes = async (domain) => {
|
|
230247
231121
|
const cloudbase = await getManager();
|
|
230248
231122
|
const result = await cloudbase.env.describeHttpServiceRoute({
|
|
230249
231123
|
EnvId: await resolveEnvId(),
|
|
231124
|
+
Limit: 1000,
|
|
230250
231125
|
...(domain
|
|
230251
231126
|
? {
|
|
230252
231127
|
Filters: [
|
|
@@ -230262,123 +231137,176 @@ function registerGatewayTools(server) {
|
|
|
230262
231137
|
return result;
|
|
230263
231138
|
};
|
|
230264
231139
|
const flattenRoutes = (result) => (result.Domains ?? []).flatMap((domainItem) => (domainItem.Routes ?? []).map((route) => ({
|
|
230265
|
-
Domain: domainItem.Domain,
|
|
231140
|
+
Domain: domainItem.Domain ?? "",
|
|
230266
231141
|
DomainType: domainItem.DomainType,
|
|
230267
231142
|
AccessType: domainItem.AccessType,
|
|
231143
|
+
IsDefault: domainItem.IsDefault,
|
|
230268
231144
|
...route,
|
|
230269
231145
|
})));
|
|
231146
|
+
const buildRouteUrls = (routes) => Array.from(new Set(routes
|
|
231147
|
+
.filter((route) => route.Domain && route.Path)
|
|
231148
|
+
.map((route) => `https://${route.Domain}${normalizeAccessPath(String(route.Path))}`)));
|
|
231149
|
+
const resolveDefaultHttpDomain = async () => {
|
|
231150
|
+
const routeInfo = await listHttpServiceRoutes();
|
|
231151
|
+
const domains = routeInfo.Domains ?? [];
|
|
231152
|
+
const defaultCandidates = domains.filter((item) => item.IsDefault === true && item.Domain);
|
|
231153
|
+
const preferred = defaultCandidates.find((item) => item.Enable !== false && item.Status === "SUCCESS") ??
|
|
231154
|
+
defaultCandidates.find((item) => item.Enable !== false) ??
|
|
231155
|
+
defaultCandidates[0];
|
|
231156
|
+
if (!preferred?.Domain) {
|
|
231157
|
+
throw new Error("环境默认 HTTP 访问域名未就绪或未开通。请先在控制台开通 HTTP 访问服务,或用 queryGateway(action=\"listRoutes\") 确认 Domains 中是否存在 IsDefault=true 的域名;也可以显式传入 domain 后重试 createRoute/updateRoute/deleteRoute。");
|
|
231158
|
+
}
|
|
231159
|
+
return preferred.Domain;
|
|
231160
|
+
};
|
|
230270
231161
|
const resolveRouteDomain = async (preferredDomain) => {
|
|
230271
231162
|
if (preferredDomain) {
|
|
230272
231163
|
return preferredDomain;
|
|
230273
231164
|
}
|
|
230274
|
-
|
|
230275
|
-
|
|
231165
|
+
return resolveDefaultHttpDomain();
|
|
231166
|
+
};
|
|
231167
|
+
const resolveUpstreamResourceType = (input) => {
|
|
231168
|
+
const resolved = input.routeUpstreamResourceType ?? input.upstreamResourceType;
|
|
231169
|
+
if (resolved) {
|
|
231170
|
+
return resolved;
|
|
231171
|
+
}
|
|
231172
|
+
throw new Error("必须提供 upstreamResourceType(或 route.upstreamResourceType):" +
|
|
231173
|
+
"WEB_SCF=HTTP云函数,SCF=Event云函数,CBR=云托管,STATIC_STORE=静态托管,LH=轻量应用服务器。" +
|
|
231174
|
+
"禁止仅凭 targetName/serviceName 推断上游类型。");
|
|
230276
231175
|
};
|
|
230277
|
-
const normalizeRoutePayload = async (
|
|
230278
|
-
const
|
|
230279
|
-
|
|
230280
|
-
|
|
230281
|
-
|
|
230282
|
-
|
|
231176
|
+
const normalizeRoutePayload = async (input) => {
|
|
231177
|
+
const upstreamResourceName = input.route?.serviceName ?? input.targetName;
|
|
231178
|
+
if (!upstreamResourceName) {
|
|
231179
|
+
throw new Error("必须提供 targetName 或 route.serviceName 作为上游资源名称:" +
|
|
231180
|
+
"云函数名、云托管服务名,或静态托管实例名(常见为 staticstore)。");
|
|
231181
|
+
}
|
|
231182
|
+
const upstreamResourceType = resolveUpstreamResourceType({
|
|
231183
|
+
upstreamResourceType: input.upstreamResourceType,
|
|
231184
|
+
routeUpstreamResourceType: input.route?.upstreamResourceType,
|
|
231185
|
+
});
|
|
231186
|
+
const path = normalizeAccessPath(input.route?.path ?? input.path ?? `/${upstreamResourceName}`);
|
|
231187
|
+
const domain = await resolveRouteDomain(input.domain);
|
|
231188
|
+
const enableAuth = input.route?.auth !== undefined
|
|
231189
|
+
? input.route.auth
|
|
231190
|
+
: input.auth !== undefined
|
|
231191
|
+
? input.auth
|
|
231192
|
+
: undefined;
|
|
231193
|
+
const enablePathTransmission = input.route?.enablePathTransmission !== undefined
|
|
231194
|
+
? input.route.enablePathTransmission
|
|
231195
|
+
: input.enablePathTransmission !== undefined
|
|
231196
|
+
? input.enablePathTransmission
|
|
231197
|
+
: undefined;
|
|
231198
|
+
const route = {
|
|
231199
|
+
Path: path,
|
|
231200
|
+
UpstreamResourceType: upstreamResourceType,
|
|
231201
|
+
UpstreamResourceName: upstreamResourceName,
|
|
231202
|
+
EnableAuth: enableAuth,
|
|
231203
|
+
};
|
|
231204
|
+
if (enablePathTransmission !== undefined) {
|
|
231205
|
+
route.EnablePathTransmission = enablePathTransmission;
|
|
230283
231206
|
}
|
|
230284
231207
|
return {
|
|
230285
231208
|
EnvId: await resolveEnvId(),
|
|
230286
231209
|
Domain: {
|
|
230287
|
-
Domain:
|
|
230288
|
-
Routes: [
|
|
230289
|
-
|
|
230290
|
-
|
|
230291
|
-
|
|
230292
|
-
|
|
230293
|
-
|
|
230294
|
-
|
|
230295
|
-
|
|
230296
|
-
|
|
230297
|
-
: undefined,
|
|
230298
|
-
},
|
|
230299
|
-
],
|
|
231210
|
+
Domain: domain,
|
|
231211
|
+
Routes: [route],
|
|
231212
|
+
},
|
|
231213
|
+
resolved: {
|
|
231214
|
+
domain,
|
|
231215
|
+
path,
|
|
231216
|
+
upstreamResourceType,
|
|
231217
|
+
upstreamResourceName,
|
|
231218
|
+
enableAuth,
|
|
231219
|
+
enablePathTransmission,
|
|
230300
231220
|
},
|
|
230301
231221
|
};
|
|
230302
231222
|
};
|
|
231223
|
+
const routeMutationNextActions = (targetName) => [
|
|
231224
|
+
{
|
|
231225
|
+
tool: "queryGateway",
|
|
231226
|
+
action: "getRoute",
|
|
231227
|
+
reason: "等待 30 秒到 3 分钟后再确认访问入口是否已生效",
|
|
231228
|
+
},
|
|
231229
|
+
{
|
|
231230
|
+
tool: "queryPermissions",
|
|
231231
|
+
action: "getResourcePermission",
|
|
231232
|
+
reason: "确认函数安全规则是否允许预期访问方;网关 EnableAuth/auth=false 不等于函数已允许匿名访问",
|
|
231233
|
+
},
|
|
231234
|
+
{
|
|
231235
|
+
tool: "managePermissions",
|
|
231236
|
+
action: "updateResourcePermission",
|
|
231237
|
+
reason: "只有在确认需要匿名或浏览器直连访问时,才按实际安全要求更新函数权限",
|
|
231238
|
+
},
|
|
231239
|
+
];
|
|
230303
231240
|
const handleQueryGateway = async (input) => {
|
|
230304
231241
|
switch (input.action) {
|
|
230305
|
-
case "listDomains": {
|
|
230306
|
-
const result = await listGatewayDomains();
|
|
230307
|
-
return buildEnvelope({
|
|
230308
|
-
action: input.action,
|
|
230309
|
-
domains: result.domains,
|
|
230310
|
-
enableService: result.enableService,
|
|
230311
|
-
raw: result.raw,
|
|
230312
|
-
}, `已获取 ${result.domains.length} 个网关域名`, [
|
|
230313
|
-
{
|
|
230314
|
-
tool: "queryGateway",
|
|
230315
|
-
action: "getAccess",
|
|
230316
|
-
reason: "查看某个目标当前的访问入口",
|
|
230317
|
-
},
|
|
230318
|
-
]);
|
|
230319
|
-
}
|
|
230320
231242
|
case "listCustomDomains": {
|
|
230321
|
-
const result = await
|
|
231243
|
+
const result = await listHttpServiceRoutes();
|
|
231244
|
+
const customDomains = (result.Domains ?? []).filter((item) => item.IsDefault !== true);
|
|
230322
231245
|
return buildEnvelope({
|
|
230323
231246
|
action: input.action,
|
|
230324
|
-
domains:
|
|
230325
|
-
total:
|
|
230326
|
-
raw: result
|
|
230327
|
-
}, `已获取 ${
|
|
231247
|
+
domains: customDomains,
|
|
231248
|
+
total: customDomains.length,
|
|
231249
|
+
raw: result,
|
|
231250
|
+
}, `已获取 ${customDomains.length} 个自定义域名`);
|
|
230328
231251
|
}
|
|
230329
231252
|
case "listRoutes": {
|
|
230330
|
-
const result = await listHttpServiceRoutes();
|
|
231253
|
+
const result = await listHttpServiceRoutes(input.domain);
|
|
230331
231254
|
const routes = flattenRoutes(result);
|
|
230332
231255
|
return buildEnvelope({
|
|
230333
231256
|
action: input.action,
|
|
230334
231257
|
routes,
|
|
231258
|
+
urls: buildRouteUrls(routes),
|
|
230335
231259
|
total: result.TotalCount ?? routes.length,
|
|
230336
231260
|
raw: result,
|
|
230337
231261
|
}, `已获取 ${result.TotalCount ?? routes.length} 条 HTTP 路由`);
|
|
230338
231262
|
}
|
|
230339
231263
|
case "getRoute": {
|
|
230340
|
-
|
|
230341
|
-
|
|
230342
|
-
|
|
231264
|
+
if (!input.routeId && !input.targetName && !input.path) {
|
|
231265
|
+
throw new Error("action=getRoute 时至少需要提供 routeId、targetName 或 path");
|
|
231266
|
+
}
|
|
231267
|
+
const result = await listHttpServiceRoutes(input.domain);
|
|
231268
|
+
const normalizedPath = input.path
|
|
231269
|
+
? normalizeAccessPath(input.path)
|
|
231270
|
+
: undefined;
|
|
231271
|
+
const matches = flattenRoutes(result).filter((item) => {
|
|
231272
|
+
if (input.routeId && item.RouteId !== input.routeId) {
|
|
231273
|
+
return false;
|
|
231274
|
+
}
|
|
231275
|
+
if (input.domain && item.Domain !== input.domain) {
|
|
231276
|
+
return false;
|
|
231277
|
+
}
|
|
231278
|
+
if (normalizedPath &&
|
|
231279
|
+
normalizeAccessPath(String(item.Path ?? "")) !== normalizedPath) {
|
|
231280
|
+
return false;
|
|
231281
|
+
}
|
|
231282
|
+
if (input.targetName &&
|
|
231283
|
+
item.UpstreamResourceName !== input.targetName) {
|
|
231284
|
+
return false;
|
|
231285
|
+
}
|
|
231286
|
+
return Boolean(input.routeId || input.targetName || normalizedPath);
|
|
231287
|
+
});
|
|
231288
|
+
const route = matches.length === 1 ? matches[0] : null;
|
|
231289
|
+
const urls = buildRouteUrls(matches);
|
|
230343
231290
|
return buildEnvelope({
|
|
230344
231291
|
action: input.action,
|
|
230345
231292
|
routeId: input.routeId ?? null,
|
|
231293
|
+
targetName: input.targetName ?? null,
|
|
231294
|
+
path: normalizedPath ?? null,
|
|
231295
|
+
domain: input.domain ?? null,
|
|
230346
231296
|
route,
|
|
230347
|
-
|
|
230348
|
-
|
|
230349
|
-
}
|
|
230350
|
-
case "getAccess": {
|
|
230351
|
-
if (!input.targetName) {
|
|
230352
|
-
throw new Error("getAccess 操作时,targetName 参数是必需的");
|
|
230353
|
-
}
|
|
230354
|
-
const cloudbase = await getManager();
|
|
230355
|
-
const [accessList, domainInfo] = await Promise.all([
|
|
230356
|
-
cloudbase.access.getAccessList({ name: input.targetName }),
|
|
230357
|
-
listGatewayDomains(),
|
|
230358
|
-
]);
|
|
230359
|
-
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, accessList);
|
|
230360
|
-
const urls = Array.from(new Set((accessList.APISet || []).flatMap((api) => domainInfo.domains.map((domain) => {
|
|
230361
|
-
const normalizedPath = normalizeAccessPath(api.Path);
|
|
230362
|
-
return `https://${domain}${normalizedPath}`;
|
|
230363
|
-
}))));
|
|
230364
|
-
return buildEnvelope({
|
|
230365
|
-
action: input.action,
|
|
230366
|
-
targetType: input.targetType ?? "function",
|
|
230367
|
-
targetName: input.targetName,
|
|
230368
|
-
apis: accessList.APISet || [],
|
|
230369
|
-
total: accessList.Total || 0,
|
|
230370
|
-
domains: domainInfo.domains,
|
|
231297
|
+
routes: matches,
|
|
231298
|
+
total: matches.length,
|
|
230371
231299
|
urls,
|
|
230372
|
-
|
|
230373
|
-
|
|
230374
|
-
|
|
230375
|
-
|
|
230376
|
-
|
|
230377
|
-
|
|
231300
|
+
raw: result,
|
|
231301
|
+
}, matches.length === 0
|
|
231302
|
+
? "未找到对应路由"
|
|
231303
|
+
: matches.length === 1
|
|
231304
|
+
? "已获取路由详情"
|
|
231305
|
+
: `匹配到 ${matches.length} 条路由,请补充 path 或 domain 精确定位`, [
|
|
230378
231306
|
{
|
|
230379
231307
|
tool: "manageGateway",
|
|
230380
|
-
action: "
|
|
230381
|
-
reason: "
|
|
231308
|
+
action: "createRoute",
|
|
231309
|
+
reason: "为该目标新增 Domain/Route 访问路径",
|
|
230382
231310
|
},
|
|
230383
231311
|
]);
|
|
230384
231312
|
}
|
|
@@ -230388,78 +231316,74 @@ function registerGatewayTools(server) {
|
|
|
230388
231316
|
};
|
|
230389
231317
|
const handleManageGateway = async (input) => {
|
|
230390
231318
|
switch (input.action) {
|
|
230391
|
-
case "
|
|
230392
|
-
if (!input.targetName || !input.targetType) {
|
|
230393
|
-
throw new Error("action=createAccess 时必须提供 targetType 和 targetName");
|
|
230394
|
-
}
|
|
230395
|
-
if (!input.type) {
|
|
230396
|
-
throw new Error("action=createAccess 且 targetType=function 时必须显式提供 type。HTTP 云函数传 HTTP;Event 函数传 Event。省略会默认按 Event 路由处理,可能让 HTTP 云函数访问后返回 FUNCTION_PARAM_INVALID。");
|
|
230397
|
-
}
|
|
231319
|
+
case "createRoute": {
|
|
230398
231320
|
const cloudbase = await getManager();
|
|
230399
|
-
const
|
|
231321
|
+
const payload = await normalizeRoutePayload(input);
|
|
230400
231322
|
let result;
|
|
230401
231323
|
try {
|
|
230402
|
-
result = await cloudbase.
|
|
230403
|
-
|
|
230404
|
-
|
|
230405
|
-
type: (input.type === "HTTP" ? 6 : 1),
|
|
230406
|
-
auth: input.auth,
|
|
231324
|
+
result = await cloudbase.env.createHttpServiceRoute({
|
|
231325
|
+
EnvId: payload.EnvId,
|
|
231326
|
+
Domain: payload.Domain,
|
|
230407
231327
|
});
|
|
230408
231328
|
}
|
|
230409
231329
|
catch (err) {
|
|
230410
|
-
|
|
230411
|
-
|
|
230412
|
-
|
|
230413
|
-
|
|
231330
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
231331
|
+
if (message.includes("An error has occurred")) {
|
|
231332
|
+
let hint = "为目标资源配置访问路由失败(后端内部错误)。请确保:1) 目标云函数已成功创建并处于 Active 状态;2) 环境默认 HTTP 域名已完成初始化(IsDefault);3) 该访问路径未被占用。";
|
|
231333
|
+
if (payload.resolved.upstreamResourceType === "WEB_SCF" ||
|
|
231334
|
+
payload.resolved.upstreamResourceType === "SCF") {
|
|
231335
|
+
hint +=
|
|
231336
|
+
"此外注意:HTTP 云函数必须用 upstreamResourceType=WEB_SCF,Event 云函数必须用 SCF;互标会导致此错误。";
|
|
230414
231337
|
}
|
|
230415
|
-
throw new Error(`${hint} 原始错误:${
|
|
231338
|
+
throw new Error(`${hint} 原始错误:${message}`);
|
|
230416
231339
|
}
|
|
230417
231340
|
throw err;
|
|
230418
231341
|
}
|
|
230419
231342
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
230420
231343
|
return buildEnvelope({
|
|
230421
231344
|
action: input.action,
|
|
230422
|
-
|
|
230423
|
-
targetName:
|
|
230424
|
-
|
|
231345
|
+
model: "httpServiceRoute",
|
|
231346
|
+
targetName: payload.resolved.upstreamResourceName,
|
|
231347
|
+
domain: payload.resolved.domain,
|
|
231348
|
+
path: payload.resolved.path,
|
|
231349
|
+
upstreamResourceType: payload.resolved.upstreamResourceType,
|
|
231350
|
+
upstreamResourceName: payload.resolved.upstreamResourceName,
|
|
231351
|
+
auth: payload.resolved.enableAuth ?? null,
|
|
231352
|
+
enablePathTransmission: payload.resolved.enablePathTransmission ?? null,
|
|
230425
231353
|
raw: result,
|
|
230426
|
-
}, `已为目标 ${
|
|
230427
|
-
|
|
230428
|
-
|
|
230429
|
-
|
|
230430
|
-
|
|
230431
|
-
|
|
230432
|
-
|
|
230433
|
-
tool: "queryPermissions",
|
|
230434
|
-
action: "getResourcePermission",
|
|
230435
|
-
reason: "确认函数安全规则是否允许预期访问方;网关 auth=false 不等于函数已允许匿名访问",
|
|
230436
|
-
},
|
|
230437
|
-
{
|
|
230438
|
-
tool: "managePermissions",
|
|
230439
|
-
action: "updateResourcePermission",
|
|
230440
|
-
reason: "只有在确认需要匿名或浏览器直连访问时,才按实际安全要求更新函数权限",
|
|
230441
|
-
},
|
|
230442
|
-
]);
|
|
230443
|
-
}
|
|
230444
|
-
case "createRoute": {
|
|
230445
|
-
const cloudbase = await getManager();
|
|
230446
|
-
const result = await cloudbase.env.createHttpServiceRoute((await normalizeRoutePayload(input.route, input, input.domain)));
|
|
230447
|
-
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
230448
|
-
return buildEnvelope({
|
|
230449
|
-
action: input.action,
|
|
230450
|
-
route: input.route ?? null,
|
|
230451
|
-
raw: result,
|
|
230452
|
-
}, "HTTP 路由创建成功");
|
|
231354
|
+
}, `已为目标 ${payload.resolved.upstreamResourceName} 在域名 ${payload.resolved.domain} 创建路由 ${payload.resolved.path}(${payload.resolved.upstreamResourceType})` +
|
|
231355
|
+
(payload.resolved.enablePathTransmission === true
|
|
231356
|
+
? ";已开启路径透传(后端收到完整请求路径)"
|
|
231357
|
+
: payload.resolved.enablePathTransmission === false
|
|
231358
|
+
? ";路径透传关闭(网关会剥掉触发路径前缀后再转发给后端)"
|
|
231359
|
+
: ";路径透传未显式设置(平台默认 false,会剥掉触发路径前缀)") +
|
|
231360
|
+
`。注意:路由配置传播通常需要等待 30 秒到 3 分钟,请勿立即访问。该操作只创建网关入口,不会自动放开上游权限;若上游是云函数且需要匿名或浏览器直接访问,请继续检查函数资源权限。`, routeMutationNextActions(payload.resolved.upstreamResourceName));
|
|
230453
231361
|
}
|
|
230454
231362
|
case "updateRoute": {
|
|
230455
231363
|
const cloudbase = await getManager();
|
|
230456
|
-
const
|
|
231364
|
+
const payload = await normalizeRoutePayload(input);
|
|
231365
|
+
const result = await cloudbase.env.modifyHttpServiceRoute({
|
|
231366
|
+
EnvId: payload.EnvId,
|
|
231367
|
+
Domain: payload.Domain,
|
|
231368
|
+
});
|
|
230457
231369
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
230458
231370
|
return buildEnvelope({
|
|
230459
231371
|
action: input.action,
|
|
230460
|
-
|
|
231372
|
+
model: "httpServiceRoute",
|
|
231373
|
+
domain: payload.resolved.domain,
|
|
231374
|
+
path: payload.resolved.path,
|
|
231375
|
+
upstreamResourceType: payload.resolved.upstreamResourceType,
|
|
231376
|
+
upstreamResourceName: payload.resolved.upstreamResourceName,
|
|
231377
|
+
auth: payload.resolved.enableAuth ?? null,
|
|
231378
|
+
enablePathTransmission: payload.resolved.enablePathTransmission ?? null,
|
|
230461
231379
|
raw: result,
|
|
230462
|
-
},
|
|
231380
|
+
}, `HTTP 路由更新成功(${payload.resolved.domain}${payload.resolved.path}` +
|
|
231381
|
+
(payload.resolved.enablePathTransmission === true
|
|
231382
|
+
? ",路径透传=开启"
|
|
231383
|
+
: payload.resolved.enablePathTransmission === false
|
|
231384
|
+
? ",路径透传=关闭"
|
|
231385
|
+
: "") +
|
|
231386
|
+
`)`, routeMutationNextActions(payload.resolved.upstreamResourceName));
|
|
230463
231387
|
}
|
|
230464
231388
|
case "deleteRoute": {
|
|
230465
231389
|
const routePath = input.route?.path ?? input.path;
|
|
@@ -230468,16 +231392,18 @@ function registerGatewayTools(server) {
|
|
|
230468
231392
|
}
|
|
230469
231393
|
const cloudbase = await getManager();
|
|
230470
231394
|
const domain = await resolveRouteDomain(input.domain);
|
|
231395
|
+
const normalizedPath = normalizeAccessPath(routePath);
|
|
230471
231396
|
const result = await cloudbase.env.deleteHttpServiceRoute({
|
|
230472
231397
|
EnvId: await resolveEnvId(),
|
|
230473
231398
|
Domain: domain,
|
|
230474
|
-
Paths: [
|
|
231399
|
+
Paths: [normalizedPath],
|
|
230475
231400
|
});
|
|
230476
231401
|
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
230477
231402
|
return buildEnvelope({
|
|
230478
231403
|
action: input.action,
|
|
231404
|
+
model: "httpServiceRoute",
|
|
230479
231405
|
domain,
|
|
230480
|
-
path:
|
|
231406
|
+
path: normalizedPath,
|
|
230481
231407
|
raw: result,
|
|
230482
231408
|
}, "HTTP 路由删除成功");
|
|
230483
231409
|
}
|
|
@@ -230517,114 +231443,32 @@ function registerGatewayTools(server) {
|
|
|
230517
231443
|
raw: result,
|
|
230518
231444
|
}, "自定义域名删除成功");
|
|
230519
231445
|
}
|
|
230520
|
-
case "deleteAccess": {
|
|
230521
|
-
const cloudbase = await getManager();
|
|
230522
|
-
const accessPath = input.path ? normalizeAccessPath(input.path) : undefined;
|
|
230523
|
-
let resolvedAccessId = input.accessId;
|
|
230524
|
-
let resolvedName;
|
|
230525
|
-
if (!resolvedAccessId) {
|
|
230526
|
-
if (!input.targetName && !accessPath) {
|
|
230527
|
-
throw new Error("action=deleteAccess 时至少需要提供 accessId,或提供 targetName/path 用于定位入口");
|
|
230528
|
-
}
|
|
230529
|
-
const accessList = await cloudbase.access.getAccessList({
|
|
230530
|
-
...(input.targetName ? { name: input.targetName } : {}),
|
|
230531
|
-
...(accessPath ? { path: accessPath } : {}),
|
|
230532
|
-
});
|
|
230533
|
-
const candidates = (accessList.APISet ?? []).filter((item) => {
|
|
230534
|
-
if (!accessPath) {
|
|
230535
|
-
return true;
|
|
230536
|
-
}
|
|
230537
|
-
return normalizeAccessPath(item.Path) === accessPath;
|
|
230538
|
-
});
|
|
230539
|
-
if (candidates.length === 0) {
|
|
230540
|
-
throw new Error("未找到可删除的访问入口,请确认 targetName/path 或直接传 accessId");
|
|
230541
|
-
}
|
|
230542
|
-
if (candidates.length > 1 && !accessPath) {
|
|
230543
|
-
throw new Error("匹配到多个访问入口,请补充 path 或直接传 accessId");
|
|
230544
|
-
}
|
|
230545
|
-
resolvedAccessId = candidates[0]?.APIId;
|
|
230546
|
-
resolvedName = candidates[0]?.Name;
|
|
230547
|
-
}
|
|
230548
|
-
else {
|
|
230549
|
-
// 仅传入 accessId 时,先查询获取 name 等信息,确保删除参数完整
|
|
230550
|
-
try {
|
|
230551
|
-
const accessList = await cloudbase.access.getAccessList({});
|
|
230552
|
-
const match = (accessList.APISet ?? []).find((item) => item.APIId === resolvedAccessId);
|
|
230553
|
-
if (match) {
|
|
230554
|
-
resolvedName = match.Name;
|
|
230555
|
-
}
|
|
230556
|
-
}
|
|
230557
|
-
catch {
|
|
230558
|
-
// 查询失败不影响主流程,继续用 accessId 删除
|
|
230559
|
-
}
|
|
230560
|
-
}
|
|
230561
|
-
if (!resolvedAccessId) {
|
|
230562
|
-
throw new Error("无法解析访问入口 accessId,请改为显式传入 accessId");
|
|
230563
|
-
}
|
|
230564
|
-
const result = await cloudbase.access.deleteAccess({
|
|
230565
|
-
...(resolvedName ? { name: resolvedName } : {}),
|
|
230566
|
-
apiId: resolvedAccessId,
|
|
230567
|
-
});
|
|
230568
|
-
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
230569
|
-
return buildEnvelope({
|
|
230570
|
-
action: input.action,
|
|
230571
|
-
targetName: resolvedName ?? input.targetName ?? null,
|
|
230572
|
-
path: accessPath ?? null,
|
|
230573
|
-
accessId: resolvedAccessId,
|
|
230574
|
-
raw: result,
|
|
230575
|
-
}, "网关访问入口删除成功");
|
|
230576
|
-
}
|
|
230577
|
-
case "updatePathAuth": {
|
|
230578
|
-
if (!input.targetName && !input.path) {
|
|
230579
|
-
throw new Error("action=updatePathAuth 时至少需要提供 targetName 或 path");
|
|
230580
|
-
}
|
|
230581
|
-
if (input.auth === undefined) {
|
|
230582
|
-
throw new Error("action=updatePathAuth 时必须提供 auth");
|
|
230583
|
-
}
|
|
230584
|
-
const cloudbase = await getManager();
|
|
230585
|
-
const accessPath = input.path ? normalizeAccessPath(input.path) : undefined;
|
|
230586
|
-
const accessList = await cloudbase.access.getAccessList({
|
|
230587
|
-
...(input.targetName ? { name: input.targetName } : {}),
|
|
230588
|
-
...(accessPath ? { path: accessPath } : {}),
|
|
230589
|
-
});
|
|
230590
|
-
const apiIds = (accessList.APISet ?? []).map((item) => item.APIId).filter(Boolean);
|
|
230591
|
-
if (apiIds.length === 0) {
|
|
230592
|
-
throw new Error("未找到可更新鉴权状态的访问入口");
|
|
230593
|
-
}
|
|
230594
|
-
const result = await cloudbase.access.switchPathAuth({
|
|
230595
|
-
apiIds,
|
|
230596
|
-
auth: input.auth,
|
|
230597
|
-
});
|
|
230598
|
-
(0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
|
|
230599
|
-
return buildEnvelope({
|
|
230600
|
-
action: input.action,
|
|
230601
|
-
targetName: input.targetName ?? null,
|
|
230602
|
-
path: accessPath ?? null,
|
|
230603
|
-
auth: input.auth,
|
|
230604
|
-
apiIds,
|
|
230605
|
-
raw: result,
|
|
230606
|
-
}, "网关路径鉴权更新成功");
|
|
230607
|
-
}
|
|
230608
231446
|
default:
|
|
230609
231447
|
throw new Error(`不支持的操作类型: ${input.action}`);
|
|
230610
231448
|
}
|
|
230611
231449
|
};
|
|
230612
231450
|
server.registerTool?.("queryGateway", {
|
|
230613
231451
|
title: "查询 CloudBase 网关",
|
|
230614
|
-
description: "CloudBase
|
|
231452
|
+
description: "CloudBase HTTP 网关统一只读入口(Domain/Route)。查询域名下路径路由及其上游:" +
|
|
231453
|
+
"WEB_SCF/SCF=云函数,CBR=云托管,STATIC_STORE=静态托管,LH=轻量应用服务器。" +
|
|
231454
|
+
"主键为 Domain + Path;listRoutes / getRoute / listCustomDomains。",
|
|
230615
231455
|
inputSchema: {
|
|
230616
231456
|
action: zod_1.z
|
|
230617
231457
|
.enum(QUERY_GATEWAY_ACTIONS)
|
|
230618
|
-
.describe("
|
|
230619
|
-
targetType: zod_1.z
|
|
230620
|
-
.enum(["function"])
|
|
230621
|
-
.optional()
|
|
230622
|
-
.describe("目标资源类型。当前支持 function,后续可扩展"),
|
|
231458
|
+
.describe("只读操作类型:listRoutes、getRoute、listCustomDomains"),
|
|
230623
231459
|
targetName: zod_1.z
|
|
230624
231460
|
.string()
|
|
230625
231461
|
.optional()
|
|
230626
|
-
.describe("
|
|
231462
|
+
.describe("上游资源名过滤(UpstreamResourceName):云函数名、云托管服务名,或静态托管实例名(常见 staticstore)。"),
|
|
230627
231463
|
routeId: zod_1.z.string().optional().describe("路由 ID。getRoute 时可选"),
|
|
231464
|
+
path: zod_1.z
|
|
231465
|
+
.string()
|
|
231466
|
+
.optional()
|
|
231467
|
+
.describe("路由路径。getRoute / listRoutes 过滤时可选"),
|
|
231468
|
+
domain: zod_1.z
|
|
231469
|
+
.string()
|
|
231470
|
+
.optional()
|
|
231471
|
+
.describe("域名。getRoute / listRoutes 过滤时可选"),
|
|
230628
231472
|
},
|
|
230629
231473
|
annotations: {
|
|
230630
231474
|
readOnlyHint: true,
|
|
@@ -230634,45 +231478,76 @@ function registerGatewayTools(server) {
|
|
|
230634
231478
|
}, async (input) => withEnvelope(() => handleQueryGateway(input)));
|
|
230635
231479
|
server.registerTool?.("manageGateway", {
|
|
230636
231480
|
title: "管理 CloudBase 网关",
|
|
230637
|
-
description: "CloudBase
|
|
231481
|
+
description: "CloudBase HTTP 网关统一写入口(Domain/Route)。createRoute/updateRoute/deleteRoute 把域名下的 path 转到上游;未传 domain 时用 IsDefault 默认域名。" +
|
|
231482
|
+
"上游类型只用一个参数 upstreamResourceType(也可写在 route.upstreamResourceType,route 优先):" +
|
|
231483
|
+
"WEB_SCF=HTTP云函数,SCF=Event云函数,CBR=云托管,STATIC_STORE=静态托管,LH=轻量应用服务器;" +
|
|
231484
|
+
"配合 targetName 或 route.serviceName(云函数名/云托管服务名/静态托管实例名,常见 staticstore)。" +
|
|
231485
|
+
"createRoute 只建网关入口,不改上游权限。" +
|
|
231486
|
+
"enablePathTransmission:默认 false 剥触发路径前缀;true 透传完整路径(CBR 多路由、WEB_SCF 自管子路径常需 true;STATIC_STORE 自定义触发路径映射站点根通常 false)。" +
|
|
231487
|
+
"⚠️ SSL 自定义域名用 bindCustomDomain;CORS/安全域名用 envDomainManagement。",
|
|
230638
231488
|
inputSchema: {
|
|
230639
231489
|
action: zod_1.z
|
|
230640
231490
|
.enum(MANAGE_GATEWAY_ACTIONS)
|
|
230641
|
-
.describe(
|
|
230642
|
-
|
|
230643
|
-
.enum(["function"])
|
|
230644
|
-
.optional()
|
|
230645
|
-
.describe("目标资源类型。当前支持 function,后续可扩展"),
|
|
231491
|
+
.describe("写操作:createRoute/updateRoute/deleteRoute 管理路由;bindCustomDomain/deleteCustomDomain 管理自定义域名。" +
|
|
231492
|
+
"createRoute/updateRoute 必须提供 upstreamResourceType。"),
|
|
230646
231493
|
targetName: zod_1.z
|
|
230647
231494
|
.string()
|
|
230648
231495
|
.optional()
|
|
230649
|
-
.describe("
|
|
231496
|
+
.describe("上游资源名称(UpstreamResourceName),与 route.serviceName 二选一(route 优先)。" +
|
|
231497
|
+
"云函数=函数名;云托管=服务名;静态托管=实例名(常见 staticstore)。不会自动推断上游类型。"),
|
|
230650
231498
|
path: zod_1.z
|
|
230651
231499
|
.string()
|
|
230652
231500
|
.optional()
|
|
230653
|
-
.describe("
|
|
230654
|
-
|
|
230655
|
-
|
|
231501
|
+
.describe("触发路径(网关匹配前缀),默认 /{上游名}。例:云函数 /api/hello、云托管 /api、静态托管 / 或 /app。" +
|
|
231502
|
+
"只建网关入口;与 enablePathTransmission 共同决定上游实际收到的路径。"),
|
|
231503
|
+
upstreamResourceType: zod_1.z
|
|
231504
|
+
.enum(UPSTREAM_RESOURCE_TYPES)
|
|
230656
231505
|
.optional()
|
|
230657
|
-
.describe("
|
|
231506
|
+
.describe("上游类型(与 route.upstreamResourceType 二选一,route 优先)。" +
|
|
231507
|
+
"WEB_SCF=HTTP云函数,SCF=Event云函数,CBR=云托管,STATIC_STORE=静态托管,LH=轻量应用服务器。" +
|
|
231508
|
+
"createRoute/updateRoute 必填其一;勿把 manageFunctions 的 type=HTTP|Event 传到本字段。"),
|
|
230658
231509
|
auth: zod_1.z
|
|
230659
231510
|
.boolean()
|
|
230660
231511
|
.optional()
|
|
230661
|
-
.describe("
|
|
231512
|
+
.describe("网关路径鉴权(EnableAuth)。匿名/浏览器公网访问通常 false。" +
|
|
231513
|
+
"只控制网关入口;云函数安全规则、云托管鉴权、静态托管权限需各自工具另行配置。"),
|
|
231514
|
+
enablePathTransmission: zod_1.z
|
|
231515
|
+
.boolean()
|
|
231516
|
+
.optional()
|
|
231517
|
+
.describe("路径透传(EnablePathTransmission),平台默认 false。例 path=/api 且请求 /api/users:" +
|
|
231518
|
+
"false→上游收到 /users;true→上游收到 /api/users。" +
|
|
231519
|
+
"CBR 云托管(Express 等自管子路由)与 WEB_SCF 多路径函数常需 true;" +
|
|
231520
|
+
"STATIC_STORE 把触发路径映射到站点根目录(如 /app → 托管 /)时通常 false;" +
|
|
231521
|
+
"单入口/根路径处理保持 false。也可用 route.enablePathTransmission(route 优先)。"),
|
|
230662
231522
|
route: zod_1.z
|
|
230663
231523
|
.object({
|
|
230664
|
-
routeId: zod_1.z.string().optional(),
|
|
230665
231524
|
path: zod_1.z.string().optional(),
|
|
230666
|
-
|
|
230667
|
-
|
|
231525
|
+
serviceName: zod_1.z
|
|
231526
|
+
.string()
|
|
231527
|
+
.optional()
|
|
231528
|
+
.describe("上游实例名:云函数名 / 云托管服务名 / 静态托管实例名(常见 staticstore)/ LH 实例。优先于顶层 targetName。"),
|
|
231529
|
+
upstreamResourceType: zod_1.z
|
|
231530
|
+
.enum(UPSTREAM_RESOURCE_TYPES)
|
|
231531
|
+
.optional()
|
|
231532
|
+
.describe("同顶层 upstreamResourceType。route 内设置时优先于顶层。"),
|
|
230668
231533
|
auth: zod_1.z.boolean().optional(),
|
|
231534
|
+
enablePathTransmission: zod_1.z
|
|
231535
|
+
.boolean()
|
|
231536
|
+
.optional()
|
|
231537
|
+
.describe("同顶层 enablePathTransmission。route 内设置时优先于顶层。"),
|
|
230669
231538
|
})
|
|
230670
231539
|
.optional()
|
|
230671
|
-
.describe("
|
|
230672
|
-
|
|
230673
|
-
|
|
230674
|
-
|
|
230675
|
-
|
|
231540
|
+
.describe("路由对象(可选写法)。例:云函数 {upstreamResourceType:\"WEB_SCF\",serviceName:\"fn\",path:\"/api\"};" +
|
|
231541
|
+
"云托管 {upstreamResourceType:\"CBR\",serviceName:\"svc\",path:\"/api\"};" +
|
|
231542
|
+
"静态托管 {upstreamResourceType:\"STATIC_STORE\",serviceName:\"staticstore\",path:\"/\"}。"),
|
|
231543
|
+
domain: zod_1.z
|
|
231544
|
+
.string()
|
|
231545
|
+
.optional()
|
|
231546
|
+
.describe("域名。省略时自动使用环境 IsDefault 默认 HTTP 域名;自定义域名场景请显式传入"),
|
|
231547
|
+
certificateId: zod_1.z
|
|
231548
|
+
.string()
|
|
231549
|
+
.optional()
|
|
231550
|
+
.describe("证书 ID。bindCustomDomain 时必填"),
|
|
230676
231551
|
},
|
|
230677
231552
|
annotations: {
|
|
230678
231553
|
readOnlyHint: false,
|
|
@@ -262997,6 +263872,17 @@ class CloudBase {
|
|
|
262997
263872
|
get aiModel() {
|
|
262998
263873
|
return this.currentEnvironment().getAiModelService();
|
|
262999
263874
|
}
|
|
263875
|
+
/**
|
|
263876
|
+
* 集成中心服务(UserKey)
|
|
263877
|
+
* 提供集成模板/实例的增删改查与绑定云函数能力
|
|
263878
|
+
*/
|
|
263879
|
+
get integration() {
|
|
263880
|
+
return this.currentEnvironment().getIntegrationService();
|
|
263881
|
+
}
|
|
263882
|
+
/**
|
|
263883
|
+
* 监控服务
|
|
263884
|
+
* 提供指标查询能力
|
|
263885
|
+
*/
|
|
263000
263886
|
get monitor() {
|
|
263001
263887
|
return this.currentEnvironment().getMonitorService();
|
|
263002
263888
|
}
|
|
@@ -264609,7 +265495,9 @@ function configToParams(options) {
|
|
|
264609
265495
|
if ((_c = func === null || func === void 0 ? void 0 : func.layers) === null || _c === void 0 ? void 0 : _c.length) {
|
|
264610
265496
|
params.Layers = func.layers.map(item => ({
|
|
264611
265497
|
LayerName: item.name,
|
|
264612
|
-
|
|
265498
|
+
// item.version 可能是字符串(如 JSON 配置文件中的 "1" 或模板变量替换结果),
|
|
265499
|
+
// SCF API 要求 LayerVersion 为 int64,需显式转为 number
|
|
265500
|
+
LayerVersion: Number(item.version)
|
|
264613
265501
|
}));
|
|
264614
265502
|
}
|
|
264615
265503
|
// 7. 代码保护
|
|
@@ -264648,8 +265536,8 @@ function configToParams(options) {
|
|
|
264648
265536
|
params.Code.ImageConfig = buildImageConfig(imageConfig);
|
|
264649
265537
|
}
|
|
264650
265538
|
// 第三阶段:统一应用默认值
|
|
264651
|
-
const runtime = params.Runtime || '
|
|
264652
|
-
return Object.assign({ Handler: func.handler || 'index.main',
|
|
265539
|
+
const runtime = params.Runtime || 'Nodejs20.19';
|
|
265540
|
+
return Object.assign({ Handler: func.handler || 'index.main', Runtime: 'Nodejs20.19', MemorySize: 256, InstallDependency: isNodeFunction(runtime) ? 'TRUE' : 'FALSE' }, params);
|
|
264653
265541
|
}
|
|
264654
265542
|
class FunctionService {
|
|
264655
265543
|
constructor(environment) {
|
|
@@ -264705,7 +265593,7 @@ class FunctionService {
|
|
|
264705
265593
|
async createFunction(funcParam) {
|
|
264706
265594
|
var _a, _b, _c;
|
|
264707
265595
|
const { namespace } = this.getFunctionConfig();
|
|
264708
|
-
const { func, functionRootPath, force = false, base64Code, codeSecret, functionPath, deployMode } = funcParam;
|
|
265596
|
+
const { func, functionRootPath, force = false, base64Code, codeSecret, functionPath, deployMode, code } = funcParam;
|
|
264709
265597
|
const funcName = func.name;
|
|
264710
265598
|
const { TopicId, LogsetId } = this.getClsServiceConfig();
|
|
264711
265599
|
const params = configToParams({
|
|
@@ -264719,20 +265607,30 @@ class FunctionService {
|
|
|
264719
265607
|
ClsLogsetId: LogsetId
|
|
264720
265608
|
}
|
|
264721
265609
|
});
|
|
265610
|
+
const customCode = code;
|
|
265611
|
+
const customImageCode = customCode === null || customCode === void 0 ? void 0 : customCode.ImageConfig;
|
|
265612
|
+
const funcImageCode = (_a = params.Code) === null || _a === void 0 ? void 0 : _a.ImageConfig;
|
|
264722
265613
|
// 根据部署方式处理 Code 参数
|
|
264723
|
-
//
|
|
264724
|
-
const isImageDeploy = deployMode === 'image' || (!deployMode && (
|
|
265614
|
+
// 优先级:deployMode=image > 显式传入 code > 原有自动打包逻辑(base64Code/functionPath/functionRootPath)
|
|
265615
|
+
const isImageDeploy = deployMode === 'image' || Boolean(customImageCode) || (!deployMode && Boolean(funcImageCode));
|
|
264725
265616
|
if (isImageDeploy) {
|
|
264726
|
-
//
|
|
265617
|
+
// 镜像部署优先使用显式 code.ImageConfig;未传时回退到 func.imageConfig(configToParams 生成)
|
|
265618
|
+
if (customImageCode) {
|
|
265619
|
+
params.Code = customCode;
|
|
265620
|
+
}
|
|
264727
265621
|
if (!((_c = (_b = params.Code) === null || _b === void 0 ? void 0 : _b.ImageConfig) === null || _c === void 0 ? void 0 : _c.ImageUri)) {
|
|
264728
265622
|
throw new error_1.CloudBaseError('镜像部署需要配置 imageConfig.imageUri');
|
|
264729
265623
|
}
|
|
264730
|
-
//
|
|
264731
|
-
// 镜像函数不需要 Handler
|
|
265624
|
+
// 镜像函数不需要 Handler / InstallDependency
|
|
264732
265625
|
delete params.Handler;
|
|
264733
|
-
// 镜像函数不需要安装依赖
|
|
264734
265626
|
delete params.InstallDependency;
|
|
264735
265627
|
}
|
|
265628
|
+
else if (customCode) {
|
|
265629
|
+
if (base64Code || functionPath || functionRootPath) {
|
|
265630
|
+
console.warn(`[${funcName}] 检测到同时传入 code 与本地打包参数,已优先使用 code 参数`);
|
|
265631
|
+
}
|
|
265632
|
+
params.Code = customCode;
|
|
265633
|
+
}
|
|
264736
265634
|
else {
|
|
264737
265635
|
// 代码部署:通过 getCodeParams 获取代码参数
|
|
264738
265636
|
params.Code = await this.getCodeParams({
|
|
@@ -264787,7 +265685,8 @@ class FunctionService {
|
|
|
264787
265685
|
functionPath,
|
|
264788
265686
|
functionRootPath,
|
|
264789
265687
|
codeSecret: codeSecret,
|
|
264790
|
-
deployMode: isImageDeploy ? 'image' :
|
|
265688
|
+
deployMode: isImageDeploy ? 'image' : deployMode,
|
|
265689
|
+
code
|
|
264791
265690
|
});
|
|
264792
265691
|
// 等待函数状态正常
|
|
264793
265692
|
await this.waitFunctionActive(funcName, codeSecret);
|
|
@@ -265235,7 +266134,9 @@ class FunctionService {
|
|
|
265235
266134
|
if ((_c = func === null || func === void 0 ? void 0 : func.layers) === null || _c === void 0 ? void 0 : _c.length) {
|
|
265236
266135
|
params.Layers = func.layers.map(item => ({
|
|
265237
266136
|
LayerName: item.name,
|
|
265238
|
-
|
|
266137
|
+
// item.version 可能是字符串(如 JSON 配置文件中的 "1" 或模板变量替换结果),
|
|
266138
|
+
// SCF API 要求 LayerVersion 为 int64,需显式转为 number
|
|
266139
|
+
LayerVersion: Number(item.version)
|
|
265239
266140
|
}));
|
|
265240
266141
|
}
|
|
265241
266142
|
// 特殊处理:HTTP 函数多并发配置(大小写不敏感)
|
|
@@ -265290,7 +266191,7 @@ class FunctionService {
|
|
|
265290
266191
|
* @memberof FunctionService
|
|
265291
266192
|
*/
|
|
265292
266193
|
async updateFunctionCode(funcParam) {
|
|
265293
|
-
const { func, functionRootPath, base64Code, codeSecret, functionPath, deployMode } = funcParam;
|
|
266194
|
+
const { func, functionRootPath, base64Code, codeSecret, functionPath, deployMode, code } = funcParam;
|
|
265294
266195
|
const funcName = func.name;
|
|
265295
266196
|
const { namespace } = this.getFunctionConfig();
|
|
265296
266197
|
// 镜像部署:使用镜像配置更新函数代码
|
|
@@ -265317,6 +266218,10 @@ class FunctionService {
|
|
|
265317
266218
|
});
|
|
265318
266219
|
}
|
|
265319
266220
|
}
|
|
266221
|
+
const customCode = code;
|
|
266222
|
+
if (customCode && (base64Code || functionPath || functionRootPath)) {
|
|
266223
|
+
console.warn(`[${funcName}] 检测到同时传入 code 与本地打包参数,已优先使用 code 参数`);
|
|
266224
|
+
}
|
|
265320
266225
|
// 代码部署:原有逻辑
|
|
265321
266226
|
let installDependency;
|
|
265322
266227
|
// Node 函数默认安装依赖
|
|
@@ -265325,7 +266230,7 @@ class FunctionService {
|
|
|
265325
266230
|
if (func.installDependency !== undefined) {
|
|
265326
266231
|
installDependency = toBooleanString(func.installDependency);
|
|
265327
266232
|
}
|
|
265328
|
-
const codeParams = await this.getCodeParams({
|
|
266233
|
+
const codeParams = customCode || await this.getCodeParams({
|
|
265329
266234
|
func,
|
|
265330
266235
|
functionPath,
|
|
265331
266236
|
functionRootPath,
|
|
@@ -265919,7 +266824,7 @@ class FunctionService {
|
|
|
265919
266824
|
const { func, functionPath, functionRootPath } = options;
|
|
265920
266825
|
const { env, appId } = this.getFunctionConfig();
|
|
265921
266826
|
if (!appId) {
|
|
265922
|
-
throw new error_1.CloudBaseError('无法获取 AppId
|
|
266827
|
+
throw new error_1.CloudBaseError('无法获取 AppId,请确认环境初始化完成或已开通存储服务');
|
|
265923
266828
|
}
|
|
265924
266829
|
const objectPath = `${appId}/${env}/${func.name}.zip`;
|
|
265925
266830
|
// 1. 生成存放函数包的临时 Cos 目录
|
|
@@ -266155,14 +267060,18 @@ class FunctionService {
|
|
|
266155
267060
|
* @memberof FunctionService
|
|
266156
267061
|
*/
|
|
266157
267062
|
getFunctionConfig() {
|
|
266158
|
-
var _a, _b, _c, _d, _e;
|
|
267063
|
+
var _a, _b, _c, _d, _e, _f;
|
|
266159
267064
|
const envConfig = this.environment.lazyEnvironmentConfig;
|
|
266160
267065
|
// Functions 可能为空
|
|
266161
267066
|
const namespace = ((_b = (_a = envConfig.Functions) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.Namespace) || '';
|
|
266162
|
-
// AppId
|
|
267067
|
+
// AppId 优先从 DescribeEnvInfo 的 UserInfo 获取(账号级标识,不依赖存储服务)
|
|
267068
|
+
// 兜底从 Storages[0] 的 AppId 或 Bucket 名提取
|
|
266163
267069
|
// COS Bucket 格式: {BucketName}-{APPID},APPID 为纯数字
|
|
266164
267070
|
const storage = (_c = envConfig.Storages) === null || _c === void 0 ? void 0 : _c[0];
|
|
266165
|
-
const appId = (
|
|
267071
|
+
const appId = ((_d = envConfig.UserInfo) === null || _d === void 0 ? void 0 : _d.AppId)
|
|
267072
|
+
|| (storage === null || storage === void 0 ? void 0 : storage.AppId)
|
|
267073
|
+
|| ((_f = (_e = storage === null || storage === void 0 ? void 0 : storage.Bucket) === null || _e === void 0 ? void 0 : _e.match(/-(\d+)$/)) === null || _f === void 0 ? void 0 : _f[1])
|
|
267074
|
+
|| '';
|
|
266166
267075
|
const { proxy } = this.environment.cloudBaseContext;
|
|
266167
267076
|
return {
|
|
266168
267077
|
proxy,
|