@cloudbase/manager-node 5.8.0-beta.1 → 5.8.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 CHANGED
@@ -1,3 +1,9 @@
1
+ ## @cloudbase/manager-node v5.7.0 版本发布 🚀(2026-08-13)
2
+
3
+
4
+
5
+ ---
6
+
1
7
  ## @cloudbase/manager-node v5.6.8 版本发布 🚀(2026-08-11)
2
8
 
3
9
  - 新增 封装 DescribeReleaseOrder 接口
@@ -158,7 +158,7 @@ class DeployOrchestrator {
158
158
  // (失败资源保留旧快照,下次 diff 仍会检测到差异);快照写入失败不影响部署结果
159
159
  if (!dryRun) {
160
160
  try {
161
- await this.persistState({ config, plan, results, cwd });
161
+ await this.persistState({ config, plan, results, cwd, envId: options.envId });
162
162
  }
163
163
  catch (e) {
164
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)}`);
@@ -203,7 +203,7 @@ class DeployOrchestrator {
203
203
  */
204
204
  async persistState(options) {
205
205
  var _a, _b, _c, _d, _e, _f;
206
- const { config, plan, results, cwd } = options;
206
+ const { config, plan, results, cwd, envId } = options;
207
207
  const stateStore = new StateStore_1.StateStore({ cwd });
208
208
  const prev = stateStore.read();
209
209
  // hosting
@@ -263,7 +263,7 @@ class DeployOrchestrator {
263
263
  resources.gateway = prev.resources.gateway;
264
264
  }
265
265
  if (Object.keys(resources).length) {
266
- stateStore.write({ resources });
266
+ stateStore.write({ resources, envId });
267
267
  }
268
268
  }
269
269
  /**
@@ -291,7 +291,11 @@ class DeployOrchestrator {
291
291
  };
292
292
  // 本地 state 快照(diff 基准):--refresh 时忽略本地 skip 判定
293
293
  const stateStore = new StateStore_1.StateStore({ cwd });
294
- const state = refresh ? null : stateStore.read();
294
+ const rawState = refresh ? null : stateStore.read();
295
+ // 跨环境保护:快照记录的 envId 与目标环境不一致(或旧格式快照无 envId)时,
296
+ // 快照是其他环境部署后的指纹,不可作为 skip 依据 → 视为无快照,强制走全量部署,
297
+ // 避免切换环境后误判「产物未变更」导致新环境 COS 404
298
+ const state = rawState && rawState.envId === envId ? rawState : null;
295
299
  const planCtx = { config, envId, stateStore, state, refresh, cwd };
296
300
  for (const type of DEPLOY_ORDER) {
297
301
  if (!isEnabled(type))
@@ -78,6 +78,7 @@ class StateStore {
78
78
  version: 1,
79
79
  updatedAt: new Date().toISOString(),
80
80
  gitCommit: (_a = state.gitCommit) !== null && _a !== void 0 ? _a : this.resolveGitCommit(),
81
+ envId: state.envId,
81
82
  resources: state.resources
82
83
  };
83
84
  fs_1.default.writeFileSync(filePath, JSON.stringify(full, null, 2), 'utf8');
@@ -33,10 +33,9 @@ function createImageArtifactFromConfig(config) {
33
33
  // imageType/registryId 的契约位置在配置顶层(IHttpImageRuntimeCommon),
34
34
  // 与 local/cloud 策略一致;imageConfig 内的同名字段仅作向后兼容兜底。
35
35
  // 个人版镜像(personal,如个人版 CCR)不应携带 registryId。
36
- const imageType = (_b = (_a = config.imageType) !== null && _a !== void 0 ? _a : imageConfig.imageType) !== null && _b !== void 0 ? _b : 'enterprise';
37
- const registryId = imageType === 'enterprise'
38
- ? (_c = config.registryId) !== null && _c !== void 0 ? _c : imageConfig.registryId
39
- : undefined;
36
+ const configuredRegistryId = (_a = config.registryId) !== null && _a !== void 0 ? _a : imageConfig.registryId;
37
+ const imageType = (_c = (_b = config.imageType) !== null && _b !== void 0 ? _b : imageConfig.imageType) !== null && _c !== void 0 ? _c : (configuredRegistryId ? 'enterprise' : 'personal');
38
+ const registryId = imageType === 'enterprise' ? configuredRegistryId : undefined;
40
39
  return {
41
40
  kind: 'image',
42
41
  deployMode: 'image',
@@ -60,14 +59,18 @@ function createImageArtifactFromConfig(config) {
60
59
  * 此处仅承载最终 imageUri/digest 与容器启动配置,之后复用镜像部署链。
61
60
  */
62
61
  function createImageArtifactFromBuild(config, build) {
63
- var _a;
62
+ var _a, _b, _c;
63
+ const imageType = (_a = config.imageType) !== null && _a !== void 0 ? _a : (((_b = config.registryId) !== null && _b !== void 0 ? _b : config.build.registryId) ? 'enterprise' : 'personal');
64
+ const registryId = imageType === 'enterprise'
65
+ ? (_c = config.registryId) !== null && _c !== void 0 ? _c : config.build.registryId
66
+ : undefined;
64
67
  return {
65
68
  kind: 'image',
66
69
  deployMode: 'image',
67
70
  imageUri: build.imageUri,
68
71
  imageDigest: build.imageDigest,
69
- // 个人版先行:local 构建统一按 personal 处理,企业版增强后置
70
- imageType: (_a = config.imageType) !== null && _a !== void 0 ? _a : 'personal',
72
+ imageType,
73
+ registryId,
71
74
  imagePort: types_1.IMAGE_FUNCTION_PORT,
72
75
  effectiveStrategy: 'local',
73
76
  entryPoint: config.entryPoint,
@@ -172,7 +175,22 @@ function toCloudFunction(config, artifact) {
172
175
  'role',
173
176
  'codeSecret',
174
177
  'l5',
175
- 'ignore'
178
+ 'ignore',
179
+ // —— 高级配置:与 fn deploy 传统通道(configToParams 白名单)对齐,
180
+ // 字段名需与其大小写不敏感匹配规则一致,否则会被下游静默丢弃 ——
181
+ 'tags',
182
+ 'clsLogsetId',
183
+ 'clsTopicId',
184
+ 'deadLetterConfig',
185
+ 'publicNetConfig',
186
+ 'cfsConfig',
187
+ 'intranetConfig',
188
+ 'initTimeout',
189
+ 'asyncRunEnable',
190
+ 'traceEnable',
191
+ 'autoDeployClsTopicIndex',
192
+ 'autoCreateClsTopic',
193
+ 'dnsCache'
176
194
  ];
177
195
  passthroughFields.forEach(field => {
178
196
  if (source[field] !== undefined) {
@@ -182,7 +182,7 @@ function inferHttpBuildStrategy(config) {
182
182
  * 以便 checkDeployConfig 汇总全部问题
183
183
  */
184
184
  function normalizeDeployConfig(config) {
185
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
185
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
186
186
  if (!config || typeof config !== 'object') {
187
187
  throw new error_1.CloudBaseError('部署配置必须是对象', {
188
188
  code: types_1.FUNCTION_DEPLOY_ERROR.CONFIG_INVALID
@@ -211,19 +211,19 @@ function normalizeDeployConfig(config) {
211
211
  ignore: ignoreList.filter((item) => !/node_modules/.test(item)) });
212
212
  }
213
213
  const normalizedImage = Object.assign(Object.assign({}, cloned), { type: 'HTTP', buildStrategy, runtime: types_1.CUSTOM_IMAGE_RUNTIME, imagePort: (_a = cloned.imagePort) !== null && _a !== void 0 ? _a : types_1.IMAGE_FUNCTION_PORT });
214
- if (buildStrategy === 'cloud' || buildStrategy === 'local') {
215
- normalizedImage.imageType =
216
- (_b = cloned.imageType) !== null && _b !== void 0 ? _b : (((_c = cloned.build) === null || _c === void 0 ? void 0 : _c.registryId) ? 'enterprise' : 'personal');
217
- }
214
+ normalizedImage.imageType =
215
+ (_d = (_b = cloned.imageType) !== null && _b !== void 0 ? _b : (_c = cloned.imageConfig) === null || _c === void 0 ? void 0 : _c.imageType) !== null && _d !== void 0 ? _d : (((_g = (_e = cloned.registryId) !== null && _e !== void 0 ? _e : (_f = cloned.imageConfig) === null || _f === void 0 ? void 0 : _f.registryId) !== null && _g !== void 0 ? _g : (_h = cloned.build) === null || _h === void 0 ? void 0 : _h.registryId)
216
+ ? 'enterprise'
217
+ : 'personal');
218
218
  // 镜像函数由容器启动命令决定入口,不使用 handler 与云端装依赖
219
219
  delete normalizedImage.handler;
220
220
  delete normalizedImage.installDependency;
221
221
  if (buildStrategy === 'cloud' || buildStrategy === 'local') {
222
- normalizedImage.build = Object.assign(Object.assign({}, (cloned.build || {})), { dockerfile: (_e = (_d = cloned.build) === null || _d === void 0 ? void 0 : _d.dockerfile) !== null && _e !== void 0 ? _e : types_1.DEFAULT_DOCKERFILE, platform: (_g = (_f = cloned.build) === null || _f === void 0 ? void 0 : _f.platform) !== null && _g !== void 0 ? _g : types_1.DEFAULT_IMAGE_PLATFORM, repository: (_j = (_h = cloned.build) === null || _h === void 0 ? void 0 : _h.repository) !== null && _j !== void 0 ? _j : cloned.name });
222
+ normalizedImage.build = Object.assign(Object.assign({}, (cloned.build || {})), { dockerfile: (_k = (_j = cloned.build) === null || _j === void 0 ? void 0 : _j.dockerfile) !== null && _k !== void 0 ? _k : types_1.DEFAULT_DOCKERFILE, platform: (_m = (_l = cloned.build) === null || _l === void 0 ? void 0 : _l.platform) !== null && _m !== void 0 ? _m : types_1.DEFAULT_IMAGE_PLATFORM, repository: (_p = (_o = cloned.build) === null || _o === void 0 ? void 0 : _o.repository) !== null && _p !== void 0 ? _p : cloned.name });
223
223
  }
224
224
  if (buildStrategy === 'local') {
225
225
  // 默认不静默改变构建环境
226
- normalizedImage.localFallback = (_k = cloned.localFallback) !== null && _k !== void 0 ? _k : 'error';
226
+ normalizedImage.localFallback = (_q = cloned.localFallback) !== null && _q !== void 0 ? _q : 'error';
227
227
  }
228
228
  return normalizedImage;
229
229
  }
@@ -377,13 +377,9 @@ function checkImageIdentity(raw, checks) {
377
377
  const imageConfig = raw === null || raw === void 0 ? void 0 : raw.imageConfig;
378
378
  const buildRegistryId = (_a = raw === null || raw === void 0 ? void 0 : raw.build) === null || _a === void 0 ? void 0 : _a.registryId;
379
379
  const buildStrategy = raw === null || raw === void 0 ? void 0 : raw.buildStrategy;
380
- const defaultImageType = buildStrategy === 'cloud' || buildStrategy === 'local'
381
- ? buildRegistryId
382
- ? 'enterprise'
383
- : 'personal'
384
- : 'enterprise';
385
- const imageType = (_c = (_b = raw === null || raw === void 0 ? void 0 : raw.imageType) !== null && _b !== void 0 ? _b : imageConfig === null || imageConfig === void 0 ? void 0 : imageConfig.imageType) !== null && _c !== void 0 ? _c : defaultImageType;
386
- const registryId = (_e = (_d = raw === null || raw === void 0 ? void 0 : raw.registryId) !== null && _d !== void 0 ? _d : imageConfig === null || imageConfig === void 0 ? void 0 : imageConfig.registryId) !== null && _e !== void 0 ? _e : buildRegistryId;
380
+ const registryId = (_c = (_b = raw === null || raw === void 0 ? void 0 : raw.registryId) !== null && _b !== void 0 ? _b : imageConfig === null || imageConfig === void 0 ? void 0 : imageConfig.registryId) !== null && _c !== void 0 ? _c : buildRegistryId;
381
+ const defaultImageType = registryId ? 'enterprise' : 'personal';
382
+ const imageType = (_e = (_d = raw === null || raw === void 0 ? void 0 : raw.imageType) !== null && _d !== void 0 ? _d : imageConfig === null || imageConfig === void 0 ? void 0 : imageConfig.imageType) !== null && _e !== void 0 ? _e : defaultImageType;
387
383
  if (imageType !== 'enterprise' && imageType !== 'personal') {
388
384
  checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_TYPE_INVALID, '镜像类型不合法', 'imageType 只能是 enterprise 或 personal'));
389
385
  return;
@@ -403,17 +399,34 @@ function checkImageIdentity(raw, checks) {
403
399
  checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_TYPE_INVALID, '个人版镜像不能配置企业版仓库实例 ID', '请移除 build.registryId,或将 imageType 设置为 enterprise'));
404
400
  }
405
401
  }
406
- /** 校验镜像构建目标配置 */
407
- function checkBuildTarget(raw, checks) {
408
- var _a, _b, _c;
409
- const build = raw.build;
410
- if (!lodash_1.default.isPlainObject(build)) {
411
- checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_CONFIG_MISSING, '缺少镜像构建配置', '请配置 build,至少包含构建上下文与 Dockerfile 信息'));
412
- return;
402
+ /** 解析构建策略使用的镜像类型 */
403
+ function resolveBuildImageType(raw, build) {
404
+ var _a, _b, _c, _d, _e, _f;
405
+ return (_c = (_a = raw.imageType) !== null && _a !== void 0 ? _a : (_b = raw.imageConfig) === null || _b === void 0 ? void 0 : _b.imageType) !== null && _c !== void 0 ? _c : (((_f = (_d = raw.registryId) !== null && _d !== void 0 ? _d : (_e = raw.imageConfig) === null || _e === void 0 ? void 0 : _e.registryId) !== null && _f !== void 0 ? _f : build.registryId) ? 'enterprise' : 'personal');
406
+ }
407
+ /** 校验构建 tag */
408
+ function checkBuildTag(raw, build) {
409
+ if (build.tag === undefined) {
410
+ return [];
413
411
  }
414
- if (raw.buildStrategy === 'cloud' && build.tag !== undefined) {
415
- checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_TAG_INVALID, 'cloud 策略不支持自定义镜像 tag', '请移除 build.tag,云端构建会统一使用平台生成的 CLOUDBASE_VERSION_NAME'));
412
+ if (raw.buildStrategy === 'cloud') {
413
+ return [
414
+ fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_TAG_INVALID, 'cloud 策略不支持自定义镜像 tag', '请移除 build.tag,云端构建会统一使用平台生成的 CLOUDBASE_VERSION_NAME')
415
+ ];
416
416
  }
417
+ if (typeof build.tag !== 'string' || !IMAGE_TAG_PATTERN.test(build.tag)) {
418
+ return [fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_TAG_INVALID, '镜像 tag 不合法')];
419
+ }
420
+ if (build.tag === 'latest') {
421
+ return [
422
+ fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_TAG_LATEST_FORBIDDEN, '镜像 tag 禁止使用 latest', '请使用不可变 tag,或不指定 tag 由构建器生成时间戳 tag')
423
+ ];
424
+ }
425
+ return [];
426
+ }
427
+ /** 校验构建上下文、Dockerfile 与目标平台 */
428
+ function checkBuildSource(build, checks) {
429
+ var _a, _b;
417
430
  if (build.cwd !== undefined && (typeof build.cwd !== 'string' || !build.cwd.trim())) {
418
431
  checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_CONTEXT_INVALID, '构建上下文目录不合法', 'build.cwd 需为非空字符串'));
419
432
  }
@@ -425,68 +438,123 @@ function checkBuildTarget(raw, checks) {
425
438
  if (platform !== types_1.DEFAULT_IMAGE_PLATFORM) {
426
439
  checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_PLATFORM_INVALID, `当前仅支持构建 ${types_1.DEFAULT_IMAGE_PLATFORM} 镜像`, `请移除 build.platform 或设置为 ${types_1.DEFAULT_IMAGE_PLATFORM}`));
427
440
  }
428
- if (build.tag !== undefined && raw.buildStrategy !== 'cloud') {
429
- if (typeof build.tag !== 'string' || !IMAGE_TAG_PATTERN.test(build.tag)) {
430
- checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_TAG_INVALID, '镜像 tag 不合法'));
431
- }
432
- else if (build.tag === 'latest') {
433
- checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_TAG_LATEST_FORBIDDEN, '镜像 tag 禁止使用 latest', '请使用不可变 tag,或不指定 tag 由构建器按内容摘要生成'));
441
+ }
442
+ /** 判断当前构建策略是否要求完整仓库地址 */
443
+ function requiresFullBuildRepository(raw, imageType) {
444
+ return raw.buildStrategy === 'local' ||
445
+ (raw.buildStrategy === 'cloud' && imageType === 'enterprise');
446
+ }
447
+ /** 添加仓库地址缺失或不完整的错误 */
448
+ function addFullRepositoryError(raw, checks, missing) {
449
+ const isLocal = raw.buildStrategy === 'local';
450
+ checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_REPOSITORY_INVALID, missing
451
+ ? isLocal
452
+ ? 'local 策略缺少目标镜像仓库地址'
453
+ : 'enterprise cloud 策略缺少目标镜像仓库地址'
454
+ : isLocal
455
+ ? 'local 策略需要完整镜像仓库地址'
456
+ : 'enterprise cloud 策略需要完整镜像仓库地址', missing
457
+ ? '请配置 build.repository 为 registry/namespace/repository'
458
+ : isLocal
459
+ ? '请配置 registry/namespace/repository;当前 local 构建使用本机 Docker 登录态直接 push'
460
+ : 'P1 自动发现 TCR 实例域名完成前,请配置 registry/namespace/repository'));
461
+ }
462
+ /** 校验目标镜像仓库 */
463
+ function checkBuildRepository(raw, build, checks) {
464
+ const imageType = resolveBuildImageType(raw, build);
465
+ const requiresFullRepository = requiresFullBuildRepository(raw, imageType);
466
+ if (build.repository === undefined) {
467
+ if (requiresFullRepository) {
468
+ addFullRepositoryError(raw, checks, true);
434
469
  }
470
+ return;
435
471
  }
436
- if (build.repository !== undefined) {
437
- if (typeof build.repository !== 'string' ||
438
- !IMAGE_REPOSITORY_PATTERN.test(build.repository)) {
439
- checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_REPOSITORY_INVALID, '镜像仓库名不合法', '只能包含小写字母、数字与 . _ - 分隔符'));
440
- }
472
+ const repository = typeof build.repository === 'string' ? build.repository.trim() : build.repository;
473
+ const parsedRepository = typeof repository === 'string' ? parseImageReference(repository) : null;
474
+ if (!parsedRepository || parsedRepository.tag || parsedRepository.digest) {
475
+ checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_REPOSITORY_INVALID, '镜像仓库地址不合法', 'repository 需为不含 tag 或 digest 的镜像仓库路径'));
476
+ return;
441
477
  }
442
- if (build.buildArgs !== undefined) {
443
- if (!lodash_1.default.isPlainObject(build.buildArgs)) {
444
- checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_ARG_INVALID, '构建参数必须是对象'));
445
- }
446
- else {
447
- Object.entries(build.buildArgs).forEach(([key, value]) => {
448
- if (!ENV_KEY_PATTERN.test(key)) {
449
- checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_ARG_INVALID, `构建参数名不合法:${key}`));
450
- }
451
- if (typeof value !== 'string') {
452
- checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_ARG_INVALID, `构建参数 ${key} 的值必须是字符串`));
453
- }
454
- if (SENSITIVE_KEY_PATTERN.test(key)) {
455
- const message = `构建参数 ${key} 可能包含敏感信息`;
456
- const remediation = '构建参数会写入镜像构建记录,请改用运行时环境变量或密钥管理';
457
- checks.push(raw.buildStrategy === 'cloud'
458
- ? fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_ARG_INVALID, message, remediation)
459
- : warn(types_1.FUNCTION_DEPLOY_ERROR.BUILD_ARG_INVALID, message, remediation));
460
- }
461
- });
462
- }
478
+ const repositoryPath = parsedRepository.repository;
479
+ if (requiresFullRepository &&
480
+ (!parsedRepository.registry || !repositoryPath.includes('/'))) {
481
+ addFullRepositoryError(raw, checks, false);
482
+ }
483
+ }
484
+ /** 校验单个 Docker build arg */
485
+ function checkBuildArg(raw, buildArg, checks) {
486
+ const [key, value] = buildArg;
487
+ if (!ENV_KEY_PATTERN.test(key)) {
488
+ checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_ARG_INVALID, `构建参数名不合法:${key}`));
489
+ }
490
+ if (typeof value !== 'string') {
491
+ checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_ARG_INVALID, `构建参数 ${key} 的值必须是字符串`));
492
+ }
493
+ if (SENSITIVE_KEY_PATTERN.test(key)) {
494
+ const message = `构建参数 ${key} 可能包含敏感信息`;
495
+ const remediation = '构建参数会写入镜像构建记录,请改用运行时环境变量或密钥管理';
496
+ checks.push(raw.buildStrategy === 'cloud'
497
+ ? fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_ARG_INVALID, message, remediation)
498
+ : warn(types_1.FUNCTION_DEPLOY_ERROR.BUILD_ARG_INVALID, message, remediation));
499
+ }
500
+ }
501
+ /** 校验 Docker build args */
502
+ function checkBuildArgs(raw, build, checks) {
503
+ if (build.buildArgs === undefined) {
504
+ return;
505
+ }
506
+ if (!lodash_1.default.isPlainObject(build.buildArgs)) {
507
+ checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_ARG_INVALID, '构建参数必须是对象'));
508
+ return;
509
+ }
510
+ Object.entries(build.buildArgs).forEach(buildArg => {
511
+ checkBuildArg(raw, buildArg, checks);
512
+ });
513
+ }
514
+ /** 校验个人版 TCR 云端推送凭证 */
515
+ function checkPersonalRegistryCredential(registryCredential, checks) {
516
+ if (!lodash_1.default.isPlainObject(registryCredential)) {
517
+ checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CLOUD_REGISTRY_CREDENTIAL_MISSING, '个人版 TCR 云端构建缺少推送凭证', '请在 build.registryCredential 中显式声明 username/password,' +
518
+ '推荐使用 {{env.TCB_TCR_USERNAME}} 与 {{env.TCB_TCR_PASSWORD}}'));
519
+ return;
520
+ }
521
+ const username = registryCredential.username;
522
+ const password = registryCredential.password;
523
+ if (typeof username !== 'string' || !/^\d{5,20}$/.test(username.trim())) {
524
+ checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CLOUD_REGISTRY_CREDENTIAL_INVALID, '个人版 TCR 登录用户名不合法', 'username 需为腾讯云账号 ID(UIN),推荐使用 {{env.TCB_TCR_USERNAME}}'));
525
+ }
526
+ if (typeof password !== 'string' || !password || password.length > 16 * 1024) {
527
+ checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CLOUD_REGISTRY_CREDENTIAL_INVALID, '个人版 TCR 固定密码未配置或长度不合法', 'password 必须显式声明且不能为空,推荐使用 {{env.TCB_TCR_PASSWORD}}'));
463
528
  }
529
+ }
530
+ /** 校验镜像仓库命名空间与推送凭证 */
531
+ function checkBuildRegistry(raw, build, checks) {
532
+ const imageType = resolveBuildImageType(raw, build);
464
533
  const registryCredential = build.registryCredential;
465
- const imageType = (_c = raw.imageType) !== null && _c !== void 0 ? _c : (build.registryId ? 'enterprise' : 'personal');
466
534
  if (raw.buildStrategy === 'cloud' && imageType === 'personal') {
467
535
  if (typeof build.namespace !== 'string' ||
468
536
  !build.namespace.trim() ||
469
537
  !IMAGE_REPOSITORY_PATTERN.test(build.namespace.trim())) {
470
538
  checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_REPOSITORY_INVALID, '个人版 TCR 命名空间未配置或不合法', '请在 build.namespace 中显式声明,推荐使用 {{env.TCB_TCR_NAMESPACE}}'));
471
539
  }
472
- if (!lodash_1.default.isPlainObject(registryCredential)) {
473
- checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CLOUD_REGISTRY_CREDENTIAL_MISSING, '个人版 TCR 云端构建缺少推送凭证', '请在 build.registryCredential 中显式声明 username/password,' +
474
- '推荐使用 {{env.TCB_TCR_USERNAME}} 与 {{env.TCB_TCR_PASSWORD}}'));
475
- }
476
- else {
477
- const username = registryCredential.username;
478
- const password = registryCredential.password;
479
- if (typeof username !== 'string' || !/^\d{5,20}$/.test(username.trim())) {
480
- checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CLOUD_REGISTRY_CREDENTIAL_INVALID, '个人版 TCR 登录用户名不合法', 'username 需为腾讯云账号 ID(UIN),推荐使用 {{env.TCB_TCR_USERNAME}}'));
481
- }
482
- if (typeof password !== 'string' || !password || password.length > 16 * 1024) {
483
- checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CLOUD_REGISTRY_CREDENTIAL_INVALID, '个人版 TCR 固定密码未配置或长度不合法', 'password 必须显式声明且不能为空,推荐使用 {{env.TCB_TCR_PASSWORD}}'));
484
- }
485
- }
540
+ checkPersonalRegistryCredential(registryCredential, checks);
486
541
  }
487
542
  else if (registryCredential !== undefined) {
488
543
  checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CLOUD_REGISTRY_CREDENTIAL_INVALID, 'registryCredential 仅支持 cloud + personal 构建', '企业版使用 STS 临时凭证换取 token;local 构建使用本机 Docker 登录态'));
489
544
  }
545
+ }
546
+ /** 校验镜像构建目标配置 */
547
+ function checkBuildTarget(raw, checks) {
548
+ const build = raw.build;
549
+ if (!lodash_1.default.isPlainObject(build)) {
550
+ checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_CONFIG_MISSING, '缺少镜像构建配置', '请配置 build,至少包含构建上下文与 Dockerfile 信息'));
551
+ return;
552
+ }
553
+ checks.push(...checkBuildTag(raw, build));
554
+ checkBuildSource(build, checks);
555
+ checkBuildRepository(raw, build, checks);
556
+ checkBuildArgs(raw, build, checks);
557
+ checkBuildRegistry(raw, build, checks);
490
558
  if (raw.imageConfig !== undefined) {
491
559
  checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_CONFIG_NOT_ALLOWED, `buildStrategy 为 ${raw.buildStrategy} 时不能指定 imageConfig`, '镜像地址将由构建结果决定,如需直接部署已有镜像请使用 buildStrategy: image'));
492
560
  }
package/lib/env/index.js CHANGED
@@ -28,6 +28,7 @@ class EnvService {
28
28
  this.tcbService = new utils_1.CloudService(environment.cloudBaseContext, 'tcb', '2018-06-08');
29
29
  this.commonService = environment.getCommonService('lowcode', '2021-01-08');
30
30
  this.billingService = new billing_1.BillingService(environment.cloudBaseContext);
31
+ this.certificateCloudService = new utils_1.CloudService(environment.cloudBaseContext, 'ssl', '2019-12-05');
31
32
  this.tokenCache = new Map();
32
33
  }
33
34
  /**
@@ -769,6 +770,23 @@ class EnvService {
769
770
  async describeCreditsUsageDetail(params) {
770
771
  return this.cloudService.request('DescribeCreditsUsageDetail', Object.assign({}, params));
771
772
  }
773
+ /**
774
+ * 获取当前凭证有权限访问的 SSL 证书列表。
775
+ *
776
+ * 证书用于自定义域名 HTTPS 配置,本接口不依赖 CloudBase 环境信息。
777
+ * @param {DescribeCertificatesParams} params 查询参数
778
+ * @returns {Promise<DescribeCertificatesRes>}
779
+ */
780
+ async describeCertificates(params = {}) {
781
+ if (params.Offset !== undefined && (!Number.isInteger(params.Offset) || params.Offset < 0)) {
782
+ throw new error_1.CloudBaseError('Offset 必须是大于等于 0 的整数');
783
+ }
784
+ if (params.Limit !== undefined &&
785
+ (!Number.isInteger(params.Limit) || params.Limit < 1 || params.Limit > 1000)) {
786
+ throw new error_1.CloudBaseError('Limit 必须是 1 至 1000 的整数');
787
+ }
788
+ return this.certificateCloudService.request('DescribeCertificates', params);
789
+ }
772
790
  /**
773
791
  * 查询HTTP访问服务域名路由信息
774
792
  * @param {DescribeHttpServiceRouteParams} params 查询参数
@@ -777,6 +795,15 @@ class EnvService {
777
795
  async describeHttpServiceRoute(params) {
778
796
  return this.cloudService.request('DescribeHTTPServiceRoute', Object.assign({}, params));
779
797
  }
798
+ /**
799
+ * 校验HTTP访问服务域名路由
800
+ * 此操作为只读预检,不会创建任何资源。
801
+ * @param {VerifyHttpServiceRouteParams} params 校验参数
802
+ * @returns {Promise<VerifyHttpServiceRouteRes>}
803
+ */
804
+ async verifyHttpServiceRoute(params) {
805
+ return this.cloudService.request('VerifyHTTPServiceRoute', Object.assign({}, params));
806
+ }
780
807
  /**
781
808
  * 创建HTTP访问服务域名路由
782
809
  * @param {CreateHttpServiceRouteParams} params 创建参数