@cloudbase/manager-node 5.8.1 → 5.8.3

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 (34) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/lib/context.js +2 -1
  3. package/lib/database/index.js +11 -6
  4. package/lib/deploy/DeployOrchestrator.js +30 -16
  5. package/lib/deploy/FunctionDeployer.js +269 -39
  6. package/lib/deploy/StaticDeployer.js +7 -1
  7. package/lib/deploy/function/builders/cloud.js +45 -348
  8. package/lib/deploy/function/cloud-build-service.js +2 -16
  9. package/lib/deploy/function/config-guard.js +42 -32
  10. package/lib/deploy/function/enterprise-tcr.js +194 -0
  11. package/lib/deploy/function/image-tag.js +31 -0
  12. package/lib/deploy/function/local-builder.js +170 -25
  13. package/lib/deploy/function/personal-tcr.js +153 -0
  14. package/lib/deploy/types.js +11 -0
  15. package/lib/hosting/index.js +27 -0
  16. package/lib/index.js +4 -3
  17. package/lib/projectValidator/index.js +22 -20
  18. package/lib/utils/cloud-api-request.js +26 -2
  19. package/package.json +2 -2
  20. package/types/context.d.ts +17 -3
  21. package/types/database/index.d.ts +1 -0
  22. package/types/deploy/DeployOrchestrator.d.ts +10 -0
  23. package/types/deploy/FunctionDeployer.d.ts +20 -2
  24. package/types/deploy/StaticDeployer.d.ts +11 -0
  25. package/types/deploy/function/builders/cloud.d.ts +6 -39
  26. package/types/deploy/function/enterprise-tcr.d.ts +32 -0
  27. package/types/deploy/function/image-tag.d.ts +10 -0
  28. package/types/deploy/function/local-builder.d.ts +35 -6
  29. package/types/deploy/function/personal-tcr.d.ts +35 -0
  30. package/types/deploy/types.d.ts +34 -8
  31. package/types/hosting/index.d.ts +13 -0
  32. package/types/index.d.ts +5 -0
  33. package/types/projectValidator/index.d.ts +3 -2
  34. package/types/utils/cloud-api-request.d.ts +3 -2
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## @cloudbase/manager-node v5.8.2 版本发布 🚀(2026-08-25)
2
+
3
+ - 新增 support requestFn injection to bypass TC3 signing
4
+
5
+ ---
6
+
7
+ ## @cloudbase/manager-node v5.8.1 版本发布 🚀(2026-08-21)
8
+
9
+ - 新增 支持一致性发布
10
+
11
+ ---
12
+
1
13
  ## @cloudbase/manager-node v5.8.0 版本发布 🚀(2026-08-20)
2
14
 
3
15
  - 新增 声明式部署编排(hosting 本地构建/gateway 域名绑定/certId 自动查询)
package/lib/context.js CHANGED
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CloudBaseContext = void 0;
4
4
  const constant_1 = require("./constant");
5
5
  class CloudBaseContext {
6
- constructor({ secretId = '', secretKey = '', token = '', proxy = '', region = '', envType = '', useInternalEndpoint = undefined, internalEndpointRegion = undefined }) {
6
+ constructor({ secretId = '', secretKey = '', token = '', proxy = '', region = '', envType = '', useInternalEndpoint = undefined, internalEndpointRegion = undefined, requestFn = undefined }) {
7
7
  this.secretId = secretId;
8
8
  this.secretKey = secretKey;
9
9
  this.token = token;
@@ -12,6 +12,7 @@ class CloudBaseContext {
12
12
  this.envType = envType;
13
13
  this.useInternalEndpoint = useInternalEndpoint;
14
14
  this.internalEndpointRegion = internalEndpointRegion;
15
+ this.requestFn = requestFn;
15
16
  }
16
17
  isInternalEndpoint() {
17
18
  return this.useInternalEndpoint !== undefined ? this.useInternalEndpoint : constant_1.USE_INTERNAL_ENDPOINT;
@@ -40,17 +40,13 @@ class DatabaseService {
40
40
  return this.environment;
41
41
  }
42
42
  getDatabaseConfig() {
43
- const currEnv = this.environment;
44
- const { Databases } = currEnv.lazyEnvironmentConfig;
45
43
  return {
46
- Tag: Databases[0].InstanceId
44
+ Tag: this.getDefaultDatabaseInstanceId()
47
45
  };
48
46
  }
49
47
  getMongoConnector() {
50
- const currEnv = this.environment;
51
- const { Databases } = currEnv.lazyEnvironmentConfig;
52
48
  return {
53
- DatabaseName: Databases[0].InstanceId,
49
+ DatabaseName: this.getDefaultDatabaseInstanceId(),
54
50
  InstanceId: 'flexdb'
55
51
  };
56
52
  }
@@ -500,6 +496,15 @@ class DatabaseService {
500
496
  TaskId
501
497
  });
502
498
  }
499
+ getDefaultDatabaseInstanceId() {
500
+ var _a, _b, _c;
501
+ const currEnv = this.environment;
502
+ const instanceId = (_c = (_b = (_a = currEnv.lazyEnvironmentConfig) === null || _a === void 0 ? void 0 : _a.Databases) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.InstanceId;
503
+ if (!instanceId) {
504
+ throw new error_1.CloudBaseError('当前环境未开通文档型数据库');
505
+ }
506
+ return instanceId;
507
+ }
503
508
  }
504
509
  exports.DatabaseService = DatabaseService;
505
510
  DatabaseService.tcbServiceVersion = {
@@ -44,8 +44,8 @@ class DeployOrchestrator {
44
44
  * - database 失败始终强制中断(无论是否 continueOnError)
45
45
  */
46
46
  async deploy(options) {
47
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
48
- const { config, envId, dryRun, cwd = process.cwd(), log, concurrency = 1, continueOnError = false } = options;
47
+ var _a, _b, _c, _d, _e, _f, _g, _h;
48
+ const { config, envId, dryRun, cwd = process.cwd(), log, concurrency = 5, continueOnError = false } = options;
49
49
  // 1. 轻量 inline 守卫(validateProject 未接入时的兜底)
50
50
  this.guardConfig(config);
51
51
  const plan = await this.buildPlan(config, options);
@@ -119,8 +119,25 @@ class DeployOrchestrator {
119
119
  const tasks = toExecute.map((item) => async () => {
120
120
  var _a, _b, _c, _d, _e;
121
121
  try {
122
- const stepResult = await this.executeStep({ item, config, envId, cwd });
123
- results.push({ type: item.type, name: item.name, ok: true, url: stepResult });
122
+ const stepResult = await this.executeStep({
123
+ item,
124
+ config,
125
+ envId,
126
+ cwd,
127
+ onProgress: options.onProgress
128
+ });
129
+ if (item.type === 'functions') {
130
+ results.push({
131
+ type: item.type,
132
+ name: item.name,
133
+ ok: true,
134
+ url: stepResult === null || stepResult === void 0 ? void 0 : stepResult.url,
135
+ detail: stepResult
136
+ });
137
+ }
138
+ else {
139
+ results.push({ type: item.type, name: item.name, ok: true, url: stepResult });
140
+ }
124
141
  }
125
142
  catch (e) {
126
143
  const message = (e === null || e === void 0 ? void 0 : e.message) || String(e);
@@ -130,8 +147,10 @@ class DeployOrchestrator {
130
147
  ((_b = e === null || e === void 0 ? void 0 : e.original) === null || _b === void 0 ? void 0 : _b.RequestId) ||
131
148
  ((_c = e === null || e === void 0 ? void 0 : e.response) === null || _c === void 0 ? void 0 : _c.requestId) ||
132
149
  ((_d = e === null || e === void 0 ? void 0 : e.response) === null || _d === void 0 ? void 0 : _d.RequestId);
133
- (_e = log === null || log === void 0 ? void 0 : log.error) === null || _e === void 0 ? void 0 : _e.call(log, `[${item.type}] ${item.name} 部署失败: ${message}`);
134
- results.push(Object.assign({ type: item.type, name: item.name, ok: false, error: message }, (requestId ? { requestId } : {})));
150
+ if (!(item.type === 'functions' && options.onProgress)) {
151
+ (_e = log === null || log === void 0 ? void 0 : log.error) === null || _e === void 0 ? void 0 : _e.call(log, `[${item.type}] ${item.name} 部署失败: ${message}`);
152
+ }
153
+ results.push(Object.assign(Object.assign(Object.assign({ type: item.type, name: item.name, ok: false, error: message }, ((e === null || e === void 0 ? void 0 : e.code) ? { errorCode: e.code } : {})), ((e === null || e === void 0 ? void 0 : e.failedStage) ? { failedStage: e.failedStage } : {})), (requestId ? { requestId } : {})));
135
154
  segmentFailed = true;
136
155
  if (item.type === 'database') {
137
156
  segmentDbFailed = true;
@@ -147,7 +166,6 @@ class DeployOrchestrator {
147
166
  return { plan, results };
148
167
  }
149
168
  if (!continueOnError) {
150
- (_h = log === null || log === void 0 ? void 0 : log.error) === null || _h === void 0 ? void 0 : _h.call(log, '存在资源部署失败,中断部署');
151
169
  return { plan, results };
152
170
  }
153
171
  }
@@ -161,7 +179,7 @@ class DeployOrchestrator {
161
179
  await this.persistState({ config, plan, results, cwd, envId: options.envId });
162
180
  }
163
181
  catch (e) {
164
- (_j = log === null || log === void 0 ? void 0 : log.warn) === null || _j === void 0 ? void 0 : _j.call(log, `状态快照写入失败:${(e === null || e === void 0 ? void 0 : e.message) || String(e)}`);
182
+ (_h = log === null || log === void 0 ? void 0 : log.warn) === null || _h === void 0 ? void 0 : _h.call(log, `状态快照写入失败:${(e === null || e === void 0 ? void 0 : e.message) || String(e)}`);
165
183
  }
166
184
  }
167
185
  }
@@ -566,7 +584,7 @@ class DeployOrchestrator {
566
584
  * 执行单个计划步骤
567
585
  */
568
586
  async executeStep(options) {
569
- const { item, config, envId, cwd } = options;
587
+ const { item, config, envId, cwd, onProgress } = options;
570
588
  switch (item.type) {
571
589
  case 'database': {
572
590
  const dbConfig = config.database;
@@ -585,14 +603,10 @@ class DeployOrchestrator {
585
603
  const functionPath = fn.dir
586
604
  ? path_1.default.resolve(cwd, fn.dir)
587
605
  : path_1.default.join(functionRootPath, fn.name);
588
- // 声明式部署幂等语义:函数已存在则覆盖更新(force=true),支持重跑续传
589
- const result = await this.functionDeployer.deploy({
590
- func: fn,
591
- functionRootPath,
592
- functionPath,
593
- force: true
606
+ const deployConfig = Object.assign(Object.assign({}, fn), { functionPath });
607
+ return this.functionDeployer.deployFunction(deployConfig, {
608
+ onProgress: event => onProgress === null || onProgress === void 0 ? void 0 : onProgress(Object.assign(Object.assign({}, event), { resourceType: 'functions', resourceName: item.name }))
594
609
  });
595
- return result.url;
596
610
  }
597
611
  case 'app': {
598
612
  const result = await this.staticDeployer.deployApp({ config: config.app, cwd });
@@ -26,6 +26,8 @@ const GatewayDeployer_1 = require("./GatewayDeployer");
26
26
  const domain_1 = require("./domain");
27
27
  const image_preflight_1 = require("./function/image-preflight");
28
28
  const local_builder_1 = require("./function/local-builder");
29
+ const enterprise_tcr_1 = require("./function/enterprise-tcr");
30
+ const personal_tcr_1 = require("./function/personal-tcr");
29
31
  const planner_1 = require("./function/planner");
30
32
  const preflight_1 = require("./function/preflight");
31
33
  const types_1 = require("./types");
@@ -41,16 +43,21 @@ const AUTHZ_USER_REGO_KEY = 'authz.user.rego';
41
43
  *
42
44
  * 已支持代码包、已有镜像、云端构建、本地构建、匿名访问与网关路径收敛;
43
45
  * 函数类型、运行时或协议等创建期字段变化时不会自动删除函数,而是引导用户
44
- * 在控制台确认并手工删除后重新部署。local 企业版 TCR 等未实现能力会显式报错。
46
+ * 在控制台确认并手工删除后重新部署。
45
47
  */
46
48
  class FunctionDeployer {
47
49
  constructor(environment, deps) {
48
- var _a;
50
+ var _a, _b, _c;
49
51
  this.environment = environment;
50
52
  this.functionService = environment.getFunctionService();
51
53
  this.imagePreflightDeps = deps === null || deps === void 0 ? void 0 : deps.imagePreflightDeps;
52
54
  this.cloudBuildServiceFactory =
53
55
  (_a = deps === null || deps === void 0 ? void 0 : deps.cloudBuildServiceFactory) !== null && _a !== void 0 ? _a : (() => (0, cloud_build_service_1.createCloudBuildService)(this.environment.getCloudAppService()));
56
+ this.localBuilderDeps = deps === null || deps === void 0 ? void 0 : deps.localBuilderDeps;
57
+ this.tcrServiceFactory =
58
+ (_b = deps === null || deps === void 0 ? void 0 : deps.tcrServiceFactory) !== null && _b !== void 0 ? _b : (() => (0, enterprise_tcr_1.createTcrApiService)(this.environment));
59
+ this.personalTcrServiceFactory =
60
+ (_c = deps === null || deps === void 0 ? void 0 : deps.personalTcrServiceFactory) !== null && _c !== void 0 ? _c : (() => (0, personal_tcr_1.createPersonalTcrService)(this.environment));
54
61
  }
55
62
  /**
56
63
  * 兼容既有声明式部署入口
@@ -214,27 +221,52 @@ class FunctionDeployer {
214
221
  steps: ctx.steps,
215
222
  warnings,
216
223
  startTime,
217
- dryRun: true
224
+ dryRun: true,
225
+ options
218
226
  });
219
227
  }
220
228
  // 不可原地更新的变更必须在任何可能产生副作用的 Preflight 之前中止。
221
229
  // 尤其避免镜像拉取授权自动补齐等云端写操作先发生,再提示用户手工删除函数。
222
230
  this.assertPlanExecutable(plan);
223
- await this.runStep(ctx, 'preflight', async () => {
224
- const report = (0, preflight_1.runLocalPreflight)(normalized);
225
- this.appendWarnings(warnings, report);
226
- this.assertPreflightReady(report);
227
- });
228
- await this.runStep(ctx, 'preflight', async () => {
229
- const report = await (0, cam_preflight_1.runImagePullAuthPreflight)(this.createCamPreflightAdapter(), normalized, options.autoGrant !== false);
230
- this.appendWarnings(warnings, report);
231
- this.assertPreflightReady(report);
232
- });
233
- await this.runStep(ctx, 'preflight', async () => {
234
- const report = await (0, image_preflight_1.runImageRemotePreflight)(normalized, this.resolveDeployRegion(), this.imagePreflightDeps);
235
- this.appendWarnings(warnings, report);
236
- this.assertPreflightReady(report);
237
- });
231
+ if (normalized.type === 'HTTP' &&
232
+ (normalized.buildStrategy === 'cloud' || normalized.buildStrategy === 'zip')) {
233
+ await this.runLabeledStep(ctx, {
234
+ stage: 'preflight',
235
+ handler: async () => {
236
+ const report = (0, preflight_1.runLocalPreflight)(normalized);
237
+ this.appendWarnings(warnings, report);
238
+ this.assertPreflightReady(report);
239
+ },
240
+ label: normalized.buildStrategy === 'cloud'
241
+ ? '检查云端构建上下文'
242
+ : '检查函数代码包'
243
+ });
244
+ }
245
+ if (normalized.type === 'HTTP' &&
246
+ (normalized.buildStrategy === 'image' ||
247
+ normalized.buildStrategy === 'local' ||
248
+ normalized.buildStrategy === 'cloud')) {
249
+ await this.runLabeledStep(ctx, {
250
+ stage: 'preflight',
251
+ handler: async () => {
252
+ const report = await (0, cam_preflight_1.runImagePullAuthPreflight)(this.createCamPreflightAdapter(), normalized, options.autoGrant !== false);
253
+ this.appendWarnings(warnings, report);
254
+ this.assertPreflightReady(report);
255
+ },
256
+ label: '检查镜像仓库访问权限'
257
+ });
258
+ }
259
+ if (normalized.type === 'HTTP' && normalized.buildStrategy === 'image') {
260
+ await this.runLabeledStep(ctx, {
261
+ stage: 'preflight',
262
+ handler: async () => {
263
+ const report = await (0, image_preflight_1.runImageRemotePreflight)(normalized, this.resolveDeployRegion(), this.imagePreflightDeps);
264
+ this.appendWarnings(warnings, report);
265
+ this.assertPreflightReady(report);
266
+ },
267
+ label: '检查远端镜像与地域'
268
+ });
269
+ }
238
270
  // noop:函数本身无变更,但仍可能需要收敛访问配置
239
271
  if (plan.action === 'noop') {
240
272
  await this.applyAccessPlan(ctx, normalized, plan);
@@ -244,7 +276,8 @@ class FunctionDeployer {
244
276
  steps: ctx.steps,
245
277
  warnings,
246
278
  startTime,
247
- dryRun: false
279
+ dryRun: false,
280
+ options
248
281
  });
249
282
  }
250
283
  const artifact = await this.prepareArtifact(ctx, normalized, warnings);
@@ -257,6 +290,7 @@ class FunctionDeployer {
257
290
  warnings,
258
291
  startTime,
259
292
  dryRun: false,
293
+ options,
260
294
  artifact
261
295
  });
262
296
  }
@@ -310,6 +344,21 @@ class FunctionDeployer {
310
344
  return undefined;
311
345
  }
312
346
  }
347
+ resolveEnvironmentId() {
348
+ try {
349
+ return this.environment.getEnvId();
350
+ }
351
+ catch (_a) {
352
+ return '';
353
+ }
354
+ }
355
+ requireDeployRegion() {
356
+ const region = this.resolveDeployRegion();
357
+ if (!region) {
358
+ throw new error_1.CloudBaseError('企业版 TCR 自动发现需要明确部署地域,请在环境配置中设置 region。', { code: types_1.FUNCTION_DEPLOY_ERROR.TCR_REGION_MISSING });
359
+ }
360
+ return region;
361
+ }
313
362
  /**
314
363
  * 构造镜像拉取授权 Preflight 所需的 CAM 适配器
315
364
  *
@@ -398,6 +447,64 @@ class FunctionDeployer {
398
447
  '\n部署器不会自动删除已有云函数。请先在云开发控制台确认触发器、访问配置和服务中断影响,手工删除该函数后重新部署。', { code: types_1.FUNCTION_DEPLOY_ERROR.FUNCTION_RECREATE_REQUIRED });
399
448
  }
400
449
  }
450
+ async prepareEnterpriseBuildConfig(ctx, config) {
451
+ const repository = config.build.repository || '';
452
+ const hasPreparedTarget = Boolean((config.registryId || config.build.registryId) && repository.split('/').length >= 3);
453
+ if (config.imageType !== 'enterprise' || hasPreparedTarget) {
454
+ return { config };
455
+ }
456
+ const prepared = await this.runLabeledStep(ctx, {
457
+ stage: 'preflight',
458
+ label: '准备企业版 TCR 目标仓库',
459
+ handler: () => (0, enterprise_tcr_1.prepareEnterpriseTcrTarget)(config, {
460
+ envId: this.resolveEnvironmentId(),
461
+ region: this.requireDeployRegion(),
462
+ service: this.tcrServiceFactory()
463
+ })
464
+ });
465
+ return { config: prepared.config, target: prepared.target };
466
+ }
467
+ async preparePersonalBuildConfig(ctx, config) {
468
+ var _a, _b, _c;
469
+ if (config.imageType !== 'personal') {
470
+ return config;
471
+ }
472
+ const envId = this.resolveEnvironmentId();
473
+ const configuredNamespace = (_a = config.build.namespace) === null || _a === void 0 ? void 0 : _a.trim();
474
+ const hasConfiguredRepository = Boolean((_b = config.build.repository) === null || _b === void 0 ? void 0 : _b.trim());
475
+ const configuredRepository = ((_c = config.build.repository) === null || _c === void 0 ? void 0 : _c.trim()) || config.name;
476
+ const parsedRepository = (0, config_guard_1.parseImageReference)(configuredRepository);
477
+ if (!parsedRepository || parsedRepository.tag || parsedRepository.digest) {
478
+ throw new error_1.CloudBaseError('个人版 TCR repository 不合法。', {
479
+ code: types_1.FUNCTION_DEPLOY_ERROR.BUILD_REPOSITORY_INVALID
480
+ });
481
+ }
482
+ const repositoryParts = parsedRepository.repository.split('/');
483
+ const repository = repositoryParts[repositoryParts.length - 1];
484
+ const repositoryNamespace = repositoryParts.length > 1 ? repositoryParts.slice(0, -1).join('/') : undefined;
485
+ const namespace = configuredNamespace || repositoryNamespace || envId;
486
+ const hasConfiguredNamespace = Boolean(configuredNamespace || repositoryNamespace);
487
+ const registry = parsedRepository.registry || personal_tcr_1.DEFAULT_PERSONAL_TCR_REGISTRY;
488
+ const credential = config.build.registryCredential;
489
+ if (!(credential === null || credential === void 0 ? void 0 : credential.username) || !credential.password) {
490
+ throw new error_1.CloudBaseError('个人版 TCR 构建缺少推送凭证。', {
491
+ code: types_1.FUNCTION_DEPLOY_ERROR.CLOUD_REGISTRY_CREDENTIAL_MISSING
492
+ });
493
+ }
494
+ await this.runLabeledStep(ctx, {
495
+ stage: 'preflight',
496
+ label: '准备个人版 TCR 目标仓库',
497
+ handler: () => this.personalTcrServiceFactory().ensurePushTarget({
498
+ namespace,
499
+ repository,
500
+ ensureNamespace: !hasConfiguredNamespace,
501
+ ensureRepository: !hasConfiguredRepository
502
+ })
503
+ });
504
+ const targetRepository = `${registry}/${namespace}/${repository}`;
505
+ const preparedConfig = Object.assign(Object.assign({}, config), { build: Object.assign(Object.assign({}, config.build), { namespace, repository: targetRepository, registryCredential: credential }) });
506
+ return preparedConfig;
507
+ }
401
508
  /**
402
509
  * 准备部署物
403
510
  *
@@ -406,8 +513,22 @@ class FunctionDeployer {
406
513
  * 其余策略走同步的 pack。
407
514
  */
408
515
  async prepareArtifact(ctx, config, warnings) {
516
+ var _a;
409
517
  if (config.buildStrategy === 'cloud') {
410
- const cloudConfig = config;
518
+ const prepared = await this.prepareEnterpriseBuildConfig(ctx, config);
519
+ const cloudConfig = await this.preparePersonalBuildConfig(ctx, prepared.config);
520
+ // 企业版登录凭证在触发 CloudApp 构建前换取,构建容器只消费 Secrets,
521
+ // 不再自行签名调用 TCR API。
522
+ const cloudRegistryId = ((_a = prepared.target) === null || _a === void 0 ? void 0 : _a.registryId) ||
523
+ cloudConfig.build.registryId ||
524
+ cloudConfig.registryId;
525
+ const enterpriseCredential = cloudConfig.imageType === 'enterprise' && cloudRegistryId
526
+ ? await this.runLabeledStep(ctx, {
527
+ stage: 'preflight',
528
+ label: '获取企业版 TCR 临时登录凭证',
529
+ handler: () => (0, enterprise_tcr_1.createEnterpriseTcrCredential)(this.tcrServiceFactory(), cloudRegistryId)
530
+ })
531
+ : undefined;
411
532
  const buildResult = await this.runStep(ctx, 'build', async () => {
412
533
  const cloudBuildService = this.cloudBuildServiceFactory();
413
534
  return (0, cloud_1.buildImageOnCloud)(cloudConfig, cloudBuildService, {
@@ -416,27 +537,99 @@ class FunctionDeployer {
416
537
  status: 'start',
417
538
  message: line
418
539
  }),
419
- region: this.resolveDeployRegion()
540
+ enterpriseCredential
420
541
  });
421
542
  });
422
- return (0, artifact_1.createImageArtifactFromCloudBuild)(config, buildResult);
543
+ return (0, artifact_1.createImageArtifactFromCloudBuild)(cloudConfig, buildResult);
423
544
  }
424
545
  if (config.buildStrategy === 'local') {
425
- await this.runStep(ctx, 'preflight', async () => {
426
- const report = await (0, docker_preflight_1.runDockerPreflight)();
427
- this.appendWarnings(warnings, report);
428
- this.assertPreflightReady(report);
546
+ await this.runLabeledStep(ctx, {
547
+ stage: 'preflight',
548
+ handler: async () => {
549
+ const report = await (0, docker_preflight_1.runDockerPreflight)();
550
+ this.appendWarnings(warnings, report);
551
+ this.assertPreflightReady(report);
552
+ },
553
+ label: '检查本地 Docker 环境'
429
554
  });
430
- const localConfig = config;
431
- const buildResult = await this.runStep(ctx, 'build', async () => {
432
- return (0, local_builder_1.buildAndPushImage)(localConfig, line => this.emitProgress(ctx.options, { stage: 'build', status: 'start', message: line }));
433
- });
434
- return (0, artifact_1.createImageArtifactFromBuild)(config, buildResult);
555
+ const prepared = await this.prepareEnterpriseBuildConfig(ctx, config);
556
+ const localConfig = await this.preparePersonalBuildConfig(ctx, prepared.config);
557
+ const credential = await this.resolveLocalRegistryCredential(localConfig);
558
+ const dockerConfig = credential ? (0, local_builder_1.createTemporaryDockerConfig)() : undefined;
559
+ const commandOptions = dockerConfig ? { env: dockerConfig.env } : undefined;
560
+ try {
561
+ if (credential) {
562
+ await this.runStep(ctx, 'login', async () => {
563
+ await (0, local_builder_1.loginDockerRegistry)(this.resolveRegistryFromRepository(localConfig.build.repository), credential, { deps: this.localBuilderDeps, commandOptions });
564
+ });
565
+ }
566
+ const buildResult = await this.runStep(ctx, 'build', async () => {
567
+ return (0, local_builder_1.buildLocalImage)(localConfig, line => this.emitProgress(ctx.options, {
568
+ stage: 'build',
569
+ status: 'start',
570
+ message: line
571
+ }), { deps: this.localBuilderDeps, commandOptions });
572
+ });
573
+ const pushResult = await this.runStep(ctx, 'push', async () => {
574
+ return (0, local_builder_1.pushLocalImage)(buildResult.imageUri, line => this.emitProgress(ctx.options, {
575
+ stage: 'push',
576
+ status: 'start',
577
+ message: line
578
+ }), { deps: this.localBuilderDeps, commandOptions });
579
+ });
580
+ if (localConfig.imageType === 'personal') {
581
+ await this.runLabeledStep(ctx, {
582
+ stage: 'push',
583
+ label: '清理历史镜像标签',
584
+ handler: () => this.personalTcrServiceFactory().cleanupTags({
585
+ namespace: localConfig.build.namespace,
586
+ repository: localConfig.build.repository,
587
+ retain: localConfig.build.retainedTags
588
+ })
589
+ });
590
+ }
591
+ return (0, artifact_1.createImageArtifactFromBuild)(localConfig, {
592
+ imageUri: buildResult.imageUri,
593
+ imageDigest: pushResult.imageDigest
594
+ });
595
+ }
596
+ finally {
597
+ dockerConfig === null || dockerConfig === void 0 ? void 0 : dockerConfig.cleanup();
598
+ }
435
599
  }
436
600
  return this.runStep(ctx, 'pack', async () => {
437
601
  return this.createArtifact(config);
438
602
  });
439
603
  }
604
+ async resolveLocalRegistryCredential(config) {
605
+ var _a, _b, _c;
606
+ const imageType = (_a = config.imageType) !== null && _a !== void 0 ? _a : (((_b = config.registryId) !== null && _b !== void 0 ? _b : config.build.registryId) ? 'enterprise' : 'personal');
607
+ if (imageType === 'enterprise') {
608
+ const registryId = (_c = config.registryId) !== null && _c !== void 0 ? _c : config.build.registryId;
609
+ if (!registryId) {
610
+ throw new error_1.CloudBaseError('企业版 TCR local 构建缺少 registryId。', {
611
+ code: types_1.FUNCTION_DEPLOY_ERROR.IMAGE_REGISTRY_ID_MISSING
612
+ });
613
+ }
614
+ const credential = await (0, enterprise_tcr_1.createEnterpriseTcrCredential)(this.tcrServiceFactory(), registryId);
615
+ return { username: credential.username, secret: credential.token };
616
+ }
617
+ const credential = config.build.registryCredential;
618
+ if (!credential) {
619
+ return undefined;
620
+ }
621
+ return { username: credential.username, secret: credential.password };
622
+ }
623
+ resolveRegistryFromRepository(repository) {
624
+ const normalized = typeof repository === 'string' ? repository.trim() : '';
625
+ const slashIndex = normalized.indexOf('/');
626
+ if (slashIndex <= 0) {
627
+ throw new error_1.CloudBaseError('local 策略的目标镜像地址缺少仓库域名', {
628
+ code: types_1.FUNCTION_DEPLOY_ERROR.LOCAL_BUILD_REPOSITORY_MISSING
629
+ });
630
+ }
631
+ return normalized.slice(0, slashIndex);
632
+ }
440
633
  /** 生成部署物 */
441
634
  createArtifact(config) {
442
635
  if (config.buildStrategy === 'image') {
@@ -499,24 +692,53 @@ class FunctionDeployer {
499
692
  });
500
693
  }
501
694
  }
502
- /** 执行单个步骤并记录耗时与进度 */
695
+ /** 执行普通步骤并记录耗时与进度 */
503
696
  async runStep(ctx, stage, handler) {
697
+ return this.executeStep(ctx, { stage, handler });
698
+ }
699
+ /** 执行带具体名称的步骤并记录耗时与进度 */
700
+ async runLabeledStep(ctx, options) {
701
+ return this.executeStep(ctx, options);
702
+ }
703
+ async executeStep(ctx, options) {
704
+ var _a, _b, _c, _d, _e;
705
+ const { stage, handler, label } = options;
504
706
  const startTime = Date.now();
505
- this.emitProgress(ctx.options, { stage, status: 'start' });
707
+ this.emitProgress(ctx.options, { stage, label, status: 'start' });
506
708
  try {
507
709
  const result = await handler();
508
- ctx.steps.push({ stage, status: 'success', elapsedTime: Date.now() - startTime });
509
- this.emitProgress(ctx.options, { stage, status: 'success' });
710
+ const elapsedTime = Date.now() - startTime;
711
+ ctx.steps.push({ stage, label, status: 'success', elapsedTime });
712
+ this.emitProgress(ctx.options, { stage, label, status: 'success', elapsedTime });
510
713
  return result;
511
714
  }
512
715
  catch (error) {
716
+ const elapsedTime = Date.now() - startTime;
513
717
  ctx.steps.push({
514
718
  stage,
719
+ label,
720
+ status: 'failed',
721
+ elapsedTime,
722
+ detail: error === null || error === void 0 ? void 0 : error.message,
723
+ errorCode: error === null || error === void 0 ? void 0 : error.code
724
+ });
725
+ this.emitProgress(ctx.options, {
726
+ stage,
727
+ label,
515
728
  status: 'failed',
516
- elapsedTime: Date.now() - startTime,
517
- detail: error === null || error === void 0 ? void 0 : error.message
729
+ message: error === null || error === void 0 ? void 0 : error.message,
730
+ elapsedTime,
731
+ errorCode: error === null || error === void 0 ? void 0 : error.code,
732
+ requestId: (error === null || error === void 0 ? void 0 : error.requestId) ||
733
+ (error === null || error === void 0 ? void 0 : error.RequestId) ||
734
+ ((_a = error === null || error === void 0 ? void 0 : error.original) === null || _a === void 0 ? void 0 : _a.requestId) ||
735
+ ((_b = error === null || error === void 0 ? void 0 : error.original) === null || _b === void 0 ? void 0 : _b.RequestId) ||
736
+ ((_d = (_c = error === null || error === void 0 ? void 0 : error.original) === null || _c === void 0 ? void 0 : _c.Response) === null || _d === void 0 ? void 0 : _d.RequestId) ||
737
+ ((_e = error === null || error === void 0 ? void 0 : error.response) === null || _e === void 0 ? void 0 : _e.RequestId)
518
738
  });
519
- this.emitProgress(ctx.options, { stage, status: 'failed', message: error === null || error === void 0 ? void 0 : error.message });
739
+ if (error && !error.failedStage) {
740
+ error.failedStage = stage;
741
+ }
520
742
  throw error;
521
743
  }
522
744
  }
@@ -567,11 +789,19 @@ class FunctionDeployer {
567
789
  return result;
568
790
  }
569
791
  emitProgressDone(params) {
792
+ const elapsedTime = Date.now() - params.startTime;
570
793
  params.steps.push({
571
794
  stage: 'done',
572
795
  status: 'success',
573
- elapsedTime: Date.now() - params.startTime
796
+ elapsedTime
574
797
  });
798
+ if (params.options) {
799
+ this.emitProgress(params.options, {
800
+ stage: 'done',
801
+ status: 'success',
802
+ elapsedTime
803
+ });
804
+ }
575
805
  }
576
806
  }
577
807
  exports.FunctionDeployer = FunctionDeployer;
@@ -73,7 +73,13 @@ class StaticDeployer {
73
73
  const hostingOptions = {
74
74
  localPath: outputDir,
75
75
  cloudPath,
76
- ignore: config.ignore || ['.DS_Store']
76
+ ignore: config.ignore || ['.DS_Store'],
77
+ // 声明式配置名为 entryFile,透传到 uploadFiles 时对应 entryFiles
78
+ entryFiles: config.entryFile,
79
+ // 一致性发布开关:任一开启即走一致性上传(uploadDirectoryConsistent)
80
+ verify: config.verify,
81
+ safe: config.safe,
82
+ prune: config.prune
77
83
  };
78
84
  await this.environment.getHostingService().uploadFiles(hostingOptions);
79
85
  const url = await this.resolveHostingUrl(cloudPath);