@cloudbase/manager-node 5.6.7 → 5.7.0-beta.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/CHANGELOG.md +6 -0
- package/lib/cloudApp/index.js +12 -3
- package/lib/deploy/DatabaseDeployer.js +238 -0
- package/lib/deploy/DeployOrchestrator.js +564 -0
- package/lib/deploy/FunctionDeployer.js +611 -0
- package/lib/deploy/GatewayDeployer.js +522 -0
- package/lib/deploy/StateStore.js +327 -0
- package/lib/deploy/StaticDeployer.js +289 -0
- package/lib/deploy/domain.js +39 -0
- package/lib/deploy/framework.js +105 -0
- package/lib/deploy/function/artifact.js +207 -0
- package/lib/deploy/function/builders/cloud.js +454 -0
- package/lib/deploy/function/cam-preflight.js +157 -0
- package/lib/deploy/function/cloud-build-service.js +170 -0
- package/lib/deploy/function/config-guard.js +588 -0
- package/lib/deploy/function/docker-preflight.js +87 -0
- package/lib/deploy/function/image-preflight.js +164 -0
- package/lib/deploy/function/local-builder.js +110 -0
- package/lib/deploy/function/planner.js +282 -0
- package/lib/deploy/function/preflight.js +264 -0
- package/lib/deploy/types.js +99 -0
- package/lib/environment.js +11 -0
- package/lib/hosting/index.js +4 -1
- package/lib/index.js +31 -0
- package/lib/projectValidator/index.js +375 -0
- package/lib/projectValidator/types.js +2 -0
- package/lib/storage/index.js +6 -7
- package/lib/utils/index.js +41 -1
- package/package.json +2 -1
- package/types/cloudApp/index.d.ts +4 -0
- package/types/cloudApp/types.d.ts +87 -6
- package/types/deploy/DatabaseDeployer.d.ts +70 -0
- package/types/deploy/DeployOrchestrator.d.ts +150 -0
- package/types/deploy/FunctionDeployer.d.ts +158 -0
- package/types/deploy/GatewayDeployer.d.ts +175 -0
- package/types/deploy/StateStore.d.ts +170 -0
- package/types/deploy/StaticDeployer.d.ts +104 -0
- package/types/deploy/domain.d.ts +17 -0
- package/types/deploy/framework.d.ts +23 -0
- package/types/deploy/function/artifact.d.ts +40 -0
- package/types/deploy/function/builders/cloud.d.ts +110 -0
- package/types/deploy/function/cam-preflight.d.ts +51 -0
- package/types/deploy/function/cloud-build-service.d.ts +10 -0
- package/types/deploy/function/config-guard.d.ts +41 -0
- package/types/deploy/function/docker-preflight.d.ts +17 -0
- package/types/deploy/function/image-preflight.d.ts +21 -0
- package/types/deploy/function/local-builder.d.ts +26 -0
- package/types/deploy/function/planner.d.ts +15 -0
- package/types/deploy/function/preflight.d.ts +9 -0
- package/types/deploy/types.d.ts +429 -0
- package/types/env/type.d.ts +4 -4
- package/types/environment.d.ts +7 -0
- package/types/function/types.d.ts +15 -0
- package/types/hosting/index.d.ts +6 -0
- package/types/index.d.ts +18 -0
- package/types/interfaces/function.interface.d.ts +1 -1
- package/types/projectValidator/index.d.ts +24 -0
- package/types/projectValidator/types.d.ts +26 -0
- package/types/storage/index.d.ts +6 -0
- package/types/utils/index.d.ts +13 -0
|
@@ -0,0 +1,375 @@
|
|
|
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
17
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.ProjectValidator = void 0;
|
|
21
|
+
const fs_1 = __importDefault(require("fs"));
|
|
22
|
+
const path_1 = __importDefault(require("path"));
|
|
23
|
+
const toolbox_1 = require("@cloudbase/toolbox");
|
|
24
|
+
class ProjectValidator {
|
|
25
|
+
async validate(options = {}) {
|
|
26
|
+
var _a;
|
|
27
|
+
const cwd = path_1.default.resolve(options.cwd || process.cwd());
|
|
28
|
+
const errors = [];
|
|
29
|
+
const warnings = [];
|
|
30
|
+
const loaded = await this.loadProjectConfig(cwd, {
|
|
31
|
+
configPath: options.configPath,
|
|
32
|
+
mode: options.mode,
|
|
33
|
+
errors,
|
|
34
|
+
});
|
|
35
|
+
const version = String(((_a = loaded.rawConfig) === null || _a === void 0 ? void 0 : _a.version) || '2.1');
|
|
36
|
+
if (loaded.rawConfig) {
|
|
37
|
+
this.validateSchema(loaded.rawConfig, errors);
|
|
38
|
+
}
|
|
39
|
+
if (loaded.config) {
|
|
40
|
+
this.validateEnvId(cwd, { raw: loaded.rawConfig || {}, resolved: loaded.config }, errors);
|
|
41
|
+
this.validatePersonalRegistryConfig(loaded.config, errors);
|
|
42
|
+
const resourceIssues = this.validateResources(cwd, loaded.config);
|
|
43
|
+
errors.push(...resourceIssues.errors);
|
|
44
|
+
warnings.push(...resourceIssues.warnings);
|
|
45
|
+
this.validateConsistency(loaded.config, errors);
|
|
46
|
+
}
|
|
47
|
+
const summary = this.getSummary(loaded.config);
|
|
48
|
+
return {
|
|
49
|
+
valid: errors.length === 0,
|
|
50
|
+
version,
|
|
51
|
+
errors,
|
|
52
|
+
warnings,
|
|
53
|
+
summary,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
async loadProjectConfig(cwd, options) {
|
|
57
|
+
const { configPath, mode, errors } = options;
|
|
58
|
+
let filepath = configPath ? path_1.default.resolve(cwd, configPath) : undefined;
|
|
59
|
+
try {
|
|
60
|
+
if (!filepath) {
|
|
61
|
+
const result = await (0, toolbox_1.searchConfig)(cwd);
|
|
62
|
+
filepath = result === null || result === void 0 ? void 0 : result.filepath;
|
|
63
|
+
}
|
|
64
|
+
if (!filepath) {
|
|
65
|
+
errors.push({
|
|
66
|
+
level: 'error',
|
|
67
|
+
field: 'cloudbaserc',
|
|
68
|
+
message: '未找到 cloudbaserc 配置文件',
|
|
69
|
+
fix: '在项目根目录新增 cloudbaserc.json / cloudbaserc.js / cloudbaserc.yaml / cloudbaserc.yml',
|
|
70
|
+
});
|
|
71
|
+
return {};
|
|
72
|
+
}
|
|
73
|
+
const rawConfig = (await (0, toolbox_1.loadConfig)({ configPath: filepath }));
|
|
74
|
+
const config = (await toolbox_1.ConfigParser.parseRawConfig(rawConfig || {}, cwd, {
|
|
75
|
+
mode,
|
|
76
|
+
}));
|
|
77
|
+
return {
|
|
78
|
+
filepath,
|
|
79
|
+
rawConfig,
|
|
80
|
+
config,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
catch (e) {
|
|
84
|
+
errors.push({
|
|
85
|
+
level: 'error',
|
|
86
|
+
field: 'cloudbaserc',
|
|
87
|
+
message: `配置文件解析失败:${this.getErrorMessage(e)}`,
|
|
88
|
+
fix: '检查 cloudbaserc 配置文件语法是否正确',
|
|
89
|
+
});
|
|
90
|
+
return { filepath };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
validateSchema(config, errors) {
|
|
94
|
+
const result = (0, toolbox_1.validateCloudBaseConfigBySchema)(config, { version: '2.1' });
|
|
95
|
+
result.errors.forEach((error) => {
|
|
96
|
+
errors.push({
|
|
97
|
+
level: 'error',
|
|
98
|
+
field: this.normalizeSchemaPath(error.dataPath),
|
|
99
|
+
message: `配置不符合 v2.1 Schema:${error.message}`,
|
|
100
|
+
fix: `请检查 ${this.normalizeSchemaPath(error.dataPath) || 'cloudbaserc'} 字段`,
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
validateEnvId(cwd, config, errors) {
|
|
105
|
+
if (this.hasPersonalCloudFunction(config.resolved)) {
|
|
106
|
+
if (typeof config.raw.envId !== 'string' || !config.raw.envId.trim()) {
|
|
107
|
+
errors.push({
|
|
108
|
+
level: 'error',
|
|
109
|
+
field: 'envId',
|
|
110
|
+
message: '个人版 TCR 云端构建必须在 cloudbaserc 中显式声明 envId',
|
|
111
|
+
fix: '声明 envId: "{{env.ENV_ID}}",并在 .env* 中设置 ENV_ID',
|
|
112
|
+
});
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (typeof config.resolved.envId !== 'string' || !config.resolved.envId.trim()) {
|
|
116
|
+
errors.push({
|
|
117
|
+
level: 'error',
|
|
118
|
+
field: 'envId',
|
|
119
|
+
message: '个人版 TCR 云端构建的 envId 未赋值',
|
|
120
|
+
fix: '在 .env* 中设置 cloudbaserc 通过 {{env.ENV_ID}} 引用的 ENV_ID',
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (config.resolved.envId || this.getLinkedEnvId(cwd)) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
errors.push({
|
|
129
|
+
level: 'error',
|
|
130
|
+
field: 'envId',
|
|
131
|
+
message: '缺少 envId,且未检测到 .tcb/project.json 关联环境',
|
|
132
|
+
fix: '在 cloudbaserc 中配置 envId,或先执行环境关联流程',
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* 个人版 TCR 的 username/password/namespace 必须在 cloudbaserc 中显式声明。
|
|
137
|
+
* ConfigParser 已先解析 {{env.*}},因此这里同时承担缺失 env 值的前置校验。
|
|
138
|
+
*/
|
|
139
|
+
validatePersonalRegistryConfig(config, errors) {
|
|
140
|
+
;
|
|
141
|
+
(config.functions || []).forEach((func, index) => {
|
|
142
|
+
var _a;
|
|
143
|
+
if (!this.isPersonalCloudFunction(func)) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const build = (_a = func.imageConfig) === null || _a === void 0 ? void 0 : _a.build;
|
|
147
|
+
const namespace = build === null || build === void 0 ? void 0 : build.namespace;
|
|
148
|
+
if (typeof namespace !== 'string' || !namespace.trim()) {
|
|
149
|
+
errors.push({
|
|
150
|
+
level: 'error',
|
|
151
|
+
resource: `functions[${index}]`,
|
|
152
|
+
field: 'imageConfig.build.namespace',
|
|
153
|
+
message: '个人版 TCR 缺少 namespace,或对应环境变量未赋值',
|
|
154
|
+
fix: '声明 namespace: "{{env.TCB_TCR_NAMESPACE}}",并在 .env* 中设置该变量',
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
const credential = build === null || build === void 0 ? void 0 : build.registryCredential;
|
|
158
|
+
if (!credential || typeof credential !== 'object') {
|
|
159
|
+
errors.push({
|
|
160
|
+
level: 'error',
|
|
161
|
+
resource: `functions[${index}]`,
|
|
162
|
+
field: 'imageConfig.build.registryCredential',
|
|
163
|
+
message: '个人版 TCR 缺少 username/password 凭证声明',
|
|
164
|
+
fix: '在 registryCredential 中使用 {{env.TCB_TCR_USERNAME}} 与 {{env.TCB_TCR_PASSWORD}}',
|
|
165
|
+
});
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const { username, password } = credential;
|
|
169
|
+
if (typeof username !== 'string' || !/^\d{5,20}$/.test(username.trim())) {
|
|
170
|
+
errors.push({
|
|
171
|
+
level: 'error',
|
|
172
|
+
resource: `functions[${index}]`,
|
|
173
|
+
field: 'imageConfig.build.registryCredential.username',
|
|
174
|
+
message: '个人版 TCR username 未赋值或不是有效腾讯云账号 ID',
|
|
175
|
+
fix: '声明 username: "{{env.TCB_TCR_USERNAME}}",并在 .env* 中设置该变量',
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
if (typeof password !== 'string' || !password || password.length > 16 * 1024) {
|
|
179
|
+
errors.push({
|
|
180
|
+
level: 'error',
|
|
181
|
+
resource: `functions[${index}]`,
|
|
182
|
+
field: 'imageConfig.build.registryCredential.password',
|
|
183
|
+
message: '个人版 TCR password 未赋值或长度不合法',
|
|
184
|
+
fix: '声明 password: "{{env.TCB_TCR_PASSWORD}}",并在 .env* 中设置该变量',
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
hasPersonalCloudFunction(config) {
|
|
190
|
+
return (config.functions || []).some((func) => this.isPersonalCloudFunction(func));
|
|
191
|
+
}
|
|
192
|
+
isPersonalCloudFunction(func) {
|
|
193
|
+
var _a;
|
|
194
|
+
return (func.type === 'HTTP' &&
|
|
195
|
+
func.buildStrategy === 'cloud' &&
|
|
196
|
+
((_a = func.imageConfig) === null || _a === void 0 ? void 0 : _a.imageType) === 'personal');
|
|
197
|
+
}
|
|
198
|
+
validateResources(cwd, config) {
|
|
199
|
+
const errors = [];
|
|
200
|
+
const warnings = [];
|
|
201
|
+
const functionRoot = config.functionRoot || './functions';
|
|
202
|
+
(config.functions || []).forEach((func, index) => {
|
|
203
|
+
const resource = `functions[${index}]`;
|
|
204
|
+
const functionDir = path_1.default.resolve(cwd, func.dir || path_1.default.join(functionRoot, func.name));
|
|
205
|
+
if (!fs_1.default.existsSync(functionDir)) {
|
|
206
|
+
errors.push({
|
|
207
|
+
level: 'error',
|
|
208
|
+
resource,
|
|
209
|
+
field: 'dir',
|
|
210
|
+
message: `函数目录不存在:${path_1.default.relative(cwd, functionDir)}`,
|
|
211
|
+
fix: '创建函数目录,或修正 functions[].dir / functionRoot 配置',
|
|
212
|
+
});
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const functionType = func.type || 'Event';
|
|
216
|
+
const buildStrategy = func.buildStrategy || 'zip';
|
|
217
|
+
if (functionType === 'HTTP' && buildStrategy === 'zip') {
|
|
218
|
+
const bootstrapPath = path_1.default.join(functionDir, 'scf_bootstrap');
|
|
219
|
+
if (!fs_1.default.existsSync(bootstrapPath)) {
|
|
220
|
+
errors.push({
|
|
221
|
+
level: 'error',
|
|
222
|
+
resource,
|
|
223
|
+
field: 'scf_bootstrap',
|
|
224
|
+
message: '缺少 scf_bootstrap 文件(HTTP 函数 zip 部署必须)',
|
|
225
|
+
fix: '在函数目录中新增 scf_bootstrap,并确保其具备执行权限',
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (functionType === 'Event') {
|
|
230
|
+
const handlerFile = `${(func.handler || 'index.main').split('.')[0]}.js`;
|
|
231
|
+
if (!fs_1.default.existsSync(path_1.default.join(functionDir, handlerFile))) {
|
|
232
|
+
warnings.push({
|
|
233
|
+
level: 'warning',
|
|
234
|
+
resource,
|
|
235
|
+
field: 'handler',
|
|
236
|
+
message: `未找到入口文件 ${handlerFile}`,
|
|
237
|
+
fix: '确认 handler 配置与函数入口文件一致',
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
(config.hosting || []).forEach((hosting, index) => {
|
|
243
|
+
const root = path_1.default.resolve(cwd, hosting.root || '.');
|
|
244
|
+
if (!fs_1.default.existsSync(root)) {
|
|
245
|
+
errors.push({
|
|
246
|
+
level: 'error',
|
|
247
|
+
resource: `hosting[${index}]`,
|
|
248
|
+
field: 'root',
|
|
249
|
+
message: `静态托管根目录不存在:${path_1.default.relative(cwd, root)}`,
|
|
250
|
+
fix: '创建目录,或修正 hosting[].root 配置',
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
if (config.database) {
|
|
255
|
+
const migrations = config.database.migrations || './cloudbase/migrations';
|
|
256
|
+
if (path_1.default.isAbsolute(migrations) || migrations.startsWith('/')) {
|
|
257
|
+
errors.push({
|
|
258
|
+
level: 'error',
|
|
259
|
+
resource: 'database',
|
|
260
|
+
field: 'migrations',
|
|
261
|
+
message: 'database.migrations 仅支持相对路径',
|
|
262
|
+
fix: '请使用相对路径,例如 ./cloudbase/migrations',
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
const migrationsDir = path_1.default.resolve(cwd, migrations);
|
|
267
|
+
if (!fs_1.default.existsSync(migrationsDir)) {
|
|
268
|
+
errors.push({
|
|
269
|
+
level: 'error',
|
|
270
|
+
resource: 'database',
|
|
271
|
+
field: 'migrations',
|
|
272
|
+
message: `数据库迁移目录不存在:${path_1.default.relative(cwd, migrationsDir)}`,
|
|
273
|
+
fix: '创建迁移目录,或修正 database.migrations 配置',
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return { errors, warnings };
|
|
279
|
+
}
|
|
280
|
+
validateConsistency(config, errors) {
|
|
281
|
+
this.validateUniqueNames('functions', config.functions || [], errors);
|
|
282
|
+
this.validateUniqueNames('hosting', config.hosting || [], errors);
|
|
283
|
+
this.validateUniqueField('hosting', config.hosting || [], { field: 'deployPath', errors });
|
|
284
|
+
this.validateGatewayTargets(config, errors);
|
|
285
|
+
}
|
|
286
|
+
validateUniqueNames(resource, items, errors) {
|
|
287
|
+
const names = new Map();
|
|
288
|
+
items.forEach((item, index) => {
|
|
289
|
+
if (!item.name)
|
|
290
|
+
return;
|
|
291
|
+
const prev = names.get(item.name);
|
|
292
|
+
if (typeof prev === 'number') {
|
|
293
|
+
errors.push({
|
|
294
|
+
level: 'error',
|
|
295
|
+
resource: `${resource}[${index}]`,
|
|
296
|
+
field: 'name',
|
|
297
|
+
message: `${resource} 名称重复:${item.name}`,
|
|
298
|
+
fix: `修改 ${resource}[${index}].name,避免与 ${resource}[${prev}].name 重复`,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
names.set(item.name, index);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
validateUniqueField(resource, items, options) {
|
|
307
|
+
const { field, errors } = options;
|
|
308
|
+
const values = new Map();
|
|
309
|
+
items.forEach((item, index) => {
|
|
310
|
+
const value = item[field];
|
|
311
|
+
if (!value)
|
|
312
|
+
return;
|
|
313
|
+
const prev = values.get(value);
|
|
314
|
+
if (typeof prev === 'number') {
|
|
315
|
+
errors.push({
|
|
316
|
+
level: 'error',
|
|
317
|
+
resource: `${resource}[${index}]`,
|
|
318
|
+
field,
|
|
319
|
+
message: `${resource}.${field} 配置重复:${value}`,
|
|
320
|
+
fix: `修改 ${resource}[${index}].${field},避免与 ${resource}[${prev}].${field} 重复`,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
values.set(value, index);
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
validateGatewayTargets(config, errors) {
|
|
329
|
+
var _a;
|
|
330
|
+
const functionNames = new Set((config.functions || []).map((item) => item.name));
|
|
331
|
+
const hostingNames = new Set((config.hosting || []).map((item) => item.name));
|
|
332
|
+
(((_a = config.gateway) === null || _a === void 0 ? void 0 : _a.routes) || []).forEach((route, index) => {
|
|
333
|
+
const [type, name] = route.target.split(':');
|
|
334
|
+
if ((type === 'function' && !functionNames.has(name)) || (type === 'hosting' && !hostingNames.has(name))) {
|
|
335
|
+
errors.push({
|
|
336
|
+
level: 'error',
|
|
337
|
+
resource: `gateway.routes[${index}]`,
|
|
338
|
+
field: 'target',
|
|
339
|
+
message: `网关目标不存在:${route.target}`,
|
|
340
|
+
fix: '确认 gateway.routes[].target 引用了已声明的 function 或 hosting 资源',
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
getSummary(config) {
|
|
346
|
+
var _a, _b, _c, _d;
|
|
347
|
+
return {
|
|
348
|
+
functions: ((_a = config === null || config === void 0 ? void 0 : config.functions) === null || _a === void 0 ? void 0 : _a.length) || 0,
|
|
349
|
+
hosting: ((_b = config === null || config === void 0 ? void 0 : config.hosting) === null || _b === void 0 ? void 0 : _b.length) || 0,
|
|
350
|
+
databases: (config === null || config === void 0 ? void 0 : config.database) ? 1 : 0,
|
|
351
|
+
gateways: ((_d = (_c = config === null || config === void 0 ? void 0 : config.gateway) === null || _c === void 0 ? void 0 : _c.routes) === null || _d === void 0 ? void 0 : _d.length) || 0,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
getLinkedEnvId(cwd) {
|
|
355
|
+
const projectPath = path_1.default.join(cwd, '.tcb', 'project.json');
|
|
356
|
+
if (!fs_1.default.existsSync(projectPath)) {
|
|
357
|
+
return '';
|
|
358
|
+
}
|
|
359
|
+
try {
|
|
360
|
+
const project = JSON.parse(fs_1.default.readFileSync(projectPath, 'utf8'));
|
|
361
|
+
return project.envId || project.EnvId || '';
|
|
362
|
+
}
|
|
363
|
+
catch (e) {
|
|
364
|
+
return '';
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
normalizeSchemaPath(dataPath) {
|
|
368
|
+
return dataPath.replace(/^\//, '').replace(/\//g, '.');
|
|
369
|
+
}
|
|
370
|
+
getErrorMessage(error) {
|
|
371
|
+
return (error === null || error === void 0 ? void 0 : error.message) || String(error);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
exports.ProjectValidator = ProjectValidator;
|
|
375
|
+
__exportStar(require("./types"), exports);
|
package/lib/storage/index.js
CHANGED
|
@@ -1046,20 +1046,19 @@ class StorageService {
|
|
|
1046
1046
|
* 配置文档
|
|
1047
1047
|
*/
|
|
1048
1048
|
async putBucketWebsite(options) {
|
|
1049
|
-
const { indexDocument, errorDocument, bucket, region, routingRules } = options;
|
|
1049
|
+
const { indexDocument, errorDocument, originalHttpStatus, charity404, autoAddressing, bucket, region, routingRules } = options;
|
|
1050
1050
|
const cos = this.getCos();
|
|
1051
1051
|
const putBucketWebsite = util_1.default.promisify(cos.putBucketWebsite).bind(cos);
|
|
1052
1052
|
let params = {
|
|
1053
1053
|
Bucket: bucket,
|
|
1054
1054
|
Region: region,
|
|
1055
|
-
WebsiteConfiguration: {
|
|
1056
|
-
IndexDocument: {
|
|
1055
|
+
WebsiteConfiguration: Object.assign(Object.assign(Object.assign({}, (autoAddressing ? { AutoAddressing: { Status: autoAddressing } } : {})), { IndexDocument: {
|
|
1057
1056
|
Suffix: indexDocument
|
|
1058
|
-
},
|
|
1059
|
-
|
|
1060
|
-
Key: errorDocument
|
|
1057
|
+
} }), (errorDocument !== undefined
|
|
1058
|
+
? {
|
|
1059
|
+
ErrorDocument: Object.assign(Object.assign({ Key: errorDocument }, (originalHttpStatus ? { OriginalHttpStatus: originalHttpStatus } : {})), (charity404 ? { Charity404: charity404 } : {}))
|
|
1061
1060
|
}
|
|
1062
|
-
|
|
1061
|
+
: {}))
|
|
1063
1062
|
};
|
|
1064
1063
|
if (routingRules) {
|
|
1065
1064
|
params.WebsiteConfiguration.RoutingRules = [];
|
package/lib/utils/index.js
CHANGED
|
@@ -34,6 +34,7 @@ const archiver_1 = __importDefault(require("archiver"));
|
|
|
34
34
|
const crypto_1 = __importDefault(require("crypto"));
|
|
35
35
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
36
36
|
const lodash_1 = __importDefault(require("lodash"));
|
|
37
|
+
const os_1 = __importDefault(require("os"));
|
|
37
38
|
const path_1 = __importDefault(require("path"));
|
|
38
39
|
const unzipper_1 = __importDefault(require("unzipper"));
|
|
39
40
|
const constant_1 = require("../constant");
|
|
@@ -47,17 +48,37 @@ __exportStar(require("./http-request"), exports);
|
|
|
47
48
|
var uuid_1 = require("./uuid");
|
|
48
49
|
Object.defineProperty(exports, "guid6", { enumerable: true, get: function () { return uuid_1.guid6; } });
|
|
49
50
|
async function compressToZip(option) {
|
|
50
|
-
const { dirPath, outputPath, ignore, pattern = '**/*' } = option;
|
|
51
|
+
const { dirPath, outputPath, ignore, pattern = '**/*', extraEntries } = option;
|
|
52
|
+
// 额外注入的文件先落地到临时目录,用 archive.file() 追加,
|
|
53
|
+
// 使 archiver 正确计算 CRC/size(archiver 3.x 的 append(buffer/string)
|
|
54
|
+
// 会写出 CRC=0 的流式条目,被严格解压器判为 bad CRC)。
|
|
55
|
+
let extraTmpDir;
|
|
56
|
+
if (extraEntries && extraEntries.length > 0) {
|
|
57
|
+
extraTmpDir = path_1.default.join(os_1.default.tmpdir(), `zip-extra-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
58
|
+
fs_extra_1.default.mkdirpSync(extraTmpDir);
|
|
59
|
+
}
|
|
51
60
|
return new Promise((resolve, reject) => {
|
|
61
|
+
const cleanupExtra = () => {
|
|
62
|
+
if (extraTmpDir) {
|
|
63
|
+
try {
|
|
64
|
+
fs_extra_1.default.removeSync(extraTmpDir);
|
|
65
|
+
}
|
|
66
|
+
catch (_a) {
|
|
67
|
+
// 清理临时目录失败不影响主流程
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
52
71
|
const output = fs_extra_1.default.createWriteStream(outputPath);
|
|
53
72
|
const archive = (0, archiver_1.default)('zip');
|
|
54
73
|
output.on('close', function () {
|
|
74
|
+
cleanupExtra();
|
|
55
75
|
resolve({
|
|
56
76
|
zipPath: outputPath,
|
|
57
77
|
size: Math.ceil(archive.pointer() / 1024)
|
|
58
78
|
});
|
|
59
79
|
});
|
|
60
80
|
archive.on('error', function (err) {
|
|
81
|
+
cleanupExtra();
|
|
61
82
|
reject(err);
|
|
62
83
|
});
|
|
63
84
|
archive.pipe(output);
|
|
@@ -68,6 +89,25 @@ async function compressToZip(option) {
|
|
|
68
89
|
ignore: ignore,
|
|
69
90
|
dot: true
|
|
70
91
|
});
|
|
92
|
+
// 追加 SDK 注入的额外文件(如构建推送脚本),不污染用户源码目录
|
|
93
|
+
if (extraEntries && extraEntries.length > 0 && extraTmpDir) {
|
|
94
|
+
try {
|
|
95
|
+
extraEntries.forEach((entry, index) => {
|
|
96
|
+
const tmpFile = path_1.default.join(extraTmpDir, `entry-${index}`);
|
|
97
|
+
const buf = Buffer.isBuffer(entry.content)
|
|
98
|
+
? entry.content
|
|
99
|
+
: Buffer.from(entry.content, 'utf8');
|
|
100
|
+
fs_extra_1.default.writeFileSync(tmpFile, buf, { mode: entry.mode });
|
|
101
|
+
archive.file(tmpFile, { name: entry.zipPath, mode: entry.mode });
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
cleanupExtra();
|
|
106
|
+
archive.abort();
|
|
107
|
+
reject(error);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
71
111
|
archive.finalize();
|
|
72
112
|
});
|
|
73
113
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudbase/manager-node",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.7.0-beta.0",
|
|
4
4
|
"description": "The node manage service api for cloudbase.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@cloudbase/signature-nodejs": "^1.0.0",
|
|
45
|
+
"@cloudbase/toolbox": "^0.8.3-beta.0",
|
|
45
46
|
"@cloudbase/database": "^0.6.2",
|
|
46
47
|
"archiver": "^3.1.1",
|
|
47
48
|
"cos-nodejs-sdk-v5": "^2.14.0",
|
|
@@ -57,6 +57,10 @@ export declare class CloudAppService {
|
|
|
57
57
|
* 注意:此方法不依赖应用是否已创建,可在创建应用前调用
|
|
58
58
|
* 返回的 cosTimestamp 用于 createApp 时传入 staticConfig.cosTimestamp
|
|
59
59
|
*
|
|
60
|
+
* 内置默认忽略:.git 目录、.DS_Store(含任意层级);node_modules 仅在
|
|
61
|
+
* excludeNodeModules 为 true(默认)时排除——云端构建(需云端 install)场景应传
|
|
62
|
+
* excludeNodeModules: false 允许私有/离线依赖随源码包上传。
|
|
63
|
+
*
|
|
60
64
|
* @param params 上传参数
|
|
61
65
|
*/
|
|
62
66
|
uploadCode(params: IUploadCloudAppCodeParams): Promise<IUploadCloudAppCodeResult>;
|
|
@@ -10,19 +10,80 @@
|
|
|
10
10
|
* 部署类型
|
|
11
11
|
* - static-hosting: 静态托管部署
|
|
12
12
|
* - http-function: HTTP 云函数部署(预留)
|
|
13
|
+
* - custom: 自定义构建(CloudApp custom:源码 ZIP → 容器内执行 CustomSteps → docker build/push TCR),
|
|
14
|
+
* 用于 HTTP 函数镜像的云端构建(buildStrategy: cloud)
|
|
13
15
|
*/
|
|
14
|
-
export type DeployType = 'static-hosting' | 'http-function';
|
|
16
|
+
export type DeployType = 'static-hosting' | 'http-function' | 'custom';
|
|
15
17
|
/**
|
|
16
18
|
* 构建类型
|
|
17
19
|
* - GIT: 从 Git 仓库构建
|
|
18
20
|
* - ZIP: 从 ZIP 包构建
|
|
19
21
|
* - TEMPLATE: 从模板构建
|
|
22
|
+
* - zip: custom 构建的源码 ZIP(与 API 文档 BuildType 大小写保持一致)
|
|
20
23
|
*/
|
|
21
|
-
export type BuildType = 'GIT' | 'ZIP' | 'TEMPLATE';
|
|
24
|
+
export type BuildType = 'GIT' | 'ZIP' | 'TEMPLATE' | 'zip' | 'git';
|
|
25
|
+
/**
|
|
26
|
+
* custom 构建源码来源
|
|
27
|
+
*对应 API 的 Source 字段,zip 模式回填 DescribeCloudAppCosInfo 返回的时间戳
|
|
28
|
+
*/
|
|
29
|
+
export interface ICustomBuildSource {
|
|
30
|
+
/** 源码类型,当前实现为 zip */
|
|
31
|
+
type: 'zip' | 'git';
|
|
32
|
+
/** COS 时间戳,原样使用 DescribeCloudAppCosInfo 返回的 UnixTimestamp */
|
|
33
|
+
cosTimestamp?: string;
|
|
34
|
+
/** COS 文件后缀,默认 .zip */
|
|
35
|
+
cosSuffix?: string;
|
|
36
|
+
}
|
|
37
|
+
/** custom 构建环境变量项(明文,禁止写入敏感值) */
|
|
38
|
+
export interface ICustomBuildEnv {
|
|
39
|
+
key: string;
|
|
40
|
+
value: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* custom 构建密钥项
|
|
44
|
+
* CloudApp 在构建容器中按 $SECRET_{NAME} 注入,Value 不应进入日志或普通环境变量。
|
|
45
|
+
*/
|
|
46
|
+
export interface ICustomBuildSecret {
|
|
47
|
+
name: string;
|
|
48
|
+
value: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* custom 构建自定义步骤
|
|
52
|
+
* SDK 固定生成模板,用户不可控字段绝不作为任意 shell 片段拼接
|
|
53
|
+
*/
|
|
54
|
+
export interface ICustomBuildStep {
|
|
55
|
+
name: string;
|
|
56
|
+
command: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* custom 构建单步状态(API 响应,字段大小写与响应一致)
|
|
60
|
+
* 注意:Duration 为字符串、Status 为小写
|
|
61
|
+
*/
|
|
62
|
+
export interface CloudAppBuildStep {
|
|
63
|
+
Name: string;
|
|
64
|
+
Status: string;
|
|
65
|
+
Duration?: string;
|
|
66
|
+
/** 失败时的退出码,用于定位 */
|
|
67
|
+
ExitCode?: number;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* custom 构建产物(API 响应)
|
|
71
|
+
* 镜像构建成功后Type 通常为 image
|
|
72
|
+
*/
|
|
73
|
+
export interface CloudAppArtifact {
|
|
74
|
+
Type: string;
|
|
75
|
+
Name: string;
|
|
76
|
+
Status?: string;
|
|
77
|
+
/** 部分场景直接回传完整镜像地址 */
|
|
78
|
+
ImageUri?: string;
|
|
79
|
+
/** 部分场景回传 digest */
|
|
80
|
+
Digest?: string;
|
|
81
|
+
}
|
|
22
82
|
/**
|
|
23
83
|
* 构建状态
|
|
84
|
+
* custom 构建终态为 SUCCESS / FAILED / CANCELED
|
|
24
85
|
*/
|
|
25
|
-
export type BuildStatus = 'BUILDING' | 'SUCCESS' | 'FAILED';
|
|
86
|
+
export type BuildStatus = 'BUILDING' | 'SUCCESS' | 'FAILED' | 'CANCELED';
|
|
26
87
|
/**
|
|
27
88
|
* 构建命令配置(入参)
|
|
28
89
|
*/
|
|
@@ -149,14 +210,24 @@ export interface CloudAppVersion {
|
|
|
149
210
|
export interface ICreateCloudAppParams {
|
|
150
211
|
/** 环境 ID(可选,默认从 Environment 获取) */
|
|
151
212
|
envId?: string;
|
|
152
|
-
/** 部署类型:static-hosting / http-function */
|
|
213
|
+
/** 部署类型:static-hosting / http-function / custom */
|
|
153
214
|
deployType: DeployType;
|
|
154
215
|
/** 服务名称 */
|
|
155
216
|
serviceName: string;
|
|
156
|
-
/** 构建类型:GIT / ZIP / TEMPLATE */
|
|
217
|
+
/** 构建类型:GIT / ZIP / TEMPLATE / zip / git */
|
|
157
218
|
buildType?: BuildType;
|
|
158
219
|
/** 静态托管配置(deployType 为 static-hosting 时使用) */
|
|
159
220
|
staticConfig?: IStaticConfig;
|
|
221
|
+
/** custom 构建源码来源(deployType 为 custom 时使用) */
|
|
222
|
+
source?: ICustomBuildSource;
|
|
223
|
+
/** custom 构建环境变量(deployType 为 custom 时使用,禁止敏感值) */
|
|
224
|
+
env?: ICustomBuildEnv[];
|
|
225
|
+
/** custom 构建密钥(deployType 为 custom 时使用) */
|
|
226
|
+
secrets?: ICustomBuildSecret[];
|
|
227
|
+
/** custom 构建自定义步骤(deployType 为 custom 时使用,SDK 固定模板) */
|
|
228
|
+
customSteps?: ICustomBuildStep[];
|
|
229
|
+
/** 跳过云端构建(纯静态直传场景:产物已直传,仅写部署记录,对应 API SkipBuild) */
|
|
230
|
+
skipBuild?: boolean;
|
|
160
231
|
}
|
|
161
232
|
export interface ICreateCloudAppResult {
|
|
162
233
|
ServiceName: string;
|
|
@@ -232,6 +303,10 @@ export interface IDescribeCloudAppVersionResult {
|
|
|
232
303
|
Framework?: string;
|
|
233
304
|
StaticConfig?: CloudAppStaticConfig;
|
|
234
305
|
BuildTime?: string;
|
|
306
|
+
/** custom 构建的分步状态(deployType 为 custom 时返回) */
|
|
307
|
+
Steps?: CloudAppBuildStep[] | null;
|
|
308
|
+
/** custom 构建产物(deployType 为 custom 时返回,镜像信息优先从此获取) */
|
|
309
|
+
Artifacts?: CloudAppArtifact[] | null;
|
|
235
310
|
RequestId: string;
|
|
236
311
|
}
|
|
237
312
|
export interface IDescribeCloudAppVersionListParams {
|
|
@@ -324,8 +399,14 @@ export interface IUploadCloudAppCodeParams {
|
|
|
324
399
|
serviceName: string;
|
|
325
400
|
/** 本地文件夹路径 */
|
|
326
401
|
localPath: string;
|
|
327
|
-
/**
|
|
402
|
+
/** 忽略的文件/目录模式(追加到内置默认规则之后) */
|
|
328
403
|
ignore?: string[];
|
|
404
|
+
/**
|
|
405
|
+
* 是否排除 node_modules:
|
|
406
|
+
* 默认 true(静态产物 / 版本快照场景,避免误传大目录);
|
|
407
|
+
* 云端构建(需云端 install)场景设为 false,允许私有/离线依赖随源码包上传
|
|
408
|
+
*/
|
|
409
|
+
excludeNodeModules?: boolean;
|
|
329
410
|
/** 进度回调 */
|
|
330
411
|
onProgress?: (progress: IProgressData) => void;
|
|
331
412
|
/** 分步计时回调(各阶段完成时触发,用于 verbose 模式输出耗时) */
|