@cloudbase/manager-node 4.11.0-alpha.1 → 4.11.0-alpha.10
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/.claude/settings.local.json +9 -0
- package/lib/agent/index.js +607 -16
- package/lib/agent/type.js +1 -0
- package/lib/cloudApp/index.js +243 -0
- package/lib/cloudApp/types.js +10 -0
- package/lib/database/index.js +79 -0
- package/lib/docs/index.js +288 -0
- package/lib/docs/types.js +2 -0
- package/lib/env/index.js +94 -0
- package/lib/environment.js +20 -0
- package/lib/function/index.js +97 -33
- package/lib/index.js +34 -0
- package/lib/interfaces/cloudApp.interface.js +20 -0
- package/lib/interfaces/index.js +1 -0
- package/lib/log/index.js +105 -0
- package/lib/log/types.js +24 -0
- package/lib/mysql/index.js +551 -0
- package/lib/mysql/types/account.js +2 -0
- package/lib/mysql/types/backup.js +2 -0
- package/lib/mysql/types/base.js +2 -0
- package/lib/mysql/types/index.js +19 -0
- package/lib/permission/index.js +313 -0
- package/lib/permission/types.js +2 -0
- package/lib/storage/index.js +424 -10
- package/lib/user/index.js +200 -0
- package/package.json +1 -1
- package/types/agent/index.d.ts +115 -5
- package/types/agent/type.d.ts +389 -0
- package/types/cloudApp/index.d.ts +73 -0
- package/types/cloudApp/types.d.ts +325 -0
- package/types/database/index.d.ts +112 -0
- package/types/docs/index.d.ts +37 -0
- package/types/docs/types.d.ts +24 -0
- package/types/env/index.d.ts +39 -1
- package/types/env/type.d.ts +187 -0
- package/types/environment.d.ts +12 -0
- package/types/function/index.d.ts +34 -5
- package/types/index.d.ts +26 -0
- package/types/interfaces/cloudApp.interface.d.ts +4 -0
- package/types/interfaces/index.d.ts +1 -0
- package/types/log/index.d.ts +53 -0
- package/types/log/types.d.ts +177 -0
- package/types/mysql/index.d.ts +261 -0
- package/types/mysql/types/account.d.ts +160 -0
- package/types/mysql/types/backup.d.ts +161 -0
- package/types/mysql/types/base.d.ts +579 -0
- package/types/mysql/types/index.d.ts +3 -0
- package/types/permission/index.d.ts +31 -0
- package/types/permission/types.d.ts +127 -0
- package/types/storage/index.d.ts +151 -3
- package/types/user/index.d.ts +17 -1
- package/types/user/types.d.ts +62 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
14
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
15
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
16
|
+
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;
|
|
17
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
18
|
+
};
|
|
19
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
20
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
21
|
+
};
|
|
22
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
23
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.CloudAppService = void 0;
|
|
27
|
+
const fs_1 = __importDefault(require("fs"));
|
|
28
|
+
const os_1 = __importDefault(require("os"));
|
|
29
|
+
const path_1 = __importDefault(require("path"));
|
|
30
|
+
const error_1 = require("../error");
|
|
31
|
+
const utils_1 = require("../utils");
|
|
32
|
+
const http_request_1 = require("../utils/http-request");
|
|
33
|
+
__exportStar(require("./types"), exports);
|
|
34
|
+
/**
|
|
35
|
+
* CloudApp 统一部署服务
|
|
36
|
+
* 提供 Web 应用的创建、查询、删除、版本管理等功能
|
|
37
|
+
*/
|
|
38
|
+
class CloudAppService {
|
|
39
|
+
constructor(environment) {
|
|
40
|
+
this.environment = environment;
|
|
41
|
+
this.tcbService = new utils_1.CloudService(environment.cloudBaseContext, 'tcb', '2018-06-08');
|
|
42
|
+
}
|
|
43
|
+
// =========== 应用管理 API ===========
|
|
44
|
+
/**
|
|
45
|
+
* 创建/更新 CloudApp 应用
|
|
46
|
+
* @param params 创建参数
|
|
47
|
+
*/
|
|
48
|
+
async createApp(params) {
|
|
49
|
+
const { envId } = this.getEnvInfo();
|
|
50
|
+
this.validateRequiredParams(params, ['serviceName', 'deployType']);
|
|
51
|
+
return this.tcbService.request('CreateCloudApp', (0, utils_1.upperCaseObjKey)(Object.assign(Object.assign({}, params), { envId: params.envId || envId })));
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 获取 CloudApp 应用列表
|
|
55
|
+
* @param params 查询参数
|
|
56
|
+
*/
|
|
57
|
+
async describeAppList(params) {
|
|
58
|
+
const { envId } = this.getEnvInfo();
|
|
59
|
+
return this.tcbService.request('DescribeCloudAppList', (0, utils_1.upperCaseObjKey)(Object.assign(Object.assign({}, params), { envId: params.envId || envId })));
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 获取 CloudApp 应用详情
|
|
63
|
+
* @param params 查询参数
|
|
64
|
+
*/
|
|
65
|
+
async describeAppInfo(params) {
|
|
66
|
+
const { envId } = this.getEnvInfo();
|
|
67
|
+
this.validateRequiredParams(params, ['serviceName', 'deployType']);
|
|
68
|
+
return this.tcbService.request('DescribeCloudAppInfo', (0, utils_1.upperCaseObjKey)(Object.assign(Object.assign({}, params), { envId: params.envId || envId })));
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 删除 CloudApp 应用
|
|
72
|
+
* @param params 删除参数
|
|
73
|
+
*/
|
|
74
|
+
async deleteApp(params) {
|
|
75
|
+
const { envId } = this.getEnvInfo();
|
|
76
|
+
this.validateRequiredParams(params, ['serviceName', 'deployType']);
|
|
77
|
+
return this.tcbService.request('DeleteCloudApp', (0, utils_1.upperCaseObjKey)(Object.assign(Object.assign({}, params), { envId: params.envId || envId })));
|
|
78
|
+
}
|
|
79
|
+
// =========== 版本管理 API ===========
|
|
80
|
+
/**
|
|
81
|
+
* 获取 CloudApp 版本详情
|
|
82
|
+
* 不指定 versionName 或 buildId 时返回最新版本
|
|
83
|
+
* @param params 查询参数
|
|
84
|
+
*/
|
|
85
|
+
async describeAppVersion(params) {
|
|
86
|
+
const { envId } = this.getEnvInfo();
|
|
87
|
+
this.validateRequiredParams(params, ['serviceName', 'deployType']);
|
|
88
|
+
return this.tcbService.request('DescribeCloudAppVersion', (0, utils_1.upperCaseObjKey)(Object.assign(Object.assign({}, params), { envId: params.envId || envId })));
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 获取 CloudApp 版本列表
|
|
92
|
+
* @param params 查询参数
|
|
93
|
+
*/
|
|
94
|
+
async describeAppVersionList(params) {
|
|
95
|
+
const { envId } = this.getEnvInfo();
|
|
96
|
+
this.validateRequiredParams(params, ['serviceName', 'deployType']);
|
|
97
|
+
return this.tcbService.request('DescribeCloudAppVersionList', (0, utils_1.upperCaseObjKey)(Object.assign(Object.assign({}, params), { envId: params.envId || envId })));
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 删除 CloudApp 版本
|
|
101
|
+
* @param params 删除参数
|
|
102
|
+
*/
|
|
103
|
+
async deleteAppVersion(params) {
|
|
104
|
+
const { envId } = this.getEnvInfo();
|
|
105
|
+
this.validateRequiredParams(params, ['serviceName', 'deployType', 'versionName']);
|
|
106
|
+
return this.tcbService.request('DeleteCloudAppVersion', (0, utils_1.upperCaseObjKey)(Object.assign(Object.assign({}, params), { envId: params.envId || envId })));
|
|
107
|
+
}
|
|
108
|
+
// =========== COS 上传相关 API ===========
|
|
109
|
+
/**
|
|
110
|
+
* 获取 CloudApp COS 上传信息(预签名 URL)
|
|
111
|
+
* @param params 查询参数
|
|
112
|
+
*/
|
|
113
|
+
async describeCosInfo(params) {
|
|
114
|
+
const { envId } = this.getEnvInfo();
|
|
115
|
+
this.validateRequiredParams(params, ['serviceName', 'deployType']);
|
|
116
|
+
return this.tcbService.request('DescribeCloudAppCosInfo', (0, utils_1.upperCaseObjKey)(Object.assign(Object.assign({}, params), { envId: params.envId || envId, suffix: params.suffix || '.zip' })));
|
|
117
|
+
}
|
|
118
|
+
// =========== 辅助方法 ===========
|
|
119
|
+
/**
|
|
120
|
+
* 上传本地代码到 COS
|
|
121
|
+
* 自动打包 ZIP 并上传到预签名 URL
|
|
122
|
+
*
|
|
123
|
+
* 注意:此方法不依赖应用是否已创建,可在创建应用前调用
|
|
124
|
+
* 返回的 cosTimestamp 用于 createApp 时传入 staticConfig.cosTimestamp
|
|
125
|
+
*
|
|
126
|
+
* @param params 上传参数
|
|
127
|
+
*/
|
|
128
|
+
async uploadCode(params) {
|
|
129
|
+
const { serviceName, localPath, ignore = [] } = params;
|
|
130
|
+
// 1. 验证本地路径
|
|
131
|
+
if (!fs_1.default.existsSync(localPath)) {
|
|
132
|
+
throw new error_1.CloudBaseError(`本地路径不存在: ${localPath}`);
|
|
133
|
+
}
|
|
134
|
+
const stat = fs_1.default.statSync(localPath);
|
|
135
|
+
if (!stat.isDirectory()) {
|
|
136
|
+
throw new error_1.CloudBaseError(`localPath 必须是目录: ${localPath}`);
|
|
137
|
+
}
|
|
138
|
+
// 2. 打包本地文件为 ZIP
|
|
139
|
+
const tmpZipPath = path_1.default.join(os_1.default.tmpdir(), `cloudapp-${Date.now()}-${Math.random().toString(36).slice(2)}.zip`);
|
|
140
|
+
const defaultIgnore = ['node_modules/**', '.git/**', '.DS_Store', '**/.DS_Store'];
|
|
141
|
+
await (0, utils_1.compressToZip)({
|
|
142
|
+
dirPath: localPath,
|
|
143
|
+
outputPath: tmpZipPath,
|
|
144
|
+
ignore: [...defaultIgnore, ...ignore]
|
|
145
|
+
});
|
|
146
|
+
try {
|
|
147
|
+
// 3. 获取上传凭证
|
|
148
|
+
const cosInfo = await this.describeCosInfo({
|
|
149
|
+
deployType: params.deployType,
|
|
150
|
+
serviceName,
|
|
151
|
+
suffix: '.zip'
|
|
152
|
+
});
|
|
153
|
+
// 4. 读取 ZIP 文件
|
|
154
|
+
const zipBuffer = fs_1.default.readFileSync(tmpZipPath);
|
|
155
|
+
// 5. 构建请求头
|
|
156
|
+
const headers = {
|
|
157
|
+
'Content-Type': 'application/zip'
|
|
158
|
+
};
|
|
159
|
+
if (cosInfo.UploadHeaders && cosInfo.UploadHeaders.length > 0) {
|
|
160
|
+
for (const h of cosInfo.UploadHeaders) {
|
|
161
|
+
headers[h.Key] = h.Value;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// 6. 使用预签名 URL 上传
|
|
165
|
+
const response = await (0, http_request_1.fetchStream)(cosInfo.UploadUrl, {
|
|
166
|
+
method: 'PUT',
|
|
167
|
+
body: zipBuffer,
|
|
168
|
+
headers
|
|
169
|
+
});
|
|
170
|
+
if (!response.ok) {
|
|
171
|
+
let errorDetail = '';
|
|
172
|
+
try {
|
|
173
|
+
const responseText = await response.text();
|
|
174
|
+
errorDetail = responseText ? ` - ${responseText.substring(0, 500)}` : '';
|
|
175
|
+
}
|
|
176
|
+
catch (_a) {
|
|
177
|
+
// 解析失败时 errorDetail 保持空串,不影响主错误抛出
|
|
178
|
+
}
|
|
179
|
+
throw new error_1.CloudBaseError(`上传失败: ${response.status} ${response.statusText}${errorDetail}`);
|
|
180
|
+
}
|
|
181
|
+
// 7. 返回结果(返回 cosTimestamp,用于创建应用)
|
|
182
|
+
return {
|
|
183
|
+
cosTimestamp: cosInfo.UnixTimestamp,
|
|
184
|
+
unixTimestamp: cosInfo.UnixTimestamp
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
finally {
|
|
188
|
+
// 8. 清理临时文件
|
|
189
|
+
if (fs_1.default.existsSync(tmpZipPath)) {
|
|
190
|
+
fs_1.default.unlinkSync(tmpZipPath);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* 获取环境信息
|
|
196
|
+
*/
|
|
197
|
+
getEnvInfo() {
|
|
198
|
+
const envConfig = this.environment.lazyEnvironmentConfig;
|
|
199
|
+
return {
|
|
200
|
+
envId: envConfig.EnvId
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* 校验必填参数
|
|
205
|
+
* @param params 参数对象
|
|
206
|
+
* @param fields 必填字段列表
|
|
207
|
+
*/
|
|
208
|
+
validateRequiredParams(params, fields) {
|
|
209
|
+
for (const field of fields) {
|
|
210
|
+
if (!params[field]) {
|
|
211
|
+
throw new error_1.CloudBaseError(`${field} 是必填参数`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
exports.CloudAppService = CloudAppService;
|
|
217
|
+
__decorate([
|
|
218
|
+
(0, utils_1.preLazy)()
|
|
219
|
+
], CloudAppService.prototype, "createApp", null);
|
|
220
|
+
__decorate([
|
|
221
|
+
(0, utils_1.preLazy)()
|
|
222
|
+
], CloudAppService.prototype, "describeAppList", null);
|
|
223
|
+
__decorate([
|
|
224
|
+
(0, utils_1.preLazy)()
|
|
225
|
+
], CloudAppService.prototype, "describeAppInfo", null);
|
|
226
|
+
__decorate([
|
|
227
|
+
(0, utils_1.preLazy)()
|
|
228
|
+
], CloudAppService.prototype, "deleteApp", null);
|
|
229
|
+
__decorate([
|
|
230
|
+
(0, utils_1.preLazy)()
|
|
231
|
+
], CloudAppService.prototype, "describeAppVersion", null);
|
|
232
|
+
__decorate([
|
|
233
|
+
(0, utils_1.preLazy)()
|
|
234
|
+
], CloudAppService.prototype, "describeAppVersionList", null);
|
|
235
|
+
__decorate([
|
|
236
|
+
(0, utils_1.preLazy)()
|
|
237
|
+
], CloudAppService.prototype, "deleteAppVersion", null);
|
|
238
|
+
__decorate([
|
|
239
|
+
(0, utils_1.preLazy)()
|
|
240
|
+
], CloudAppService.prototype, "describeCosInfo", null);
|
|
241
|
+
__decorate([
|
|
242
|
+
(0, utils_1.preLazy)()
|
|
243
|
+
], CloudAppService.prototype, "uploadCode", null);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CloudApp 统一部署服务类型定义
|
|
4
|
+
* 支持 static-hosting(静态托管)和 http-function(预留)两种部署类型
|
|
5
|
+
*
|
|
6
|
+
* 命名规范:
|
|
7
|
+
* - 入参接口(IXxxParams):camelCase,通过 upperCaseObjKey 转换后透传给 API
|
|
8
|
+
* - 返回值接口(IXxxResult):PascalCase,与 API 响应字段保持一致
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
package/lib/database/index.js
CHANGED
|
@@ -217,6 +217,70 @@ class DatabaseService {
|
|
|
217
217
|
}
|
|
218
218
|
return this.dbOpService.request('DatabaseMigrateExport', Object.assign({ CollectionName: collectionName, FilePath: filePath, FileType: fileType, EnvId: this.envId }, options));
|
|
219
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* 获取可回档表格
|
|
222
|
+
* @param time 回档时间,格式 YYYY-MM-DD HH:MM:SS
|
|
223
|
+
* @param filters 过滤器(可选)
|
|
224
|
+
* @param instanceId 实例ID(可选,不传则自动使用当前环境的数据库实例 ID)
|
|
225
|
+
* @returns 可回档表格列表
|
|
226
|
+
*/
|
|
227
|
+
async describeRestoreTables(time, filters, instanceId) {
|
|
228
|
+
const { Tag } = this.getDatabaseConfig();
|
|
229
|
+
const params = {
|
|
230
|
+
InstanceId: instanceId || Tag,
|
|
231
|
+
Time: time
|
|
232
|
+
};
|
|
233
|
+
if (filters && filters.length > 0) {
|
|
234
|
+
params.Filters = filters;
|
|
235
|
+
}
|
|
236
|
+
return this.dbOpService.request('DescribeRestoreTables', params);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* 获取回档任务
|
|
240
|
+
* @param instanceId 实例ID(可选,不传则自动使用当前环境的数据库实例 ID)
|
|
241
|
+
* @returns 回档任务列表
|
|
242
|
+
*/
|
|
243
|
+
async describeRestoreTask(instanceId) {
|
|
244
|
+
const { Tag } = this.getDatabaseConfig();
|
|
245
|
+
return this.dbOpService.request('DescribeRestoreTask', {
|
|
246
|
+
InstanceId: instanceId || Tag
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* 获取可回档时间
|
|
251
|
+
* @param instanceId 实例ID(可选,不传则自动使用当前环境的数据库实例 ID)
|
|
252
|
+
* @returns 可回档时间列表及任意时间回档列表
|
|
253
|
+
*/
|
|
254
|
+
async describeRestoreTime(instanceId) {
|
|
255
|
+
const { Tag } = this.getDatabaseConfig();
|
|
256
|
+
return this.dbOpService.request('DescribeRestoreTime', {
|
|
257
|
+
InstanceId: instanceId || Tag
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* 实例表格回档
|
|
262
|
+
* @param time 回档时间,格式 YYYY-MM-DD HH:MM:SS
|
|
263
|
+
* @param modifyTableNamesInfo 回档表格信息(原表名 → 新表名映射)
|
|
264
|
+
* @param instanceId 实例ID(可选,不传则自动使用当前环境的数据库实例 ID)
|
|
265
|
+
* @returns 流程ID
|
|
266
|
+
*/
|
|
267
|
+
async restoreTables(time, modifyTableNamesInfo, instanceId) {
|
|
268
|
+
const { Tag } = this.getDatabaseConfig();
|
|
269
|
+
return this.dbOpService.request('RestoreTCBTables', {
|
|
270
|
+
InstanceId: instanceId || Tag,
|
|
271
|
+
Time: time,
|
|
272
|
+
ModifyTableNamesInfo: modifyTableNamesInfo
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* 执行文档型数据库命令
|
|
277
|
+
* @param options 命令参数(命令列表、可选的 Tag/EnvId/MongoConnector)
|
|
278
|
+
* @returns 执行结果,Data 为 JSON 字符串数组
|
|
279
|
+
*/
|
|
280
|
+
async runCommands(options) {
|
|
281
|
+
let { Tag } = this.getDatabaseConfig();
|
|
282
|
+
return this.dbOpService.request('RunCommands', Object.assign({ MgoCommands: options.MgoCommands, Tag: options.Tag || Tag, EnvId: options.EnvId || this.envId }, (options.MongoConnector ? { MongoConnector: options.MongoConnector } : {})));
|
|
283
|
+
}
|
|
220
284
|
}
|
|
221
285
|
exports.DatabaseService = DatabaseService;
|
|
222
286
|
DatabaseService.tcbServiceVersion = {
|
|
@@ -242,3 +306,18 @@ __decorate([
|
|
|
242
306
|
__decorate([
|
|
243
307
|
preLazy()
|
|
244
308
|
], DatabaseService.prototype, "listCollections", null);
|
|
309
|
+
__decorate([
|
|
310
|
+
preLazy()
|
|
311
|
+
], DatabaseService.prototype, "describeRestoreTables", null);
|
|
312
|
+
__decorate([
|
|
313
|
+
preLazy()
|
|
314
|
+
], DatabaseService.prototype, "describeRestoreTask", null);
|
|
315
|
+
__decorate([
|
|
316
|
+
preLazy()
|
|
317
|
+
], DatabaseService.prototype, "describeRestoreTime", null);
|
|
318
|
+
__decorate([
|
|
319
|
+
preLazy()
|
|
320
|
+
], DatabaseService.prototype, "restoreTables", null);
|
|
321
|
+
__decorate([
|
|
322
|
+
preLazy()
|
|
323
|
+
], DatabaseService.prototype, "runCommands", null);
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DocsService = void 0;
|
|
4
|
+
const http_request_1 = require("../utils/http-request");
|
|
5
|
+
const DOCS_BASE_URL = 'https://docs.cloudbase.net';
|
|
6
|
+
const CATEGORY_URL = `${DOCS_BASE_URL}/category.json`;
|
|
7
|
+
const ALGOLIA_SEARCH_URL = 'https://70wjvl90pg-dsn.algolia.net/1/indexes/*/queries' +
|
|
8
|
+
'?x-algolia-agent=Algolia%20for%20JavaScript%20(4.19.1)%3B%20Browser%20(lite)%3B%20docsearch%20(3.6.1)%3B%20docsearch-react%20(3.6.1)%3B%20docusaurus%20(2.4.3)' +
|
|
9
|
+
'&x-algolia-api-key=69281afd904cb72b399c92fb62b3ce25' +
|
|
10
|
+
'&x-algolia-application-id=70WJVL90PG';
|
|
11
|
+
const ALGOLIA_SEARCH_PARAMS = 'attributesToRetrieve=["hierarchy.lvl0","hierarchy.lvl1","hierarchy.lvl2","hierarchy.lvl3","hierarchy.lvl4","hierarchy.lvl5","hierarchy.lvl6","content","type","url"]' +
|
|
12
|
+
'&attributesToSnippet=["hierarchy.lvl1:10","hierarchy.lvl2:10","hierarchy.lvl3:10","hierarchy.lvl4:10","hierarchy.lvl5:10","hierarchy.lvl6:10","content:10"]' +
|
|
13
|
+
'&snippetEllipsisText=\u2026' +
|
|
14
|
+
'&highlightPreTag=<mark>&highlightPostTag=</mark>' +
|
|
15
|
+
'&hitsPerPage=20' +
|
|
16
|
+
'&clickAnalytics=false' +
|
|
17
|
+
'&facetFilters=["language:zh-Hans",["docusaurus_tag:default","docusaurus_tag:docs-default-current"]]';
|
|
18
|
+
class DocsService {
|
|
19
|
+
async listModules() {
|
|
20
|
+
const category = await this.getCategory();
|
|
21
|
+
// 列出所有二级目录模块
|
|
22
|
+
const modules = new Set();
|
|
23
|
+
for (const topModule of Object.values(category)) {
|
|
24
|
+
if (typeof topModule === 'object') {
|
|
25
|
+
for (const key of Object.keys(topModule)) {
|
|
26
|
+
modules.add(key);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return Array.from(modules).sort();
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Case: tcb docs read 快速开始
|
|
34
|
+
* 根据模块名获取该模块下的所有文档(返回嵌套的对象结构)
|
|
35
|
+
*/
|
|
36
|
+
async listModuleDocs(moduleName) {
|
|
37
|
+
const category = await this.getCategory();
|
|
38
|
+
// 先尝试直接匹配顶级模块
|
|
39
|
+
const moduleData = category[moduleName];
|
|
40
|
+
if (moduleData !== undefined) {
|
|
41
|
+
return moduleData;
|
|
42
|
+
}
|
|
43
|
+
// 递归搜索嵌套模块
|
|
44
|
+
const found = this.findNestedModule(category, moduleName);
|
|
45
|
+
if (found) {
|
|
46
|
+
return found;
|
|
47
|
+
}
|
|
48
|
+
throw new Error(`Module "${moduleName}" not found`);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Case: tcb docs read <任意输入>
|
|
52
|
+
* 统一的查找入口,自动识别输入格式并返回模块数据或文档URL
|
|
53
|
+
*
|
|
54
|
+
* 支持的输入格式:
|
|
55
|
+
* 1. URL路径格式(包含 /,相对或完整): tcb docs read quick-start/create-env
|
|
56
|
+
* 2. 点分隔路径: tcb docs read 云托管.快速开始..NET 快速开始
|
|
57
|
+
* 3. 模块名: tcb docs read 快速开始 「命中第一个 底下其他同理」
|
|
58
|
+
* 4. 嵌套模块名: tcb docs read 微信生态(嵌套在"社区.最佳实践"下)
|
|
59
|
+
* 5. 文档标题: tcb docs read 小程序快速开始
|
|
60
|
+
* 6. 前缀模糊匹配: tcb docs read PHP(匹配 "PHP 快速开始")
|
|
61
|
+
*/
|
|
62
|
+
async findByName(input) {
|
|
63
|
+
const category = await this.getCategory();
|
|
64
|
+
// 模式1: URL路径格式(包含 /),直接作为文档URL返回
|
|
65
|
+
if (input.includes('/')) {
|
|
66
|
+
// 直接返回 URL 路径,交给 readDoc 处理
|
|
67
|
+
return { type: 'doc', data: input };
|
|
68
|
+
}
|
|
69
|
+
// 模式2: 点分隔的层级导航,如 "云托管.快速开始..NET 快速开始" 处理多点情况
|
|
70
|
+
if (input.includes('.')) {
|
|
71
|
+
const result = this.findByDotPath(category, input);
|
|
72
|
+
if (result) {
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
throw new Error(`未找到模块或文档 "${input}"`);
|
|
76
|
+
}
|
|
77
|
+
// 模式3: 模块名查找(顶级或嵌套)复用上面已获取的 category
|
|
78
|
+
const topLevelModule = category[input];
|
|
79
|
+
if (topLevelModule && typeof topLevelModule === 'object') {
|
|
80
|
+
return { type: 'module', data: topLevelModule };
|
|
81
|
+
}
|
|
82
|
+
const nestedModule = this.findNestedModule(category, input);
|
|
83
|
+
if (nestedModule) {
|
|
84
|
+
return { type: 'module', data: nestedModule };
|
|
85
|
+
}
|
|
86
|
+
// 模式4: 文档标题精确搜索
|
|
87
|
+
const docPath = this.findDocPathByTitle(category, input);
|
|
88
|
+
if (docPath) {
|
|
89
|
+
return { type: 'doc', data: docPath };
|
|
90
|
+
}
|
|
91
|
+
// 模式5: 前缀模糊搜索(处理含空格的 key 被命令行截断的情况)
|
|
92
|
+
const prefixResult = this.findByKeyPrefix(category, input);
|
|
93
|
+
if (prefixResult) {
|
|
94
|
+
return prefixResult;
|
|
95
|
+
}
|
|
96
|
+
throw new Error(`未找到模块或文档 "${input}"`);
|
|
97
|
+
}
|
|
98
|
+
async readDoc(docPath) {
|
|
99
|
+
const normalizedPath = docPath.endsWith('.md') ? docPath : `${docPath}/index.md`;
|
|
100
|
+
// 支持完整URL和相对路径
|
|
101
|
+
const url = normalizedPath.startsWith('http') ? normalizedPath : `${DOCS_BASE_URL}/${normalizedPath}`;
|
|
102
|
+
const res = await (0, http_request_1.fetchStream)(url);
|
|
103
|
+
let text = await res.text();
|
|
104
|
+
// 移除 UTF-8 BOM
|
|
105
|
+
if (text.charCodeAt(0) === 0xFEFF) {
|
|
106
|
+
text = text.slice(1);
|
|
107
|
+
}
|
|
108
|
+
return text;
|
|
109
|
+
}
|
|
110
|
+
async searchDocs(query) {
|
|
111
|
+
var _a, _b, _c;
|
|
112
|
+
const body = JSON.stringify({
|
|
113
|
+
requests: [
|
|
114
|
+
{
|
|
115
|
+
query,
|
|
116
|
+
indexName: 'cloudbase',
|
|
117
|
+
params: ALGOLIA_SEARCH_PARAMS
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
});
|
|
121
|
+
const res = await (0, http_request_1.fetch)(ALGOLIA_SEARCH_URL, {
|
|
122
|
+
method: 'POST',
|
|
123
|
+
headers: { 'Content-Type': 'application/json' },
|
|
124
|
+
body
|
|
125
|
+
});
|
|
126
|
+
const hits = (_c = (_b = (_a = res === null || res === void 0 ? void 0 : res.results) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.hits) !== null && _c !== void 0 ? _c : [];
|
|
127
|
+
return hits.map((hit) => {
|
|
128
|
+
var _a, _b;
|
|
129
|
+
const levels = ['lvl6', 'lvl5', 'lvl4', 'lvl3', 'lvl2', 'lvl1', 'lvl0'];
|
|
130
|
+
const title = (_a = levels.map(l => { var _a; return (_a = hit.hierarchy) === null || _a === void 0 ? void 0 : _a[l]; }).find(Boolean)) !== null && _a !== void 0 ? _a : '';
|
|
131
|
+
return {
|
|
132
|
+
url: hit.url,
|
|
133
|
+
title,
|
|
134
|
+
content: (_b = hit.content) !== null && _b !== void 0 ? _b : null
|
|
135
|
+
};
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
async getCategory() {
|
|
139
|
+
return (0, http_request_1.fetch)(CATEGORY_URL);
|
|
140
|
+
}
|
|
141
|
+
// Case: tcb docs read PHP(模糊匹配出 "PHP 快速开始",处理命令行空格截断问题)
|
|
142
|
+
// 在整棵树中查找以 prefix + 空格 开头的唯一 key
|
|
143
|
+
findByKeyPrefix(node, prefix) {
|
|
144
|
+
if (typeof node === 'string') {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
for (const [key, value] of Object.entries(node)) {
|
|
148
|
+
if (key.startsWith(prefix + ' ')) {
|
|
149
|
+
if (typeof value === 'string') {
|
|
150
|
+
return { type: 'doc', data: value };
|
|
151
|
+
}
|
|
152
|
+
else if (typeof value === 'object') {
|
|
153
|
+
return { type: 'module', data: value };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (typeof value === 'object') {
|
|
157
|
+
const found = this.findByKeyPrefix(value, prefix);
|
|
158
|
+
if (found) {
|
|
159
|
+
return found;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
// Case: tcb docs read 云托管.快速开始..NET 快速开始
|
|
166
|
+
// 使用贪心匹配策略:优先匹配最长的 key(避免 ".NET" 被错误分割)按分隔路径查找
|
|
167
|
+
// 若从根节点无法匹配,则在整棵树中深度优先搜索,支持输入中间路径段
|
|
168
|
+
findByDotPath(category, dotPath) {
|
|
169
|
+
const rootResult = this.matchDotPath(category, dotPath);
|
|
170
|
+
if (rootResult) {
|
|
171
|
+
return rootResult;
|
|
172
|
+
}
|
|
173
|
+
return this.searchDotPathInTree(category, dotPath);
|
|
174
|
+
}
|
|
175
|
+
// 从每个子节点尝试匹配 dotPath 支持中间路径段搜索
|
|
176
|
+
searchDotPathInTree(node, dotPath) {
|
|
177
|
+
if (typeof node !== 'object' || node === null) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
for (const value of Object.values(node)) {
|
|
181
|
+
if (typeof value === 'object' && value !== null) {
|
|
182
|
+
const result = this.matchDotPath(value, dotPath);
|
|
183
|
+
if (result) {
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
const nested = this.searchDotPathInTree(value, dotPath);
|
|
187
|
+
if (nested) {
|
|
188
|
+
return nested;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
// 递归匹配点分隔路径
|
|
195
|
+
matchDotPath(node, remainingPath) {
|
|
196
|
+
if (!remainingPath) {
|
|
197
|
+
if (typeof node === 'string') {
|
|
198
|
+
return { type: 'doc', data: node };
|
|
199
|
+
}
|
|
200
|
+
else if (typeof node === 'object' && node !== null) {
|
|
201
|
+
return { type: 'module', data: node };
|
|
202
|
+
}
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
// 当前节点不是对象,无法继续匹配
|
|
206
|
+
if (typeof node !== 'object' || node === null) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
// 获取所有 key,按长度降序排序(贪心策略:优先匹配长 key)
|
|
210
|
+
const keys = Object.keys(node).sort((a, b) => b.length - a.length);
|
|
211
|
+
for (const key of keys) {
|
|
212
|
+
// 情况1: 完全匹配(路径末尾)
|
|
213
|
+
if (remainingPath === key) {
|
|
214
|
+
const value = node[key];
|
|
215
|
+
if (typeof value === 'string') {
|
|
216
|
+
return { type: 'doc', data: value };
|
|
217
|
+
}
|
|
218
|
+
else if (typeof value === 'object') {
|
|
219
|
+
return { type: 'module', data: value };
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// 情况2: 前缀匹配(还有下级路径)
|
|
223
|
+
if (remainingPath.startsWith(key + '.')) {
|
|
224
|
+
const nextPath = remainingPath.slice(key.length + 1);
|
|
225
|
+
const result = this.matchDotPath(node[key], nextPath);
|
|
226
|
+
if (result) {
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// 情况3: 模糊前缀匹配(key 含空格,命令行未加引号时 remainingPath 被空格截断)
|
|
232
|
+
// 例如: remainingPath = "PHP" 匹配 key = "PHP 快速开始" 避免含空格时无法匹配 case:云托管.快速开始.PHP 快速开始
|
|
233
|
+
// 仅在 remainingPath 不含点时使用(确保是最后一个路径段)
|
|
234
|
+
if (!remainingPath.includes('.')) {
|
|
235
|
+
const fuzzyMatches = keys.filter(k => k.startsWith(remainingPath + ' '));
|
|
236
|
+
if (fuzzyMatches.length === 1) {
|
|
237
|
+
const value = node[fuzzyMatches[0]];
|
|
238
|
+
if (typeof value === 'string') {
|
|
239
|
+
return { type: 'doc', data: value };
|
|
240
|
+
}
|
|
241
|
+
else if (typeof value === 'object') {
|
|
242
|
+
return { type: 'module', data: value };
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
// Case: tcb docs read 微信生态("微信生态"是嵌套在"社区/最佳实践"下的二级模块)
|
|
249
|
+
// 递归查找嵌套模块(不一定是顶级key)
|
|
250
|
+
findNestedModule(node, targetName) {
|
|
251
|
+
if (typeof node === 'string') {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
for (const [key, value] of Object.entries(node)) {
|
|
255
|
+
if (key === targetName && typeof value === 'object') {
|
|
256
|
+
return value;
|
|
257
|
+
}
|
|
258
|
+
// 继续递归搜索
|
|
259
|
+
if (typeof value === 'object') {
|
|
260
|
+
const found = this.findNestedModule(value, targetName);
|
|
261
|
+
if (found) {
|
|
262
|
+
return found;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
// Case: tcb docs read 小程序快速开始(通过文档标题精确查找)
|
|
269
|
+
// 递归查找文档路径(匹配key而不是value)
|
|
270
|
+
findDocPathByTitle(node, targetTitle) {
|
|
271
|
+
if (typeof node === 'string') {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
for (const [key, value] of Object.entries(node)) {
|
|
275
|
+
if (key === targetTitle && typeof value === 'string') {
|
|
276
|
+
return value;
|
|
277
|
+
}
|
|
278
|
+
if (typeof value === 'object') {
|
|
279
|
+
const found = this.findDocPathByTitle(value, targetTitle);
|
|
280
|
+
if (found) {
|
|
281
|
+
return found;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
exports.DocsService = DocsService;
|