const fs = require('fs-extra');
|
const { execSync } = require('child_process');
|
const os = require('os');
|
const path = require('path');
|
const chalk = require('chalk');
|
require('dotenv').config({ path: '.env.local' });
|
|
// 获取当前脚本所在的目录
|
const scriptDir = __dirname;
|
const rootDir = path.resolve(scriptDir, '..');
|
const argv2 = process.argv[2];
|
const customerList = argv2?.split(' ') ?? '';
|
const publicDir = path.join(rootDir, 'public');
|
// process.env.VITE_OUTPUT_DIR ||
|
const distDir = path.join(rootDir, 'dist');
|
const customerListDir = path.join(rootDir, 'customer_list');
|
const customerProjectListDir = path.join(rootDir, 'src', 'views', 'project');
|
const firstCustomerName = customerList[0]?.split(':')[0];
|
/** 公共文件夹,所有客户文件夹共享文件 */
|
const commonDir = path.join(customerListDir, 'common');
|
|
const item = customerList[0];
|
const customerSplit = item?.split(':');
|
const deployEnv = customerSplit?.[1];
|
// 是否为生产环境
|
const isPro = deployEnv === 'pro';
|
// const deployEnv = process.argv[3];
|
|
const logColor = (text, color) => {
|
console.log(chalk[color](text));
|
};
|
|
const logError = (text) => {
|
logColor(text, 'red');
|
};
|
|
const logSuccess = (text) => {
|
logColor(text, 'green');
|
};
|
|
const logWarn = (text) => {
|
logColor(text, 'yellow');
|
};
|
|
/**
|
* 退出脚本
|
*/
|
const exit = () => {
|
process.exit(1); // 退出脚本
|
};
|
|
/**
|
* 检查文件是否存在
|
* @param {*} file
|
* @param {*} fileText
|
*/
|
const checkFileExist = (file, fileText = '') => {
|
// 验证源文件夹是否存在
|
if (!fs.existsSync(file)) {
|
console.error(chalk.red(`${fileText ? fileText + ' ' : ''}"${file}" 不存在!`));
|
exit(); // 退出脚本
|
}
|
};
|
|
const checkCustomerDirExist = (customer) => {
|
const customerDir = path.join(customerListDir, customer);
|
checkFileExist(customerDir, '客户文件夹');
|
};
|
|
/**
|
* 检查命令,是否加上用户名。以及用户文件夹是否存在
|
* @param {*} command
|
* @param {*} customer
|
*/
|
const checkCustomer = (command, customer = firstCustomerName) => {
|
if (!customer) {
|
console.error(chalk.red(`请正确使用命令 "${command} [customer]"`));
|
|
exit(); // 退出脚本
|
}
|
checkCustomerDirExist(customer);
|
};
|
|
const replaceFileContent = (path, callback) => {
|
const data = fs.readFileSync(path, 'utf8');
|
if (!data) return;
|
const newData = callback(data);
|
fs.writeFileSync(path, newData, 'utf8');
|
};
|
|
const replaceGlobImportPattern = /import.meta.glob\s*\((\s*.*\/views\/\*\*.*)\)/;
|
const matchAllPattern = '../views/**/*.{vue,tsx}';
|
|
/**
|
* 修改 src/router/backEnd.ts 文件,只导入当前项目文件
|
* @param {*} command
|
*/
|
const updateImportGlob = () => {
|
const backEndFilePath = path.join(rootDir, 'src', 'router', 'backEnd.ts');
|
checkFileExist(backEndFilePath, `${backEndFilePath}文件`);
|
const subFile = fs.readdirSync(customerListDir);
|
const filterSubFile = subFile?.filter((item) => item !== firstCustomerName);
|
|
replaceFileContent(backEndFilePath, (data) => {
|
const excludeCustomer = filterSubFile && filterSubFile.length > 0 ? `/(${filterSubFile.join('|')})` : '';
|
const excludePattern = `!../views/project${excludeCustomer}/**/*`;
|
const replaceStr = `import.meta.glob(['${matchAllPattern}', '${excludePattern}'])`;
|
const newData = data.replace(replaceGlobImportPattern, replaceStr);
|
return newData;
|
});
|
};
|
|
const restoreImportGlob = () => {
|
const backEndFilePath = path.join(rootDir, 'src', 'router', 'backEnd.ts');
|
checkFileExist(backEndFilePath, `${backEndFilePath}文件`);
|
|
replaceFileContent(backEndFilePath, (data) => {
|
const replaceStr = `import.meta.glob('${matchAllPattern}')`;
|
const newData = data.replace(replaceGlobImportPattern, replaceStr);
|
return newData;
|
});
|
};
|
|
/**
|
* 获取当前日期是第几周
|
* @param dateTime 当前传入的日期值
|
* @returns 返回第几周数字值
|
*/
|
const getWeek = (dateTime) => {
|
const temptTime = new Date(dateTime.getTime());
|
// 周几
|
const weekday = temptTime.getDay() || 7;
|
// 周1+5天=周六
|
temptTime.setDate(temptTime.getDate() - weekday + 1 + 5);
|
let firstDay = new Date(temptTime.getFullYear(), 0, 1);
|
const dayOfWeek = firstDay.getDay();
|
let spendDay = 1;
|
if (dayOfWeek != 0) spendDay = 7 - dayOfWeek + 1;
|
firstDay = new Date(temptTime.getFullYear(), 0, 1 + spendDay);
|
const d = Math.ceil((temptTime.valueOf() - firstDay.valueOf()) / 86400000);
|
const result = Math.ceil(d / 7);
|
return result;
|
};
|
|
/**
|
* 时间日期转换
|
* @param date 当前时间,new Date() 格式
|
* @param format 需要转换的时间格式字符串,默认值YYYY-mm-dd HH:MM:SS
|
* @description format 字符串随意,如 `YYYY-mm、YYYY-mm-dd`
|
* @description format 季度:"YYYY-mm-dd HH:MM:SS QQQQ"
|
* @description format 星期:"YYYY-mm-dd HH:MM:SS WWW"
|
* @description format 几周:"YYYY-mm-dd HH:MM:SS ZZZ"
|
* @description format 季度 + 星期 + 几周:"YYYY-mm-dd HH:MM:SS WWW QQQQ ZZZ"
|
* @returns 返回拼接后的时间字符串
|
*/
|
const formatDate = (date, format = 'YYYY-mm-dd HH:MM:SS') => {
|
const we = date.getDay(); // 星期
|
const z = getWeek(date); // 周
|
const qut = Math.floor((date.getMonth() + 3) / 3).toString(); // 季度
|
const opt = {
|
'Y+': date.getFullYear().toString(), // 年
|
'm+': (date.getMonth() + 1).toString(), // 月(月份从0开始,要+1)
|
'd+': date.getDate().toString(), // 日
|
'H+': date.getHours().toString(), // 时
|
'M+': date.getMinutes().toString(), // 分
|
'S+': date.getSeconds().toString(), // 秒
|
'q+': qut, // 季度
|
};
|
// 中文数字 (星期)
|
const week = {
|
0: '日',
|
1: '一',
|
2: '二',
|
3: '三',
|
4: '四',
|
5: '五',
|
6: '六',
|
};
|
// 中文数字(季度)
|
const quarter = {
|
1: '一',
|
2: '二',
|
3: '三',
|
4: '四',
|
};
|
if (/(W+)/.test(format))
|
format = format.replace(RegExp.$1, RegExp.$1.length > 1 ? (RegExp.$1.length > 2 ? '星期' + week[we] : '周' + week[we]) : week[we]);
|
if (/(Q+)/.test(format)) format = format.replace(RegExp.$1, RegExp.$1.length == 4 ? '第' + quarter[qut] + '季度' : quarter[qut]);
|
if (/(Z+)/.test(format)) format = format.replace(RegExp.$1, RegExp.$1.length == 3 ? '第' + z + '周' : z + '');
|
for (const k in opt) {
|
const r = new RegExp('(' + k + ')').exec(format);
|
// 若输入的长度不为1,则前面补零
|
if (r) format = format.replace(r[1], RegExp.$1.length == 1 ? opt[k] : opt[k].padStart(RegExp.$1.length, '0'));
|
}
|
return format;
|
};
|
|
/**
|
* 复制文件到 public 文件夹中
|
* (只能复制一个客户文件配置)
|
* @param {*} command
|
* @param {*} customer
|
*/
|
function copyFile() {
|
// 确保 public 文件,不存在则创建
|
fs.ensureDirSync(publicDir);
|
// 清空 public 文件夹
|
fs.emptyDirSync(publicDir);
|
|
// 使用 filter 过滤器,排除特定文件
|
const filter = (src, dest) => {
|
const fileName = path.basename(src);
|
// 排除特定文件
|
if (fileName === 'deploy.json') {
|
return false;
|
}
|
// 其他文件保留
|
return true;
|
};
|
|
const customerDir = path.join(customerListDir, firstCustomerName);
|
// 复制源文件夹中的所有文件到 public 文件夹
|
fs.copySync(customerDir, publicDir, { filter });
|
fs.copySync(commonDir, publicDir);
|
}
|
|
/**
|
* 上传文件到服务器
|
* @param {*} command
|
*/
|
const uploadFiles = async () => {
|
let customerConfig = {};
|
for (let index = 0; index < customerList.length; index++) {
|
const item = customerList[index];
|
const customerSplit = item.split(':');
|
const customerName = customerSplit[0];
|
checkCustomerDirExist(customerName);
|
const deployJSON = path.join(customerListDir, customerName, 'deploy.json');
|
// 验证部署配置文件是否存在
|
checkFileExist(deployJSON, '配置文件');
|
// 读取 JSON 文件
|
const config = await fs.readJson(deployJSON).catch((err) => {
|
console.error(`读取配置文件"${deployJSON}"出错:`, err);
|
exit();
|
});
|
|
let deployConfig = null;
|
const deployEnv = customerSplit[1];
|
if (deployEnv === 'pro') {
|
deployConfig = config?.product;
|
} else {
|
deployConfig = config?.test;
|
}
|
if (!deployConfig || Object.values(deployConfig).some((item) => !item)) {
|
console.error(chalk.red(`${customerName} ${deployEnv} 配置不完整!`));
|
exit(); // 退出脚本
|
}
|
// 缓存部署配置
|
customerConfig[item] = deployConfig;
|
}
|
for (const customerDeployName in customerConfig) {
|
if (Object.hasOwnProperty.call(customerConfig, customerDeployName)) {
|
const customerName = customerDeployName.split(':')[0];
|
const deployConfig = customerConfig[customerDeployName];
|
const remoteFolderPath = deployConfig.path;
|
// 获取用户临时目录
|
const userTempDir = os.tmpdir();
|
const timeStamp = new Date().getTime();
|
const uploadScriptFile = path.join(userTempDir, `psftpBatchFile${timeStamp}${customerDeployName}`);
|
// 构建 sftp 命令
|
const sftpCommand = 'psftp';
|
// 上传 dist 中的 assets 和 index.html
|
let uploadCommand = `put -r ${path.join(distDir, 'assets')} ${remoteFolderPath + '/assets'}
|
put ${path.join(distDir, 'index.html')} ${remoteFolderPath + '/index.html'}\n`;
|
|
const customerFolder = path.join(customerListDir, customerName);
|
// 客户文件夹中除 deploy.json 全部上传
|
try {
|
const contents = fs.readdirSync(customerFolder);
|
for (let index = 0; index < contents.length; index++) {
|
const item = contents[index];
|
if (item === 'deploy.json') {
|
continue;
|
}
|
const itemPath = path.join(customerFolder, item);
|
const remotePath = remoteFolderPath + `/${item}`;
|
const stats = fs.statSync(itemPath);
|
|
// 文件夹需要加 -r
|
if (stats.isDirectory()) {
|
uploadCommand += `put -r ${itemPath} ${remotePath}\n`;
|
// 文件不需要加
|
} else if (stats.isFile()) {
|
uploadCommand += `put ${itemPath} ${remotePath}\n`;
|
}
|
}
|
} catch (error) {
|
console.error(`读取"${customerFolder}"失败`, error);
|
}
|
|
try {
|
// 批量备份文件夹到本地
|
// FIXME: 文件夹如果是中文,会乱码
|
// uploadCommand=`get -r /D:/IStation.SQI.WebAirp E:\\139.224.246.185_bak\\IStation.SQI.WebAirp`
|
fs.writeFileSync(uploadScriptFile, uploadCommand);
|
const sftpArgs = [
|
`${deployConfig.username}@${deployConfig.host} -P ${deployConfig.port} -pw ${deployConfig.password} -b ${uploadScriptFile}`,
|
];
|
// 完整的执行命令
|
const fullCommand = `${sftpCommand} ${sftpArgs.join(' ')}`;
|
logSuccess(`正在上传【${customerName}】项目至服务器【${deployConfig.host}:${remoteFolderPath}】...`);
|
execSync(fullCommand, { stdio: ['pipe', 'inherit', 'inherit'] });
|
} catch (error) {
|
console.error(chalk.red(error));
|
} finally {
|
if (fs.existsSync(uploadScriptFile)) {
|
fs.unlinkSync(uploadScriptFile);
|
}
|
}
|
}
|
}
|
logSuccess(`${formatDate(new Date(), 'HH:MM:SS')} > 🎉🎉🎉【${customerList.join(',')}】项目已成功部署!🎉🎉🎉`);
|
};
|
|
/**
|
* 切换分支
|
*/
|
const changeBranch = () => {
|
return;
|
if (isPro) {
|
try {
|
execSync('git checkout master', { stdio: 'inherit' });
|
} catch (error) {}
|
} else {
|
try {
|
execSync('git checkout test', { stdio: 'inherit' });
|
} catch (error) {}
|
}
|
};
|
|
module.exports = {
|
isPro,
|
firstCustomerName,
|
exit,
|
customerList,
|
replaceFileContent,
|
checkFileExist,
|
//#region ====================== 文件路径 ======================
|
rootDir,
|
scriptDir,
|
publicDir,
|
distDir,
|
customerListDir,
|
//#endregion
|
|
//#region ====================== 工具函数 ======================
|
checkCustomerDirExist,
|
//#endregion
|
checkCustomer,
|
copyFile,
|
uploadFiles,
|
|
//#region ====================== 打印 ======================
|
logError,
|
logSuccess,
|
logWarn,
|
//#endregion
|
formatDate,
|
|
updateImportGlob,
|
restoreImportGlob,
|
deployEnv,
|
changeBranch,
|
};
|