@cloudbase/manager-node 5.8.2 → 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.
Files changed (36) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/lib/database/index.js +11 -6
  3. package/lib/deploy/DeployOrchestrator.js +170 -278
  4. package/lib/deploy/FunctionDeployer.js +310 -52
  5. package/lib/deploy/StaticDeployer.js +7 -1
  6. package/lib/deploy/function/artifact.js +55 -41
  7. package/lib/deploy/function/builders/cloud.js +58 -359
  8. package/lib/deploy/function/cam-preflight.js +2 -1
  9. package/lib/deploy/function/cloud-build-service.js +2 -16
  10. package/lib/deploy/function/config-guard.js +210 -55
  11. package/lib/deploy/function/enterprise-tcr.js +195 -0
  12. package/lib/deploy/function/image-tag.js +31 -0
  13. package/lib/deploy/function/local-builder.js +178 -32
  14. package/lib/deploy/function/personal-tcr.js +153 -0
  15. package/lib/deploy/function/preflight.js +3 -2
  16. package/lib/deploy/types.js +11 -0
  17. package/lib/hosting/index.js +27 -0
  18. package/lib/projectValidator/index.js +22 -23
  19. package/package.json +2 -2
  20. package/types/database/index.d.ts +1 -0
  21. package/types/deploy/DeployOrchestrator.d.ts +19 -25
  22. package/types/deploy/FunctionDeployer.d.ts +35 -3
  23. package/types/deploy/StaticDeployer.d.ts +11 -0
  24. package/types/deploy/function/artifact.d.ts +0 -5
  25. package/types/deploy/function/builders/cloud.d.ts +6 -39
  26. package/types/deploy/function/config-guard.d.ts +18 -0
  27. package/types/deploy/function/enterprise-tcr.d.ts +32 -0
  28. package/types/deploy/function/image-tag.d.ts +10 -0
  29. package/types/deploy/function/local-builder.d.ts +35 -6
  30. package/types/deploy/function/personal-tcr.d.ts +35 -0
  31. package/types/deploy/types.d.ts +110 -28
  32. package/types/hosting/index.d.ts +13 -0
  33. package/types/projectValidator/index.d.ts +3 -2
  34. package/types/projectValidator/types.d.ts +0 -1
  35. package/lib/deploy/StateStore.js +0 -328
  36. package/types/deploy/StateStore.d.ts +0 -174
@@ -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;
@@ -29,7 +32,7 @@ const IMAGE_TAG_PATTERN = /^[A-Za-z0-9_][A-Za-z0-9._-]{0,127}$/;
29
32
  /** 镜像 digest 规则 */
30
33
  const IMAGE_DIGEST_PATTERN = /^[a-z0-9]+(?:[.+_-][a-z0-9]+)*:[a-fA-F0-9]{32,}$/;
31
34
  /** 镜像仓库路径规则 */
32
- const IMAGE_REPOSITORY_PATTERN = /^[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*(?:\/[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*)*$/;
35
+ const IMAGE_REPOSITORY_PATTERN = /^[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9][a-z0-9._-]*)*$/;
33
36
  /** 网关路径字符白名单,避免注入与歧义字符 */
34
37
  const GATEWAY_PATH_PATTERN = /^[A-Za-z0-9\-._~%/:@]+$/;
35
38
  /** 环境变量与构建参数名规则 */
@@ -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, _p, _q;
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, repository: (_p = (_o = cloned.build) === null || _o === void 0 ? void 0 : _o.repository) !== null && _p !== void 0 ? _p : cloned.name });
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 = (_q = cloned.localFallback) !== null && _q !== void 0 ? _q : '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,40 +506,46 @@ 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 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;
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
- checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_TYPE_INVALID, '镜像类型不合法', 'imageType 只能是 enterprise 或 personal'));
521
+ checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_TYPE_INVALID, buildStrategy === 'cloud' || buildStrategy === 'local'
522
+ ? `${buildStrategy} 策略必须显式声明镜像类型`
523
+ : '镜像类型不合法', '请配置 imageConfig.imageType 为 enterprise 或 personal'));
385
524
  return;
386
525
  }
387
526
  if (imageType === 'enterprise') {
388
- const registryField = buildStrategy === 'cloud' || buildStrategy === 'local'
389
- ? 'build.registryId'
390
- : 'registryId';
391
- if (registryId === undefined || registryId === null) {
392
- checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_REGISTRY_ID_MISSING, '企业版镜像未配置仓库实例 ID', `请配置 ${registryField};如果当前没有企业版 TCR 实例,请将 imageType 设置为 personal 并改用个人版仓库配置`));
527
+ if (buildStrategy === 'image' && (registryId === undefined || registryId === null)) {
528
+ checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_REGISTRY_ID_MISSING, '企业版镜像未配置仓库实例 ID', '请配置 imageConfig.registryId'));
393
529
  }
394
- else if (typeof registryId !== 'string' || !registryId.trim()) {
395
- checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_REGISTRY_ID_INVALID, '企业版镜像仓库实例 ID 无效', `${registryField} 已声明但解析结果为空;若使用 {{env.*}},请检查对应环境变量已赋值为实际企业版 TCR 实例 ID。没有企业版实例时请改用 imageType=personal`));
530
+ else if (registryId !== undefined && (typeof registryId !== 'string' || !registryId.trim())) {
531
+ const registryField = buildStrategy === 'cloud' || buildStrategy === 'local'
532
+ ? 'imageConfig.build.registryId'
533
+ : 'imageConfig.registryId';
534
+ checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_REGISTRY_ID_INVALID, '企业版镜像仓库实例 ID 无效', `${registryField} 已声明但解析结果为空;若使用 {{env.*}},请检查对应环境变量已赋值为实际企业版 TCR 实例 ID`));
396
535
  }
397
536
  }
398
537
  else if (registryId !== undefined) {
399
- 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'));
400
539
  }
401
540
  }
402
541
  /** 解析构建策略使用的镜像类型 */
403
542
  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');
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'
545
+ ? 'enterprise'
546
+ : ((_d = (_c = raw.imageConfig) === null || _c === void 0 ? void 0 : _c.registryId) !== null && _d !== void 0 ? _d : build.registryId)
547
+ ? 'enterprise'
548
+ : 'personal');
406
549
  }
407
550
  /** 校验构建 tag */
408
551
  function checkBuildTag(raw, build) {
@@ -441,8 +584,9 @@ function checkBuildSource(build, checks) {
441
584
  }
442
585
  /** 判断当前构建策略是否要求完整仓库地址 */
443
586
  function requiresFullBuildRepository(raw, imageType) {
444
- return raw.buildStrategy === 'local' ||
445
- (raw.buildStrategy === 'cloud' && imageType === 'enterprise');
587
+ var _a, _b;
588
+ return raw.buildStrategy === 'local' && imageType === 'personal' &&
589
+ ((_b = (_a = raw.imageConfig) === null || _a === void 0 ? void 0 : _a.build) === null || _b === void 0 ? void 0 : _b.repository) !== undefined;
446
590
  }
447
591
  /** 添加仓库地址缺失或不完整的错误 */
448
592
  function addFullRepositoryError(raw, checks, missing) {
@@ -470,14 +614,21 @@ function checkBuildRepository(raw, build, checks) {
470
614
  return;
471
615
  }
472
616
  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) {
617
+ const isShortRepository = typeof repository === 'string' &&
618
+ IMAGE_REPOSITORY_PATTERN.test(repository) &&
619
+ !repository.split('/')[0].includes('.') &&
620
+ !repository.split('/')[0].includes(':');
621
+ const parsedRepository = typeof repository === 'string' && !isShortRepository
622
+ ? parseImageReference(repository)
623
+ : null;
624
+ if (!isShortRepository && (!parsedRepository || parsedRepository.tag || parsedRepository.digest)) {
475
625
  checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_REPOSITORY_INVALID, '镜像仓库地址不合法', 'repository 需为不含 tag 或 digest 的镜像仓库路径'));
476
626
  return;
477
627
  }
478
- const repositoryPath = parsedRepository.repository;
628
+ const repositoryPath = isShortRepository ? repository : parsedRepository.repository;
479
629
  if (requiresFullRepository &&
480
- (!parsedRepository.registry || !repositoryPath.includes('/'))) {
630
+ (!(parsedRepository === null || parsedRepository === void 0 ? void 0 : parsedRepository.registry) || !repositoryPath.includes('/')) &&
631
+ repository !== raw.name) {
481
632
  addFullRepositoryError(raw, checks, false);
482
633
  }
483
634
  }
@@ -511,10 +662,10 @@ function checkBuildArgs(raw, build, checks) {
511
662
  checkBuildArg(raw, buildArg, checks);
512
663
  });
513
664
  }
514
- /** 校验个人版 TCR 云端推送凭证 */
665
+ /** 校验个人版 TCR 推送凭证 */
515
666
  function checkPersonalRegistryCredential(registryCredential, checks) {
516
667
  if (!lodash_1.default.isPlainObject(registryCredential)) {
517
- checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CLOUD_REGISTRY_CREDENTIAL_MISSING, '个人版 TCR 云端构建缺少推送凭证', '请在 build.registryCredential 中显式声明 username/password,' +
668
+ checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CLOUD_REGISTRY_CREDENTIAL_MISSING, '个人版 TCR 构建缺少推送凭证', '请在 build.registryCredential 中显式声明 username/password,' +
518
669
  '推荐使用 {{env.TCB_TCR_USERNAME}} 与 {{env.TCB_TCR_PASSWORD}}'));
519
670
  return;
520
671
  }
@@ -527,27 +678,30 @@ function checkPersonalRegistryCredential(registryCredential, checks) {
527
678
  checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CLOUD_REGISTRY_CREDENTIAL_INVALID, '个人版 TCR 固定密码未配置或长度不合法', 'password 必须显式声明且不能为空,推荐使用 {{env.TCB_TCR_PASSWORD}}'));
528
679
  }
529
680
  }
530
- /** 校验镜像仓库命名空间与推送凭证 */
681
+ /** 校验镜像仓库配置 */
531
682
  function checkBuildRegistry(raw, build, checks) {
532
683
  const imageType = resolveBuildImageType(raw, build);
533
684
  const registryCredential = build.registryCredential;
534
- if (raw.buildStrategy === 'cloud' && imageType === 'personal') {
535
- if (typeof build.namespace !== 'string' ||
536
- !build.namespace.trim() ||
537
- !IMAGE_REPOSITORY_PATTERN.test(build.namespace.trim())) {
538
- checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_REPOSITORY_INVALID, '个人版 TCR 命名空间未配置或不合法', '请在 build.namespace 中显式声明,推荐使用 {{env.TCB_TCR_NAMESPACE}}'));
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 默认补齐'));
539
691
  }
540
692
  checkPersonalRegistryCredential(registryCredential, checks);
693
+ return;
541
694
  }
542
- else if (registryCredential !== undefined) {
543
- checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CLOUD_REGISTRY_CREDENTIAL_INVALID, 'registryCredential 仅支持 cloud + personal 构建', '企业版使用 STS 临时凭证换取 token;local 构建使用本机 Docker 登录态'));
695
+ if (registryCredential !== undefined) {
696
+ checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CLOUD_REGISTRY_CREDENTIAL_INVALID, 'registryCredential 仅支持 personal 镜像构建', '企业版使用 STS 临时凭证换取 token;personal local/cloud 必须显式提供 username/password'));
544
697
  }
545
698
  }
546
699
  /** 校验镜像构建目标配置 */
547
700
  function checkBuildTarget(raw, checks) {
548
- const build = raw.build;
701
+ var _a, _b;
702
+ const build = (_a = raw.imageConfig) === null || _a === void 0 ? void 0 : _a.build;
549
703
  if (!lodash_1.default.isPlainObject(build)) {
550
- 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 信息'));
551
705
  return;
552
706
  }
553
707
  checks.push(...checkBuildTag(raw, build));
@@ -555,8 +709,8 @@ function checkBuildTarget(raw, checks) {
555
709
  checkBuildRepository(raw, build, checks);
556
710
  checkBuildArgs(raw, build, checks);
557
711
  checkBuildRegistry(raw, build, checks);
558
- if (raw.imageConfig !== undefined) {
559
- 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'));
560
714
  }
561
715
  }
562
716
  /**
@@ -564,6 +718,7 @@ function checkBuildTarget(raw, checks) {
564
718
  * @returns 检查报告,包含全部问题而非仅第一个
565
719
  */
566
720
  function checkDeployConfig(config) {
721
+ var _a;
567
722
  const checks = [];
568
723
  if (!config || typeof config !== 'object') {
569
724
  return {
@@ -613,16 +768,16 @@ function checkDeployConfig(config) {
613
768
  }
614
769
  else {
615
770
  checkImageReference(raw.imageConfig, checks);
616
- // imageType 在顶层,registryId 允许顶层或 imageConfig,统一传 raw
771
+ // imageType 与 registryId 位于 imageConfig,统一传 raw 由 checkImageIdentity 读取
617
772
  checkImageIdentity(raw, checks);
618
- // 注:imagePort 定义在顶层,已由 checkImageRuntimeCommon 校验,此处不重复
773
+ // 注:imagePort 位于 imageConfig,已由 checkImageRuntimeCommon 校验,此处不重复
619
774
  }
620
775
  }
621
776
  else {
622
777
  checkImageIdentity(raw, checks);
623
778
  checkBuildTarget(raw, checks);
624
779
  if (strategy === 'local') {
625
- const fallback = raw.localFallback;
780
+ const fallback = (_a = raw.imageConfig) === null || _a === void 0 ? void 0 : _a.localFallback;
626
781
  if (fallback !== undefined && fallback !== 'cloud' && fallback !== 'error') {
627
782
  checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.LOCAL_FALLBACK_INVALID, 'localFallback 取值不合法', "只能是 'cloud' 或 'error'"));
628
783
  }
@@ -0,0 +1,195 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.prepareEnterpriseTcrTarget = prepareEnterpriseTcrTarget;
4
+ exports.createEnterpriseTcrCredential = createEnterpriseTcrCredential;
5
+ exports.createTcrApiService = createTcrApiService;
6
+ const error_1 = require("../../error");
7
+ const types_1 = require("../types");
8
+ const config_guard_1 = require("./config-guard");
9
+ const TCR_API_VERSION = '2019-09-24';
10
+ const NAMESPACE_PATTERN = /^[a-z0-9](?:[a-z0-9]|[._-](?![._-])){0,28}[a-z0-9]$/;
11
+ function responseOf(result) {
12
+ return (result === null || result === void 0 ? void 0 : result.Response) || result || {};
13
+ }
14
+ function requestIdOf(result) {
15
+ var _a;
16
+ const requestId = (_a = responseOf(result)) === null || _a === void 0 ? void 0 : _a.RequestId;
17
+ return typeof requestId === 'string' && requestId ? requestId : undefined;
18
+ }
19
+ function errorRequestId(error) {
20
+ var _a, _b, _c;
21
+ return (error === null || error === void 0 ? void 0 : error.requestId) || ((_a = error === null || error === void 0 ? void 0 : error.original) === null || _a === void 0 ? void 0 : _a.requestId) || ((_c = (_b = error === null || error === void 0 ? void 0 : error.original) === null || _b === void 0 ? void 0 : _b.Response) === null || _c === void 0 ? void 0 : _c.RequestId);
22
+ }
23
+ function throwTcrError(message, code, requestId) {
24
+ throw new error_1.CloudBaseError(message, Object.assign({ code }, (requestId ? { requestId } : {})));
25
+ }
26
+ function assertNamespace(namespace) {
27
+ if (!NAMESPACE_PATTERN.test(namespace)) {
28
+ throwTcrError(`企业版 TCR 命名空间不合法:${namespace}。名称需为 2-30 位小写字母、数字、点、下划线或中划线,且不能以分隔符开头、结尾或连续出现。`, types_1.FUNCTION_DEPLOY_ERROR.TCR_NAMESPACE_INVALID);
29
+ }
30
+ }
31
+ function parseRepositoryTarget(repository) {
32
+ const parsed = (0, config_guard_1.parseImageReference)(repository);
33
+ if (!parsed || parsed.tag || parsed.digest) {
34
+ throwTcrError('企业版 TCR repository 不合法,需为 repository、namespace/repository 或 registry/namespace/repository,且不能包含 tag 或 digest。', types_1.FUNCTION_DEPLOY_ERROR.BUILD_REPOSITORY_INVALID);
35
+ }
36
+ const parts = parsed.repository.split('/');
37
+ if (parsed.registry) {
38
+ if (parts.length < 2) {
39
+ throwTcrError('完整企业版 TCR repository 必须包含 namespace/repository。', types_1.FUNCTION_DEPLOY_ERROR.BUILD_REPOSITORY_INVALID);
40
+ }
41
+ return {
42
+ registry: parsed.registry,
43
+ namespace: parts.slice(0, -1).join('/'),
44
+ repository: parts[parts.length - 1]
45
+ };
46
+ }
47
+ if (parts.length >= 2) {
48
+ return {
49
+ namespace: parts.slice(0, -1).join('/'),
50
+ repository: parts[parts.length - 1]
51
+ };
52
+ }
53
+ return { repository: parts[0] };
54
+ }
55
+ function isRunningRegistry(registry) {
56
+ return !registry.Status || registry.Status.toLowerCase() === 'running';
57
+ }
58
+ async function resolveRegistry(service, region, explicitRegistryId) {
59
+ let result;
60
+ try {
61
+ result = await service.call({
62
+ Action: 'DescribeInstances',
63
+ Param: Object.assign({ AllRegion: true, Limit: 100 }, (explicitRegistryId ? { Registryids: [explicitRegistryId] } : {}))
64
+ });
65
+ }
66
+ catch (error) {
67
+ throw new error_1.CloudBaseError('查询企业版 TCR 实例失败,请确认当前身份具备 DescribeInstances 权限。', {
68
+ code: types_1.FUNCTION_DEPLOY_ERROR.TCR_INSTANCE_DISCOVERY_FAILED,
69
+ requestId: errorRequestId(error) || ''
70
+ });
71
+ }
72
+ const response = responseOf(result);
73
+ const registries = Array.isArray(response.Registries)
74
+ ? response.Registries
75
+ : Array.isArray(response.InstanceSet)
76
+ ? response.InstanceSet
77
+ : [];
78
+ const matching = registries.filter(item => {
79
+ const registryId = item.RegistryId || item.InstanceId;
80
+ const registryRegion = item.RegionName || item.Region;
81
+ return ((!explicitRegistryId || registryId === explicitRegistryId) &&
82
+ registryRegion === region &&
83
+ isRunningRegistry(item));
84
+ });
85
+ const requestId = requestIdOf(result);
86
+ if (matching.length === 0) {
87
+ const message = explicitRegistryId
88
+ ? `未找到地域 ${region} 内可用的企业版 TCR 实例 ${explicitRegistryId},请确认实例 ID、状态及其与 SCF 的地域一致。`
89
+ : `地域 ${region} 内没有可用的企业版 TCR 实例,请先创建实例或显式指定同地域 registryId。`;
90
+ throwTcrError(message, types_1.FUNCTION_DEPLOY_ERROR.TCR_INSTANCE_NOT_FOUND, requestId);
91
+ }
92
+ if (matching.length > 1) {
93
+ throwTcrError(`地域 ${region} 内发现多个可用的企业版 TCR 实例,请通过 imageConfig.registryId 显式指定目标实例。`, types_1.FUNCTION_DEPLOY_ERROR.TCR_INSTANCE_AMBIGUOUS, requestId);
94
+ }
95
+ const selected = matching[0];
96
+ const registryId = selected.RegistryId || selected.InstanceId;
97
+ const registry = selected.PublicDomain || selected.RegistryDomain || selected.RegistryDomainName;
98
+ if (!registryId || !registry) {
99
+ throwTcrError('企业版 TCR 实例信息缺少 RegistryId 或 PublicDomain,无法生成镜像地址。', types_1.FUNCTION_DEPLOY_ERROR.TCR_INSTANCE_INVALID, requestId);
100
+ }
101
+ return { registryId, registry };
102
+ }
103
+ function isNamespaceConflict(error) {
104
+ var _a, _b;
105
+ const code = String((error === null || error === void 0 ? void 0 : error.code) || ((_b = (_a = error === null || error === void 0 ? void 0 : error.original) === null || _a === void 0 ? void 0 : _a.Error) === null || _b === void 0 ? void 0 : _b.Code) || '');
106
+ return /ResourceConflict|AlreadyExists|ResourceInUse/i.test(code);
107
+ }
108
+ async function ensureNamespace(service, registryId, namespace) {
109
+ var _a;
110
+ let describeResult;
111
+ try {
112
+ describeResult = await service.call({
113
+ Action: 'DescribeNamespaces',
114
+ Param: { RegistryId: registryId, NamespaceName: namespace, Limit: 1, Offset: 0 }
115
+ });
116
+ }
117
+ catch (error) {
118
+ throw new error_1.CloudBaseError(`查询企业版 TCR 命名空间 ${namespace} 失败。`, {
119
+ code: types_1.FUNCTION_DEPLOY_ERROR.TCR_NAMESPACE_QUERY_FAILED,
120
+ requestId: errorRequestId(error) || ''
121
+ });
122
+ }
123
+ const namespaceList = (_a = responseOf(describeResult)) === null || _a === void 0 ? void 0 : _a.NamespaceList;
124
+ if (Array.isArray(namespaceList) && namespaceList.some(item => (item === null || item === void 0 ? void 0 : item.Name) === namespace)) {
125
+ return;
126
+ }
127
+ try {
128
+ await service.call({
129
+ Action: 'CreateNamespace',
130
+ Param: { RegistryId: registryId, NamespaceName: namespace, IsPublic: false }
131
+ });
132
+ }
133
+ catch (error) {
134
+ if (isNamespaceConflict(error)) {
135
+ return;
136
+ }
137
+ throw new error_1.CloudBaseError(`创建企业版 TCR 命名空间 ${namespace} 失败。`, {
138
+ code: types_1.FUNCTION_DEPLOY_ERROR.TCR_NAMESPACE_CREATE_FAILED,
139
+ requestId: errorRequestId(error) || ''
140
+ });
141
+ }
142
+ }
143
+ async function prepareEnterpriseTcrTarget(config, options) {
144
+ var _a;
145
+ const build = config.imageConfig.build;
146
+ const configuredRegistryId = (_a = config.imageConfig.registryId) !== null && _a !== void 0 ? _a : build.registryId;
147
+ const explicitRegistryId = typeof configuredRegistryId === 'string' && configuredRegistryId.trim()
148
+ ? configuredRegistryId.trim()
149
+ : undefined;
150
+ const resolvedRegistry = await resolveRegistry(options.service, options.region, explicitRegistryId);
151
+ const repositoryTarget = parseRepositoryTarget(build.repository || config.name);
152
+ const namespace = (build.namespace || repositoryTarget.namespace || options.envId).trim();
153
+ assertNamespace(namespace);
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);
156
+ }
157
+ await ensureNamespace(options.service, resolvedRegistry.registryId, namespace);
158
+ const registry = repositoryTarget.registry || resolvedRegistry.registry;
159
+ const repository = repositoryTarget.repository || config.name;
160
+ const imageRepository = `${registry}/${namespace}/${repository}`;
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 }) }) });
162
+ return {
163
+ config: preparedConfig,
164
+ target: {
165
+ registryId: resolvedRegistry.registryId,
166
+ registry,
167
+ namespace,
168
+ repository,
169
+ imageRepository
170
+ }
171
+ };
172
+ }
173
+ async function createEnterpriseTcrCredential(service, registryId) {
174
+ let result;
175
+ try {
176
+ result = await service.call({
177
+ Action: 'CreateInstanceToken',
178
+ Param: { RegistryId: registryId, TokenType: 'temp' }
179
+ });
180
+ }
181
+ catch (error) {
182
+ throw new error_1.CloudBaseError('获取企业版 TCR 临时登录凭证失败,请确认当前身份具备 CreateInstanceToken 权限。', {
183
+ code: types_1.FUNCTION_DEPLOY_ERROR.TCR_TOKEN_CREATE_FAILED,
184
+ requestId: errorRequestId(error) || ''
185
+ });
186
+ }
187
+ const response = responseOf(result);
188
+ if (typeof response.Username !== 'string' || !response.Username || typeof response.Token !== 'string' || !response.Token) {
189
+ throwTcrError('CreateInstanceToken 未返回有效的 Username 或 Token。', types_1.FUNCTION_DEPLOY_ERROR.TCR_TOKEN_INVALID, requestIdOf(result));
190
+ }
191
+ return { username: response.Username, token: response.Token };
192
+ }
193
+ function createTcrApiService(environment) {
194
+ return environment.getCommonService('tcr', TCR_API_VERSION);
195
+ }