@vectorx/cloud-toolkit 0.5.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/agent.kit.js +77 -48
- package/lib/config/api.config.js +3 -8
- package/lib/container/container.js +4 -0
- package/lib/container/identifiers.js +2 -0
- package/lib/container/index.js +6 -0
- package/lib/env.kit.js +40 -0
- package/lib/fun.kit.js +34 -0
- package/lib/index.js +4 -0
- package/lib/services/auth-service.js +2 -1
- package/lib/services/cloud-env-service.js +166 -0
- package/lib/services/fun-service.js +378 -0
- package/lib/services/index.js +2 -0
- package/lib/utils/fun-project-validator.js +212 -0
- package/lib/utils/output.js +53 -19
- package/lib/utils/request.js +20 -0
- package/lib/utils/table.js +17 -0
- package/lib/utils/tracker/apm.js +135 -0
- package/lib/utils/tracker/cli-apm.js +103 -0
- package/lib/utils/tracker/index.js +17 -131
- package/package.json +11 -3
- package/types/agent.kit.d.ts +21 -2
- package/types/config/api.config.d.ts +1 -2
- package/types/container/identifiers.d.ts +3 -1
- package/types/container/index.d.ts +3 -1
- package/types/container/types.d.ts +36 -0
- package/types/env.kit.d.ts +43 -0
- package/types/fun.kit.d.ts +63 -0
- package/types/index.d.ts +4 -0
- package/types/services/cloud-env-service.d.ts +10 -0
- package/types/services/fun-service.d.ts +23 -0
- package/types/services/index.d.ts +2 -0
- package/types/utils/fun-project-validator.d.ts +22 -0
- package/types/utils/table.d.ts +2 -0
- package/types/utils/tracker/apm.d.ts +28 -0
- package/types/utils/tracker/cli-apm.d.ts +27 -0
- package/types/utils/tracker/index.d.ts +4 -27
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
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;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
12
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
13
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
14
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
15
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
16
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
17
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
21
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
22
|
+
};
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.FunService = void 0;
|
|
25
|
+
const fs_1 = __importDefault(require("fs"));
|
|
26
|
+
const path_1 = __importDefault(require("path"));
|
|
27
|
+
const archiver_1 = __importDefault(require("archiver"));
|
|
28
|
+
const inversify_1 = require("inversify");
|
|
29
|
+
const agent_kit_1 = require("../agent.kit");
|
|
30
|
+
const config_1 = require("../config");
|
|
31
|
+
const container_1 = require("../container");
|
|
32
|
+
const cos_uploader_1 = require("../utils/cos-uploader");
|
|
33
|
+
const env_helper_1 = require("../utils/env-helper");
|
|
34
|
+
const logger_1 = require("../utils/logger");
|
|
35
|
+
const port_finder_1 = require("../utils/port-finder");
|
|
36
|
+
const request_1 = require("../utils/request");
|
|
37
|
+
const DEFAULT_DEBUGGER_API_PORT = 9230;
|
|
38
|
+
let FunService = class FunService {
|
|
39
|
+
constructor() {
|
|
40
|
+
this.authService = container_1.container.get(container_1.SERVICE_IDENTIFIERS.AuthService);
|
|
41
|
+
}
|
|
42
|
+
startFunDev() {
|
|
43
|
+
return __awaiter(this, arguments, void 0, function* (options = {}) {
|
|
44
|
+
var _a;
|
|
45
|
+
const { workDir = process.cwd(), uiPort, apiPort, watch = true, open = false, quiet = false, envId, mode = "fun", } = options;
|
|
46
|
+
const absWorkDir = path_1.default.isAbsolute(workDir) ? workDir : path_1.default.resolve(process.cwd(), workDir);
|
|
47
|
+
if (!fs_1.default.existsSync(absWorkDir)) {
|
|
48
|
+
throw new Error(`目录不存在: ${absWorkDir}`);
|
|
49
|
+
}
|
|
50
|
+
let ffPort;
|
|
51
|
+
if (apiPort) {
|
|
52
|
+
const candidate = apiPort + 1;
|
|
53
|
+
if (yield (0, port_finder_1.isPortAvailable)(candidate)) {
|
|
54
|
+
ffPort = candidate;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
ffPort = yield (0, port_finder_1.findAvailablePort)(apiPort + 2);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
ffPort = yield (0, port_finder_1.findAvailablePort)(9000);
|
|
62
|
+
}
|
|
63
|
+
const ffUrl = `http://localhost:${ffPort}`;
|
|
64
|
+
const logCollector = [];
|
|
65
|
+
let logBroadcastFn = null;
|
|
66
|
+
let startFunctionDebugger = (_a = options === null || options === void 0 ? void 0 : options.deps) === null || _a === void 0 ? void 0 : _a.startFunctionDebugger;
|
|
67
|
+
if (!startFunctionDebugger) {
|
|
68
|
+
try {
|
|
69
|
+
({ startFunctionDebugger } = require("@vectorx/function-debugger/lib/App"));
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
const msg = (err === null || err === void 0 ? void 0 : err.message) || String(err);
|
|
73
|
+
const isMissing = (err === null || err === void 0 ? void 0 : err.code) === "MODULE_NOT_FOUND" &&
|
|
74
|
+
(msg.includes("@vectorx/function-debugger") || msg.includes("function-debugger"));
|
|
75
|
+
if (!isMissing) {
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
throw new Error([
|
|
79
|
+
"未检测到可选依赖 @vectorx/function-debugger,无法启动本地调试器。",
|
|
80
|
+
"如在 CLI 中使用,请升级/使用最新的 rcb(CLI 会自动注入该依赖)。",
|
|
81
|
+
"如需在当前项目手动启用该能力,请安装:",
|
|
82
|
+
" pnpm add @vectorx/function-debugger",
|
|
83
|
+
"或在 monorepo 根目录安装:",
|
|
84
|
+
" pnpm add -w @vectorx/function-debugger",
|
|
85
|
+
].join("\n"));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
let debuggerApiPort;
|
|
89
|
+
if (typeof apiPort === "number" && apiPort > 0) {
|
|
90
|
+
debuggerApiPort = apiPort;
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
debuggerApiPort = yield (0, port_finder_1.findAvailablePort)(DEFAULT_DEBUGGER_API_PORT);
|
|
94
|
+
}
|
|
95
|
+
yield (0, agent_kit_1.startRcbFramework)(absWorkDir, ffPort, Boolean(watch), undefined, { mode }, (log) => {
|
|
96
|
+
if (logBroadcastFn) {
|
|
97
|
+
logBroadcastFn(log);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
logCollector.push(log);
|
|
101
|
+
if (logCollector.length > 1000) {
|
|
102
|
+
logCollector.shift();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
const runtime = yield startFunctionDebugger({
|
|
107
|
+
source: absWorkDir,
|
|
108
|
+
uiPort,
|
|
109
|
+
apiPort: debuggerApiPort,
|
|
110
|
+
watch,
|
|
111
|
+
quiet,
|
|
112
|
+
openBrowser: open,
|
|
113
|
+
envId,
|
|
114
|
+
targetBaseUrl: ffUrl,
|
|
115
|
+
onLogBroadcastReady: (broadcastFn) => {
|
|
116
|
+
logBroadcastFn = broadcastFn;
|
|
117
|
+
logCollector.forEach((log) => {
|
|
118
|
+
broadcastFn(log);
|
|
119
|
+
});
|
|
120
|
+
logCollector.length = 0;
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
return {
|
|
124
|
+
workDir: absWorkDir,
|
|
125
|
+
uiUrl: runtime.uiUrl,
|
|
126
|
+
apiUrl: runtime.apiUrl,
|
|
127
|
+
wsUrl: runtime.wsUrl,
|
|
128
|
+
ffUrl,
|
|
129
|
+
close: runtime.close,
|
|
130
|
+
};
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
deploy(options) {
|
|
134
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
135
|
+
var _a, _b;
|
|
136
|
+
const { envId, workDir = process.cwd(), includeNodeModules = true, desc, logCollector } = options;
|
|
137
|
+
const log = (message_1, ...args_1) => __awaiter(this, [message_1, ...args_1], void 0, function* (message, type = "info", loading = false) {
|
|
138
|
+
if (logCollector) {
|
|
139
|
+
yield logCollector.addLog(message, type, loading);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
switch (type) {
|
|
143
|
+
case "success":
|
|
144
|
+
console.log(`✓ ${message}`);
|
|
145
|
+
break;
|
|
146
|
+
case "error":
|
|
147
|
+
console.error(`✗ ${message}`);
|
|
148
|
+
break;
|
|
149
|
+
case "warn":
|
|
150
|
+
console.warn(`⚠ ${message}`);
|
|
151
|
+
break;
|
|
152
|
+
default:
|
|
153
|
+
console.log(message);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
yield log("初始化检查...", "info");
|
|
158
|
+
const absWorkDir = path_1.default.isAbsolute(workDir) ? workDir : path_1.default.resolve(process.cwd(), workDir);
|
|
159
|
+
if (!fs_1.default.existsSync(absWorkDir)) {
|
|
160
|
+
const errorMsg = `目录不存在: ${absWorkDir}`;
|
|
161
|
+
yield log(`错误: ${errorMsg}`, "error");
|
|
162
|
+
throw new Error(errorMsg);
|
|
163
|
+
}
|
|
164
|
+
yield log(`工作目录: ${absWorkDir}`, "info");
|
|
165
|
+
yield log("检查登录状态...", "info");
|
|
166
|
+
const loginInfo = yield this.authService.getLoginInfo();
|
|
167
|
+
if (!loginInfo) {
|
|
168
|
+
const errorMsg = "未检测到登录信息,请先执行 rcb login。";
|
|
169
|
+
yield log(`错误: ${errorMsg}`, "error");
|
|
170
|
+
throw new Error(errorMsg);
|
|
171
|
+
}
|
|
172
|
+
yield log("登录状态检查通过", "success");
|
|
173
|
+
const baseDesc = desc || `函数环境(${envId}) 代码包部署`;
|
|
174
|
+
yield log(`部署描述: ${baseDesc}`, "info");
|
|
175
|
+
yield log("开始预检...", "info");
|
|
176
|
+
yield log(`环境 ID: ${envId}`, "info");
|
|
177
|
+
const preCheckResponse = yield request_1.request.fetch({
|
|
178
|
+
method: "POST",
|
|
179
|
+
url: `${(0, config_1.getBaseUrl)()}/api/eros/mp/red/code/cloud_env_pkg/develop/upload_pre_check`,
|
|
180
|
+
headers: {
|
|
181
|
+
"Content-Type": "application/json",
|
|
182
|
+
},
|
|
183
|
+
data: {
|
|
184
|
+
query: {
|
|
185
|
+
secret_id: loginInfo.secret_id,
|
|
186
|
+
cli_token: loginInfo.cli_token,
|
|
187
|
+
},
|
|
188
|
+
body: {
|
|
189
|
+
cloud_env_id: envId,
|
|
190
|
+
desc: baseDesc,
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
const preCheckData = preCheckResponse.data;
|
|
195
|
+
if (!preCheckData.success || preCheckData.code !== 0) {
|
|
196
|
+
const errorMsg = `函数部署预检失败:${preCheckData.msg}`;
|
|
197
|
+
yield log(`预检失败: ${errorMsg}`, "error");
|
|
198
|
+
throw new Error(errorMsg);
|
|
199
|
+
}
|
|
200
|
+
yield log("预检通过", "success");
|
|
201
|
+
yield log("开始打包代码...", "info", true);
|
|
202
|
+
const outputDir = path_1.default.join(absWorkDir, "dist");
|
|
203
|
+
if (!fs_1.default.existsSync(outputDir)) {
|
|
204
|
+
fs_1.default.mkdirSync(outputDir, { recursive: true });
|
|
205
|
+
yield log(`创建输出目录: ${outputDir}`, "info");
|
|
206
|
+
}
|
|
207
|
+
const zipFilename = `fun-${envId}.zip`;
|
|
208
|
+
const outputPath = path_1.default.join(outputDir, zipFilename);
|
|
209
|
+
yield log(`打包文件: ${zipFilename}`, "info");
|
|
210
|
+
yield log(`输出路径: ${outputPath}`, "info");
|
|
211
|
+
yield log(includeNodeModules ? "包含 node_modules" : "排除 node_modules", "info");
|
|
212
|
+
yield new Promise((resolve, reject) => {
|
|
213
|
+
const output = fs_1.default.createWriteStream(outputPath);
|
|
214
|
+
const archive = (0, archiver_1.default)("zip", {
|
|
215
|
+
zlib: { level: 9 },
|
|
216
|
+
});
|
|
217
|
+
output.on("close", () => __awaiter(this, void 0, void 0, function* () {
|
|
218
|
+
yield log("代码打包完成", "success");
|
|
219
|
+
resolve();
|
|
220
|
+
}));
|
|
221
|
+
archive.on("error", (err) => __awaiter(this, void 0, void 0, function* () {
|
|
222
|
+
const errorMsg = `打包失败: ${err.message}`;
|
|
223
|
+
yield log(`错误: ${errorMsg}`, "error");
|
|
224
|
+
reject(err);
|
|
225
|
+
}));
|
|
226
|
+
archive.pipe(output);
|
|
227
|
+
archive.directory(absWorkDir, false, (data) => {
|
|
228
|
+
const excludePatterns = [/^\.git/, /^dist/, /\.zip$/, /\.DS_Store$/];
|
|
229
|
+
if (!includeNodeModules) {
|
|
230
|
+
excludePatterns.push(/^node_modules/);
|
|
231
|
+
}
|
|
232
|
+
if (excludePatterns.some((pattern) => pattern.test(data.name))) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
return data;
|
|
236
|
+
});
|
|
237
|
+
archive.finalize();
|
|
238
|
+
});
|
|
239
|
+
const stats = fs_1.default.statSync(outputPath);
|
|
240
|
+
const fileSizeMB = (stats.size / 1024 / 1024).toFixed(2);
|
|
241
|
+
yield log(`打包文件大小: ${fileSizeMB} MB`, "info");
|
|
242
|
+
yield log("开始上传代码包...", "info", true);
|
|
243
|
+
yield log("获取上传凭证...", "info", true);
|
|
244
|
+
const uploader = new cos_uploader_1.Uploader({
|
|
245
|
+
bizName: "fe",
|
|
246
|
+
scene: "fe-platform",
|
|
247
|
+
getToken: () => __awaiter(this, void 0, void 0, function* () {
|
|
248
|
+
if (env_helper_1.isIDE) {
|
|
249
|
+
const errorMsg = "IDE 模式暂不支持函数部署上传。";
|
|
250
|
+
yield log(`错误: ${errorMsg}`, "error");
|
|
251
|
+
throw new Error(errorMsg);
|
|
252
|
+
}
|
|
253
|
+
const permitResp = yield request_1.request.fetch({
|
|
254
|
+
url: `${(0, config_1.getBaseUrl)()}/api/eros/mp/red/code/cloud_env_pkg/upload/permit`,
|
|
255
|
+
method: "GET",
|
|
256
|
+
headers: {
|
|
257
|
+
"Content-Type": "application/json",
|
|
258
|
+
},
|
|
259
|
+
data: {
|
|
260
|
+
params: {
|
|
261
|
+
secret_id: loginInfo.secret_id,
|
|
262
|
+
cli_token: loginInfo.cli_token,
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
if (permitResp.data.code !== 0) {
|
|
267
|
+
const errorMsg = permitResp.data.msg || "获取上传凭证失败";
|
|
268
|
+
yield log(`错误: ${errorMsg}`, "error");
|
|
269
|
+
throw new Error(errorMsg);
|
|
270
|
+
}
|
|
271
|
+
yield log("上传凭证获取成功", "success");
|
|
272
|
+
return permitResp.data.data;
|
|
273
|
+
}),
|
|
274
|
+
});
|
|
275
|
+
yield log("读取打包文件...", "info");
|
|
276
|
+
const file = fs_1.default.readFileSync(outputPath);
|
|
277
|
+
const fileName = path_1.default.basename(outputPath);
|
|
278
|
+
yield log(`开始上传文件: ${fileName}`, "info", true);
|
|
279
|
+
const uploadResp = yield uploader.post({
|
|
280
|
+
Body: file,
|
|
281
|
+
fileInfo: {
|
|
282
|
+
name: fileName,
|
|
283
|
+
type: "application/octet-stream",
|
|
284
|
+
size: stats.size,
|
|
285
|
+
},
|
|
286
|
+
FilePath: outputPath,
|
|
287
|
+
});
|
|
288
|
+
if (!uploadResp.previewUrl) {
|
|
289
|
+
const errorMsg = "函数代码包上传失败,未获取到预览地址。";
|
|
290
|
+
yield log(`错误: ${errorMsg}`, "error");
|
|
291
|
+
throw new Error(errorMsg);
|
|
292
|
+
}
|
|
293
|
+
logger_1.logger.breakLine();
|
|
294
|
+
logger_1.logger.info(`[rcb-fun] 函数代码包上传成功: ${uploadResp.previewUrl}`);
|
|
295
|
+
yield log(`代码包上传成功: ${uploadResp.previewUrl}`, "success");
|
|
296
|
+
const fileUrl = uploadResp.previewUrl;
|
|
297
|
+
yield log("开始部署...", "info", true);
|
|
298
|
+
yield log(`环境 ID: ${envId}`, "info");
|
|
299
|
+
yield log(`文件 URL: ${fileUrl}`, "info");
|
|
300
|
+
const uploadResponse = yield request_1.request.fetch({
|
|
301
|
+
method: "POST",
|
|
302
|
+
url: `${(0, config_1.getBaseUrl)()}/api/eros/mp/red/code/cloud_env_pkg/develop/upload`,
|
|
303
|
+
headers: {
|
|
304
|
+
"Content-Type": "application/json",
|
|
305
|
+
},
|
|
306
|
+
data: {
|
|
307
|
+
query: {
|
|
308
|
+
secret_id: loginInfo.secret_id,
|
|
309
|
+
cli_token: loginInfo.cli_token,
|
|
310
|
+
},
|
|
311
|
+
body: {
|
|
312
|
+
cloud_env_id: envId,
|
|
313
|
+
desc: baseDesc,
|
|
314
|
+
file_url: fileUrl,
|
|
315
|
+
cloud_type: 4,
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
const uploadData = uploadResponse.data;
|
|
320
|
+
if (!uploadData.success || uploadData.code !== 0) {
|
|
321
|
+
const errorMsg = `函数部署失败:${uploadData.msg}`;
|
|
322
|
+
yield log(`部署失败: ${errorMsg}`, "error");
|
|
323
|
+
throw new Error(errorMsg);
|
|
324
|
+
}
|
|
325
|
+
yield log("部署成功", "success");
|
|
326
|
+
yield log(`部署结果: ${uploadData.msg || "完成"}`, "success");
|
|
327
|
+
const pkgDto = (_b = (_a = uploadResponse.data) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.cloud_env_dev_pkg_dto;
|
|
328
|
+
const publisher = pkgDto === null || pkgDto === void 0 ? void 0 : pkgDto.committerName;
|
|
329
|
+
return {
|
|
330
|
+
success: true,
|
|
331
|
+
code: uploadData.code,
|
|
332
|
+
msg: uploadData.msg,
|
|
333
|
+
publisher,
|
|
334
|
+
};
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
getPublishHistory(options) {
|
|
338
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
339
|
+
var _a, _b;
|
|
340
|
+
const { envId, pageNo = 1, pageSize = 10 } = options;
|
|
341
|
+
const loginInfo = yield this.authService.getLoginInfo();
|
|
342
|
+
if (!loginInfo) {
|
|
343
|
+
throw new Error("未检测到登录信息,请先执行 rcb login。");
|
|
344
|
+
}
|
|
345
|
+
const response = yield request_1.request.fetch({
|
|
346
|
+
method: "POST",
|
|
347
|
+
url: `${(0, config_1.getBaseUrl)()}/api/eros/mp/red/code/cloud_env/upload_history/query`,
|
|
348
|
+
headers: {
|
|
349
|
+
"Content-Type": "application/json",
|
|
350
|
+
},
|
|
351
|
+
data: {
|
|
352
|
+
query: {
|
|
353
|
+
secret_id: loginInfo.secret_id,
|
|
354
|
+
cli_token: loginInfo.cli_token,
|
|
355
|
+
},
|
|
356
|
+
body: {
|
|
357
|
+
cloud_env_id: envId,
|
|
358
|
+
page_no: String(pageNo),
|
|
359
|
+
page_size: String(pageSize),
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
const responseData = response.data;
|
|
364
|
+
if (!responseData.success || responseData.code !== 0) {
|
|
365
|
+
throw new Error(`查询发布历史失败:${responseData.msg || "未知错误"}`);
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
pkgList: ((_a = responseData.data) === null || _a === void 0 ? void 0 : _a.pkg_list) || [],
|
|
369
|
+
total: ((_b = responseData.data) === null || _b === void 0 ? void 0 : _b.total) || 0,
|
|
370
|
+
};
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
exports.FunService = FunService;
|
|
375
|
+
exports.FunService = FunService = __decorate([
|
|
376
|
+
(0, inversify_1.injectable)(),
|
|
377
|
+
__metadata("design:paramtypes", [])
|
|
378
|
+
], FunService);
|
package/lib/services/index.js
CHANGED
|
@@ -16,3 +16,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./auth-service"), exports);
|
|
18
18
|
__exportStar(require("./upload-service"), exports);
|
|
19
|
+
__exportStar(require("./cloud-env-service"), exports);
|
|
20
|
+
__exportStar(require("./fun-service"), exports);
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.FunProjectValidator = void 0;
|
|
16
|
+
const fs_1 = __importDefault(require("fs"));
|
|
17
|
+
const fs_2 = require("fs");
|
|
18
|
+
const path_1 = __importDefault(require("path"));
|
|
19
|
+
const path_2 = require("path");
|
|
20
|
+
const util_1 = require("util");
|
|
21
|
+
const readFileAsync = (0, util_1.promisify)(fs_1.default.readFile);
|
|
22
|
+
const accessAsync = (0, util_1.promisify)(fs_1.default.access);
|
|
23
|
+
class FunProjectValidator {
|
|
24
|
+
constructor(workDir) {
|
|
25
|
+
this.workDir = path_1.default.isAbsolute(workDir) ? workDir : path_1.default.resolve(process.cwd(), workDir);
|
|
26
|
+
}
|
|
27
|
+
validate() {
|
|
28
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
29
|
+
const errors = [];
|
|
30
|
+
const warnings = [];
|
|
31
|
+
const functionsConfigPath = path_1.default.join(this.workDir, "agent-cloudbase-functions.json");
|
|
32
|
+
if (!fs_1.default.existsSync(functionsConfigPath)) {
|
|
33
|
+
errors.push([
|
|
34
|
+
"agent-cloudbase-functions.json 文件不存在。",
|
|
35
|
+
"当前目录可能并不是一个“云函数(fun)项目”,请确认你选择的工作目录是否正确。",
|
|
36
|
+
"参考文档:https://miniapp.xiaohongshu.com/doc/DC185638",
|
|
37
|
+
].join(" "));
|
|
38
|
+
return { valid: false, errors, warnings };
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const functionsConfig = JSON.parse(yield readFileAsync(functionsConfigPath, "utf-8"));
|
|
42
|
+
const validationResult = yield this.validateFunctionsConfig(functionsConfig, functionsConfigPath);
|
|
43
|
+
errors.push(...validationResult.errors);
|
|
44
|
+
warnings.push(...(validationResult.warnings || []));
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (error instanceof Error) {
|
|
48
|
+
errors.push(`agent-cloudbase-functions.json 解析错误: ${error.message}`);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
errors.push("agent-cloudbase-functions.json 解析错误: 未知错误");
|
|
52
|
+
}
|
|
53
|
+
return { valid: false, errors, warnings };
|
|
54
|
+
}
|
|
55
|
+
const nodeModulesPath = path_1.default.join(this.workDir, "node_modules");
|
|
56
|
+
if (!fs_1.default.existsSync(nodeModulesPath)) {
|
|
57
|
+
errors.push("node_modules 目录不存在,请先运行 npm install 或 pnpm install");
|
|
58
|
+
return { valid: false, errors, warnings };
|
|
59
|
+
}
|
|
60
|
+
const packageJsonPath = path_1.default.join(this.workDir, "package.json");
|
|
61
|
+
if (!fs_1.default.existsSync(packageJsonPath)) {
|
|
62
|
+
errors.push("package.json 文件不存在");
|
|
63
|
+
return { valid: false, errors, warnings };
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const depErrors = yield this.validateDependencies(packageJsonPath, nodeModulesPath);
|
|
67
|
+
errors.push(...depErrors);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (error instanceof Error) {
|
|
71
|
+
errors.push(`依赖检查失败: ${error.message}`);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
errors.push("依赖检查失败: 未知错误");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const projectConfigPath = path_1.default.join(this.workDir, "project.config.json");
|
|
78
|
+
if (fs_1.default.existsSync(projectConfigPath)) {
|
|
79
|
+
try {
|
|
80
|
+
const projectConfig = JSON.parse(yield readFileAsync(projectConfigPath, "utf-8"));
|
|
81
|
+
if (projectConfig.agentId) {
|
|
82
|
+
errors.push("当前项目可能是一个 Agent 项目,请参考 agent 发布进行操作。更多查看:https://miniapp.xiaohongshu.com/doc/DC341455");
|
|
83
|
+
return { valid: false, errors, warnings };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
warnings.push("project.config.json 文件存在但解析失败,将忽略该文件");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
valid: errors.length === 0,
|
|
92
|
+
errors,
|
|
93
|
+
warnings,
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
validateFunctionsConfig(config, configPath) {
|
|
98
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
99
|
+
const errors = [];
|
|
100
|
+
const warnings = [];
|
|
101
|
+
if (!config.functionsRoot) {
|
|
102
|
+
errors.push("agent-cloudbase-functions.json 中缺少 functionsRoot 字段");
|
|
103
|
+
return { valid: false, errors, warnings };
|
|
104
|
+
}
|
|
105
|
+
const configDir = (0, path_2.dirname)(configPath);
|
|
106
|
+
const functionsRoot = (0, path_2.join)(configDir, config.functionsRoot);
|
|
107
|
+
if (!(0, fs_2.existsSync)(functionsRoot)) {
|
|
108
|
+
errors.push(`functionsRoot 目录不存在: ${functionsRoot}`);
|
|
109
|
+
return { valid: false, errors, warnings };
|
|
110
|
+
}
|
|
111
|
+
if (!config.functions || !Array.isArray(config.functions)) {
|
|
112
|
+
errors.push("agent-cloudbase-functions.json 中 functions 字段必须是一个非空数组");
|
|
113
|
+
return { valid: false, errors, warnings };
|
|
114
|
+
}
|
|
115
|
+
if (config.functions.length === 0) {
|
|
116
|
+
errors.push("agent-cloudbase-functions.json 中 functions 数组不能为空,请至少配置一个函数");
|
|
117
|
+
return { valid: false, errors, warnings };
|
|
118
|
+
}
|
|
119
|
+
const functionNames = new Set();
|
|
120
|
+
const triggerPaths = new Set();
|
|
121
|
+
for (let i = 0; i < config.functions.length; i++) {
|
|
122
|
+
const fn = config.functions[i];
|
|
123
|
+
const fnIndex = i + 1;
|
|
124
|
+
if (!fn.name) {
|
|
125
|
+
errors.push(`functions[${fnIndex}] 缺少 name 字段`);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (!fn.directory) {
|
|
129
|
+
errors.push(`functions[${fnIndex}] (name: ${fn.name}) 缺少 directory 字段`);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (!fn.source) {
|
|
133
|
+
errors.push(`functions[${fnIndex}] (name: ${fn.name}) 缺少 source 字段`);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (!fn.triggerPath) {
|
|
137
|
+
errors.push(`functions[${fnIndex}] (name: ${fn.name}) 缺少 triggerPath 字段`);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (functionNames.has(fn.name)) {
|
|
141
|
+
errors.push(`函数名重复: ${fn.name}`);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
functionNames.add(fn.name);
|
|
145
|
+
}
|
|
146
|
+
if (triggerPaths.has(fn.triggerPath)) {
|
|
147
|
+
errors.push(`triggerPath 重复: ${fn.triggerPath}`);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
triggerPaths.add(fn.triggerPath);
|
|
151
|
+
}
|
|
152
|
+
const fnDirectory = (0, path_2.join)(functionsRoot, fn.directory);
|
|
153
|
+
if (!(0, fs_2.existsSync)(fnDirectory)) {
|
|
154
|
+
errors.push(`函数目录不存在: ${fn.directory} (函数名: ${fn.name})`);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const sourcePath = (0, path_2.join)(fnDirectory, fn.source);
|
|
158
|
+
if (!(0, fs_2.existsSync)(sourcePath)) {
|
|
159
|
+
errors.push(`函数入口文件不存在: ${(0, path_2.join)(fn.directory, fn.source)} (函数名: ${fn.name})`);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
const module = require(sourcePath);
|
|
164
|
+
if (!module[fn.name] || typeof module[fn.name] !== "function") {
|
|
165
|
+
errors.push(`函数 ${fn.name} 在文件 ${sourcePath} 中未正确导出`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
if (error instanceof Error) {
|
|
170
|
+
errors.push(`函数 ${fn.name} 加载失败: ${error.message}`);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
errors.push(`函数 ${fn.name} 加载失败: 未知错误`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
valid: errors.length === 0,
|
|
179
|
+
errors,
|
|
180
|
+
warnings,
|
|
181
|
+
};
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
validateDependencies(packageJsonPath, nodeModulesPath) {
|
|
185
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
186
|
+
const errors = [];
|
|
187
|
+
try {
|
|
188
|
+
const packageJson = JSON.parse(yield readFileAsync(packageJsonPath, "utf-8"));
|
|
189
|
+
const dependencies = Object.assign(Object.assign({}, packageJson.dependencies), packageJson.devDependencies);
|
|
190
|
+
for (const [dep, version] of Object.entries(dependencies)) {
|
|
191
|
+
const depPath = path_1.default.join(nodeModulesPath, dep);
|
|
192
|
+
try {
|
|
193
|
+
yield accessAsync(depPath, fs_1.default.constants.F_OK);
|
|
194
|
+
}
|
|
195
|
+
catch (_a) {
|
|
196
|
+
errors.push(`依赖缺失: ${dep}@${version}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
if (error instanceof Error) {
|
|
202
|
+
errors.push(`依赖检查失败: ${error.message}`);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
errors.push("依赖检查失败: 未知错误");
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return errors;
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
exports.FunProjectValidator = FunProjectValidator;
|