@cloudbase/manager-node 5.8.0-beta.1 → 5.8.0-beta.2

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.
@@ -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
  }
@@ -1167,7 +1167,7 @@ class FunctionService {
1167
1167
  * @returns {Promise<void>}
1168
1168
  */
1169
1169
  async downloadFunctionCode(options) {
1170
- const { destPath, envId, functionName, codeSecret } = options;
1170
+ const { destPath, envId, functionName, codeSecret, qualifier } = options;
1171
1171
  // 检验路径是否存在
1172
1172
  (0, utils_1.checkFullAccess)(destPath, true);
1173
1173
  // 获取下载链接:ENABLE_TCBF 开启走 tcb 的 DownloadFunction,否则走 scf 的 GetFunctionAddress
@@ -1176,6 +1176,10 @@ class FunctionService {
1176
1176
  FunctionName: functionName,
1177
1177
  [this.namespaceParamKey]: envId
1178
1178
  };
1179
+ // 指定版本时透传 Qualifier;默认 $LATEST 与现状一致
1180
+ if (qualifier) {
1181
+ params.Qualifier = qualifier;
1182
+ }
1179
1183
  if (codeSecret) {
1180
1184
  params.CodeSecret = codeSecret;
1181
1185
  }
@@ -190,10 +190,12 @@ class ProjectValidator {
190
190
  return (config.functions || []).some((func) => this.isPersonalCloudFunction(func));
191
191
  }
192
192
  isPersonalCloudFunction(func) {
193
- var _a;
193
+ var _a, _b, _c, _d, _e, _f;
194
+ const registryId = (_b = (_a = func.imageConfig) === null || _a === void 0 ? void 0 : _a.registryId) !== null && _b !== void 0 ? _b : (_d = (_c = func.imageConfig) === null || _c === void 0 ? void 0 : _c.build) === null || _d === void 0 ? void 0 : _d.registryId;
195
+ const imageType = (_f = (_e = func.imageConfig) === null || _e === void 0 ? void 0 : _e.imageType) !== null && _f !== void 0 ? _f : (registryId ? 'enterprise' : 'personal');
194
196
  return (func.type === 'HTTP' &&
195
197
  func.buildStrategy === 'cloud' &&
196
- ((_a = func.imageConfig) === null || _a === void 0 ? void 0 : _a.imageType) === 'personal');
198
+ imageType === 'personal');
197
199
  }
198
200
  validateResources(cwd, config) {
199
201
  const errors = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudbase/manager-node",
3
- "version": "5.8.0-beta.1",
3
+ "version": "5.8.0-beta.2",
4
4
  "description": "The node manage service api for cloudbase.",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -42,7 +42,7 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "@cloudbase/signature-nodejs": "^1.0.0",
45
- "@cloudbase/toolbox": "^0.8.3-beta.1",
45
+ "@cloudbase/toolbox": "^0.8.3-beta.2",
46
46
  "@cloudbase/database": "^0.6.2",
47
47
  "archiver": "^3.1.1",
48
48
  "cos-nodejs-sdk-v5": "^2.14.0",
@@ -58,6 +58,8 @@ export interface IDeployState {
58
58
  version: number;
59
59
  updatedAt: string;
60
60
  gitCommit?: string;
61
+ /** 写入快照时的目标环境 ID:跨环境切换(如上海 → 新加坡)后旧快照不可信,skip 判定强制失效 */
62
+ envId?: string;
61
63
  resources: {
62
64
  hosting?: IHostingStateEntry[];
63
65
  gateway?: unknown;
@@ -102,6 +104,8 @@ export declare class StateStore {
102
104
  write(state: {
103
105
  resources: IDeployState['resources'];
104
106
  gitCommit?: string;
107
+ /** 本次部署的目标环境 ID(用于跨环境切换时使旧快照 skip 判定失效) */
108
+ envId?: string;
105
109
  }): void;
106
110
  /**
107
111
  * 计算产物目录逐文件 sha256 指纹(仅 hash,兼容旧调用)
@@ -156,6 +156,35 @@ export interface IFunctionDeployCommon {
156
156
  /** 声明式配置中的函数代码目录,由 DeployOrchestrator 解析为绝对 functionPath */
157
157
  dir?: string;
158
158
  l5?: boolean | 'TRUE' | 'FALSE';
159
+ /** 标签列表(透传为 SCF Tags) */
160
+ tags?: {
161
+ Key: string;
162
+ Value: string;
163
+ }[];
164
+ /** 日志投递 CLS 日志集 ID */
165
+ clsLogsetId?: string;
166
+ /** 日志投递 CLS 主题 ID */
167
+ clsTopicId?: string;
168
+ /** 死信队列配置(透传为 SCF DeadLetterConfig) */
169
+ deadLetterConfig?: Record<string, unknown>;
170
+ /** 公网访问配置(透传为 SCF PublicNetConfig) */
171
+ publicNetConfig?: Record<string, unknown>;
172
+ /** 文件系统 CFS 配置(透传为 SCF CfsConfig) */
173
+ cfsConfig?: Record<string, unknown>;
174
+ /** 内网固定 IP 配置(透传为 SCF IntranetConfig) */
175
+ intranetConfig?: Record<string, unknown>;
176
+ /** 初始化超时时间(秒),范围 1-60 */
177
+ initTimeout?: number;
178
+ /** 是否开启异步属性(仅 CreateFunction 支持) */
179
+ asyncRunEnable?: boolean | 'TRUE' | 'FALSE';
180
+ /** 是否开启事件追踪(仅 CreateFunction 支持) */
181
+ traceEnable?: boolean | 'TRUE' | 'FALSE';
182
+ /** 是否自动创建 CLS 索引(仅 CreateFunction 支持) */
183
+ autoDeployClsTopicIndex?: boolean | 'TRUE' | 'FALSE';
184
+ /** 是否自动创建 CLS 主题(仅 CreateFunction 支持) */
185
+ autoCreateClsTopic?: boolean | 'TRUE' | 'FALSE';
186
+ /** 是否开启 DNS 缓存(仅 Event 函数支持) */
187
+ dnsCache?: boolean | 'TRUE' | 'FALSE';
159
188
  }
160
189
  /** 本地代码来源,三者互斥 */
161
190
  export interface ILocalCodeSource {
@@ -233,11 +262,11 @@ export interface IImageBuildTarget {
233
262
  namespace?: string;
234
263
  /**
235
264
  * 目标镜像仓库路径。
236
- * cloud 策略可提供 repository 名、namespace/repository 或完整
237
- * registry/namespace/repository;省略时默认使用函数名。
265
+ * personal cloud 可省略并默认使用函数名;enterprise cloud 在自动实例发现完成前需提供完整地址;
266
+ * local 策略当前需提供可直接 push 的完整 registry/namespace/repository 地址。
238
267
  */
239
268
  repository?: string;
240
- /** 目标镜像 tag,缺省时由构建器按构建上下文摘要生成 */
269
+ /** local 策略的目标镜像 tag;cloud 策略由平台生成,不接受自定义 tag */
241
270
  tag?: string;
242
271
  /** 目标平台,默认 linux/amd64 */
243
272
  platform?: string;
@@ -253,9 +282,9 @@ export interface IHttpImageRuntimeCommon extends IHttpFunctionDeployCommon {
253
282
  runtime?: typeof CUSTOM_IMAGE_RUNTIME;
254
283
  /** 容器监听端口,仅允许 9000 */
255
284
  imagePort?: number;
256
- /** 镜像类型,默认 enterprise */
285
+ /** 镜像类型;省略时有 registryId 推断为 enterprise,否则推断为 personal */
257
286
  imageType?: 'enterprise' | 'personal';
258
- /** 企业版 TCR 实例 ID;个人版镜像不传 */
287
+ /** 企业版 TCR 实例 ID 的运行时规范位置;个人版镜像不传 */
259
288
  registryId?: string;
260
289
  entryPoint?: string;
261
290
  command?: string;
@@ -202,6 +202,7 @@ export interface IFunctionCodeOptions {
202
202
  destPath: string;
203
203
  functionName: string;
204
204
  codeSecret?: string;
205
+ qualifier?: string;
205
206
  }
206
207
  export interface ILayerDownloadOptions {
207
208
  name: string;