@cloudbase/manager-node 5.7.1-beta.0 → 5.8.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.
Files changed (62) hide show
  1. package/lib/cloudApp/index.js +12 -3
  2. package/lib/deploy/DatabaseDeployer.js +238 -0
  3. package/lib/deploy/DeployOrchestrator.js +645 -0
  4. package/lib/deploy/FunctionDeployer.js +611 -0
  5. package/lib/deploy/GatewayDeployer.js +612 -0
  6. package/lib/deploy/StateStore.js +327 -0
  7. package/lib/deploy/StaticDeployer.js +236 -0
  8. package/lib/deploy/domain.js +41 -0
  9. package/lib/deploy/framework.js +230 -0
  10. package/lib/deploy/function/artifact.js +207 -0
  11. package/lib/deploy/function/builders/cloud.js +654 -0
  12. package/lib/deploy/function/cam-preflight.js +157 -0
  13. package/lib/deploy/function/cloud-build-service.js +191 -0
  14. package/lib/deploy/function/config-guard.js +595 -0
  15. package/lib/deploy/function/docker-preflight.js +87 -0
  16. package/lib/deploy/function/image-preflight.js +164 -0
  17. package/lib/deploy/function/local-builder.js +129 -0
  18. package/lib/deploy/function/planner.js +278 -0
  19. package/lib/deploy/function/preflight.js +329 -0
  20. package/lib/deploy/types.js +103 -0
  21. package/lib/env/index.js +0 -9
  22. package/lib/environment.js +11 -0
  23. package/lib/function/index.js +1 -1
  24. package/lib/hosting/index.js +4 -1
  25. package/lib/index.js +31 -0
  26. package/lib/projectValidator/index.js +452 -0
  27. package/lib/projectValidator/types.js +2 -0
  28. package/lib/storage/index.js +6 -7
  29. package/lib/utils/index.js +62 -5
  30. package/package.json +2 -1
  31. package/types/cloudApp/index.d.ts +4 -0
  32. package/types/cloudApp/types.d.ts +87 -6
  33. package/types/deploy/DatabaseDeployer.d.ts +70 -0
  34. package/types/deploy/DeployOrchestrator.d.ts +170 -0
  35. package/types/deploy/FunctionDeployer.d.ts +159 -0
  36. package/types/deploy/GatewayDeployer.d.ts +194 -0
  37. package/types/deploy/StateStore.d.ts +170 -0
  38. package/types/deploy/StaticDeployer.d.ts +97 -0
  39. package/types/deploy/domain.d.ts +17 -0
  40. package/types/deploy/framework.d.ts +63 -0
  41. package/types/deploy/function/artifact.d.ts +40 -0
  42. package/types/deploy/function/builders/cloud.d.ts +110 -0
  43. package/types/deploy/function/cam-preflight.d.ts +51 -0
  44. package/types/deploy/function/cloud-build-service.d.ts +10 -0
  45. package/types/deploy/function/config-guard.d.ts +41 -0
  46. package/types/deploy/function/docker-preflight.d.ts +17 -0
  47. package/types/deploy/function/image-preflight.d.ts +21 -0
  48. package/types/deploy/function/local-builder.d.ts +26 -0
  49. package/types/deploy/function/planner.d.ts +15 -0
  50. package/types/deploy/function/preflight.d.ts +9 -0
  51. package/types/deploy/types.d.ts +429 -0
  52. package/types/env/index.d.ts +1 -8
  53. package/types/env/type.d.ts +6 -97
  54. package/types/environment.d.ts +7 -0
  55. package/types/function/types.d.ts +15 -0
  56. package/types/hosting/index.d.ts +6 -0
  57. package/types/index.d.ts +18 -0
  58. package/types/interfaces/function.interface.d.ts +1 -1
  59. package/types/projectValidator/index.d.ts +38 -0
  60. package/types/projectValidator/types.d.ts +26 -0
  61. package/types/storage/index.d.ts +6 -0
  62. package/types/utils/index.d.ts +13 -0
@@ -0,0 +1,452 @@
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, warnings);
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, warnings = []) {
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
+ this.validateGatewayPathRewriteConflicts(config, warnings);
286
+ this.validateHostingSubPathBaseRisks(config, warnings);
287
+ }
288
+ /**
289
+ * 检测 hosting 路由中 enablePathTransmission=true 与自动生成 pathRewrite 的冲突
290
+ *
291
+ * hosting 目标未显式配置 pathRewrite 时,部署会自动生成 PathRewrite.Prefix = hosting.deployPath。
292
+ * 若同时显式开启 enablePathTransmission=true,路径透传会覆盖路径重写,导致请求打到错误的托管路径(404)。
293
+ */
294
+ validateGatewayPathRewriteConflicts(config, warnings) {
295
+ var _a;
296
+ const hostingMap = new Map((config.hosting || []).map((h) => [h.name, h]));
297
+ (((_a = config.gateway) === null || _a === void 0 ? void 0 : _a.routes) || []).forEach((route, index) => {
298
+ var _a, _b, _c;
299
+ if (!((_a = route.target) === null || _a === void 0 ? void 0 : _a.startsWith('hosting:')))
300
+ return;
301
+ // 用户显式配置了 pathRewrite → 无自动重写,无冲突
302
+ if (((_b = route.pathRewrite) === null || _b === void 0 ? void 0 : _b.prefix) || ((_c = route.pathRewrite) === null || _c === void 0 ? void 0 : _c.staticStorePrefix))
303
+ return;
304
+ // 未显式开启透传 → 无冲突
305
+ if (route.enablePathTransmission !== true)
306
+ return;
307
+ const hostingName = route.target.slice('hosting:'.length);
308
+ const hosting = hostingMap.get(hostingName);
309
+ // hosting 无 deployPath → 不会自动生成 PathRewrite,不冲突
310
+ if (!(hosting === null || hosting === void 0 ? void 0 : hosting.deployPath))
311
+ return;
312
+ warnings.push({
313
+ level: 'warning',
314
+ resource: `gateway.routes[${index}]`,
315
+ field: 'enablePathTransmission',
316
+ message: `hosting 路由 ${route.target} 未显式配置 pathRewrite,部署将自动生成 ` +
317
+ `PathRewrite.Prefix=${hosting.deployPath},但 enablePathTransmission=true 会覆盖路径重写,` +
318
+ `导致请求打不到托管实际部署路径`,
319
+ fix: '删除 enablePathTransmission,让自动生成的 pathRewrite 生效;' +
320
+ '或显式配置 pathRewrite.prefix 指向托管部署路径',
321
+ });
322
+ });
323
+ }
324
+ /**
325
+ * 子路径部署(deployPath !== '/')下,前端构建产物若仍使用根路径 base(如 Vite 默认 '/'),
326
+ * 会导致资源请求落到 /assets/* 而非 /{deployPath}/assets/*,出现 404/白屏。
327
+ *
328
+ * 这里在“可能存在构建步骤”的 hosting 配置上给出防坑告警。
329
+ */
330
+ validateHostingSubPathBaseRisks(config, warnings) {
331
+ ;
332
+ (config.hosting || []).forEach((hosting, index) => {
333
+ const deployPath = typeof hosting.deployPath === 'string' ? hosting.deployPath.trim() : '';
334
+ if (!deployPath || deployPath === '/')
335
+ return;
336
+ const framework = typeof hosting.framework === 'string' ? hosting.framework.trim().toLowerCase() : '';
337
+ const hasBuildStep = (typeof hosting.buildCommand === 'string' && hosting.buildCommand.trim().length > 0) ||
338
+ (!!framework && framework !== 'static' && framework !== 'custom');
339
+ if (!hasBuildStep)
340
+ return;
341
+ warnings.push({
342
+ level: 'warning',
343
+ resource: `hosting[${index}]`,
344
+ field: 'deployPath',
345
+ message: `静态托管部署在子路径 ${deployPath},请确认前端构建 base 与子路径匹配,` +
346
+ `否则资源可能请求到根路径(如 /assets/*)导致 404/白屏`,
347
+ fix: "建议将前端 base 配置为 './' 或与 deployPath 对齐(例如 '/serviceName/')",
348
+ });
349
+ });
350
+ }
351
+ validateUniqueNames(resource, items, errors) {
352
+ const names = new Map();
353
+ items.forEach((item, index) => {
354
+ if (!item.name)
355
+ return;
356
+ const prev = names.get(item.name);
357
+ if (typeof prev === 'number') {
358
+ errors.push({
359
+ level: 'error',
360
+ resource: `${resource}[${index}]`,
361
+ field: 'name',
362
+ message: `${resource} 名称重复:${item.name}`,
363
+ fix: `修改 ${resource}[${index}].name,避免与 ${resource}[${prev}].name 重复`,
364
+ });
365
+ }
366
+ else {
367
+ names.set(item.name, index);
368
+ }
369
+ });
370
+ }
371
+ validateUniqueField(resource, items, options) {
372
+ const { field, errors } = options;
373
+ const values = new Map();
374
+ items.forEach((item, index) => {
375
+ const value = item[field];
376
+ if (!value)
377
+ return;
378
+ const prev = values.get(value);
379
+ if (typeof prev === 'number') {
380
+ errors.push({
381
+ level: 'error',
382
+ resource: `${resource}[${index}]`,
383
+ field,
384
+ message: `${resource}.${field} 配置重复:${value}`,
385
+ fix: `修改 ${resource}[${index}].${field},避免与 ${resource}[${prev}].${field} 重复`,
386
+ });
387
+ }
388
+ else {
389
+ values.set(value, index);
390
+ }
391
+ });
392
+ }
393
+ validateGatewayTargets(config, errors) {
394
+ var _a;
395
+ const functionNames = new Set((config.functions || []).map((item) => item.name));
396
+ const hostingNames = new Set((config.hosting || []).map((item) => item.name));
397
+ (((_a = config.gateway) === null || _a === void 0 ? void 0 : _a.routes) || []).forEach((route, index) => {
398
+ // 与 validateGatewayPathRewriteConflicts 的可选链防护保持一致:
399
+ // 先校验 target 存在且为字符串,畸形配置优雅收集错误而非抛 TypeError 中断整个 validate()
400
+ if (typeof route.target !== 'string' || !route.target) {
401
+ errors.push({
402
+ level: 'error',
403
+ resource: `gateway.routes[${index}]`,
404
+ field: 'target',
405
+ message: '网关目标 target 缺失或非法',
406
+ fix: '为 gateway.routes[].target 配置目标资源,格式为 function:<名称> 或 hosting:<名称>',
407
+ });
408
+ return;
409
+ }
410
+ const [type, name] = route.target.split(':');
411
+ if ((type === 'function' && !functionNames.has(name)) || (type === 'hosting' && !hostingNames.has(name))) {
412
+ errors.push({
413
+ level: 'error',
414
+ resource: `gateway.routes[${index}]`,
415
+ field: 'target',
416
+ message: `网关目标不存在:${route.target}`,
417
+ fix: '确认 gateway.routes[].target 引用了已声明的 function 或 hosting 资源',
418
+ });
419
+ }
420
+ });
421
+ }
422
+ getSummary(config) {
423
+ var _a, _b, _c, _d;
424
+ return {
425
+ functions: ((_a = config === null || config === void 0 ? void 0 : config.functions) === null || _a === void 0 ? void 0 : _a.length) || 0,
426
+ hosting: ((_b = config === null || config === void 0 ? void 0 : config.hosting) === null || _b === void 0 ? void 0 : _b.length) || 0,
427
+ databases: (config === null || config === void 0 ? void 0 : config.database) ? 1 : 0,
428
+ 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,
429
+ };
430
+ }
431
+ getLinkedEnvId(cwd) {
432
+ const projectPath = path_1.default.join(cwd, '.tcb', 'project.json');
433
+ if (!fs_1.default.existsSync(projectPath)) {
434
+ return '';
435
+ }
436
+ try {
437
+ const project = JSON.parse(fs_1.default.readFileSync(projectPath, 'utf8'));
438
+ return project.envId || project.EnvId || '';
439
+ }
440
+ catch (e) {
441
+ return '';
442
+ }
443
+ }
444
+ normalizeSchemaPath(dataPath) {
445
+ return dataPath.replace(/^\//, '').replace(/\//g, '.');
446
+ }
447
+ getErrorMessage(error) {
448
+ return (error === null || error === void 0 ? void 0 : error.message) || String(error);
449
+ }
450
+ }
451
+ exports.ProjectValidator = ProjectValidator;
452
+ __exportStar(require("./types"), exports);
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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
- ErrorDocument: {
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 = [];
@@ -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,18 +48,53 @@ __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
+ // 幂等清理:archiver 的 error 事件与 output.close 事件在不同代码路径上,
62
+ // 但 extraEntries 写入异常分支调用 archive.abort() 后的异步收尾流程
63
+ // 仍可能间接触发 archive.error,导致 cleanupExtra 被多次调用。
64
+ // 用 done 标志保证清理和 resolve/reject 仅生效一次。
65
+ let done = false;
66
+ const settle = (fn) => {
67
+ if (done)
68
+ return;
69
+ done = true;
70
+ fn();
71
+ };
72
+ const cleanupExtra = () => {
73
+ if (extraTmpDir) {
74
+ try {
75
+ fs_extra_1.default.removeSync(extraTmpDir);
76
+ }
77
+ catch (_a) {
78
+ // 清理临时目录失败不影响主流程
79
+ }
80
+ }
81
+ };
52
82
  const output = fs_extra_1.default.createWriteStream(outputPath);
53
83
  const archive = (0, archiver_1.default)('zip');
54
84
  output.on('close', function () {
55
- resolve({
56
- zipPath: outputPath,
57
- size: Math.ceil(archive.pointer() / 1024)
85
+ settle(() => {
86
+ cleanupExtra();
87
+ resolve({
88
+ zipPath: outputPath,
89
+ size: Math.ceil(archive.pointer() / 1024)
90
+ });
58
91
  });
59
92
  });
60
93
  archive.on('error', function (err) {
61
- reject(err);
94
+ settle(() => {
95
+ cleanupExtra();
96
+ reject(err);
97
+ });
62
98
  });
63
99
  archive.pipe(output);
64
100
  // append files from a glob pattern
@@ -68,6 +104,27 @@ async function compressToZip(option) {
68
104
  ignore: ignore,
69
105
  dot: true
70
106
  });
107
+ // 追加 SDK 注入的额外文件(如构建推送脚本),不污染用户源码目录
108
+ if (extraEntries && extraEntries.length > 0 && extraTmpDir) {
109
+ try {
110
+ extraEntries.forEach((entry, index) => {
111
+ const tmpFile = path_1.default.join(extraTmpDir, `entry-${index}`);
112
+ const buf = Buffer.isBuffer(entry.content)
113
+ ? entry.content
114
+ : Buffer.from(entry.content, 'utf8');
115
+ fs_extra_1.default.writeFileSync(tmpFile, buf, { mode: entry.mode });
116
+ archive.file(tmpFile, { name: entry.zipPath, mode: entry.mode });
117
+ });
118
+ }
119
+ catch (error) {
120
+ settle(() => {
121
+ cleanupExtra();
122
+ archive.abort();
123
+ reject(error);
124
+ });
125
+ return;
126
+ }
127
+ }
71
128
  archive.finalize();
72
129
  });
73
130
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudbase/manager-node",
3
- "version": "5.7.1-beta.0",
3
+ "version": "5.8.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>;