@cloudbase/manager-node 5.8.3 → 5.8.4

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.
@@ -6,6 +6,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.parseImageReference = parseImageReference;
7
7
  exports.normalizeGatewayPath = normalizeGatewayPath;
8
8
  exports.isCustomImageRuntime = isCustomImageRuntime;
9
+ exports.applyMinimalDeployConfigDefaults = applyMinimalDeployConfigDefaults;
10
+ exports.getMissingDeployInputs = getMissingDeployInputs;
11
+ exports.prepareMinimalDeployConfig = prepareMinimalDeployConfig;
9
12
  exports.normalizeDeployConfig = normalizeDeployConfig;
10
13
  exports.checkDeployConfig = checkDeployConfig;
11
14
  exports.assertDeployConfig = assertDeployConfig;
@@ -163,18 +166,151 @@ function inferHttpBuildStrategy(config) {
163
166
  if (config === null || config === void 0 ? void 0 : config.buildStrategy) {
164
167
  return config.buildStrategy;
165
168
  }
166
- if (config === null || config === void 0 ? void 0 : config.imageConfig) {
167
- return 'image';
168
- }
169
- if (config === null || config === void 0 ? void 0 : config.build) {
169
+ const imageConfig = config === null || config === void 0 ? void 0 : config.imageConfig;
170
+ if (imageConfig === null || imageConfig === void 0 ? void 0 : imageConfig.build) {
170
171
  // 有构建上下文但未声明策略时,默认使用无需本地 Docker 的云端构建
171
172
  return 'cloud';
172
173
  }
174
+ if (imageConfig === null || imageConfig === void 0 ? void 0 : imageConfig.imageUri) {
175
+ return 'image';
176
+ }
173
177
  if (isCustomImageRuntime(config === null || config === void 0 ? void 0 : config.runtime)) {
174
178
  return 'image';
175
179
  }
176
180
  return 'zip';
177
181
  }
182
+ /**
183
+ * 应用最小化镜像配置的可安全默认值,不执行 IO,不生成凭证。
184
+ * 仅填充构建上下文、仓库名、namespace 和运行时固定值;用户凭证必须显式传入。
185
+ *
186
+ * 直接在 cloudbaserc 的嵌套形状上操作:镜像字段位于 imageConfig,构建目标位于
187
+ * imageConfig.build,不再做字段平铺映射。
188
+ */
189
+ function applyMinimalDeployConfigDefaults(config, context = {}) {
190
+ var _a, _b, _c, _d, _e, _f, _g;
191
+ const result = lodash_1.default.cloneDeep(config);
192
+ if ((result === null || result === void 0 ? void 0 : result.type) !== 'HTTP') {
193
+ return result;
194
+ }
195
+ const strategy = inferHttpBuildStrategy(result);
196
+ if (!['image', 'local', 'cloud'].includes(strategy)) {
197
+ return result;
198
+ }
199
+ result.buildStrategy = strategy;
200
+ const imageConfig = Object.assign({}, (result.imageConfig || {}));
201
+ result.imageConfig = imageConfig;
202
+ if (strategy === 'image') {
203
+ if (imageConfig.imageUri === undefined && ((_a = context.defaults) === null || _a === void 0 ? void 0 : _a.imageUri) !== undefined) {
204
+ imageConfig.imageUri = context.defaults.imageUri;
205
+ }
206
+ return result;
207
+ }
208
+ if (result.runtime === undefined) {
209
+ result.runtime = types_1.CUSTOM_IMAGE_RUNTIME;
210
+ }
211
+ if (imageConfig.imagePort === undefined) {
212
+ imageConfig.imagePort = types_1.IMAGE_FUNCTION_PORT;
213
+ }
214
+ const build = Object.assign({}, (imageConfig.build || {}));
215
+ imageConfig.build = build;
216
+ const functionPath = context.functionPath ||
217
+ (context.functionRootPath && result.name ? `${context.functionRootPath}/${result.name}` : undefined);
218
+ if (build.cwd === undefined && functionPath) {
219
+ build.cwd = functionPath;
220
+ }
221
+ if (build.dockerfile === undefined) {
222
+ build.dockerfile = types_1.DEFAULT_DOCKERFILE;
223
+ }
224
+ if (build.platform === undefined) {
225
+ build.platform = types_1.DEFAULT_IMAGE_PLATFORM;
226
+ }
227
+ const defaultRepository = (_c = (_b = context.defaults) === null || _b === void 0 ? void 0 : _b.repository) !== null && _c !== void 0 ? _c : result.name;
228
+ const defaultNamespace = (_e = (_d = context.defaults) === null || _d === void 0 ? void 0 : _d.namespace) !== null && _e !== void 0 ? _e : context.envId;
229
+ if (build.repository === undefined && defaultRepository !== undefined) {
230
+ build.repository = defaultRepository;
231
+ }
232
+ if (build.namespace === undefined && defaultNamespace !== undefined) {
233
+ build.namespace = defaultNamespace;
234
+ }
235
+ if (imageConfig.registryId === undefined && ((_f = context.defaults) === null || _f === void 0 ? void 0 : _f.registryId) !== undefined) {
236
+ imageConfig.registryId = context.defaults.registryId;
237
+ }
238
+ if (build.registryCredential === undefined && ((_g = context.defaults) === null || _g === void 0 ? void 0 : _g.registryCredential) !== undefined) {
239
+ build.registryCredential = lodash_1.default.cloneDeep(context.defaults.registryCredential);
240
+ }
241
+ return result;
242
+ }
243
+ /**
244
+ * 返回最小化部署在当前配置下仍缺失的输入字段;不执行网络、文件或凭证操作。
245
+ */
246
+ function getMissingDeployInputs(config, context = {}) {
247
+ var _a, _b, _c;
248
+ const normalized = lodash_1.default.cloneDeep(config);
249
+ const missing = [];
250
+ if ((normalized === null || normalized === void 0 ? void 0 : normalized.type) !== 'HTTP') {
251
+ return missing;
252
+ }
253
+ const strategy = (_a = normalized.buildStrategy) !== null && _a !== void 0 ? _a : inferHttpBuildStrategy(normalized);
254
+ if (!['image', 'local', 'cloud'].includes(strategy)) {
255
+ return missing;
256
+ }
257
+ const imageConfig = normalized.imageConfig || {};
258
+ if (strategy === 'image') {
259
+ if (!imageConfig.imageUri) {
260
+ missing.push({ name: 'imageUri', path: 'imageConfig.imageUri', required: true, reason: '已有镜像部署需要固定 tag 或 digest 的 imageUri' });
261
+ }
262
+ return missing;
263
+ }
264
+ const build = imageConfig.build || {};
265
+ const imageType = imageConfig.imageType || (imageConfig.registryId || build.registryId ? 'enterprise' : 'personal');
266
+ if (imageType === 'enterprise') {
267
+ if (!imageConfig.registryId && !build.registryId) {
268
+ missing.push({ name: 'registryId', path: 'imageConfig.registryId', required: true, reason: '企业版镜像构建需要指定 TCR 实例 ID' });
269
+ }
270
+ if (!build.repository) {
271
+ missing.push({ name: 'repository', path: 'imageConfig.build.repository', required: true, reason: '企业版镜像构建需要目标仓库' });
272
+ }
273
+ return missing;
274
+ }
275
+ const namespace = build.namespace || context.envId;
276
+ if (!namespace) {
277
+ missing.push({ name: 'namespace', path: 'imageConfig.build.namespace', required: true, reason: '个人版镜像构建需要 namespace,且无法从 envId 推导' });
278
+ }
279
+ if (!((_b = build.registryCredential) === null || _b === void 0 ? void 0 : _b.username)) {
280
+ missing.push({ name: 'registryCredential.username', path: 'imageConfig.build.registryCredential.username', required: true, reason: '个人版 TCR 构建需要推送用户名' });
281
+ }
282
+ if (!((_c = build.registryCredential) === null || _c === void 0 ? void 0 : _c.password)) {
283
+ missing.push({ name: 'registryCredential.password', path: 'imageConfig.build.registryCredential.password', required: true, reason: '个人版 TCR 构建需要推送密码' });
284
+ }
285
+ return missing;
286
+ }
287
+ /**
288
+ * 生成最小化配置准备结果:先应用安全默认值,再返回剩余缺失字段和默认值路径。
289
+ */
290
+ function prepareMinimalDeployConfig(config, context = {}) {
291
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3;
292
+ const prepared = applyMinimalDeployConfigDefaults(config, context);
293
+ const missing = getMissingDeployInputs(prepared, { envId: context.envId });
294
+ const original = lodash_1.default.cloneDeep(config);
295
+ const preparedAny = prepared;
296
+ const appliedDefaults = [];
297
+ const candidates = [
298
+ ['imageConfig.imageUri', (_a = original.imageConfig) === null || _a === void 0 ? void 0 : _a.imageUri, (_b = preparedAny.imageConfig) === null || _b === void 0 ? void 0 : _b.imageUri],
299
+ ['imageConfig.build.cwd', (_d = (_c = original.imageConfig) === null || _c === void 0 ? void 0 : _c.build) === null || _d === void 0 ? void 0 : _d.cwd, (_f = (_e = preparedAny.imageConfig) === null || _e === void 0 ? void 0 : _e.build) === null || _f === void 0 ? void 0 : _f.cwd],
300
+ ['imageConfig.build.dockerfile', (_h = (_g = original.imageConfig) === null || _g === void 0 ? void 0 : _g.build) === null || _h === void 0 ? void 0 : _h.dockerfile, (_k = (_j = preparedAny.imageConfig) === null || _j === void 0 ? void 0 : _j.build) === null || _k === void 0 ? void 0 : _k.dockerfile],
301
+ ['imageConfig.build.platform', (_m = (_l = original.imageConfig) === null || _l === void 0 ? void 0 : _l.build) === null || _m === void 0 ? void 0 : _m.platform, (_p = (_o = preparedAny.imageConfig) === null || _o === void 0 ? void 0 : _o.build) === null || _p === void 0 ? void 0 : _p.platform],
302
+ ['imageConfig.build.repository', (_r = (_q = original.imageConfig) === null || _q === void 0 ? void 0 : _q.build) === null || _r === void 0 ? void 0 : _r.repository, (_t = (_s = preparedAny.imageConfig) === null || _s === void 0 ? void 0 : _s.build) === null || _t === void 0 ? void 0 : _t.repository],
303
+ ['imageConfig.build.namespace', (_v = (_u = original.imageConfig) === null || _u === void 0 ? void 0 : _u.build) === null || _v === void 0 ? void 0 : _v.namespace, (_x = (_w = preparedAny.imageConfig) === null || _w === void 0 ? void 0 : _w.build) === null || _x === void 0 ? void 0 : _x.namespace],
304
+ ['imageConfig.registryId', (_y = original.imageConfig) === null || _y === void 0 ? void 0 : _y.registryId, (_z = preparedAny.imageConfig) === null || _z === void 0 ? void 0 : _z.registryId],
305
+ ['imageConfig.build.registryCredential', (_1 = (_0 = original.imageConfig) === null || _0 === void 0 ? void 0 : _0.build) === null || _1 === void 0 ? void 0 : _1.registryCredential, (_3 = (_2 = preparedAny.imageConfig) === null || _2 === void 0 ? void 0 : _2.build) === null || _3 === void 0 ? void 0 : _3.registryCredential]
306
+ ];
307
+ candidates.forEach(([fieldPath, before, after]) => {
308
+ if (before === undefined && after !== undefined) {
309
+ appliedDefaults.push(fieldPath);
310
+ }
311
+ });
312
+ return { config: prepared, missing, appliedDefaults };
313
+ }
178
314
  /**
179
315
  * 规范化部署配置
180
316
  *
@@ -182,7 +318,7 @@ function inferHttpBuildStrategy(config) {
182
318
  * 以便 checkDeployConfig 汇总全部问题
183
319
  */
184
320
  function normalizeDeployConfig(config) {
185
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
321
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
186
322
  if (!config || typeof config !== 'object') {
187
323
  throw new error_1.CloudBaseError('部署配置必须是对象', {
188
324
  code: types_1.FUNCTION_DEPLOY_ERROR.CONFIG_INVALID
@@ -210,20 +346,20 @@ function normalizeDeployConfig(config) {
210
346
  // node_modules 必须随代码包上传,不能被 ignore 排除
211
347
  ignore: ignoreList.filter((item) => !/node_modules/.test(item)) });
212
348
  }
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
- 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');
349
+ const imageConfig = Object.assign({}, (cloned.imageConfig || {}));
350
+ const normalizedImage = Object.assign(Object.assign({}, cloned), { type: 'HTTP', buildStrategy, runtime: types_1.CUSTOM_IMAGE_RUNTIME, imageConfig });
351
+ imageConfig.imagePort = (_a = imageConfig.imagePort) !== null && _a !== void 0 ? _a : types_1.IMAGE_FUNCTION_PORT;
352
+ imageConfig.imageType =
353
+ (_b = imageConfig.imageType) !== null && _b !== void 0 ? _b : (((_c = imageConfig.registryId) !== null && _c !== void 0 ? _c : (_d = imageConfig.build) === null || _d === void 0 ? void 0 : _d.registryId) ? 'enterprise' : 'personal');
218
354
  // 镜像函数由容器启动命令决定入口,不使用 handler 与云端装依赖
219
355
  delete normalizedImage.handler;
220
356
  delete normalizedImage.installDependency;
221
357
  if (buildStrategy === 'cloud' || buildStrategy === 'local') {
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 });
358
+ imageConfig.build = Object.assign(Object.assign({}, (imageConfig.build || {})), { dockerfile: (_f = (_e = imageConfig.build) === null || _e === void 0 ? void 0 : _e.dockerfile) !== null && _f !== void 0 ? _f : types_1.DEFAULT_DOCKERFILE, platform: (_h = (_g = imageConfig.build) === null || _g === void 0 ? void 0 : _g.platform) !== null && _h !== void 0 ? _h : types_1.DEFAULT_IMAGE_PLATFORM });
223
359
  }
224
360
  if (buildStrategy === 'local') {
225
361
  // 默认不静默改变构建环境
226
- normalizedImage.localFallback = (_o = cloned.localFallback) !== null && _o !== void 0 ? _o : 'error';
362
+ imageConfig.localFallback = (_j = imageConfig.localFallback) !== null && _j !== void 0 ? _j : 'error';
227
363
  }
228
364
  return normalizedImage;
229
365
  }
@@ -334,10 +470,11 @@ function checkHttpCommon(raw, checks) {
334
470
  }
335
471
  /** 校验镜像函数公共约束 */
336
472
  function checkImageRuntimeCommon(raw, checks) {
473
+ var _a;
337
474
  if (raw.runtime !== undefined && !isCustomImageRuntime(raw.runtime)) {
338
475
  checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_RUNTIME_INVALID, `镜像部署要求 runtime 为 ${types_1.CUSTOM_IMAGE_RUNTIME}`, `当前为 ${raw.runtime},请移除 runtime 或设置为 ${types_1.CUSTOM_IMAGE_RUNTIME}`));
339
476
  }
340
- if (raw.imagePort !== undefined && raw.imagePort !== types_1.IMAGE_FUNCTION_PORT) {
477
+ if (((_a = raw.imageConfig) === null || _a === void 0 ? void 0 : _a.imagePort) !== undefined && raw.imageConfig.imagePort !== types_1.IMAGE_FUNCTION_PORT) {
341
478
  checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_PORT_INVALID, `镜像监听端口必须为 ${types_1.IMAGE_FUNCTION_PORT}`, `请让容器监听 ${types_1.IMAGE_FUNCTION_PORT} 端口`));
342
479
  }
343
480
  if (raw.handler !== undefined) {
@@ -369,17 +506,17 @@ function checkImageReference(imageConfig, checks) {
369
506
  }
370
507
  /**
371
508
  * 校验镜像类型与仓库 ID
372
- * image 策略从 imageConfig.registryId(兼容顶层 registryId)读取;
373
- * local/cloud 策略从 build.registryId 读取,imageType 仍位于镜像运行时公共配置顶层。
509
+ * 直接读取 cloudbaserc 嵌套形状:imageType 位于 imageConfig.imageType;
510
+ * registryId 位于 imageConfig.registryId(cloud/local 兼容 imageConfig.build.registryId)。
374
511
  */
375
512
  function checkImageIdentity(raw, checks) {
376
- var _a, _b, _c, _d, _e;
513
+ var _a;
377
514
  const imageConfig = raw === null || raw === void 0 ? void 0 : raw.imageConfig;
378
- const buildRegistryId = (_a = raw === null || raw === void 0 ? void 0 : raw.build) === null || _a === void 0 ? void 0 : _a.registryId;
515
+ const build = imageConfig === null || imageConfig === void 0 ? void 0 : imageConfig.build;
516
+ const buildRegistryId = build === null || build === void 0 ? void 0 : build.registryId;
379
517
  const buildStrategy = raw === null || raw === void 0 ? void 0 : raw.buildStrategy;
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 explicitImageType = (_d = raw === null || raw === void 0 ? void 0 : raw.imageType) !== null && _d !== void 0 ? _d : imageConfig === null || imageConfig === void 0 ? void 0 : imageConfig.imageType;
382
- const imageType = resolveBuildImageType(raw, (_e = raw === null || raw === void 0 ? void 0 : raw.build) !== null && _e !== void 0 ? _e : {});
518
+ const registryId = (_a = imageConfig === null || imageConfig === void 0 ? void 0 : imageConfig.registryId) !== null && _a !== void 0 ? _a : buildRegistryId;
519
+ const imageType = resolveBuildImageType(raw, build !== null && build !== void 0 ? build : {});
383
520
  if (imageType !== 'enterprise' && imageType !== 'personal') {
384
521
  checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_TYPE_INVALID, buildStrategy === 'cloud' || buildStrategy === 'local'
385
522
  ? `${buildStrategy} 策略必须显式声明镜像类型`
@@ -388,25 +525,25 @@ function checkImageIdentity(raw, checks) {
388
525
  }
389
526
  if (imageType === 'enterprise') {
390
527
  if (buildStrategy === 'image' && (registryId === undefined || registryId === null)) {
391
- checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_REGISTRY_ID_MISSING, '企业版镜像未配置仓库实例 ID', '请配置 imageConfig.registryId 或顶层 registryId'));
528
+ checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_REGISTRY_ID_MISSING, '企业版镜像未配置仓库实例 ID', '请配置 imageConfig.registryId'));
392
529
  }
393
530
  else if (registryId !== undefined && (typeof registryId !== 'string' || !registryId.trim())) {
394
531
  const registryField = buildStrategy === 'cloud' || buildStrategy === 'local'
395
- ? 'build.registryId'
396
- : 'registryId';
532
+ ? 'imageConfig.build.registryId'
533
+ : 'imageConfig.registryId';
397
534
  checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_REGISTRY_ID_INVALID, '企业版镜像仓库实例 ID 无效', `${registryField} 已声明但解析结果为空;若使用 {{env.*}},请检查对应环境变量已赋值为实际企业版 TCR 实例 ID`));
398
535
  }
399
536
  }
400
537
  else if (registryId !== undefined) {
401
- checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_TYPE_INVALID, '个人版镜像不能配置企业版仓库实例 ID', '请移除 build.registryId,或将 imageType 设置为 enterprise'));
538
+ checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_TYPE_INVALID, '个人版镜像不能配置企业版仓库实例 ID', '请移除 imageConfig.build.registryId,或将 imageConfig.imageType 设置为 enterprise'));
402
539
  }
403
540
  }
404
541
  /** 解析构建策略使用的镜像类型 */
405
542
  function resolveBuildImageType(raw, build) {
406
- var _a, _b, _c, _d, _e, _f;
407
- 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 : (raw.buildStrategy === 'local' || raw.buildStrategy === 'cloud'
543
+ var _a, _b, _c, _d;
544
+ return (_b = (_a = raw.imageConfig) === null || _a === void 0 ? void 0 : _a.imageType) !== null && _b !== void 0 ? _b : (raw.buildStrategy === 'local' || raw.buildStrategy === 'cloud'
408
545
  ? 'enterprise'
409
- : ((_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)
546
+ : ((_d = (_c = raw.imageConfig) === null || _c === void 0 ? void 0 : _c.registryId) !== null && _d !== void 0 ? _d : build.registryId)
410
547
  ? 'enterprise'
411
548
  : 'personal');
412
549
  }
@@ -447,9 +584,9 @@ function checkBuildSource(build, checks) {
447
584
  }
448
585
  /** 判断当前构建策略是否要求完整仓库地址 */
449
586
  function requiresFullBuildRepository(raw, imageType) {
450
- var _a;
587
+ var _a, _b;
451
588
  return raw.buildStrategy === 'local' && imageType === 'personal' &&
452
- ((_a = raw.build) === null || _a === void 0 ? void 0 : _a.repository) !== undefined;
589
+ ((_b = (_a = raw.imageConfig) === null || _a === void 0 ? void 0 : _a.build) === null || _b === void 0 ? void 0 : _b.repository) !== undefined;
453
590
  }
454
591
  /** 添加仓库地址缺失或不完整的错误 */
455
592
  function addFullRepositoryError(raw, checks, missing) {
@@ -546,6 +683,12 @@ function checkBuildRegistry(raw, build, checks) {
546
683
  const imageType = resolveBuildImageType(raw, build);
547
684
  const registryCredential = build.registryCredential;
548
685
  if (imageType === 'personal') {
686
+ if (build.namespace !== undefined &&
687
+ (typeof build.namespace !== 'string' ||
688
+ !build.namespace.trim() ||
689
+ !IMAGE_REPOSITORY_PATTERN.test(build.namespace.trim()))) {
690
+ checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_REPOSITORY_INVALID, '个人版 TCR 命名空间未配置或不合法', 'build.namespace 必须为合法的个人版 TCR 命名空间;未声明时可由 envId 默认补齐'));
691
+ }
549
692
  checkPersonalRegistryCredential(registryCredential, checks);
550
693
  return;
551
694
  }
@@ -555,9 +698,10 @@ function checkBuildRegistry(raw, build, checks) {
555
698
  }
556
699
  /** 校验镜像构建目标配置 */
557
700
  function checkBuildTarget(raw, checks) {
558
- const build = raw.build;
701
+ var _a, _b;
702
+ const build = (_a = raw.imageConfig) === null || _a === void 0 ? void 0 : _a.build;
559
703
  if (!lodash_1.default.isPlainObject(build)) {
560
- checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_CONFIG_MISSING, '缺少镜像构建配置', '请配置 build,至少包含构建上下文与 Dockerfile 信息'));
704
+ checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_CONFIG_MISSING, '缺少镜像构建配置', '请配置 imageConfig.build,至少包含构建上下文与 Dockerfile 信息'));
561
705
  return;
562
706
  }
563
707
  checks.push(...checkBuildTag(raw, build));
@@ -565,8 +709,8 @@ function checkBuildTarget(raw, checks) {
565
709
  checkBuildRepository(raw, build, checks);
566
710
  checkBuildArgs(raw, build, checks);
567
711
  checkBuildRegistry(raw, build, checks);
568
- if (raw.imageConfig !== undefined) {
569
- checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_CONFIG_NOT_ALLOWED, `buildStrategy 为 ${raw.buildStrategy} 时不能指定 imageConfig`, '镜像地址将由构建结果决定,如需直接部署已有镜像请使用 buildStrategy: image'));
712
+ if (((_b = raw.imageConfig) === null || _b === void 0 ? void 0 : _b.imageUri) !== undefined) {
713
+ checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_CONFIG_NOT_ALLOWED, `buildStrategy 为 ${raw.buildStrategy} 时不能指定 imageConfig.imageUri`, '镜像地址将由构建结果决定,如需直接部署已有镜像请使用 buildStrategy: image'));
570
714
  }
571
715
  }
572
716
  /**
@@ -574,6 +718,7 @@ function checkBuildTarget(raw, checks) {
574
718
  * @returns 检查报告,包含全部问题而非仅第一个
575
719
  */
576
720
  function checkDeployConfig(config) {
721
+ var _a;
577
722
  const checks = [];
578
723
  if (!config || typeof config !== 'object') {
579
724
  return {
@@ -623,16 +768,16 @@ function checkDeployConfig(config) {
623
768
  }
624
769
  else {
625
770
  checkImageReference(raw.imageConfig, checks);
626
- // imageType 在顶层,registryId 允许顶层或 imageConfig,统一传 raw
771
+ // imageType 与 registryId 位于 imageConfig,统一传 raw 由 checkImageIdentity 读取
627
772
  checkImageIdentity(raw, checks);
628
- // 注:imagePort 定义在顶层,已由 checkImageRuntimeCommon 校验,此处不重复
773
+ // 注:imagePort 位于 imageConfig,已由 checkImageRuntimeCommon 校验,此处不重复
629
774
  }
630
775
  }
631
776
  else {
632
777
  checkImageIdentity(raw, checks);
633
778
  checkBuildTarget(raw, checks);
634
779
  if (strategy === 'local') {
635
- const fallback = raw.localFallback;
780
+ const fallback = (_a = raw.imageConfig) === null || _a === void 0 ? void 0 : _a.localFallback;
636
781
  if (fallback !== undefined && fallback !== 'cloud' && fallback !== 'error') {
637
782
  checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.LOCAL_FALLBACK_INVALID, 'localFallback 取值不合法', "只能是 'cloud' 或 'error'"));
638
783
  }
@@ -142,22 +142,23 @@ async function ensureNamespace(service, registryId, namespace) {
142
142
  }
143
143
  async function prepareEnterpriseTcrTarget(config, options) {
144
144
  var _a;
145
- const configuredRegistryId = (_a = config.registryId) !== null && _a !== void 0 ? _a : config.build.registryId;
145
+ const build = config.imageConfig.build;
146
+ const configuredRegistryId = (_a = config.imageConfig.registryId) !== null && _a !== void 0 ? _a : build.registryId;
146
147
  const explicitRegistryId = typeof configuredRegistryId === 'string' && configuredRegistryId.trim()
147
148
  ? configuredRegistryId.trim()
148
149
  : undefined;
149
150
  const resolvedRegistry = await resolveRegistry(options.service, options.region, explicitRegistryId);
150
- const repositoryTarget = parseRepositoryTarget(config.build.repository || config.name);
151
- const namespace = (config.build.namespace || repositoryTarget.namespace || options.envId).trim();
151
+ const repositoryTarget = parseRepositoryTarget(build.repository || config.name);
152
+ const namespace = (build.namespace || repositoryTarget.namespace || options.envId).trim();
152
153
  assertNamespace(namespace);
153
- if (config.build.namespace && repositoryTarget.namespace && config.build.namespace !== repositoryTarget.namespace) {
154
- throwTcrError('build.namespace 与 build.repository 中的命名空间不一致。', types_1.FUNCTION_DEPLOY_ERROR.BUILD_REPOSITORY_INVALID);
154
+ if (build.namespace && repositoryTarget.namespace && build.namespace !== repositoryTarget.namespace) {
155
+ throwTcrError('imageConfig.build.namespace 与 imageConfig.build.repository 中的命名空间不一致。', types_1.FUNCTION_DEPLOY_ERROR.BUILD_REPOSITORY_INVALID);
155
156
  }
156
157
  await ensureNamespace(options.service, resolvedRegistry.registryId, namespace);
157
158
  const registry = repositoryTarget.registry || resolvedRegistry.registry;
158
159
  const repository = repositoryTarget.repository || config.name;
159
160
  const imageRepository = `${registry}/${namespace}/${repository}`;
160
- const preparedConfig = Object.assign(Object.assign({}, config), { imageType: 'enterprise', registryId: resolvedRegistry.registryId, build: Object.assign(Object.assign({}, config.build), { registryId: resolvedRegistry.registryId, namespace, repository: imageRepository }) });
161
+ const preparedConfig = Object.assign(Object.assign({}, config), { imageConfig: Object.assign(Object.assign({}, config.imageConfig), { imageType: 'enterprise', registryId: resolvedRegistry.registryId, build: Object.assign(Object.assign({}, build), { registryId: resolvedRegistry.registryId, namespace, repository: imageRepository }) }) });
161
162
  return {
162
163
  config: preparedConfig,
163
164
  target: {
@@ -61,18 +61,19 @@ const defaultDeps = {
61
61
  };
62
62
  /** 解析构建上下文目录(绝对路径) */
63
63
  function resolveContext(config) {
64
- const cwd = config.build.cwd || config.functionPath || process.cwd();
64
+ const cwd = config.imageConfig.build.cwd || config.functionPath || process.cwd();
65
65
  return path_1.default.resolve(cwd);
66
66
  }
67
67
  /** 解析目标镜像完整引用 imageUri(repository 需为含域名与命名空间的完整仓库地址,个人版/企业版通用) */
68
68
  async function resolveImageUri(config, context) {
69
- const repository = config.build.repository;
69
+ const build = config.imageConfig.build;
70
+ const repository = build.repository;
70
71
  if (typeof repository !== 'string' || !repository.trim()) {
71
- throw new error_1.CloudBaseError('local 策略缺少目标镜像仓库地址 build.repository', {
72
+ throw new error_1.CloudBaseError('local 策略缺少目标镜像仓库地址 imageConfig.build.repository', {
72
73
  code: types_1.FUNCTION_DEPLOY_ERROR.LOCAL_BUILD_REPOSITORY_MISSING
73
74
  });
74
75
  }
75
- const tag = await (0, image_tag_1.resolveImageTag)({ explicitTag: config.build.tag, cwd: context });
76
+ const tag = await (0, image_tag_1.resolveImageTag)({ explicitTag: build.tag, cwd: context });
76
77
  return `${repository.trim()}:${tag}`;
77
78
  }
78
79
  /** 从 buildx imagetools inspect 输出解析 digest */
@@ -106,7 +107,7 @@ async function buildLocalImage(config, onLog, options = {}) {
106
107
  code: types_1.FUNCTION_DEPLOY_ERROR.LOCAL_BUILD_CONTEXT_MISSING
107
108
  });
108
109
  }
109
- const dockerfile = config.build.dockerfile || types_1.DEFAULT_DOCKERFILE;
110
+ const dockerfile = config.imageConfig.build.dockerfile || types_1.DEFAULT_DOCKERFILE;
110
111
  const dockerfilePath = path_1.default.resolve(context, dockerfile);
111
112
  if (!deps.existsSync(dockerfilePath) || !deps.isFile(dockerfilePath)) {
112
113
  throw new error_1.CloudBaseError(`Dockerfile 不存在:${dockerfilePath}`, {
@@ -114,7 +115,7 @@ async function buildLocalImage(config, onLog, options = {}) {
114
115
  });
115
116
  }
116
117
  const imageUri = await resolveImageUri(config, context);
117
- const platform = config.build.platform || types_1.DEFAULT_IMAGE_PLATFORM;
118
+ const platform = config.imageConfig.build.platform || types_1.DEFAULT_IMAGE_PLATFORM;
118
119
  const args = [
119
120
  'buildx',
120
121
  'build',
@@ -126,10 +127,10 @@ async function buildLocalImage(config, onLog, options = {}) {
126
127
  imageUri,
127
128
  '--load'
128
129
  ];
129
- if (config.build.forceBuild) {
130
+ if (config.imageConfig.build.forceBuild) {
130
131
  args.push('--no-cache');
131
132
  }
132
- const buildArgs = config.build.buildArgs || {};
133
+ const buildArgs = config.imageConfig.build.buildArgs || {};
133
134
  for (const key of Object.keys(buildArgs)) {
134
135
  args.push('--build-arg', `${key}=${buildArgs[key]}`);
135
136
  }
@@ -244,7 +245,7 @@ function createTemporaryDockerConfig() {
244
245
  */
245
246
  async function buildAndPushImage(config, onLog, deps = defaultDeps) {
246
247
  const startedAt = Date.now();
247
- const credential = config.build.registryCredential;
248
+ const credential = config.imageConfig.build.registryCredential;
248
249
  const dockerConfig = credential ? createTemporaryDockerConfig() : undefined;
249
250
  const commandOptions = dockerConfig ? { env: dockerConfig.env } : undefined;
250
251
  try {
@@ -71,7 +71,8 @@ function safeStat(target) {
71
71
  }
72
72
  /** 检查 cloud 构建上下文与 Dockerfile,确保任何云端操作前即可失败 */
73
73
  function checkCloudBuildSource(config, checks) {
74
- const cwd = path_1.default.resolve(config.build.cwd || config.functionPath || process.cwd());
74
+ const build = config.imageConfig.build;
75
+ const cwd = path_1.default.resolve(build.cwd || config.functionPath || process.cwd());
75
76
  const contextStat = safeStat(cwd);
76
77
  if (contextStat === null) {
77
78
  checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_CONTEXT_INVALID, '云端镜像构建上下文目录不存在', {
@@ -87,7 +88,7 @@ function checkCloudBuildSource(config, checks) {
87
88
  }));
88
89
  return;
89
90
  }
90
- const dockerfile = config.build.dockerfile || types_1.DEFAULT_DOCKERFILE;
91
+ const dockerfile = build.dockerfile || types_1.DEFAULT_DOCKERFILE;
91
92
  const dockerfilePath = path_1.default.resolve(cwd, dockerfile);
92
93
  const dockerfileStat = safeStat(dockerfilePath);
93
94
  if (dockerfileStat === null || !dockerfileStat.isFile()) {
@@ -23,7 +23,6 @@ const path_1 = __importDefault(require("path"));
23
23
  const toolbox_1 = require("@cloudbase/toolbox");
24
24
  class ProjectValidator {
25
25
  async validate(options = {}) {
26
- var _a;
27
26
  const cwd = path_1.default.resolve(options.cwd || process.cwd());
28
27
  const errors = [];
29
28
  const warnings = [];
@@ -32,7 +31,6 @@ class ProjectValidator {
32
31
  mode: options.mode,
33
32
  errors,
34
33
  });
35
- const version = String(((_a = loaded.rawConfig) === null || _a === void 0 ? void 0 : _a.version) || '2.1');
36
34
  if (loaded.rawConfig) {
37
35
  this.validateSchema(loaded.rawConfig, errors);
38
36
  }
@@ -47,7 +45,6 @@ class ProjectValidator {
47
45
  const summary = this.getSummary(loaded.config);
48
46
  return {
49
47
  valid: errors.length === 0,
50
- version,
51
48
  errors,
52
49
  warnings,
53
50
  summary,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudbase/manager-node",
3
- "version": "5.8.3",
3
+ "version": "5.8.4",
4
4
  "description": "The node manage service api for cloudbase.",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -19,8 +19,6 @@ export interface IDeployOptions {
19
19
  yes?: boolean;
20
20
  /** 已存在资源(函数)的覆盖更新确认回调:返回 true 部署、false 跳过(CLI 注入 autoConfirm) */
21
21
  confirmUpdate?: (item: IDeployPlanItem) => Promise<boolean>;
22
- /** 强制云端对比(漂移检测):忽略本地 state 快照的 skip 判定,强制重新对比/部署 */
23
- refresh?: boolean;
24
22
  /** 同类型资源最大并行数,默认 1(串行)。仅作用于同类型连续实例之间,跨类型依赖顺序不变 */
25
23
  concurrency?: number;
26
24
  /** 失败后继续部署其余资源(database 失败仍强制中断) */
@@ -51,13 +49,6 @@ export interface IDeployPlanItem {
51
49
  from?: string;
52
50
  to?: string;
53
51
  }>;
54
- /** hosting 文件级 diff(新增/修改/删除,前 N 条 + 统计) */
55
- fileDiff?: {
56
- added: string[];
57
- modified: string[];
58
- deleted: string[];
59
- totalChanged: number;
60
- };
61
52
  /** 资源级详情子行(dry-run 树状渲染用):如函数 runtime/构建策略、迁移逐条状态等 */
62
53
  details?: Array<{
63
54
  label: string;
@@ -109,6 +100,11 @@ export declare class DeployOrchestrator {
109
100
  * - database 失败始终强制中断(无论是否 continueOnError)
110
101
  */
111
102
  deploy(options: IDeployOptions): Promise<IDeployResult>;
103
+ /**
104
+ * 归一化声明式函数配置:直接消费 cloudbaserc 的嵌套形状(imageConfig.build),
105
+ * 不做字段平铺映射;非 HTTP 函数原样返回。
106
+ */
107
+ private normalizeFunctionConfigs;
112
108
  /**
113
109
  * 将扁平 plan 按「连续相同资源类型」切成有序 segments
114
110
  *
@@ -117,22 +113,14 @@ export declare class DeployOrchestrator {
117
113
  * → [[db], [fnA, fnB], [hostingX, hostingY], [gateway]]
118
114
  */
119
115
  private segmentPlan;
120
- /**
121
- * 部署成功后写回本地 state 快照
122
- *
123
- * - hosting:成功部署/未变更(skip)的项重算产物指纹;失败项保留旧快照
124
- * - app:成功部署写 buildAppConfig 快照;skip 复用旧快照
125
- * - gateway:保留旧快照(本轮不做路由级实时快照)
126
- */
127
- private persistState;
128
116
  /**
129
117
  * 构建完整部署计划(按依赖顺序 + only/skip 过滤)
130
118
  *
131
119
  * 通过查询云端状态判定每个资源的动作:
132
120
  * - database:先按迁移文件做 preview,再聚合成单条资源级计划项(pending→create,存在 conflict→conflict,全部 applied→不出现在 plan)
133
121
  * - functions:ListFunctions 判断是否已存在 → create / update(不做本地 hash / 字段 diff)
134
- * - app:describeAppInfo 判断是否已存在 → create / update;本地 state 配置一致 → skip
135
- * - hosting:本地 state 指纹 diff → 一致 skip(不重新上传)/ 有差异 deploy(带文件级明细)
122
+ * - app:describeAppInfo 判断是否已存在 → create / update
123
+ * - hosting:直传覆盖(无存在性概念)
136
124
  * - gateway:planRoutes 对比云端路由 → create / update / skip(含变更字段明细)
137
125
  * 云端查询失败时降级为保守判定(全 create),不影响 dry-run 展示。
138
126
  */
@@ -146,11 +134,11 @@ export declare class DeployOrchestrator {
146
134
  */
147
135
  private buildDatabasePlan;
148
136
  /**
149
- * app 计划:本地 state 配置一致 → skip;否则按云端存在性 → create / update
137
+ * app 计划:按云端存在性 → create / update
150
138
  */
151
139
  private buildAppPlan;
152
140
  /**
153
- * hosting 计划:本地 state 指纹 diff → 一致 skip / 有差异 deploy(带文件级明细)
141
+ * hosting 计划:每次直传覆盖(无本地指纹 skip)
154
142
  */
155
143
  private buildHostingPlans;
156
144
  /**
@@ -169,10 +157,6 @@ export declare class DeployOrchestrator {
169
157
  * 执行单个计划步骤
170
158
  */
171
159
  private executeStep;
172
- /** 深比较(JSON 快照语义):仅用于 app 配置快照等简单对象 */
173
- private deepEqual;
174
- /** app 配置差异明细(from → to) */
175
- private appConfigChanges;
176
160
  /**
177
161
  * 轻量 inline 守卫:配置合法 + 目录存在(validateProject 接线点)
178
162
  */
@@ -80,6 +80,20 @@ export declare class FunctionDeployer {
80
80
  * 只做纯函数、无 IO 的配置契约校验,一次返回全部问题
81
81
  */
82
82
  checkConfig(config: IFunctionDeployConfig): ICheckReport;
83
+ /**
84
+ * 计算最小化部署配置的默认值,不执行网络或云端变更。
85
+ */
86
+ applyMinimalConfigDefaults(config: IFunctionDeployConfig, context?: import('./types').IMinimalDeployConfigContext): IFunctionDeployConfig;
87
+ /**
88
+ * 返回最小化配置仍缺失的字段,不执行网络或云端变更。
89
+ */
90
+ getMissingConfigInputs(config: IFunctionDeployConfig, context?: {
91
+ envId?: string;
92
+ }): import('./types').IDeployInputField[];
93
+ /**
94
+ * 应用安全默认值并返回缺失字段,供 CLI/MCP 逐步收集用户输入。
95
+ */
96
+ prepareMinimalConfig(config: IFunctionDeployConfig, context?: import('./types').IMinimalDeployConfigContext): import('./types').IMinimalDeployConfigResult;
83
97
  /**
84
98
  * 生成部署计划
85
99
  *
@@ -166,7 +180,7 @@ export declare class FunctionDeployer {
166
180
  private emitProgressDone;
167
181
  }
168
182
  export * from './types';
169
- export { assertDeployConfig, checkDeployConfig, isCustomImageRuntime, normalizeDeployConfig, normalizeGatewayPath, parseImageReference } from './function/config-guard';
183
+ export { assertDeployConfig, checkDeployConfig, applyMinimalDeployConfigDefaults, getMissingDeployInputs, prepareMinimalDeployConfig, isCustomImageRuntime, normalizeDeployConfig, normalizeGatewayPath, parseImageReference } from './function/config-guard';
170
184
  export { createDeployPlan, toCurrentState } from './function/planner';
171
185
  export { createCodeArtifact, createImageArtifactFromConfig, toCloudFunction } from './function/artifact';
172
186
  export { buildImageOnCloud } from './function/builders/cloud';
@@ -1,10 +1,5 @@
1
1
  import { ICloudFunction } from '../../interfaces';
2
2
  import { ICodeArtifact, IFunctionArtifact, IImageArtifact, INormalizedDeployConfig, INormalizedHttpCloudConfig, INormalizedHttpImageConfig, INormalizedHttpLocalConfig } from '../types';
3
- /**
4
- * 部署物构造与 SCF 参数映射
5
- *
6
- * 四种构建策略最终都产出统一的部署物,之后走同一条 SCF 创建或更新流程
7
- */
8
3
  /** 由本地代码来源构造代码类部署物 */
9
4
  export declare function createCodeArtifact(config: INormalizedDeployConfig): ICodeArtifact;
10
5
  /** 由已有镜像配置构造镜像类部署物 */