This commit is contained in:
2026-04-07 14:23:09 +08:00
commit 787a10ce5d
148 changed files with 45241 additions and 0 deletions

171
deploy.js Normal file
View File

@@ -0,0 +1,171 @@
const { execSync } = require('node:child_process');
const fs = require('node:fs');
const path = require('node:path');
// --- 【配置区】 ---
const CONFIG = {
localProjectRoot: path.join(__dirname, '..'),
localReleasesDir: path.join(__dirname, '../releases'),
deployRemote: true,
remote: {
host: '1.2.3.4', // 服务器IP
user: 'root', // 用户名
path: '/www/wwwroot/my-app', // 服务器项目根目录
},
};
// 颜色工具
const log = {
info: (msg) => console.log(`\x1b[36m>>> ${msg}\x1b[0m`),
success: (msg) => console.log(`\x1b[32m✔ ${msg}\x1b[0m`),
warn: (msg) => console.log(`\x1b[33m${msg}\x1b[0m`),
error: (msg) => console.log(`\x1b[31m✘ ${msg}\x1b[0m`),
};
async function start() {
let version = '';
// 1. 获取 Git Tag
try {
version = execSync('git describe --tags --abbrev=0').toString().trim();
log.success(`读取到目标版本: ${version}`);
} catch (_e) {
log.error('无法获取 Git Tag。请确保本地有 tag (例如执行: git tag v1.0.0)');
process.exit(1);
}
// 2. 执行编译
try {
log.info('开始执行构建 (npm run build)...');
execSync('npm run build', {
stdio: 'inherit',
env: { ...process.env, UMI_APP_SENTRY_RELEASE: version },
});
log.success('项目构建成功');
} catch (_e) {
log.error('项目构建失败,请检查代码语法或 Umi/Webpack 配置');
process.exit(1);
}
// 3. 上传 SourceMap
try {
log.info('正在尝试上传 SourceMap 到 GlitchTip...');
// 如果这里报错,通常不中断部署,因为可能是网络波动,以后可以补传
execSync(
`sentry-cli releases files "${version}" upload-sourcemaps ./dist --url-prefix "~/"`,
{ stdio: 'inherit' },
);
log.success('SourceMap 上传成功');
} catch (_e) {
log.warn(
'SourceMap 上传失败原因可能是sentry-cli 未安装、环境变量未配置、.sentryclirc 文件未配置或网络不通。',
);
log.warn('您可以稍后手动补传,部署将继续进行...');
}
// 4. 清理并本地归档
try {
log.info('正在清理.map 文件并执行本地归档...');
cleanSourceMaps(path.join(CONFIG.localProjectRoot, 'dist'));
deployToLocal(version);
log.success('本地归档与软链接更新完成');
} catch (_e) {
log.error(`本地归档失败: ${e.message}`);
process.exit(1);
}
// 5. 远程部署
if (CONFIG.deployRemote) {
try {
log.info(`准备向远程服务器部署: ${CONFIG.remote.host}...`);
deployToRemote(version);
log.success('远程部署任务全部完成');
} catch (_e) {
log.error(`远程部署失败!错误详情: ${e.message}`);
log.error(
'请检查1.服务器IP/路径是否正确2.是否配置了 SSH 免密登录3.服务器磁盘空间是否充足。',
);
process.exit(1);
}
}
log.info(`\n🎉 所有任务执行完毕 [Version: ${version}]`);
}
// --- 逻辑封装函数 ---
function deployToLocal(version) {
const distDir = path.join(CONFIG.localProjectRoot, 'dist');
const targetDir = path.join(CONFIG.localReleasesDir, version);
const currentLink = path.join(CONFIG.localProjectRoot, 'current');
if (!fs.existsSync(CONFIG.localReleasesDir))
fs.mkdirSync(CONFIG.localReleasesDir);
if (fs.existsSync(targetDir))
fs.rmSync(targetDir, { recursive: true, force: true });
// 移动 dist 到 releases
fs.renameSync(distDir, targetDir);
// 更新本地软链接 (Junction)
if (fs.existsSync(currentLink)) {
try {
fs.rmSync(currentLink, { recursive: true, force: true });
} catch (_e) {
throw new Error(`无法删除旧的本地软链接: ${e.message}`);
}
}
const linkType = process.platform === 'win32' ? 'junction' : 'dir';
fs.symlinkSync(targetDir, currentLink, linkType);
}
function deployToRemote(version) {
const { host, user, path: remotePath } = CONFIG.remote;
const localVersionDir = path.join(CONFIG.localReleasesDir, version);
const remoteVersionDir = `${remotePath}/releases/${version}`;
const remoteCurrentLink = `${remotePath}/current`;
// A. 在服务器创建目录
try {
execSync(`ssh ${user}@${host} "mkdir -p ${remotePath}/releases"`, {
timeout: 10000,
});
} catch (_e) {
throw new Error(`无法连接服务器或创建目录: ${e.message}`);
}
// B. 使用 scp 上传文件夹
try {
log.info(` 正在上传至: ${remoteVersionDir} (请耐心等待...)`);
execSync(
`scp -r "${localVersionDir}" ${user}@${host}:${remotePath}/releases/`,
{ timeout: 60000 },
);
} catch (_e) {
throw new Error(`文件传输失败 (SCP): ${e.message}`);
}
// C. 更新远程软链接
try {
const sshCmd = `ln -snf ${remoteVersionDir} ${remoteCurrentLink}`;
execSync(`ssh ${user}@${host} "${sshCmd}"`);
} catch (_e) {
throw new Error(`远程软链接更新失败: ${e.message}`);
}
}
function cleanSourceMaps(dir) {
if (!fs.existsSync(dir)) return;
const files = fs.readdirSync(dir);
for (const file of files) {
const fullPath = path.join(dir, file);
if (fs.statSync(fullPath).isDirectory()) {
cleanSourceMaps(fullPath);
} else if (file.endsWith('.map')) {
fs.unlinkSync(fullPath);
}
}
}
// 启动执行
start();