@cloudbase/manager-node 4.3.1 → 4.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,6 +10,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.CloudRunService = void 0;
13
+ const archiver_1 = __importDefault(require("archiver"));
14
+ const fs_extra_1 = require("fs-extra");
13
15
  const path_1 = __importDefault(require("path"));
14
16
  const utils_1 = require("../utils");
15
17
  /**
@@ -63,10 +65,7 @@ class CloudRunService {
63
65
  /**
64
66
  * 获取最新版本
65
67
  */
66
- const cloudRunServerDetailRes = await this.tcbrService.request('DescribeCloudRunServerDetail', {
67
- EnvId: envConfig.EnvId,
68
- ServerName: serverName
69
- });
68
+ const cloudRunServerDetailRes = await this.detail({ serverName });
70
69
  const version = (_b = (_a = cloudRunServerDetailRes.OnlineVersionInfos) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.VersionName;
71
70
  if (!version) {
72
71
  throw new Error('未找到云托管服务版本');
@@ -131,6 +130,109 @@ class CloudRunService {
131
130
  ServerName: params.serverName // 要删除的服务名称
132
131
  });
133
132
  }
133
+ /**
134
+ * 本地代码部署云托管服务
135
+ * @param {Object} params 部署参数
136
+ * @param {string} params.serverName 要部署的服务名称
137
+ * @param {string} params.targetPath 本地代码路径
138
+ * @param {Object} [params.serverConfig] 服务配置项(可选)
139
+ * @param {string[]} [params.serverConfig.OpenAccessTypes] 开放访问类型
140
+ * @param {number} [params.serverConfig.Cpu] CPU规格
141
+ * @param {number} [params.serverConfig.Mem] 内存规格
142
+ * @param {number} [params.serverConfig.MinNum] 最小实例数
143
+ * @param {number} [params.serverConfig.MaxNum] 最大实例数
144
+ * @param {Object} [params.serverConfig.PolicyDetails] 策略详情
145
+ * @param {Object} [params.serverConfig.CustomLogs] 自定义日志配置
146
+ * @param {Object} [params.serverConfig.EnvParams] 环境变量参数
147
+ * @param {number} [params.serverConfig.Port] 端口(函数型服务不允许设置)
148
+ * @param {string} [params.serverConfig.Dockerfile] Dockerfile路径
149
+ * @param {string} [params.serverConfig.BuildDir] 构建目录
150
+ * @param {boolean} [params.serverConfig.InternalAccess] 是否开启内网访问
151
+ * @param {string} [params.serverConfig.InternalDomain] 内网域名
152
+ * @param {string} [params.serverConfig.EntryPoint] 入口文件
153
+ * @param {string} [params.serverConfig.Cmd] 启动命令
154
+ * @returns {Promise<IResponseInfo>} 返回部署操作的响应信息
155
+ */
156
+ async deploy(params) {
157
+ const { serverName, targetPath = process.cwd(), serverConfig } = params;
158
+ /**
159
+ * 参数校验和默认值设置
160
+ */
161
+ if (!serverName) {
162
+ throw new Error('Missing required parameters: serviceName');
163
+ }
164
+ // 获取当前环境配置(包含EnvId)
165
+ const envConfig = this.environment.lazyEnvironmentConfig;
166
+ /**
167
+ * 获取部署包上传信息
168
+ */
169
+ const { UploadUrl: uploadUrl, UploadHeaders: uploadHeaders, PackageName: packageName, PackageVersion: packageVersion } = await this.tcbService.request('DescribeCloudBaseBuildService', {
170
+ EnvId: envConfig.EnvId,
171
+ ServiceName: serverName
172
+ });
173
+ const deployInfo = {
174
+ DeployType: 'package',
175
+ PackageName: packageName,
176
+ PackageVersion: packageVersion
177
+ };
178
+ /**
179
+ * 上传部署包
180
+ */
181
+ const zipFile = await codeToZip(targetPath, { installDependency: true });
182
+ await upload({
183
+ action: uploadUrl,
184
+ file: zipFile,
185
+ filename: 'file',
186
+ headers: (uploadHeaders || []).reduce((map, item) => {
187
+ map[item.Key] = item.Value;
188
+ return map;
189
+ }, {}) || {},
190
+ method: 'PUT',
191
+ send: (file) => file
192
+ });
193
+ /**
194
+ * 执行部署
195
+ */
196
+ if (await this._checkFunctionExist(serverName)) {
197
+ // 更新
198
+ const serverDetail = await this.detail({ serverName });
199
+ const _serverConfig = Object.assign(Object.assign(Object.assign({}, ((serverDetail === null || serverDetail === void 0 ? void 0 : serverDetail.ServerConfig) || {})), serverConfig), ((serverDetail === null || serverDetail === void 0 ? void 0 : serverDetail.ServerConfig.Tag) === 'function:' ? { Port: 3000 } : {}) // 函数型不能指定端口,需要固定为3000
200
+ );
201
+ deployInfo.ReleaseType = 'FULL';
202
+ return this._upsertFunction(false, {
203
+ name: serverName,
204
+ deployInfo,
205
+ serverConfig: _serverConfig
206
+ });
207
+ }
208
+ else {
209
+ // 创建
210
+ /**
211
+ * 判断是容器器还是函数型
212
+ */
213
+ let type = 'function';
214
+ if (serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.Dockerfile) {
215
+ type = 'container';
216
+ }
217
+ else {
218
+ if (await (0, fs_extra_1.pathExists)(path_1.default.join(targetPath, 'Dockerfile'))) {
219
+ type = 'container';
220
+ }
221
+ }
222
+ if (type === 'function') {
223
+ deployInfo.BuildPacks = {
224
+ LanguageVersion: '20.18',
225
+ RepoLanguage: 'Node.js'
226
+ };
227
+ }
228
+ const _serverConfig = Object.assign(Object.assign(Object.assign({ OpenAccessTypes: ['OA', 'PUBLIC'], Cpu: 0, Mem: 0, MinNum: 0, MaxNum: 0, PolicyDetails: [], EnvParams: JSON.stringify({}), InitialDelaySeconds: 0, CustomLogs: '', HasDockerfile: true, CreateTime: '', EnvId: envConfig.EnvId, ServerName: serverName, Port: type === 'container' ? 80 : 3000, Dockerfile: 'Dockerfile', BuildDir: '' }, serverConfig), (type === 'function' ? { Port: 3000 } : {})), { Tag: type === 'container' ? '' : 'function:' });
229
+ return this._upsertFunction(true, {
230
+ name: serverName,
231
+ deployInfo,
232
+ serverConfig: _serverConfig
233
+ });
234
+ }
235
+ }
134
236
  /**
135
237
  * 获取云托管服务模板列表
136
238
  * @returns {Promise<ITemplate[]>} 返回模板数组
@@ -138,6 +240,32 @@ class CloudRunService {
138
240
  async getTemplates() {
139
241
  return (0, utils_1.fetchTemplates)(['tcbrFunc', 'tcbrContainer']);
140
242
  }
243
+ async _checkFunctionExist(name) {
244
+ try {
245
+ await this.detail({
246
+ serverName: name
247
+ });
248
+ return true;
249
+ }
250
+ catch (e) {
251
+ if (e.code === 'ResourceNotFound' ||
252
+ // 备注:以下条件当 NotFound 处理(已与 fisheryan 确认过)
253
+ (e.code === 'InvalidParameter' && e.original.Message === 'service data illegal')) {
254
+ return false;
255
+ }
256
+ throw e;
257
+ }
258
+ }
259
+ _upsertFunction(isNew, data) {
260
+ const { name, deployInfo, serverConfig } = data;
261
+ const envConfig = this.environment.lazyEnvironmentConfig;
262
+ return this.tcbrService.request(isNew ? 'CreateCloudRunServer' : 'UpdateCloudRunServer', {
263
+ EnvId: envConfig.EnvId,
264
+ ServerName: name,
265
+ DeployInfo: deployInfo,
266
+ ServerConfig: serverConfig
267
+ });
268
+ }
141
269
  }
142
270
  exports.CloudRunService = CloudRunService;
143
271
  __decorate([
@@ -155,3 +283,87 @@ __decorate([
155
283
  __decorate([
156
284
  (0, utils_1.preLazy)()
157
285
  ], CloudRunService.prototype, "delete", null);
286
+ __decorate([
287
+ (0, utils_1.preLazy)()
288
+ ], CloudRunService.prototype, "deploy", null);
289
+ async function codeToZip(cwd, options) {
290
+ const archive = (0, archiver_1.default)('zip', {
291
+ zlib: { level: 1 } // 保持与之前相同的压缩级别
292
+ });
293
+ const chunks = [];
294
+ const bufferPromise = new Promise((resolve, reject) => {
295
+ archive.on('data', (chunk) => {
296
+ chunks.push(chunk);
297
+ });
298
+ archive.on('end', () => {
299
+ resolve(Buffer.concat(chunks));
300
+ });
301
+ archive.on('error', err => {
302
+ reject(err);
303
+ });
304
+ });
305
+ async function addFilesToArchive(dir, root = false, relativePath = '') {
306
+ const entries = await (0, fs_extra_1.readdir)(dir, { withFileTypes: true });
307
+ for (let entry of entries) {
308
+ const fullPath = path_1.default.join(dir, entry.name);
309
+ const entryRelativePath = path_1.default.join(relativePath, entry.name);
310
+ if (entry.isDirectory()) {
311
+ if (['logs', '.git'].includes(entry.name)) {
312
+ // 忽略 logs 等目录
313
+ continue;
314
+ }
315
+ if (options === null || options === void 0 ? void 0 : options.installDependency) {
316
+ // 忽略 node_modules 等目录
317
+ if (['node_modules'].includes(entry.name)) {
318
+ continue;
319
+ }
320
+ }
321
+ await addFilesToArchive(fullPath, false, entryRelativePath);
322
+ }
323
+ else {
324
+ if (root) {
325
+ // 可以配置忽略指定文件名的文件
326
+ if ([''].includes(entry.name)) {
327
+ continue;
328
+ }
329
+ }
330
+ // 保持与之前相同的文件添加方式,包括相对路径处理
331
+ archive.file(fullPath, { name: entryRelativePath });
332
+ }
333
+ }
334
+ }
335
+ await addFilesToArchive(path_1.default.resolve(cwd), true);
336
+ await archive.finalize();
337
+ return bufferPromise;
338
+ }
339
+ function upload({ action, file, method = 'POST', headers = {}, withCredentials = false }) {
340
+ return (0, utils_1.fetchStream)(action, {
341
+ method,
342
+ headers,
343
+ body: file,
344
+ credentials: withCredentials ? 'include' : 'same-origin'
345
+ })
346
+ .then(async (response) => {
347
+ const text = await response.text();
348
+ const dataMeta = {
349
+ data: null,
350
+ context: {
351
+ response,
352
+ file
353
+ }
354
+ };
355
+ try {
356
+ dataMeta.data = JSON.parse(text);
357
+ }
358
+ catch (e) {
359
+ // If parsing fails, keep data as null
360
+ }
361
+ if (!response.ok) {
362
+ throw new Error(`${response.status} ${response.statusText} HTTP ERROR`);
363
+ }
364
+ return dataMeta;
365
+ })
366
+ .catch(error => {
367
+ throw error; // 或者可以在这里进行错误转换
368
+ });
369
+ }
@@ -91,29 +91,39 @@ async function downloadAndExtractRemoteZip(downloadUrl, targetPath) {
91
91
  let fileCount = 0;
92
92
  let extractedCount = 0;
93
93
  downloadResponse
94
- .body.pipe(unzipper_1.default.Parse()) // 使用 unzipper.Parse() 逐一处理是为了避免解压时丢失文件
94
+ .body.pipe(unzipper_1.default.Parse())
95
95
  .on('error', reject)
96
- .on('entry', entry => {
97
- fileCount++;
96
+ .on('entry', async (entry) => {
98
97
  const filePath = path_1.default.join(dirPath, entry.path);
99
- // 确保目录存在
100
- fs_extra_1.default.ensureDirSync(path_1.default.dirname(filePath));
101
- entry
102
- .pipe(fs_extra_1.default.createWriteStream(filePath))
103
- .on('error', reject)
104
- .on('finish', () => {
105
- extractedCount++;
106
- if (extractedCount === fileCount) {
107
- resolve('');
98
+ try {
99
+ // 确保父目录存在(处理嵌套目录情况)
100
+ await fs_extra_1.default.ensureDir(path_1.default.dirname(filePath));
101
+ // 如果是目录则创建,否则写入文件
102
+ if (entry.type === 'Directory') {
103
+ await fs_extra_1.default.ensureDir(filePath);
104
+ extractedCount++;
108
105
  }
109
- });
106
+ else {
107
+ fileCount++;
108
+ entry
109
+ .pipe(fs_extra_1.default.createWriteStream(filePath))
110
+ .on('error', reject)
111
+ .on('finish', () => {
112
+ extractedCount++;
113
+ checkCompletion();
114
+ });
115
+ }
116
+ }
117
+ catch (err) {
118
+ reject(err);
119
+ }
110
120
  })
111
- .on('close', () => {
112
- // 确保所有文件都已处理
113
- if (fileCount === extractedCount) {
121
+ .on('close', checkCompletion);
122
+ function checkCompletion() {
123
+ if (fileCount > 0 && fileCount === extractedCount) {
114
124
  resolve('');
115
125
  }
116
- });
126
+ }
117
127
  });
118
128
  }
119
129
  function getRuntime() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudbase/manager-node",
3
- "version": "4.3.1",
3
+ "version": "4.3.2",
4
4
  "description": "The node manage service api for cloudbase.",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -1,6 +1,6 @@
1
1
  import { Environment } from '../environment';
2
2
  import { IResponseInfo } from '../interfaces';
3
- import { CloudrunServerType, ICloudrunDetailResponse, ICloudrunListResponse, ITemplate } from './type';
3
+ import { CloudrunServerType, ICloudrunDetailResponse, ICloudrunListResponse, ICloudrunServerBaseConfig, ITemplate } from './type';
4
4
  /**
5
5
  * 云托管服务管理类
6
6
  * 提供云托管服务的初始化、下载、列表查询和删除等功能
@@ -73,9 +73,39 @@ export declare class CloudRunService {
73
73
  delete(params: {
74
74
  serverName: string;
75
75
  }): Promise<IResponseInfo>;
76
+ /**
77
+ * 本地代码部署云托管服务
78
+ * @param {Object} params 部署参数
79
+ * @param {string} params.serverName 要部署的服务名称
80
+ * @param {string} params.targetPath 本地代码路径
81
+ * @param {Object} [params.serverConfig] 服务配置项(可选)
82
+ * @param {string[]} [params.serverConfig.OpenAccessTypes] 开放访问类型
83
+ * @param {number} [params.serverConfig.Cpu] CPU规格
84
+ * @param {number} [params.serverConfig.Mem] 内存规格
85
+ * @param {number} [params.serverConfig.MinNum] 最小实例数
86
+ * @param {number} [params.serverConfig.MaxNum] 最大实例数
87
+ * @param {Object} [params.serverConfig.PolicyDetails] 策略详情
88
+ * @param {Object} [params.serverConfig.CustomLogs] 自定义日志配置
89
+ * @param {Object} [params.serverConfig.EnvParams] 环境变量参数
90
+ * @param {number} [params.serverConfig.Port] 端口(函数型服务不允许设置)
91
+ * @param {string} [params.serverConfig.Dockerfile] Dockerfile路径
92
+ * @param {string} [params.serverConfig.BuildDir] 构建目录
93
+ * @param {boolean} [params.serverConfig.InternalAccess] 是否开启内网访问
94
+ * @param {string} [params.serverConfig.InternalDomain] 内网域名
95
+ * @param {string} [params.serverConfig.EntryPoint] 入口文件
96
+ * @param {string} [params.serverConfig.Cmd] 启动命令
97
+ * @returns {Promise<IResponseInfo>} 返回部署操作的响应信息
98
+ */
99
+ deploy(params: {
100
+ serverName: string;
101
+ targetPath: string;
102
+ serverConfig?: Partial<Pick<ICloudrunServerBaseConfig, 'OpenAccessTypes' | 'Cpu' | 'Mem' | 'MinNum' | 'MaxNum' | 'PolicyDetails' | 'CustomLogs' | 'EnvParams' | 'Port' | 'Dockerfile' | 'BuildDir' | 'InternalAccess' | 'InternalDomain' | 'EntryPoint' | 'Cmd'>>;
103
+ }): Promise<IResponseInfo>;
76
104
  /**
77
105
  * 获取云托管服务模板列表
78
106
  * @returns {Promise<ITemplate[]>} 返回模板数组
79
107
  */
80
108
  getTemplates(): Promise<ITemplate[]>;
109
+ private _checkFunctionExist;
110
+ private _upsertFunction;
81
111
  }