@cloudbase/manager-node 5.8.1 → 5.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/lib/context.js +2 -1
  3. package/lib/database/index.js +11 -6
  4. package/lib/deploy/DeployOrchestrator.js +30 -16
  5. package/lib/deploy/FunctionDeployer.js +269 -39
  6. package/lib/deploy/StaticDeployer.js +7 -1
  7. package/lib/deploy/function/builders/cloud.js +45 -348
  8. package/lib/deploy/function/cloud-build-service.js +2 -16
  9. package/lib/deploy/function/config-guard.js +42 -32
  10. package/lib/deploy/function/enterprise-tcr.js +194 -0
  11. package/lib/deploy/function/image-tag.js +31 -0
  12. package/lib/deploy/function/local-builder.js +170 -25
  13. package/lib/deploy/function/personal-tcr.js +153 -0
  14. package/lib/deploy/types.js +11 -0
  15. package/lib/hosting/index.js +27 -0
  16. package/lib/index.js +4 -3
  17. package/lib/projectValidator/index.js +22 -20
  18. package/lib/utils/cloud-api-request.js +26 -2
  19. package/package.json +2 -2
  20. package/types/context.d.ts +17 -3
  21. package/types/database/index.d.ts +1 -0
  22. package/types/deploy/DeployOrchestrator.d.ts +10 -0
  23. package/types/deploy/FunctionDeployer.d.ts +20 -2
  24. package/types/deploy/StaticDeployer.d.ts +11 -0
  25. package/types/deploy/function/builders/cloud.d.ts +6 -39
  26. package/types/deploy/function/enterprise-tcr.d.ts +32 -0
  27. package/types/deploy/function/image-tag.d.ts +10 -0
  28. package/types/deploy/function/local-builder.d.ts +35 -6
  29. package/types/deploy/function/personal-tcr.d.ts +35 -0
  30. package/types/deploy/types.d.ts +34 -8
  31. package/types/hosting/index.d.ts +13 -0
  32. package/types/index.d.ts +5 -0
  33. package/types/projectValidator/index.d.ts +3 -2
  34. package/types/utils/cloud-api-request.d.ts +3 -2
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PersonalTcrService = exports.DEFAULT_PERSONAL_TCR_REGISTRY = void 0;
4
+ exports.createPersonalTcrService = createPersonalTcrService;
5
+ const config_guard_1 = require("./config-guard");
6
+ const PERSONAL_TCR_VERSION = '2019-09-24';
7
+ exports.DEFAULT_PERSONAL_TCR_REGISTRY = 'ccr.ccs.tencentyun.com';
8
+ const DEFAULT_RETAINED_TAGS = 30;
9
+ const ALREADY_EXISTS_CODES = new Set([
10
+ 'InvalidParameter.ErrNamespaceExist',
11
+ 'InvalidParameter.ErrRepoExist',
12
+ 'ResourceInUse.NamespaceExist',
13
+ 'ResourceInUse.RepositoryExist',
14
+ 'FailedOperation.AlreadyExists',
15
+ 'ResourceConflict'
16
+ ]);
17
+ function dataOf(response) {
18
+ var _a;
19
+ return (response === null || response === void 0 ? void 0 : response.Data) || ((_a = response === null || response === void 0 ? void 0 : response.Response) === null || _a === void 0 ? void 0 : _a.Data) || (response === null || response === void 0 ? void 0 : response.Response) || response || {};
20
+ }
21
+ function getErrorCode(error) {
22
+ var _a, _b;
23
+ return (error === null || error === void 0 ? void 0 : error.code) || (error === null || error === void 0 ? void 0 : error.Code) || ((_b = (_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.Error) === null || _b === void 0 ? void 0 : _b.Code) || '';
24
+ }
25
+ function toTagTimestamp(item) {
26
+ const value = item.PushTime || item.CreationTime;
27
+ if (!value) {
28
+ return 0;
29
+ }
30
+ const timestamp = Date.parse(value);
31
+ return Number.isFinite(timestamp) ? timestamp : 0;
32
+ }
33
+ function resolvePersonalRepoName(namespace, repository) {
34
+ const parsed = (0, config_guard_1.parseImageReference)(repository);
35
+ if (parsed === null || parsed === void 0 ? void 0 : parsed.registry) {
36
+ return parsed.repository;
37
+ }
38
+ if (parsed === null || parsed === void 0 ? void 0 : parsed.repository.includes('/')) {
39
+ return parsed.repository;
40
+ }
41
+ return `${namespace}/${repository}`;
42
+ }
43
+ function isAlreadyExists(error) {
44
+ const code = getErrorCode(error);
45
+ const message = (error === null || error === void 0 ? void 0 : error.message) || (error === null || error === void 0 ? void 0 : error.Message) || '';
46
+ return (ALREADY_EXISTS_CODES.has(code) ||
47
+ /Err(?:Namespace|Repo)Exist|AlreadyExists|ResourceInUse|ResourceConflict/i.test(code) ||
48
+ /already exists?|has already (?:exist|register)|已存在/i.test(message));
49
+ }
50
+ class PersonalTcrService {
51
+ constructor(environment) {
52
+ const service = environment.getCommonService('tcr', PERSONAL_TCR_VERSION);
53
+ this.request = (action, data = {}) => service.call({ Action: action, Param: data });
54
+ }
55
+ async ensurePushTarget(options) {
56
+ const { namespace, repository, ensureNamespace, ensureRepository } = options;
57
+ if (ensureNamespace && !(await this.namespaceExists(namespace))) {
58
+ await this.createIgnoringExisting('CreateNamespacePersonal', {
59
+ Namespace: namespace
60
+ });
61
+ }
62
+ if (ensureRepository && !(await this.repositoryExists(namespace, repository))) {
63
+ await this.createIgnoringExisting('CreateRepositoryPersonal', {
64
+ RepoName: `${namespace}/${repository}`,
65
+ Public: 0,
66
+ Description: 'Managed by CloudBase CLI'
67
+ });
68
+ }
69
+ }
70
+ async cleanupTags(options) {
71
+ var _a;
72
+ const retain = (_a = options.retain) !== null && _a !== void 0 ? _a : DEFAULT_RETAINED_TAGS;
73
+ const repoName = resolvePersonalRepoName(options.namespace, options.repository);
74
+ const tags = await this.listAllTags(repoName);
75
+ const staleTags = tags
76
+ .filter(item => item.TagName)
77
+ .sort((left, right) => toTagTimestamp(right) - toTagTimestamp(left))
78
+ .slice(retain);
79
+ for (const item of staleTags) {
80
+ try {
81
+ await this.request('DeleteImagePersonal', {
82
+ RepoName: repoName,
83
+ Tag: item.TagName
84
+ });
85
+ }
86
+ catch (error) {
87
+ const code = getErrorCode(error);
88
+ if (!/not.?found|not.?exist/i.test(code) && !/不存在/i.test((error === null || error === void 0 ? void 0 : error.message) || '')) {
89
+ throw error;
90
+ }
91
+ }
92
+ }
93
+ }
94
+ async namespaceExists(namespace) {
95
+ const response = await this.request('DescribeNamespacePersonal', {
96
+ Namespace: namespace,
97
+ Limit: 100,
98
+ Offset: 0
99
+ });
100
+ const data = dataOf(response);
101
+ const namespaces = Array.isArray(data.NamespaceInfo)
102
+ ? data.NamespaceInfo
103
+ : [];
104
+ return namespaces.some(item => item.Namespace === namespace);
105
+ }
106
+ async repositoryExists(namespace, repository) {
107
+ const repoName = `${namespace}/${repository}`;
108
+ const response = await this.request('DescribeRepositoryFilterPersonal', {
109
+ RepoName: repoName,
110
+ Limit: 100,
111
+ Offset: 0
112
+ });
113
+ const data = dataOf(response);
114
+ const repositories = Array.isArray(data.RepoInfo)
115
+ ? data.RepoInfo
116
+ : [];
117
+ return repositories.some(item => item.RepoName === repoName);
118
+ }
119
+ async createIgnoringExisting(action, data) {
120
+ try {
121
+ await this.request(action, data);
122
+ }
123
+ catch (error) {
124
+ if (!isAlreadyExists(error)) {
125
+ throw error;
126
+ }
127
+ }
128
+ }
129
+ async listAllTags(repoName) {
130
+ var _a;
131
+ const result = [];
132
+ const limit = 100;
133
+ let offset = 0;
134
+ while (true) {
135
+ const response = await this.request('DescribeImagePersonal', {
136
+ RepoName: repoName,
137
+ Limit: limit,
138
+ Offset: offset
139
+ });
140
+ const data = (response === null || response === void 0 ? void 0 : response.Data) || ((_a = response === null || response === void 0 ? void 0 : response.Response) === null || _a === void 0 ? void 0 : _a.Data) || {};
141
+ const page = data.TagInfo || [];
142
+ result.push(...page);
143
+ offset += page.length;
144
+ if (!page.length || offset >= Number(data.TagCount || result.length)) {
145
+ return result;
146
+ }
147
+ }
148
+ }
149
+ }
150
+ exports.PersonalTcrService = PersonalTcrService;
151
+ function createPersonalTcrService(environment) {
152
+ return new PersonalTcrService(environment);
153
+ }
@@ -55,6 +55,17 @@ exports.FUNCTION_DEPLOY_ERROR = {
55
55
  BUILD_ARG_INVALID: 'BUILD_ARG_INVALID',
56
56
  CLOUD_REGISTRY_CREDENTIAL_MISSING: 'CLOUD_REGISTRY_CREDENTIAL_MISSING',
57
57
  CLOUD_REGISTRY_CREDENTIAL_INVALID: 'CLOUD_REGISTRY_CREDENTIAL_INVALID',
58
+ TCR_REGION_MISSING: 'TCR_REGION_MISSING',
59
+ TCR_INSTANCE_DISCOVERY_FAILED: 'TCR_INSTANCE_DISCOVERY_FAILED',
60
+ TCR_INSTANCE_NOT_FOUND: 'TCR_INSTANCE_NOT_FOUND',
61
+ TCR_INSTANCE_AMBIGUOUS: 'TCR_INSTANCE_AMBIGUOUS',
62
+ TCR_INSTANCE_INVALID: 'TCR_INSTANCE_INVALID',
63
+ TCR_NAMESPACE_INVALID: 'TCR_NAMESPACE_INVALID',
64
+ TCR_NAMESPACE_QUERY_FAILED: 'TCR_NAMESPACE_QUERY_FAILED',
65
+ TCR_NAMESPACE_CREATE_FAILED: 'TCR_NAMESPACE_CREATE_FAILED',
66
+ TCR_TOKEN_CREATE_FAILED: 'TCR_TOKEN_CREATE_FAILED',
67
+ TCR_TOKEN_INVALID: 'TCR_TOKEN_INVALID',
68
+ TCR_DOCKER_LOGIN_FAILED: 'TCR_DOCKER_LOGIN_FAILED',
58
69
  GATEWAY_PATH_INVALID: 'GATEWAY_PATH_INVALID',
59
70
  LOCAL_FALLBACK_INVALID: 'LOCAL_FALLBACK_INVALID',
60
71
  // Preflight(有 IO 的本地检查)
@@ -348,19 +348,35 @@ class HostingService {
348
348
  }
349
349
  /**
350
350
  * 查询域名状态信息
351
+ *
352
+ * 仅在 CDN 域名(domainType=cdn)或老环境(未返回 domainType)时调用 CDN 接口,
353
+ * 其他域名类型(gateway / eo 等)返回空结果
351
354
  * @param options
352
355
  */
353
356
  async tcbCheckResource(options) {
357
+ const domainType = await this.getStorageDomainType();
358
+ // 白名单:只有 cdn 或空值(老环境)才调用 CDN 接口
359
+ if (domainType !== 'cdn' && domainType) {
360
+ return { Domains: [], RecordCount: 0, RequestId: '' };
361
+ }
354
362
  return this.cdnService.request('TcbCheckResource', {
355
363
  Domains: options.domains
356
364
  });
357
365
  }
358
366
  /**
359
367
  * 域名配置变更
368
+ *
369
+ * 仅在 CDN 域名(domainType=cdn)或老环境(未返回 domainType)时调用 CDN 接口,
370
+ * 其他域名类型(gateway / eo 等)返回空结果
360
371
  * @param options
361
372
  */
362
373
  async tcbModifyAttribute(options) {
363
374
  const { domain, domainId, domainConfig } = options;
375
+ const domainType = await this.getStorageDomainType();
376
+ // 白名单:只有 cdn 或空值(老环境)才调用 CDN 接口
377
+ if (domainType !== 'cdn' && domainType) {
378
+ return { RequestId: '' };
379
+ }
364
380
  const res = await this.cdnService.request('TcbModifyAttribute', {
365
381
  Domain: domain,
366
382
  DomainId: domainId,
@@ -445,6 +461,17 @@ class HostingService {
445
461
  }
446
462
  return cloudPath[cloudPath.length - 1] === '/' ? cloudPath : `${cloudPath}/`;
447
463
  }
464
+ /**
465
+ * 获取静态托管域名类型(cdn / gateway / eo)
466
+ *
467
+ * 后端通过 DescribeEnvInfo 返回 StaticStorages[0].DomainType,
468
+ * 仅 cdn 或空值(老环境)才允许调用 CDN 相关接口。
469
+ */
470
+ async getStorageDomainType() {
471
+ var _a, _b;
472
+ const { EnvInfo } = await this.environment.getEnvService().getEnvInfo();
473
+ return (_b = (_a = EnvInfo === null || EnvInfo === void 0 ? void 0 : EnvInfo.StaticStorages) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.DomainType;
474
+ }
448
475
  }
449
476
  exports.HostingService = HostingService;
450
477
  __decorate([
package/lib/index.js CHANGED
@@ -21,9 +21,9 @@ class CloudBase {
21
21
  }
22
22
  constructor(config = {}) {
23
23
  this.cloudBaseConfig = {};
24
- let { secretId, secretKey, token, envId, proxy, region, envType, useInternalEndpoint, internalEndpointRegion } = config;
25
- // config 中传入的 secretId secretkey 必须同时存在
26
- if ((secretId && !secretKey) || (!secretId && secretKey)) {
24
+ let { secretId, secretKey, token, envId, proxy, region, envType, useInternalEndpoint, internalEndpointRegion, requestFn } = config;
25
+ // config 中传入的 secretId secretkey 必须同时存在(有 requestFn 时可以不传)
26
+ if (!requestFn && ((secretId && !secretKey) || (!secretId && secretKey))) {
27
27
  throw new Error('secretId and secretKey must be a pair');
28
28
  }
29
29
  this.cloudBaseConfig = {
@@ -36,6 +36,7 @@ class CloudBase {
36
36
  region,
37
37
  useInternalEndpoint,
38
38
  internalEndpointRegion,
39
+ requestFn,
39
40
  };
40
41
  // 初始化 context
41
42
  this.context = new context_1.CloudBaseContext(this.cloudBaseConfig);
@@ -92,12 +92,25 @@ class ProjectValidator {
92
92
  }
93
93
  validateSchema(config, errors) {
94
94
  const result = (0, toolbox_1.validateCloudBaseConfigBySchema)(config);
95
- result.errors.forEach((error) => {
95
+ result.errors
96
+ .filter((error) => error.keyword !== 'if')
97
+ .forEach((error) => {
98
+ const errorParams = error.params;
99
+ const missingProperty = error.keyword === 'required' && typeof errorParams.missingProperty === 'string'
100
+ ? errorParams.missingProperty
101
+ : undefined;
102
+ const baseField = this.normalizeSchemaPath(error.dataPath);
103
+ const field = [baseField, missingProperty].filter(Boolean).join('.');
104
+ const isImageTypeMissing = missingProperty === 'imageType';
96
105
  errors.push({
97
106
  level: 'error',
98
- field: this.normalizeSchemaPath(error.dataPath),
99
- message: `配置不符合 v2.1 Schema:${error.message}`,
100
- fix: `请检查 ${this.normalizeSchemaPath(error.dataPath) || 'cloudbaserc'} 字段`,
107
+ field,
108
+ message: isImageTypeMissing
109
+ ? 'local/cloud 镜像构建必须显式声明 imageConfig.imageType'
110
+ : `配置不符合 v2.1 Schema:${error.message}`,
111
+ fix: isImageTypeMissing
112
+ ? '请根据镜像仓库类型填写 imageConfig.imageType: "enterprise" 或 "personal"'
113
+ : `请检查 ${field || 'cloudbaserc'} 字段`,
101
114
  });
102
115
  });
103
116
  }
@@ -133,8 +146,9 @@ class ProjectValidator {
133
146
  });
134
147
  }
135
148
  /**
136
- * 个人版 TCR 的 username/password/namespace 必须在 cloudbaserc 中显式声明。
137
- * ConfigParser 已先解析 {{env.*}},因此这里同时承担缺失 env 值的前置校验。
149
+ * 个人版 TCR 的 username/password 必须在 cloudbaserc 中显式声明。
150
+ * namespace/repository 可省略,分别由部署器按 envId/函数名推导。
151
+ * ConfigParser 已先解析 {{env.*}},因此这里同时承担缺失凭证 env 值的前置校验。
138
152
  */
139
153
  validatePersonalRegistryConfig(config, errors) {
140
154
  ;
@@ -144,16 +158,6 @@ class ProjectValidator {
144
158
  return;
145
159
  }
146
160
  const build = (_a = func.imageConfig) === null || _a === void 0 ? void 0 : _a.build;
147
- const namespace = build === null || build === void 0 ? void 0 : build.namespace;
148
- if (typeof namespace !== 'string' || !namespace.trim()) {
149
- errors.push({
150
- level: 'error',
151
- resource: `functions[${index}]`,
152
- field: 'imageConfig.build.namespace',
153
- message: '个人版 TCR 缺少 namespace,或对应环境变量未赋值',
154
- fix: '声明 namespace: "{{env.TCB_TCR_NAMESPACE}}",并在 .env* 中设置该变量',
155
- });
156
- }
157
161
  const credential = build === null || build === void 0 ? void 0 : build.registryCredential;
158
162
  if (!credential || typeof credential !== 'object') {
159
163
  errors.push({
@@ -190,12 +194,10 @@ class ProjectValidator {
190
194
  return (config.functions || []).some((func) => this.isPersonalCloudFunction(func));
191
195
  }
192
196
  isPersonalCloudFunction(func) {
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');
197
+ var _a;
196
198
  return (func.type === 'HTTP' &&
197
199
  func.buildStrategy === 'cloud' &&
198
- imageType === 'personal');
200
+ ((_a = func.imageConfig) === null || _a === void 0 ? void 0 : _a.imageType) === 'personal');
199
201
  }
200
202
  validateResources(cwd, config) {
201
203
  const errors = [];
@@ -53,12 +53,13 @@ function getDate(timestamp) {
53
53
  }
54
54
  class CloudService {
55
55
  /* eslint-disable-next-line */
56
- constructor(context, service, version, baseParams) {
56
+ constructor(context, service, version, baseParams, requestFn) {
57
57
  this.service = service;
58
58
  this.version = version;
59
59
  this.timeout = 60000;
60
60
  this.baseParams = baseParams || {};
61
61
  this.cloudBaseContext = context;
62
+ this.requestFn = requestFn !== null && requestFn !== void 0 ? requestFn : context === null || context === void 0 ? void 0 : context.requestFn;
62
63
  }
63
64
  get baseUrl() {
64
65
  const internalEndpoint = this.cloudBaseContext.isInternalEndpoint();
@@ -90,9 +91,32 @@ class CloudService {
90
91
  }
91
92
  }
92
93
  async request(action, data = {}, method = 'POST') {
94
+ var _a;
93
95
  this.action = action;
94
- this.data = deepRemoveVoid(Object.assign(Object.assign({}, data), this.baseParams));
95
96
  this.method = method;
97
+ this.data = deepRemoveVoid(Object.assign(Object.assign({}, data), this.baseParams));
98
+ // 如果注入了 requestFn,直接委托,跳过 TC3 签名逻辑
99
+ if (this.requestFn) {
100
+ try {
101
+ const result = await this.requestFn({
102
+ service: this.service,
103
+ action,
104
+ version: this.version,
105
+ region: (_a = this.cloudBaseContext.region) !== null && _a !== void 0 ? _a : '',
106
+ payload: this.data,
107
+ });
108
+ return result;
109
+ }
110
+ catch (e) {
111
+ if (e.name === 'CloudBaseError') {
112
+ throw e;
113
+ }
114
+ throw new error_1.CloudBaseError(e.message, {
115
+ action,
116
+ code: e.code
117
+ });
118
+ }
119
+ }
96
120
  this.url = this.baseUrl;
97
121
  let { secretId, secretKey, token } = this.cloudBaseContext;
98
122
  // 当在云函数环境下执行时,可init时不传入密钥,取环境变量中密钥使用
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudbase/manager-node",
3
- "version": "5.8.1",
3
+ "version": "5.8.3",
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",
45
+ "@cloudbase/toolbox": "~0.8.4",
46
46
  "@cloudbase/database": "^0.6.2",
47
47
  "archiver": "^3.1.1",
48
48
  "cos-nodejs-sdk-v5": "^2.14.0",
@@ -1,3 +1,15 @@
1
+ /**
2
+ * 自定义 API 请求函数,由外部注入,替代 TC3 签名发请求。
3
+ * 入参对齐 CAPI 模式:service + action + version + region + payload。
4
+ * 返回腾讯云 API 响应中 Response 字段的内容(已解包,不含 Response 包装层)。
5
+ */
6
+ export type CloudApiRequestFn = (params: {
7
+ service: string;
8
+ action: string;
9
+ version: string;
10
+ region: string;
11
+ payload: Record<string, any>;
12
+ }) => Promise<any>;
1
13
  export declare class CloudBaseContext {
2
14
  readonly secretId: string;
3
15
  readonly secretKey: string;
@@ -8,15 +20,17 @@ export declare class CloudBaseContext {
8
20
  readonly envType: string;
9
21
  readonly useInternalEndpoint?: boolean;
10
22
  readonly internalEndpointRegion?: string;
11
- constructor({ secretId, secretKey, token, proxy, region, envType, useInternalEndpoint, internalEndpointRegion }: {
23
+ readonly requestFn?: CloudApiRequestFn;
24
+ constructor({ secretId, secretKey, token, proxy, region, envType, useInternalEndpoint, internalEndpointRegion, requestFn }: {
12
25
  secretId?: string;
13
26
  secretKey?: string;
14
27
  token?: string;
15
28
  proxy?: string;
16
29
  region?: string;
17
30
  envType?: string;
18
- useInternalEndpoint?: any;
19
- internalEndpointRegion?: any;
31
+ useInternalEndpoint?: boolean;
32
+ internalEndpointRegion?: string;
33
+ requestFn?: CloudApiRequestFn;
20
34
  });
21
35
  isInternalEndpoint(): boolean;
22
36
  getInternalEndpointRegion(): string;
@@ -269,5 +269,6 @@ export declare class DatabaseService {
269
269
  * @returns 任务状态详情(TaskType、Status、Phase、Reason 等)
270
270
  */
271
271
  describeTaskResult(options: IDescribeTaskResultOptions): Promise<IDescribeTaskResultResult>;
272
+ private getDefaultDatabaseInstanceId;
272
273
  }
273
274
  export {};
@@ -1,4 +1,5 @@
1
1
  import { Environment } from '../environment';
2
+ import { IDeployProgressEvent } from './types';
2
3
  /** 可编排的资源类型 */
3
4
  export type ResourceType = 'database' | 'functions' | 'app' | 'hosting' | 'gateway';
4
5
  /** 声明式部署选项 */
@@ -24,6 +25,11 @@ export interface IDeployOptions {
24
25
  concurrency?: number;
25
26
  /** 失败后继续部署其余资源(database 失败仍强制中断) */
26
27
  continueOnError?: boolean;
28
+ /** 函数资源内部部署阶段进度(校验、构建、推送、部署、等待可用、访问配置) */
29
+ onProgress?: (event: IDeployProgressEvent & {
30
+ resourceType: ResourceType;
31
+ resourceName: string;
32
+ }) => void;
27
33
  log?: {
28
34
  info?: (msg: string) => void;
29
35
  success?: (msg: string) => void;
@@ -66,7 +72,11 @@ export interface IDeployResult {
66
72
  name: string;
67
73
  ok: boolean;
68
74
  url?: string;
75
+ /** 资源部署器返回的结构化详情(函数包含策略、镜像、访问地址、阶段状态和耗时) */
76
+ detail?: Record<string, any>;
69
77
  error?: string;
78
+ errorCode?: string;
79
+ failedStage?: string;
70
80
  /** 原始异常中的请求 ID(若可提取),用于定位平台侧接口报错 */
71
81
  requestId?: string;
72
82
  /** 未执行原因:skip=未变更自动跳过;no-confirm=无确认机制保守跳过;skipped-by-user=用户拒绝覆盖 */
@@ -1,6 +1,9 @@
1
1
  import { Environment } from '../environment';
2
2
  import { ICloudBuildService } from './function/builders/cloud';
3
3
  import { IImagePreflightDeps } from './function/image-preflight';
4
+ import { ILocalBuilderDeps } from './function/local-builder';
5
+ import { ITcrApiService } from './function/enterprise-tcr';
6
+ import { IPersonalTcrService } from './function/personal-tcr';
4
7
  import { ICheckReport, IFunctionDeployConfig, IFunctionDeployOptions, IFunctionDeployPlan, IFunctionDeployResult, INormalizedDeployConfig } from './types';
5
8
  interface ILegacyFunctionDeployResult {
6
9
  name: string;
@@ -16,7 +19,7 @@ interface ILegacyFunctionDeployResult {
16
19
  *
17
20
  * 已支持代码包、已有镜像、云端构建、本地构建、匿名访问与网关路径收敛;
18
21
  * 函数类型、运行时或协议等创建期字段变化时不会自动删除函数,而是引导用户
19
- * 在控制台确认并手工删除后重新部署。local 企业版 TCR 等未实现能力会显式报错。
22
+ * 在控制台确认并手工删除后重新部署。
20
23
  */
21
24
  export declare class FunctionDeployer {
22
25
  private readonly environment;
@@ -28,9 +31,15 @@ export declare class FunctionDeployer {
28
31
  * 测试可注入以替换打包/网络/tcb API,避免真实 IO
29
32
  */
30
33
  private readonly cloudBuildServiceFactory;
34
+ private readonly localBuilderDeps?;
35
+ private readonly tcrServiceFactory;
36
+ private readonly personalTcrServiceFactory;
31
37
  constructor(environment: Environment, deps?: {
32
38
  imagePreflightDeps?: IImagePreflightDeps;
33
39
  cloudBuildServiceFactory?: () => ICloudBuildService;
40
+ localBuilderDeps?: ILocalBuilderDeps;
41
+ tcrServiceFactory?: () => ITcrApiService;
42
+ personalTcrServiceFactory?: () => IPersonalTcrService;
34
43
  });
35
44
  /**
36
45
  * 兼容既有声明式部署入口
@@ -101,6 +110,8 @@ export declare class FunctionDeployer {
101
110
  * 由image-preflight 自动跳过地域校验,不误报。
102
111
  */
103
112
  private resolveDeployRegion;
113
+ private resolveEnvironmentId;
114
+ private requireDeployRegion;
104
115
  /**
105
116
  * 构造镜像拉取授权 Preflight 所需的 CAM 适配器
106
117
  *
@@ -125,6 +136,8 @@ export declare class FunctionDeployer {
125
136
  * 未实现的能力必须显式报错,避免用户误认为访问规则或镜像构建已经生效
126
137
  */
127
138
  private assertPlanExecutable;
139
+ private prepareEnterpriseBuildConfig;
140
+ private preparePersonalBuildConfig;
128
141
  /**
129
142
  * 准备部署物
130
143
  *
@@ -133,14 +146,19 @@ export declare class FunctionDeployer {
133
146
  * 其余策略走同步的 pack。
134
147
  */
135
148
  private prepareArtifact;
149
+ private resolveLocalRegistryCredential;
150
+ private resolveRegistryFromRepository;
136
151
  /** 生成部署物 */
137
152
  private createArtifact;
138
153
  /** 执行部署计划 */
139
154
  private applyPlan;
140
155
  /** 收敛 HTTP 函数访问配置(匿名访问 + 网关路由) */
141
156
  private applyAccessPlan;
142
- /** 执行单个步骤并记录耗时与进度 */
157
+ /** 执行普通步骤并记录耗时与进度 */
143
158
  private runStep;
159
+ /** 执行带具体名称的步骤并记录耗时与进度 */
160
+ private runLabeledStep;
161
+ private executeStep;
144
162
  /** 上报进度,回调异常不影响部署流程 */
145
163
  private emitProgress;
146
164
  /** 汇总部署结果 */
@@ -28,6 +28,17 @@ export interface IHostingDeployConfig {
28
28
  ignore?: string[];
29
29
  /** 构建时环境变量(注入本地构建进程,非敏感) */
30
30
  envVariables?: Record<string, string | number | boolean>;
31
+ /**
32
+ * 多页应用(MPA)入口文件列表,相对于产物目录,默认 ["index.html"]。
33
+ * 一致性上传时用于识别入口文件(内部映射为 uploadFiles 的 entryFiles)。
34
+ */
35
+ entryFile?: string[];
36
+ /** 发布后校验远端文件与本地产物一致(开启后走一致性上传) */
37
+ verify?: boolean;
38
+ /** 发布前备份并在上传或校验失败时自动回滚(开启后走一致性上传) */
39
+ safe?: boolean;
40
+ /** 发布成功后清理远端冗余文件(危险操作,开启后走一致性上传) */
41
+ prune?: boolean;
31
42
  }
32
43
  export interface IStaticDeployResult {
33
44
  name: string;
@@ -1,5 +1,6 @@
1
1
  import { ICustomBuildEnv, ICustomBuildSecret, ICustomBuildStep, CloudAppArtifact, CloudAppBuildStep } from '../../../cloudApp/types';
2
2
  import { INormalizedHttpCloudConfig } from '../../types';
3
+ import { IEnterpriseTcrCredential } from '../enterprise-tcr';
3
4
  export interface ICloudBuildResult {
4
5
  imageUri: string;
5
6
  imageDigest?: string;
@@ -39,26 +40,13 @@ export interface ICloudVersionStatus {
39
40
  /** DescribeCloudAppVersion 接口返回的 RequestId,便于把构建问题反馈给平台后端定位 */
40
41
  requestId?: string;
41
42
  }
42
- /** 需要额外注入到构建 zip 的文件条目(zip 内路径 + 内容 + 可选 unix 权限位) */
43
- export interface ICloudBuildExtraEntry {
44
- /** zip 包内的相对路径,如 scripts/tcr-login-and-push.sh */
45
- zipPath: string;
46
- /** 文件文本内容 */
47
- content: string;
48
- /** unix 权限位(八进制),如 0o755;脚本需可执行 */
49
- mode?: number;
50
- }
51
43
  /**
52
44
  * 云端构建所需的外部能力,由编排器注入
53
45
  *每个方法对应构建方案(https://iwiki.woa.com/p/4020519533)的一个 API/IO 步骤
54
46
  */
55
47
  export interface ICloudBuildService {
56
- /**
57
- * 打包构建上下文为 ZIP,返回本地临时 zip 路径
58
- * @param cwd 构建上下文目录
59
- * @param extraEntries 额外注入zip 的文件(如 push 脚本),不写入用户源码目录
60
- */
61
- packContext: (cwd: string, extraEntries?: ICloudBuildExtraEntry[]) => Promise<string>;
48
+ /** 打包构建上下文为 ZIP,返回本地临时 zip 路径 */
49
+ packContext: (cwd: string) => Promise<string>;
62
50
  /** 获取 COS 上传凭证(DescribeCloudAppCosInfo,DeployType:custom) */
63
51
  getCosInfo: (serviceName: string) => Promise<ICloudCosInfo>;
64
52
  /** PUT 上传 zip 到 COS,必须携带全部 uploadHeaders */
@@ -72,39 +60,18 @@ export interface ICloudBuildService {
72
60
  /** 延时(便于测试注入立即返回) */
73
61
  delay: (ms: number) => Promise<void>;
74
62
  }
75
- /** 注入到构建上下文 zip 中的推送脚本相对路径(与 CustomSteps 的 push命令一致) */
76
- export declare const TCR_PUSH_SCRIPT_ZIP_PATH = "scripts/tcr-login-and-push.sh";
77
- /**
78
- * 注入到构建 zip 的 TCR/CCR 登录推送脚本(完整可运行)
79
- *
80
- * 对应构建方案(https://iwiki.woa.com/p/4020519533)「阶段 A」的 push 步骤:
81
- * push-image 步骤执行 `bash./scripts/tcr-login-and-push.sh`,该脚本必须随源码
82
- * 一同打进zip 根目录,否则云端会 `No such file or directory` 秒挂。
83
- *
84
- * 关键设计:
85
- * - 地域参数化:REGION 读环境变量 $TCR_REGION(由 buildEnv 按部署地域注入),
86
- * 避免硬编码 ap-shanghai跨地域时 AuthFailure.SignatureFailure。
87
- * - 双仓库兼容:设置了 $TCR_INSTANCE_ID 走企业版 TCR CreateInstanceToken;
88
- * 否则走个人版 CCR,用户名来自普通 Env,固定密码来自 CloudApp Secrets。
89
- * - 安全红线:凭证一律经 --password-stdin 传入,绝不出现在 ps/日志;
90
- * 不打印任何 $API_SECRET_* / token / $SECRET_TCR_PASSWORD_B64 或解码后的密码。
91
- *
92
- * 脚本内容为SDK 固定模板,不拼接任何用户输入(镜像地址等均由已校验的
93
- * 环境变量在容器内组装),无命令注入面。
94
- */
95
- export declare const TCR_LOGIN_PUSH_SCRIPT = "#!/usr/bin/env bash\n# scripts/tcr-login-and-push.sh\n# \u7531 CloudBase SDK \u81EA\u52A8\u6CE8\u5165\uFF1A\u4F01\u4E1A\u7248\u4F7F\u7528 STS \u6362\u53D6\u4E34\u65F6 token\uFF0C\u4E2A\u4EBA\u7248\u4F7F\u7528 CloudApp Secret\nset -euo pipefail\n\n: \"${TCR_REGISTRY:?TCR_REGISTRY \u672A\u8BBE\u7F6E}\"\n: \"${TCR_NAMESPACE:?TCR_NAMESPACE \u672A\u8BBE\u7F6E}\"\n: \"${CLOUDBASE_SERVICE_NAME:?\u5E73\u53F0\u53D8\u91CF\u7F3A\u5931}\"\n: \"${CLOUDBASE_VERSION_NAME:?\u5E73\u53F0\u53D8\u91CF\u7F3A\u5931}\"\n\nREGION=\"${TCR_REGION:-ap-shanghai}\"\nIMAGE=\"${TCR_REGISTRY}/${TCR_NAMESPACE}/${CLOUDBASE_SERVICE_NAME}:${CLOUDBASE_VERSION_NAME}\"\nIS_PERSONAL_REGISTRY=0\n\nif [ -n \"${TCR_INSTANCE_ID:-}\" ]; then\n : \"${API_SECRET_ID:?STS \u51ED\u8BC1\u7F3A\u5931}\"\n : \"${API_SECRET_KEY:?STS \u51ED\u8BC1\u7F3A\u5931}\"\n : \"${API_TOKEN:?STS \u51ED\u8BC1\u7F3A\u5931}\"\n\n # ---- \u4F01\u4E1A\u7248 TCR\uFF1ACreateInstanceToken \u6362\u767B\u5F55\u6001 ----\n HOST=\"tcr.tencentcloudapi.com\"\n SERVICE=\"tcr\"\n VERSION=\"2019-09-24\"\n ACTION=\"CreateInstanceToken\"\n ALGORITHM=\"TC3-HMAC-SHA256\"\n TIMESTAMP=$(date +%s)\n DATE=$(date -u -d \"@${TIMESTAMP}\" +\"%Y-%m-%d\" 2>/dev/null || date -u -r \"${TIMESTAMP}\" +\"%Y-%m-%d\")\n\n # \u6784\u5EFA\u9636\u6BB5\u53EA\u9700\u8981\u4E00\u6B21 docker login/push\uFF0C\u4F7F\u7528 1 \u5C0F\u65F6\u4E34\u65F6\u51ED\u8BC1\uFF0C\u907F\u514D\u521B\u5EFA\u957F\u671F\u51ED\u8BC1\u3002\n PAYLOAD=$(printf '{\"RegistryId\":\"%s\",\"TokenType\":\"temp\"}' \"${TCR_INSTANCE_ID}\")\n HASHED_PAYLOAD=$(printf '%s' \"${PAYLOAD}\" | openssl dgst -sha256 -hex | awk '{print $NF}')\n CANONICAL_HEADERS=\"content-type:application/json; charset=utf-8\\nhost:${HOST}\\nx-tc-action:$(echo \"${ACTION}\" | tr '[:upper:]' '[:lower:]')\\n\"\n SIGNED_HEADERS=\"content-type;host;x-tc-action\"\n CANONICAL_REQUEST=\"POST\\n/\\n\\n${CANONICAL_HEADERS}\\n${SIGNED_HEADERS}\\n${HASHED_PAYLOAD}\"\n\n CREDENTIAL_SCOPE=\"${DATE}/${SERVICE}/tc3_request\"\n HASHED_CR=$(printf \"${CANONICAL_REQUEST}\" | openssl dgst -sha256 -hex | awk '{print $NF}')\n STRING_TO_SIGN=\"${ALGORITHM}\\n${TIMESTAMP}\\n${CREDENTIAL_SCOPE}\\n${HASHED_CR}\"\n\n secret_date=$(printf '%s' \"${DATE}\" | openssl dgst -sha256 -hmac \"TC3${API_SECRET_KEY}\" -hex | awk '{print $NF}')\n secret_svc=$(printf '%s' \"${SERVICE}\" | openssl dgst -sha256 -mac HMAC -macopt hexkey:\"${secret_date}\" -hex | awk '{print $NF}')\n secret_sign=$(printf '%s' \"tc3_request\" | openssl dgst -sha256 -mac HMAC -macopt hexkey:\"${secret_svc}\" -hex | awk '{print $NF}')\n SIGNATURE=$(printf \"${STRING_TO_SIGN}\" | openssl dgst -sha256 -mac HMAC -macopt hexkey:\"${secret_sign}\" -hex | awk '{print $NF}')\n\n AUTHZ=\"${ALGORITHM} Credential=${API_SECRET_ID}/${CREDENTIAL_SCOPE}, SignedHeaders=${SIGNED_HEADERS}, Signature=${SIGNATURE}\"\n\n RESP=$(curl -sS -X POST \"https://${HOST}\" \\\n -H \"Authorization: ${AUTHZ}\" \\\n -H \"Content-Type: application/json; charset=utf-8\" \\\n -H \"Host: ${HOST}\" \\\n -H \"X-TC-Action: ${ACTION}\" \\\n -H \"X-TC-Timestamp: ${TIMESTAMP}\" \\\n -H \"X-TC-Version: ${VERSION}\" \\\n -H \"X-TC-Region: ${REGION}\" \\\n -H \"X-TC-Token: ${API_TOKEN}\" \\\n --data \"${PAYLOAD}\")\n\n # CloudApp custom Runner \u5DF2\u63D0\u4F9B awk\uFF08\u4E0A\u9762\u7684 TC3 \u7B7E\u540D\u4E5F\u4F9D\u8D56\u5B83\uFF09\uFF0C\u4F46\u4E0D\u4FDD\u8BC1\u5B89\u88C5 jq\u3002\n # \u8FD9\u91CC\u5148\u9650\u5B9A\u9876\u5C42 Response \u5BF9\u8C61\uFF0C\u518D\u89E3\u6790\u5176\u4E2D\u7684\u975E\u7A7A JSON \u5B57\u7B26\u4E32\u5B57\u6BB5\uFF1B\u51ED\u8BC1\u5B57\u6BB5\n # \u4E0D\u5141\u8BB8 JSON \u8F6C\u4E49\uFF0C\u907F\u514D\u7528 sed/grep \u5BF9\u8BA4\u8BC1\u54CD\u5E94\u505A\u5BBD\u677E\u5339\u914D\u3002\n extract_json_string() {\n local field=\"$1\"\n printf '%s' \"${RESP}\" | JSON_FIELD=\"${field}\" awk '\n BEGIN {\n field = ENVIRON[\"JSON_FIELD\"]\n quote = sprintf(\"%c\", 34)\n backslash = sprintf(\"%c\", 92)\n json = \"\"\n }\n { json = json $0 }\n END {\n response_needle = quote \"Response\" quote \"[[:space:]]*:[[:space:]]*\\{\"\n if (!match(json, response_needle)) exit 1\n response_start = RSTART + RLENGTH - 1\n response = \"\"\n depth = 0\n in_string = 0\n escaped = 0\n for (i = response_start; i <= length(json); i++) {\n ch = substr(json, i, 1)\n response = response ch\n if (in_string) {\n if (escaped) escaped = 0\n else if (ch == backslash) escaped = 1\n else if (ch == quote) in_string = 0\n continue\n }\n if (ch == quote) in_string = 1\n else if (ch == \"{\") depth++\n else if (ch == \"}\") {\n depth--\n if (depth == 0) break\n }\n }\n if (depth != 0) exit 1\n\n depth = 1\n for (i = 2; i < length(response); i++) {\n ch = substr(response, i, 1)\n if (ch == \"{\") depth++\n else if (ch == \"}\") depth--\n else if (ch == quote) {\n key = \"\"\n escaped = 0\n for (j = i + 1; j <= length(response); j++) {\n current = substr(response, j, 1)\n if (escaped) {\n escaped = 0\n key = key current\n } else if (current == backslash) {\n escaped = 1\n key = key current\n } else if (current == quote) break\n else key = key current\n }\n if (j > length(response)) exit 1\n k = j + 1\n while (substr(response, k, 1) ~ /[[:space:]]/) k++\n if (depth == 1 && substr(response, k, 1) == \":\" && key == field) {\n k++\n while (substr(response, k, 1) ~ /[[:space:]]/) k++\n if (substr(response, k, 1) != quote) exit 1\n value = \"\"\n for (k = k + 1; k <= length(response); k++) {\n current = substr(response, k, 1)\n if (current == quote) {\n if (length(value) == 0) exit 1\n printf \"%s\", value\n exit 0\n }\n if (current == backslash) exit 2\n value = value current\n }\n exit 1\n }\n i = j\n }\n }\n exit 1\n }\n '\n }\n\n extract_tcr_error_code() {\n printf '%s' \"${RESP}\" | awk '\n BEGIN {\n quote = sprintf(\"%c\", 34)\n json = \"\"\n }\n { json = json $0 }\n END {\n error_needle = quote \"Error\" quote \"[[:space:]]*:[[:space:]]*\\{\"\n if (!match(json, error_needle)) exit 1\n error_json = substr(json, RSTART + RLENGTH)\n code_needle = quote \"Code\" quote \"[[:space:]]*:[[:space:]]*\" quote\n if (!match(error_json, code_needle)) exit 1\n rest = substr(error_json, RSTART + RLENGTH)\n value = \"\"\n for (i = 1; i <= length(rest); i++) {\n ch = substr(rest, i, 1)\n if (ch == quote) {\n if (length(value) == 0) exit 1\n printf \"%s\", value\n exit 0\n }\n if (ch == backslash) exit 1\n value = value ch\n }\n exit 1\n }\n '\n }\n\n ERROR_CODE=$(extract_tcr_error_code || true)\n if [ -n \"${ERROR_CODE}\" ]; then\n case \"${ERROR_CODE}\" in\n AuthFailure*|UnauthorizedOperation*|FailedOperation.NoPermission*)\n echo \"\u4F01\u4E1A\u7248 TCR \u9274\u6743\u5931\u8D25\uFF0C\u8BF7\u786E\u8BA4\u5F53\u524D\u8EAB\u4EFD\u6709\u6743\u8BBF\u95EE\u8BE5\u5B9E\u4F8B\u5E76\u8C03\u7528 CreateInstanceToken\" >&2\n exit 42\n ;;\n ResourceNotFound*|InvalidParameterValue.RegistryId*)\n echo \"\u4F01\u4E1A\u7248 TCR \u5B9E\u4F8B\u4E0D\u5B58\u5728\u6216\u5B9E\u4F8B ID \u65E0\u6548\" >&2\n exit 43\n ;;\n *)\n echo \"\u4F01\u4E1A\u7248 TCR \u83B7\u53D6\u767B\u5F55\u51ED\u8BC1\u5931\u8D25\uFF1A${ERROR_CODE}\" >&2\n exit 1\n ;;\n esac\n fi\n\n if ! TOKEN_USER=$(extract_json_string \"Username\"); then\n ERROR_CODE=$(extract_tcr_error_code || true)\n echo \"CreateInstanceToken \u672A\u8FD4\u56DE\u6709\u6548 Username${ERROR_CODE:+\uFF0C\u9519\u8BEF\u7801\uFF1A${ERROR_CODE}}\" >&2\n exit 42\n fi\n if ! TOKEN_PASS=$(extract_json_string \"Token\"); then\n ERROR_CODE=$(extract_tcr_error_code || true)\n echo \"CreateInstanceToken \u672A\u8FD4\u56DE\u6709\u6548 Token${ERROR_CODE:+\uFF0C\u9519\u8BEF\u7801\uFF1A${ERROR_CODE}}\" >&2\n exit 42\n fi\n\n printf '%s' \"${TOKEN_PASS}\" | docker login -u \"${TOKEN_USER}\" --password-stdin \"${TCR_REGISTRY}\"\nelse\n # ---- \u4E2A\u4EBA\u7248 CCR\uFF1A\u8D26\u53F7 ID + \u56FA\u5B9A\u5BC6\u7801\uFF08Base64 \u5C01\u88C5\u540E\u7531 CloudApp Secrets \u6CE8\u5165\uFF09 ----\n IS_PERSONAL_REGISTRY=1\n : \"${TCR_USERNAME:?TCR_USERNAME \u672A\u8BBE\u7F6E}\"\n : \"${SECRET_TCR_PASSWORD_B64:?TCR_PASSWORD \u672A\u8BBE\u7F6E}\"\n printf '%s' \"${SECRET_TCR_PASSWORD_B64}\" | base64 --decode | docker login -u \"${TCR_USERNAME}\" --password-stdin \"${TCR_REGISTRY}\"\nfi\n\nPUSH_LOG=$(mktemp)\ntrap 'rm -f \"${PUSH_LOG}\"' EXIT\nset +e\ndocker push \"${IMAGE}\" 2>&1 | tee \"${PUSH_LOG}\"\nPUSH_STATUS=${PIPESTATUS[0]}\nset -e\n\nif [ \"${PUSH_STATUS}\" -ne 0 ]; then\n if [ \"${IS_PERSONAL_REGISTRY}\" -eq 1 ] && {\n grep -Fq 'LimitExceeded.ErrRepoMaxLimit' \"${PUSH_LOG}\" ||\n grep -Fq '\u7528\u6237\u4ED3\u5E93\u5DF2\u7ECF\u8FBE\u5230\u6700\u5927\u914D\u989D' \"${PUSH_LOG}\"\n }; then\n echo \"\u4E2A\u4EBA\u7248 TCR \u955C\u50CF\u4ED3\u5E93\u6570\u91CF\u5DF2\u8FBE\u5230\u5F53\u524D\u5730\u57DF\u914D\u989D\uFF0C\u65E0\u6CD5\u81EA\u52A8\u521B\u5EFA\u76EE\u6807\u4ED3\u5E93\" >&2\n exit 44\n fi\n exit 1\nfi\n\necho \"::output::tag=${CLOUDBASE_VERSION_NAME}\"\n";
96
63
  /** buildImageOnCloud 可选项 */
97
64
  export interface IBuildImageOnCloudOptions {
98
65
  /** 日志回调,用于透出构建进度(不含凭证) */
99
66
  onLog?: (line: string) => void;
100
- /** 部署地域,注入构建环境供推送脚本换取仓库登录态(跨地域必需) */
101
- region?: string;
67
+ /** 企业版 TCR 临时登录凭证,由 manager-node 在 CreateCloudApp 前换取 */
68
+ enterpriseCredential?: IEnterpriseTcrCredential;
102
69
  }
103
70
  /**
104
71
  * 执行云端构建并推送镜像
105
72
  *
106
73
  * @param config 规范化的 cloud 部署配置
107
74
  * @param service 注入的外部能力(打包/tcb API/COS 上传)
108
- * @param options 可选项:日志回调 onLog、部署地域 region
75
+ * @param options 可选项:日志回调 onLog、企业版临时登录凭证
109
76
  */
110
77
  export declare function buildImageOnCloud(config: INormalizedHttpCloudConfig, service: ICloudBuildService, options?: IBuildImageOnCloudOptions): Promise<ICloudBuildResult>;