@cloudbase/manager-node 4.11.0-alpha.0 → 4.11.0-alpha.2
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/lib/docs/index.js +288 -0
- package/lib/docs/types.js +2 -0
- package/lib/index.js +7 -0
- package/package.json +1 -1
- package/types/docs/index.d.ts +37 -0
- package/types/docs/types.d.ts +24 -0
- package/types/index.d.ts +3 -0
|
@@ -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;
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const context_1 = require("./context");
|
|
3
3
|
const environmentManager_1 = require("./environmentManager");
|
|
4
|
+
const docs_1 = require("./docs");
|
|
4
5
|
class CloudBase {
|
|
5
6
|
/**
|
|
6
7
|
* init 初始化 为单例
|
|
@@ -81,6 +82,12 @@ class CloudBase {
|
|
|
81
82
|
get user() {
|
|
82
83
|
return this.currentEnvironment().getUserService();
|
|
83
84
|
}
|
|
85
|
+
get docs() {
|
|
86
|
+
if (!this.docsService) {
|
|
87
|
+
this.docsService = new docs_1.DocsService();
|
|
88
|
+
}
|
|
89
|
+
return this.docsService;
|
|
90
|
+
}
|
|
84
91
|
getEnvironmentManager() {
|
|
85
92
|
return this.environmentManager;
|
|
86
93
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { SearchResult, ModuleDocs } from './types';
|
|
2
|
+
export declare class DocsService {
|
|
3
|
+
listModules(): Promise<string[]>;
|
|
4
|
+
/**
|
|
5
|
+
* Case: tcb docs read 快速开始
|
|
6
|
+
* 根据模块名获取该模块下的所有文档(返回嵌套的对象结构)
|
|
7
|
+
*/
|
|
8
|
+
listModuleDocs(moduleName: string): Promise<ModuleDocs>;
|
|
9
|
+
/**
|
|
10
|
+
* Case: tcb docs read <任意输入>
|
|
11
|
+
* 统一的查找入口,自动识别输入格式并返回模块数据或文档URL
|
|
12
|
+
*
|
|
13
|
+
* 支持的输入格式:
|
|
14
|
+
* 1. URL路径格式(包含 /,相对或完整): tcb docs read quick-start/create-env
|
|
15
|
+
* 2. 点分隔路径: tcb docs read 云托管.快速开始..NET 快速开始
|
|
16
|
+
* 3. 模块名: tcb docs read 快速开始 「命中第一个 底下其他同理」
|
|
17
|
+
* 4. 嵌套模块名: tcb docs read 微信生态(嵌套在"社区.最佳实践"下)
|
|
18
|
+
* 5. 文档标题: tcb docs read 小程序快速开始
|
|
19
|
+
* 6. 前缀模糊匹配: tcb docs read PHP(匹配 "PHP 快速开始")
|
|
20
|
+
*/
|
|
21
|
+
findByName(input: string): Promise<{
|
|
22
|
+
type: 'module';
|
|
23
|
+
data: ModuleDocs;
|
|
24
|
+
} | {
|
|
25
|
+
type: 'doc';
|
|
26
|
+
data: string;
|
|
27
|
+
}>;
|
|
28
|
+
readDoc(docPath: string): Promise<string>;
|
|
29
|
+
searchDocs(query: string): Promise<SearchResult[]>;
|
|
30
|
+
private getCategory;
|
|
31
|
+
private findByKeyPrefix;
|
|
32
|
+
private findByDotPath;
|
|
33
|
+
private searchDotPathInTree;
|
|
34
|
+
private matchDotPath;
|
|
35
|
+
private findNestedModule;
|
|
36
|
+
private findDocPathByTitle;
|
|
37
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type CategoryNode = string | {
|
|
2
|
+
[key: string]: CategoryNode;
|
|
3
|
+
};
|
|
4
|
+
export interface Category {
|
|
5
|
+
[key: string]: CategoryNode;
|
|
6
|
+
}
|
|
7
|
+
export interface ModuleDocs {
|
|
8
|
+
[key: string]: CategoryNode;
|
|
9
|
+
}
|
|
10
|
+
export interface DocEntry {
|
|
11
|
+
title: string;
|
|
12
|
+
path: string;
|
|
13
|
+
}
|
|
14
|
+
export interface SearchHit {
|
|
15
|
+
url: string;
|
|
16
|
+
content: string | null;
|
|
17
|
+
type: string;
|
|
18
|
+
hierarchy: Record<string, string | null>;
|
|
19
|
+
}
|
|
20
|
+
export interface SearchResult {
|
|
21
|
+
url: string;
|
|
22
|
+
title: string;
|
|
23
|
+
content: string | null;
|
|
24
|
+
}
|
package/types/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { ThirdService } from './third';
|
|
|
12
12
|
import { AccessService } from './access';
|
|
13
13
|
import { UserService } from './user';
|
|
14
14
|
import { CloudBaseRunService } from './cloudBaseRun';
|
|
15
|
+
import { DocsService } from './docs';
|
|
15
16
|
interface CloudBaseConfig {
|
|
16
17
|
secretId?: string;
|
|
17
18
|
secretKey?: string;
|
|
@@ -37,6 +38,7 @@ declare class CloudBase {
|
|
|
37
38
|
private context;
|
|
38
39
|
private cloudBaseConfig;
|
|
39
40
|
private environmentManager;
|
|
41
|
+
private docsService;
|
|
40
42
|
constructor(config?: CloudBaseConfig);
|
|
41
43
|
addEnvironment(envId: string): void;
|
|
42
44
|
currentEnvironment(): Environment;
|
|
@@ -52,6 +54,7 @@ declare class CloudBase {
|
|
|
52
54
|
get env(): EnvService;
|
|
53
55
|
get third(): ThirdService;
|
|
54
56
|
get user(): UserService;
|
|
57
|
+
get docs(): DocsService;
|
|
55
58
|
getEnvironmentManager(): EnvironmentManager;
|
|
56
59
|
getManagerConfig(): CloudBaseConfig;
|
|
57
60
|
get isInternalEndpoint(): Boolean;
|