UNPKG

billd-deploy

Version:
1,200 lines (1,182 loc) 42.3 kB
import { execSync, exec } from 'child_process'; import chalk from 'chalk'; import { cdn } from 'tencentcloud-sdk-nodejs-cdn'; import fs from 'fs'; import path from 'path'; import OSS from 'ali-oss'; import OBS from 'esdk-obs-nodejs'; import qiniu from 'qiniu'; import COS from 'cos-nodejs-sdk-v5'; import { NodeSSH } from 'node-ssh'; import { logData } from 'billd-html-webpack-plugin'; const cache = { build: "ok", ssh: "ok", cos: "ok", cdn: "ok" }; const chalkINFO = (v) => { const time = new Date().toLocaleString("zh-CN"); const prefix = `[${time}] INFO `; return `${chalk.bgBlueBright.black(prefix)} ${chalk.blueBright(v)}`; }; const chalkSUCCESS = (v) => { const time = new Date().toLocaleString("zh-CN"); const prefix = `[${time}] SUCCESS `; return `${chalk.bgGreenBright.black(prefix)} ${chalk.greenBright(v)}`; }; const chalkERROR = (v) => { const time = new Date().toLocaleString("zh-CN"); const prefix = `[${time}] ERROR `; return `${chalk.bgRedBright.black(prefix)} ${chalk.redBright(v)}`; }; const chalkWARN = (v) => { const time = new Date().toLocaleString("zh-CN"); const prefix = `[${time}] WARN `; return `${chalk.bgHex("#FFA500").black(`${prefix}`)} ${chalk.hex("#FFA500")( v )}`; }; const handleBuild = (buildCmd = "") => { if (buildCmd === "") { console.log(chalkERROR(`buildCmd\u4E3A\u7A7A\uFF01`)); return; } try { execSync(buildCmd, { stdio: "inherit", cwd: process.cwd() }); console.log(chalkSUCCESS(`\u6784\u5EFA\u5B8C\u6210`)); } catch (error) { console.log(chalkERROR(`\u6784\u5EFA\u5931\u8D25`)); console.log(error); cache.build = "error"; } }; const handleTencentCdn = async function(data) { const { tencentCdnConfig: cdnConfig, tencentCdnJob: cdnJob } = data.config; if (!cdnConfig || !cdnJob) { console.log(chalkERROR(`CDN\u914D\u7F6E\u9519\u8BEF\uFF01`)); return; } const { SecretId, SecretKey } = cdnConfig(data); const { Purge } = cdnJob(data); try { const CdnClient = cdn.v20180606.Client; const client = new CdnClient({ // 为了保护密钥安全,建议将密钥设置在环境变量中或者配置文件中,请参考本文凭证管理章节。 // 硬编码密钥到代码中有可能随代码泄露而暴露,有安全隐患,并不推荐。 credential: { secretId: SecretId, secretKey: SecretKey } }); if (Purge.urls) { let errmsg = ""; try { await client.PurgeUrlsCache(Purge.urls, (err) => { if (err) { errmsg = err; } }); console.log(chalkSUCCESS(`\u63D0\u4EA4URL\u5237\u65B0\u6210\u529F\uFF1A${Purge.urls.Urls.join()}`)); } catch (error) { console.error(error); errmsg = error; } if (errmsg !== "") { console.log(chalkERROR("\u63D0\u4EA4URL\u5237\u65B0\u5931\u8D25")); console.log(chalkERROR(errmsg)); } } if (Purge.paths) { let errmsg = ""; try { await client.PurgePathCache(Purge.paths, (err) => { if (err) { errmsg = err; } }); console.log( chalkSUCCESS(`\u63D0\u4EA4\u76EE\u5F55\u5237\u65B0\u6210\u529F\uFF1A${Purge.paths.Paths.join()}`) ); } catch (error) { console.error(error); errmsg = error; } if (errmsg !== "") { console.log(chalkERROR("\u63D0\u4EA4\u76EE\u5F55\u5237\u65B0\u5931\u8D25")); console.log(chalkERROR(errmsg)); } } } catch (err) { console.log(err); return { err }; } }; class ConcurrentPoll { tasks = []; max = 0; total = 0; done; constructor({ max = 5, done }) { this.tasks = []; this.max = max; this.total = 0; this.done = done; setTimeout(() => { this.run(); }, 0); } addTask(task) { this.tasks.push(task); this.total += 1; } run() { if (this.tasks.length === 0) { return Promise.resolve(""); } const min = Math.min(this.tasks.length, this.max); for (let i = 0; i < min; i += 1) { this.max -= 1; const task = this.tasks.shift(); task().then(() => { }).catch((err) => { console.log(err); }).finally(() => { this.max += 1; this.total -= 1; this.run(); if (this.total === 0) { this.done?.(); } }); } } } const handleAliOss = function(data) { const { aliOssConfig: cosConfig, aliOssFileConfig: cosFileConfig } = data.config; if (!cosConfig || !cosFileConfig) { console.log(chalkERROR(`\u963F\u91CC\u4E91oss\u914D\u7F6E\u9519\u8BEF\uFF01`)); return; } const aliOssConfig = cosConfig(data); const aliOssFileConfig = cosFileConfig(data); function findFile(inputDir) { const res = []; function loop(dirArr) { for (let i = 0; i < dirArr.length; i += 1) { const file = dirArr[i]; const filePath = path.resolve(inputDir, file); const stat = fs.statSync(filePath); const isDir = stat.isDirectory(); if (!isDir) { res.push(filePath); } else { loop(fs.readdirSync(filePath).map((key) => path.join(file, key))); } } } let inputDirArr = []; try { inputDirArr = fs.readdirSync(inputDir); } catch (error) { console.log(chalkERROR(`${inputDir},\u8DEF\u5F84\u4E0D\u5B58\u5728\uFF01`)); throw new Error(`${inputDir},\u8DEF\u5F84\u4E0D\u5B58\u5728\uFF01`); } loop(inputDirArr); return res; } const uploadOkRecord = /* @__PURE__ */ new Map(); const uploadErrRecord = /* @__PURE__ */ new Map(); const allFile = []; try { const client = new OSS({ // yourregion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。 region: aliOssConfig.region, // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。 accessKeyId: aliOssConfig.accessKeyId, accessKeySecret: aliOssConfig.accessKeySecret, // 填写Bucket名称。 bucket: aliOssConfig.bucket, // 当前的oss前缀 prefix: aliOssConfig.prefix }); if (aliOssFileConfig.dir) { allFile.push(...findFile(aliOssFileConfig.dir.local)); } else { console.log(chalkWARN("\u6CA1\u6709\u914D\u7F6E\u4E0A\u4F20\u672C\u5730\u76EE\u5F55\u5230\u963F\u91CC\u4E91oss\u76EE\u5F55")); } if (aliOssFileConfig.file) { aliOssFileConfig.file.local.forEach((item) => { allFile.push(item); }); } else { console.log(chalkWARN("\u6CA1\u6709\u914D\u7F6E\u4E0A\u4F20\u672C\u5730\u6587\u4EF6\u5230\u963F\u91CC\u4E91oss\u76EE\u5F55")); } async function put(ossFlieName, filePath) { try { const result = await client.put( ossFlieName, filePath, // 自定义headers { headers: { // 指定PutObject操作时是否覆盖同名目标Object。此处设置为true,表示禁止覆盖同名Object。设置为false,表示允许覆盖同名Object。 "x-oss-forbid-overwrite": "false" } } ); const status = result.res.status; if (status === 200) { uploadOkRecord.set(filePath, status); console.log( chalkSUCCESS( `\u4E0A\u4F20\u963F\u91CC\u4E91oss\u6210\u529F(${uploadOkRecord.size}/${allFile.length}): ${filePath} ===> ${ossFlieName}` ) ); } else { uploadErrRecord.set(filePath, status); console.log(result); cache.cos = "error"; console.log( chalkERROR( // eslint-disable-next-line `\u4E0A\u4F20\u963F\u91CC\u4E91oss\u5931\u8D25(${uploadErrRecord.size}/${allFile.length}): ${filePath} ===> ${ossFlieName}` ) ); } const progress = uploadOkRecord.size + uploadErrRecord.size; if (progress === allFile.length) { console.log( chalkINFO( `\u6240\u6709\u6587\u4EF6\u4E0A\u4F20\u963F\u91CC\u4E91oss\u5B8C\u6210\u3002\u6210\u529F\uFF1A${uploadOkRecord.size}/${allFile.length}\uFF1B\u5931\u8D25\uFF1A${uploadErrRecord.size}/${allFile.length}` ) ); if (uploadErrRecord.size) { cache.cos = "error"; console.log(chalkERROR(`\u4E0A\u4F20\u963F\u91CC\u4E91oss\u5931\u8D25\u6570\u636E`), uploadErrRecord); } } } catch (error) { cache.cos = "error"; console.log(chalkERROR(`\u4E0A\u4F20\u963F\u91CC\u4E91oss\u9519\u8BEF`), error); } } return new Promise((resolve) => { const uploadQueue = new ConcurrentPoll({ max: 5, done: () => { if (uploadErrRecord.size) { resolve({ code: 1, msg: "\u963F\u91CC\u4E91oss\u4E0A\u4F20\u6587\u4EF6\u5B58\u5728\u9519\u8BEF\u8BB0\u5F55\uFF01" }); } else { resolve({ code: 0, msg: "" }); } } }); allFile.forEach((filePath) => { if (aliOssFileConfig.file) { if (aliOssFileConfig.file.local.includes(filePath)) { const filename = filePath.split(path.sep).pop() || ""; const ossFlieName = path.join(aliOssConfig.prefix, filename); uploadQueue.addTask( () => put( path.sep === "/" ? ossFlieName : ossFlieName.replace(/\\/g, "/"), filePath ) ); } else { if (aliOssFileConfig.dir) { const dirName = aliOssFileConfig.dir.local.split(path.sep).pop() || ""; const ignoreDir = aliOssFileConfig.dir.ignoreDir; const ossFlieName = aliOssConfig.prefix + filePath.replace( aliOssFileConfig.dir.local, ignoreDir ? "" : path.sep + dirName ); uploadQueue.addTask( () => put( path.sep === "/" ? ossFlieName : ossFlieName.replace(/\\/g, "/"), filePath ) ); } } } }); }); } catch (error) { console.log(chalkERROR(`cdn\u811A\u672C\u9519\u8BEF`), error); } }; const handleHuaweiObs = function(data) { const { huaweiObsConfig: cosConfig, huaweiObsFileConfig: cosFileConfig } = data.config; if (!cosConfig || !cosFileConfig) { console.log(chalkERROR(`\u534E\u4E3A\u4E91obs\u914D\u7F6E\u9519\u8BEF\uFF01`)); return; } const huaweiObsConfig = cosConfig(data); const huaweiObsFileConfig = cosFileConfig(data); const { bucket: obsBucket, prefix: obsPrefix } = huaweiObsConfig; function findFile(inputDir) { const res = []; function loop(dirArr) { for (let i = 0; i < dirArr.length; i += 1) { const file = dirArr[i]; const filePath = path.resolve(inputDir, file); const stat = fs.statSync(filePath); const isDir = stat.isDirectory(); if (!isDir) { res.push(filePath); } else { loop(fs.readdirSync(filePath).map((key) => path.join(file, key))); } } } let inputDirArr = []; try { inputDirArr = fs.readdirSync(inputDir); } catch (error) { console.log(chalkERROR(`${inputDir},\u8DEF\u5F84\u4E0D\u5B58\u5728\uFF01`)); throw new Error(`${inputDir},\u8DEF\u5F84\u4E0D\u5B58\u5728\uFF01`); } loop(inputDirArr); return res; } const uploadOkRecord = /* @__PURE__ */ new Map(); const uploadErrRecord = /* @__PURE__ */ new Map(); const allFile = []; try { const client = new OBS({ access_key_id: huaweiObsConfig.access_key_id, secret_access_key: huaweiObsConfig.secret_access_key, // server : 'https://your-endpoint' server: huaweiObsConfig.server // 华为obs browser+客户端里面,桶列表,找到对应的桶,看旁边的操作栏里面基本信息,找到Endpoint }); if (huaweiObsFileConfig.dir) { allFile.push(...findFile(huaweiObsFileConfig.dir.local)); } else { console.log(chalkWARN("\u6CA1\u6709\u914D\u7F6E\u4E0A\u4F20\u672C\u5730\u76EE\u5F55\u5230huawei-obs\u76EE\u5F55")); } if (huaweiObsFileConfig.file) { huaweiObsFileConfig.file.local.forEach((item) => { allFile.push(item); }); } else { console.log(chalkWARN("\u6CA1\u6709\u914D\u7F6E\u4E0A\u4F20\u672C\u5730\u6587\u4EF6\u5230huawei-obs\u76EE\u5F55")); } async function put(obsBucket2, obsFlieName, filePath) { try { const result = await new Promise( (resolve) => { client.putObject( { Bucket: obsBucket2, Key: obsFlieName, SourceFile: filePath }, // eslint-disable-next-line (err, result2) => { if (err) { return resolve({ code: 500, result: result2, err }); } resolve({ code: 200, result: result2 }); } ); } ); const status = result.code; if (status === 200) { uploadOkRecord.set(filePath, status); console.log( chalkSUCCESS( `\u4E0A\u4F20huawei-obs\u6210\u529F(${uploadOkRecord.size}/${allFile.length}): ${filePath} ===> ${obsFlieName}` ) ); } else { uploadErrRecord.set(filePath, status); console.log(result); cache.cos = "error"; console.log( chalkERROR( // eslint-disable-next-line `\u4E0A\u4F20huawei-obs\u5931\u8D25(${uploadErrRecord.size}/${allFile.length}): ${filePath} ===> ${obsFlieName}` ) ); } const progress = uploadOkRecord.size + uploadErrRecord.size; if (progress === allFile.length) { console.log( chalkINFO( `\u6240\u6709\u6587\u4EF6\u4E0A\u4F20huawei-obs\u5B8C\u6210\u3002\u6210\u529F\uFF1A${uploadOkRecord.size}/${allFile.length}\uFF1B\u5931\u8D25\uFF1A${uploadErrRecord.size}/${allFile.length}` ) ); if (uploadErrRecord.size) { cache.cos = "error"; console.log(chalkERROR(`\u4E0A\u4F20huawei-obs\u5931\u8D25\u6570\u636E`), uploadErrRecord); } } } catch (error) { cache.cos = "error"; console.log(chalkERROR(`\u4E0A\u4F20huawei-obs\u9519\u8BEF`), error); } } return new Promise((resolve) => { const uploadQueue = new ConcurrentPoll({ max: 5, done: () => { if (uploadErrRecord.size) { resolve({ code: 1, msg: "\u534E\u4E3A\u4E91obs\u4E0A\u4F20\u6587\u4EF6\u5B58\u5728\u9519\u8BEF\u8BB0\u5F55\uFF01" }); } else { resolve({ code: 0, msg: "" }); } } }); allFile.forEach((filePath) => { if (huaweiObsFileConfig.file) { if (huaweiObsFileConfig.file.local.includes(filePath)) { const filename = filePath.split(path.sep).pop() || ""; const obsFlieName = path.join(obsPrefix, filename); uploadQueue.addTask( () => put( obsBucket, path.sep === "/" ? obsFlieName : obsFlieName.replace(/\\/g, "/"), filePath ) ); } else { if (huaweiObsFileConfig.dir) { const dirName = huaweiObsFileConfig.dir.local.split(path.sep).pop() || ""; const ignoreDir = huaweiObsFileConfig.dir.ignoreDir; const obsFlieName = obsPrefix + filePath.replace( huaweiObsFileConfig.dir.local, ignoreDir ? "" : path.sep + dirName ); uploadQueue.addTask( () => put( obsBucket, path.sep === "/" ? obsFlieName : obsFlieName.replace(/\\/g, "/"), filePath ) ); } } } }); }); } catch (error) { console.log(chalkERROR(`cdn\u811A\u672C\u9519\u8BEF`), error); } }; const handleQiniuKodo = function(data) { const { qiniuKodoConfig: cosConfig, qiniuKodoFileConfig: cosFileConfig } = data.config; if (!cosConfig || !cosFileConfig) { console.log(chalkERROR(`\u4E03\u725B\u4E91kodo\u914D\u7F6E\u9519\u8BEF\uFF01`)); return; } const qiniuKodoConfig = cosConfig(data); const qiniuKodoFileConfig = cosFileConfig(data); const mac = new qiniu.auth.digest.Mac( qiniuKodoConfig.accessKey, qiniuKodoConfig.secretKey ); const qiniuConfConfig = new qiniu.conf.Config(); qiniuConfConfig.zone = qiniuKodoConfig.zone; function findFile(inputDir) { const res = []; function loop(dirArr) { for (let i = 0; i < dirArr.length; i += 1) { const file = dirArr[i]; const filePath = path.resolve(inputDir, file); const stat = fs.statSync(filePath); const isDir = stat.isDirectory(); if (!isDir) { res.push(filePath); } else { loop(fs.readdirSync(filePath).map((key) => path.join(file, key))); } } } let inputDirArr = []; try { inputDirArr = fs.readdirSync(inputDir); } catch (error) { console.log(chalkERROR(`${inputDir},\u8DEF\u5F84\u4E0D\u5B58\u5728\uFF01`)); throw new Error(`${inputDir},\u8DEF\u5F84\u4E0D\u5B58\u5728\uFF01`); } loop(inputDirArr); return res; } const uploadOkRecord = /* @__PURE__ */ new Map(); const uploadErrRecord = /* @__PURE__ */ new Map(); const allFile = []; try { if (qiniuKodoFileConfig.dir) { allFile.push(...findFile(qiniuKodoFileConfig.dir.local)); } else { console.log(chalkWARN("\u6CA1\u6709\u914D\u7F6E\u4E0A\u4F20\u672C\u5730\u76EE\u5F55\u5230\u4E03\u725B\u4E91kodo\u76EE\u5F55")); } if (qiniuKodoFileConfig.file) { qiniuKodoFileConfig.file.local.forEach((item) => { allFile.push(item); }); } else { console.log(chalkWARN("\u6CA1\u6709\u914D\u7F6E\u4E0A\u4F20\u672C\u5730\u6587\u4EF6\u5230\u4E03\u725B\u4E91kodo\u76EE\u5F55")); } async function put(key, filePath) { try { const formUploader = new qiniu.form_up.FormUploader(qiniuConfConfig); const options = { // eslint-disable-next-line scope: `${qiniuKodoConfig.bucket}:${key}` }; const putPolicy = new qiniu.rs.PutPolicy(options); const uploadToken = putPolicy.uploadToken(mac); const putExtra = new qiniu.form_up.PutExtra(); const result = await new Promise((resolve) => { formUploader.putFile( uploadToken, key, filePath, putExtra, function(respErr, respBody, respInfo) { if (respErr) { return resolve({ code: 500, respErr, respBody, respInfo }); } if (respInfo.statusCode == 200) { return resolve({ code: 200 }); } else { return resolve({ code: 500, respErr, respBody, respInfo }); } } ); }); const status = result.code; if (status === 200) { uploadOkRecord.set(filePath, status); console.log( chalkSUCCESS( `\u4E0A\u4F20\u4E03\u725B\u4E91kodo\u6210\u529F(${uploadOkRecord.size}/${allFile.length}): ${filePath} ===> ${key}` ) ); } else { uploadErrRecord.set(filePath, status); console.log(result); cache.cos = "error"; console.log( chalkERROR( // eslint-disable-next-line `\u4E0A\u4F20\u4E03\u725B\u4E91kodo\u5931\u8D25(${uploadErrRecord.size}/${allFile.length}): ${filePath} ===> ${key}` ) ); } const progress = uploadOkRecord.size + uploadErrRecord.size; if (progress === allFile.length) { console.log( chalkINFO( `\u6240\u6709\u6587\u4EF6\u4E0A\u4F20\u4E03\u725B\u4E91kodo\u5B8C\u6210\u3002\u6210\u529F\uFF1A${uploadOkRecord.size}/${allFile.length}\uFF1B\u5931\u8D25\uFF1A${uploadErrRecord.size}/${allFile.length}` ) ); if (uploadErrRecord.size) { cache.cos = "error"; console.log(chalkERROR(`\u4E0A\u4F20\u4E03\u725B\u4E91kodo\u5931\u8D25\u6570\u636E`), uploadErrRecord); } } } catch (error) { cache.cos = "error"; console.log(chalkERROR(`\u4E0A\u4F20\u4E03\u725B\u4E91kodo\u9519\u8BEF`), error); } } return new Promise((resolve) => { const uploadQueue = new ConcurrentPoll({ max: 5, done: () => { if (uploadErrRecord.size) { resolve({ code: 1, msg: "\u4E03\u725B\u4E91kodo\u4E0A\u4F20\u6587\u4EF6\u5B58\u5728\u9519\u8BEF\u8BB0\u5F55\uFF01" }); } else { resolve({ code: 0, msg: "" }); } } }); allFile.forEach((filePath) => { if (qiniuKodoFileConfig.file) { if (qiniuKodoFileConfig.file.local.includes(filePath)) { const filename = filePath.split(path.sep).pop() || ""; const key = path.join(qiniuKodoConfig.prefix, filename); uploadQueue.addTask( () => put(path.sep === "/" ? key : key.replace(/\\/g, "/"), filePath) ); } else { if (qiniuKodoFileConfig.dir) { const dirName = qiniuKodoFileConfig.dir.local.split(path.sep).pop() || ""; const ignoreDir = qiniuKodoFileConfig.dir.ignoreDir; const key = qiniuKodoConfig.prefix + filePath.replace( qiniuKodoFileConfig.dir.local, ignoreDir ? "" : path.sep + dirName ); uploadQueue.addTask( () => put(path.sep === "/" ? key : key.replace(/\\/g, "/"), filePath) ); } } } }); }); } catch (error) { console.log(chalkERROR(`cdn\u811A\u672C\u9519\u8BEF`), error); } }; const handleTencentCos = function(data) { const { tencentCosConfig: cosConfig, tencentCosFileConfig: cosFileConfig } = data.config; if (!cosConfig || !cosFileConfig) { console.log(chalkERROR(`\u817E\u8BAF\u4E91cos\u914D\u7F6E\u9519\u8BEF\uFF01`)); return; } const tencentCosConfig = cosConfig(data); const tencentCosFileConfig = cosFileConfig(data); function findFile(inputDir) { const res = []; function loop(dirArr) { for (let i = 0; i < dirArr.length; i += 1) { const file = dirArr[i]; const filePath = path.resolve(inputDir, file); const stat = fs.statSync(filePath); const isDir = stat.isDirectory(); if (!isDir) { res.push(filePath); } else { loop(fs.readdirSync(filePath).map((key) => path.join(file, key))); } } } let inputDirArr = []; try { inputDirArr = fs.readdirSync(inputDir); } catch (error) { console.log(chalkERROR(`${inputDir},\u8DEF\u5F84\u4E0D\u5B58\u5728\uFF01`)); throw new Error(`${inputDir},\u8DEF\u5F84\u4E0D\u5B58\u5728\uFF01`); } loop(inputDirArr); return res; } const uploadOkRecord = /* @__PURE__ */ new Map(); const uploadErrRecord = /* @__PURE__ */ new Map(); const allFile = []; try { const client = new COS({ SecretId: tencentCosConfig.SecretId, // 推荐使用环境变量获取;用户的 SecretId,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参考https://www.tencentcloud.com/document/product/598/37140?from_cn_redirect=1 SecretKey: tencentCosConfig.SecretKey, // 推荐使用环境变量获取;用户的 SecretKey,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参考https://www.tencentcloud.com/document/product/598/37140?from_cn_redirect=1 FileParallelLimit: 10, // 同一个实例下上传的文件并发数,默认值3 ChunkParallelLimit: 10 // 同一个上传文件的分块并发数,默认值3 }); if (tencentCosFileConfig.dir) { allFile.push(...findFile(tencentCosFileConfig.dir.local)); } else { console.log(chalkWARN("\u6CA1\u6709\u914D\u7F6E\u4E0A\u4F20\u672C\u5730\u6587\u4EF6\u5230\u817E\u8BAF\u4E91cos\u76EE\u5F55")); } if (tencentCosFileConfig.file) { tencentCosFileConfig.file.local.forEach((item) => { allFile.push(item); }); } else { console.log(chalkWARN("\u6CA1\u6709\u914D\u7F6E\u4E0A\u4F20\u672C\u5730\u6587\u4EF6\u5230\u817E\u8BAF\u4E91cos\u76EE\u5F55")); } async function put(cosFlieName, filePath) { try { const result = await new Promise((resolve) => { client.putObject( { Bucket: tencentCosConfig.Bucket, Region: tencentCosConfig.Region, Key: cosFlieName, StorageClass: tencentCosConfig.StorageClass, Body: fs.createReadStream(filePath) // 上传文件对象 // onProgress(progressData) { // console.log('progressData', JSON.stringify(progressData)); // }, }, function(err, res) { if (res) { resolve({ code: 200, res }); } else { resolve({ code: 400, res, err }); } } ); }); const status = result.code; if (status === 200) { uploadOkRecord.set(filePath, status); console.log( chalkSUCCESS( `\u4E0A\u4F20\u817E\u8BAF\u4E91cos\u6210\u529F(${uploadOkRecord.size}/${allFile.length}): ${filePath} ===> ${cosFlieName}` ) ); } else { uploadErrRecord.set(filePath, status); console.log(result); cache.cos = "error"; console.log( chalkERROR( // eslint-disable-next-line `\u4E0A\u4F20\u817E\u8BAF\u4E91cos\u5931\u8D25(${uploadErrRecord.size}/${allFile.length}): ${filePath} ===> ${cosFlieName}` ) ); } const progress = uploadOkRecord.size + uploadErrRecord.size; if (progress === allFile.length) { console.log( chalkINFO( `\u6240\u6709\u6587\u4EF6\u4E0A\u4F20\u817E\u8BAF\u4E91cos\u5B8C\u6210\u3002\u6210\u529F\uFF1A${uploadOkRecord.size}/${allFile.length}\uFF1B\u5931\u8D25\uFF1A${uploadErrRecord.size}/${allFile.length}` ) ); if (uploadErrRecord.size) { cache.cos = "error"; console.log(chalkERROR(`\u4E0A\u4F20\u817E\u8BAF\u4E91cos\u5931\u8D25\u6570\u636E`), uploadErrRecord); } } } catch (error) { cache.cos = "error"; console.log(chalkERROR(`\u4E0A\u4F20\u817E\u8BAF\u4E91cos\u9519\u8BEF`), error); } } return new Promise((resolve) => { const uploadQueue = new ConcurrentPoll({ max: 5, done: () => { if (uploadErrRecord.size) { resolve({ code: 1, msg: "\u817E\u8BAF\u4E91cos\u4E0A\u4F20\u6587\u4EF6\u5B58\u5728\u9519\u8BEF\u8BB0\u5F55\uFF01" }); } else { resolve({ code: 0, msg: "" }); } } }); allFile.forEach((filePath) => { if (tencentCosFileConfig.file) { if (tencentCosFileConfig.file.local.includes(filePath)) { const filename = filePath.split(path.sep).pop() || ""; const cosFlieName = path.join( tencentCosConfig.prefix || "", filename ); uploadQueue.addTask( () => put( path.sep === "/" ? cosFlieName : cosFlieName.replace(/\\/g, "/"), filePath ) ); } else { if (tencentCosFileConfig.dir) { const dirName = tencentCosFileConfig.dir.local.split(path.sep).pop() || ""; const ignoreDir = tencentCosFileConfig.dir.ignoreDir; const cosFlieName = (tencentCosConfig.prefix || "") + filePath.replace( tencentCosFileConfig.dir.local, ignoreDir ? "" : path.sep + dirName ); uploadQueue.addTask( () => put( path.sep === "/" ? cosFlieName : cosFlieName.replace(/\\/g, "/"), filePath ) ); } } } }); }); } catch (error) { console.log(chalkERROR(`cdn\u811A\u672C\u9519\u8BEF`), error); } }; var CosEnum = /* @__PURE__ */ ((CosEnum2) => { CosEnum2["ali"] = "ali"; CosEnum2["huawei"] = "huawei"; CosEnum2["qiniu"] = "qiniu"; CosEnum2["tencent"] = "tencent"; CosEnum2["none"] = "none"; return CosEnum2; })(CosEnum || {}); var CdnEnum = /* @__PURE__ */ ((CdnEnum2) => { CdnEnum2["tencent"] = "tencent"; CdnEnum2["none"] = "none"; return CdnEnum2; })(CdnEnum || {}); function isInstalledGit() { return new Promise((resolve, reject) => { exec( // 'git -v', "git --version", { cwd: process.cwd() }, (error, stdout, stderr) => { if (error || stderr) { console.log(chalkERROR("\u672A\u5B89\u88C5git")); console.log("error", error); console.log("stderr", stderr); reject(error || stderr); } if (stdout.length) { console.log(chalkINFO("\u5DF2\u5B89\u88C5git"), stdout); resolve("ok"); } else { console.log(chalkINFO("\u5DF2\u5B89\u88C5git"), stdout); resolve("ok"); } } ); }); } function gitIsClean() { return new Promise((resolve, reject) => { exec( "git status -s", { cwd: process.cwd() }, (error, stdout, stderr) => { if (error || stderr) { reject(error || stderr); } if (stdout.length) { reject(new Error(`\u3010\u63D0\u4EA4commit\u5230\u672C\u5730\u4ED3\u5E93\u3011\u8BF7\u786E\u4FDDgit\u5DE5\u4F5C\u533A\u5E72\u51C0\uFF01`)); } else { resolve("ok"); } } ); }); } function hasRemoteBranch() { return new Promise((resolve, reject) => { const commitBranch = execSync("git branch --show-current", { cwd: process.cwd() }).toString().trim(); const remoteBranch = execSync("git ls-remote", { cwd: process.cwd() }).toString().trim(); if (remoteBranch.search(`refs/heads/${commitBranch}`) === -1) { reject( new Error( `\u3010\u63D0\u4EA4\u5206\u652F\u5230\u8FDC\u7A0B\u4ED3\u5E93\u3011\u8FDC\u7A0B\u4ED3\u5E93\u4E0D\u5B58\u5728${commitBranch}\u5206\u652F\uFF0C\u8BF7\u5C06${commitBranch}\u5206\u652F\u63D0\u4EA4\u5230\u8FDC\u7A0B\u4ED3\u5E93\uFF01` ) ); } else { resolve("ok"); } }); } function diffRemote() { return new Promise((resolve, reject) => { const commitBranch = execSync("git branch --show-current", { cwd: process.cwd() }).toString().trim(); exec( `git diff --stat ${commitBranch} origin/${commitBranch}`, { cwd: process.cwd() }, (error, stdout, stderr) => { if (error || stderr) { console.log(error); console.log(stderr); reject(error || stderr); } if (stdout.length) { reject( new Error( `\u3010\u63D0\u4EA4commit\u5230\u8FDC\u7A0B\u4ED3\u5E93\u3011\u672C\u5730\u7684${commitBranch}\u5206\u652F\u7684git\u63D0\u4EA4\u548C\u8FDC\u7A0B\u4ED3\u5E93\u7684${commitBranch}\u5206\u652F\u7684git\u63D0\u4EA4\u4E0D\u4E00\u81F4\uFF01` ) ); } else { resolve("ok"); } } ); }); } const handleRelease = async (verifyGit, shouldRelease) => { if (verifyGit) { await isInstalledGit(); await gitIsClean(); await hasRemoteBranch(); await diffRemote(); } else { console.log(chalkWARN("\u4E0D\u9A8C\u8BC1git")); } if (shouldRelease) { execSync(`npm run release`, { stdio: "inherit", cwd: process.cwd() }); console.log(chalkSUCCESS("\u66F4\u65B0\u7248\u672C\u5B8C\u6210")); execSync(`git push --follow-tags`, { stdio: "inherit", cwd: process.cwd() }); console.log(chalkSUCCESS("\u63D0\u4EA4tag\u5B8C\u6210")); } else { console.log(chalkWARN("\u4E0D\u6267\u884Crelease")); } }; const handleSSH = async function(data) { const { sshFileConfig, sshConfig } = data.config; if (!sshConfig || !sshFileConfig) return; const serverConfig = sshConfig(data); const serverFile = sshFileConfig(data); const ssh = new NodeSSH(); async function connectServer() { await ssh.connect(serverConfig); console.log(chalkSUCCESS(`ssh\u8FDE\u63A5${serverConfig.host}\u6210\u529F`)); } async function uploadFiles() { if (serverFile.dir) { const dirName = serverFile.dir.local.split(path.sep).pop() || ""; if (!fs.existsSync(serverFile.dir.local)) { console.log(chalkERROR(`${serverFile.dir.local},\u4E0D\u5B58\u5728\uFF01`)); return; } const localDirectory = serverFile.dir.local; const oldRemote = serverFile.dir.remote.replace(/\/$/, ""); let oldRemoteDirectory = ""; if (serverFile.dir.ignoreDir) { oldRemoteDirectory = `${oldRemote}`; } else { oldRemoteDirectory = `${oldRemote}/${dirName}`; } const remoteDirectory = path.sep === "/" ? oldRemoteDirectory : oldRemoteDirectory.replace(/\\/g, "/"); const dirUploadStatus = await ssh.putDirectory( localDirectory, path.sep === "/" ? remoteDirectory : remoteDirectory.replace(/\\/g, "/"), { recursive: true, // 递归 concurrency: 10 // 并发 } ); console.log( chalkSUCCESS( `\u5C06\u672C\u5730\u7684\u76EE\u5F55: ${localDirectory}, \u4E0A\u4F20\u5230${serverConfig.host}\u670D\u52A1\u5668\u76EE\u5F55${remoteDirectory}${dirUploadStatus ? "\u6210\u529F" : "\u5931\u8D25"}` ) ); } else { console.log(chalkWARN("\u6CA1\u6709\u914D\u7F6E\u4E0A\u4F20\u672C\u5730\u76EE\u5F55\u5230\u670D\u52A1\u5668\u76EE\u5F55")); } const arr = []; if (serverFile.file) { serverFile.file.local.forEach((item) => { const filename = item.split(path.sep).pop() || ""; if (serverFile.file) { const oldRemote1 = serverFile.file.remote.replace(/\/$/, ""); const oldRemote2 = `${oldRemote1}/${filename}`; const newRemote = path.sep === "/" ? oldRemote2 : oldRemote2.replace(/\\/g, "/"); arr.push({ local: item, remote: newRemote }); } }); await ssh.putFiles(arr); console.log( chalkSUCCESS( `\u5C06\u672C\u5730\u7684\u6587\u4EF6: ${serverFile.file.local.join()}, \u4E0A\u4F20\u5230${serverConfig.host}\u670D\u52A1\u5668\u76EE\u5F55${serverFile.file.remote}\u6210\u529F` ) ); } else { console.log(chalkWARN("\u6CA1\u6709\u914D\u7F6E\u4E0A\u4F20\u672C\u5730\u6587\u4EF6\u5230\u670D\u52A1\u5668\u76EE\u5F55")); } } await connectServer(); await uploadFiles(); ssh.dispose(); console.log(chalkSUCCESS(`\u5173\u95ED${serverConfig.host}\u7684ssh`)); }; function calculateRemainingTime({ startTime, endTime }) { const duration = endTime - startTime; const ms = 1; const second = ms * 1e3; const minute = second * 60; const hour = minute * 60; const day = hour * 24; if (duration > day) { const res = (duration / day).toFixed(4).split("."); return `${res[0]}\u5929${Math.floor(Number(`0.${res[1]}`) * 24)}\u65F6`; } else if (duration > hour) { const res = (duration / hour).toFixed(4).split("."); return `${res[0]}\u65F6${Math.floor(Number(`0.${res[1]}`) * 60)}\u5206`; } else if (duration > minute) { const res = (duration / minute).toFixed(4).split("."); return `${res[0]}\u5206${Math.floor(Number(`0.${res[1]}`) * 60)}\u79D2`; } else { const res = (duration / second).toFixed(4).split("."); return `${res[0]}\u79D2`; } } const generateDeployFile = () => { fs.writeFileSync( path.resolve(process.cwd(), "deploy.json"), JSON.stringify(logData(), null, 2) ); console.log(chalkSUCCESS(`\u751F\u6210\u4E34\u65F6deploy.json\u6587\u4EF6\u5B8C\u6210`)); }; const deleteDeployFile = () => { fs.rmSync(path.resolve(process.cwd(), "deploy.json")); console.log(chalkSUCCESS(`\u5220\u9664\u4E34\u65F6deploy.json\u6587\u4EF6\u5B8C\u6210`)); }; const handlePm2Tip = (data) => { const { sshFileConfig, sshConfig } = data.config; if (!sshConfig || !sshFileConfig) return; const serverConfig = sshConfig(data); const configFile = [ fs.existsSync(path.resolve(process.cwd(), "nuxt.config.ts")), fs.existsSync(path.resolve(process.cwd(), "nuxt.config.js")), fs.existsSync(path.resolve(process.cwd(), "ecosystem.config.js")) ].includes(true); if (configFile) { console.log(chalkWARN(`\u68C0\u6D4B\u5230\u5F53\u524D\u9879\u76EE\u9700\u8981\u4F7F\u7528pm2\u7EF4\u62A4\uFF0C\u8BF7\u6267\u884C\u4EE5\u4E0B\u64CD\u4F5C\uFF1A`)); console.log( chalkWARN( `1.\u8FDB\u5165${serverConfig.host}\u670D\u52A1\u5668\uFF0C\u518D\u8FDB\u5165\u5BF9\u5E94\u7684\u9879\u76EE\u8DEF\u5F84\uFF0C\u7136\u540E\u91CD\u65B0\u5B89\u88C5\u4F9D\u8D56\uFF08\u5982\u679C\u6CA1\u5F71\u54CDpackage.json\u7684\u53EF\u5FFD\u7565\uFF09` ) ); console.log( chalkWARN( `2.\u6839\u636E\u5F53\u524D\u9879\u76EE\u7684ecosystem.config.js\uFF0C\u91CD\u542F\u5BF9\u5E94\u7684pm2\u670D\u52A1\uFF08\u5982\u679C\u662F\u9996\u6B21\u90E8\u7F72,\u5219\u6267\u884Cpm2 start ecosystem.config.js\uFF09` ) ); } }; function checkHasError() { Object.keys(cache).forEach((key) => { if (cache[key] === "error") { console.log(chalkERROR(`${key}\u5931\u8D25`)); process.exit(1); } }); } const deploy = async function(data) { console.log(chalkSUCCESS(`\u5F00\u59CB\u90E8\u7F72`)); const startTime = new Date().getTime(); const { shouldBuild = true, buildCmd, verifyGit = true, shouldRelease = true, config, deployDoneCb } = data; if (!config) { console.log(chalkERROR("\u7F3A\u5C11config\uFF01")); return; } if (config.cos) { const allowCos = Object.keys(CosEnum); if (!allowCos.includes(config.cos(data))) { console.log( chalkERROR( `config.cos\u9519\u8BEF, config.cos\u5FC5\u987B\u662F: ${allowCos.toString()}\u4E4B\u4E00` ) ); return; } } if (config.cdn) { const allowCdn = Object.keys(CdnEnum); if (!allowCdn.includes(config.cdn(data))) { console.log( chalkERROR( `config.cdn\u9519\u8BEF, config.cdn\u5FC5\u987B\u662F: ${allowCdn.toString()}\u4E4B\u4E00` ) ); return; } } try { await handleRelease(verifyGit, shouldRelease); if (shouldBuild) { console.log(chalkWARN("\u914D\u7F6E\u4E86\u6253\u5305,\u5F00\u59CB\u6267\u884C\u6253\u5305\u547D\u4EE4")); handleBuild(buildCmd); checkHasError(); } else { console.log(chalkWARN("\u6CA1\u6709\u914D\u7F6E\u6253\u5305")); } generateDeployFile(); if (config.cos && config.cos(data)) { console.log(chalkWARN("\u914D\u7F6E\u4E86cos\u5BF9\u8C61\u5B58\u50A8,\u5F00\u59CB\u6267\u884Ccos\u5BF9\u8C61\u5B58\u50A8\u64CD\u4F5C")); if (config.cos(data) === CosEnum.none) { console.log(chalkWARN("\u914D\u7F6E\u4E86cos\u5BF9\u8C61\u5B58\u50A8\u4E3Anone\uFF0C\u4E0D\u6267\u884C")); } else { let res; switch (config.cos(data)) { case CosEnum.huawei: res = await handleHuaweiObs(data); break; case CosEnum.ali: res = await handleAliOss(data); break; case CosEnum.qiniu: res = await handleQiniuKodo(data); break; case CosEnum.tencent: res = await handleTencentCos(data); break; } if (res.code !== 0) { throw new Error(`cos\u5BF9\u8C61\u5B58\u50A8\u9519\u8BEF\uFF01${res.msg}`); } } checkHasError(); } else { console.log(chalkWARN("\u6CA1\u6709\u914D\u7F6E\u5BF9\u8C61\u5B58\u50A8")); } if (config.cdn && config.cdn(data)) { console.log(chalkWARN("\u914D\u7F6E\u4E86cdn\u5185\u5BB9\u5206\u53D1,\u5F00\u59CB\u6267\u884Ccdn\u5185\u5BB9\u5206\u53D1\u64CD\u4F5C")); if (config.cdn(data) === CdnEnum.none) { console.log(chalkWARN("\u914D\u7F6E\u4E86cdn\u5185\u5BB9\u5206\u53D1\u4E3Anone\uFF0C\u4E0D\u6267\u884C")); } else { switch (config.cdn(data)) { case CdnEnum.tencent: await handleTencentCdn(data); break; } } checkHasError(); } else { console.log(chalkWARN("\u6CA1\u6709\u914D\u7F6Ecdn\u5185\u5BB9\u5206\u53D1")); } if (config.ssh && config.ssh(data)) { console.log(chalkWARN("\u914D\u7F6E\u4E86SSH,\u5F00\u59CB\u6267\u884CSSH\u64CD\u4F5C")); if (config.ssh(data) === false) { console.log(chalkWARN("\u914D\u7F6E\u4E86SSH\u4E3Afalse\uFF0C\u4E0D\u6267\u884C")); } else { await handleSSH(data); } checkHasError(); } else { console.log(chalkWARN("\u6CA1\u6709\u914D\u7F6ESSH")); } deleteDeployFile(); const endTime = new Date().getTime(); handlePm2Tip(data); console.log( chalkSUCCESS( `\u90E8\u7F72\u5B8C\u6210\uFF0C\u603B\u8017\u65F6\uFF1A${calculateRemainingTime({ startTime, endTime })}` ) ); deployDoneCb?.({ err: false }); } catch (error) { console.log(chalkERROR(`\u90E8\u7F72\u51FA\u9519`), error); deployDoneCb?.({ err: true }); process.exit(1); } }; export { CdnEnum, CosEnum, deploy };