import type { Options as SshSftpOptions } from '@qse/ssh-sftp' import { sshSftp } from '@qse/ssh-sftp' import chalk from 'chalk' import fs from 'fs-extra' import { format } from 'prettier' import paths from './config/paths.js' import changeDeployVersion from './config/plugins/change-deploy-version.js' import appConfig from './utils/appConfig.js' const appPkg = fs.readJsonSync(paths.package) const baseConfig = { localPath: 'dist', ignore: [], cleanRemoteFiles: false, securityLock: false, noWarn: true, } async function normalDeploy(args: DeployArgs) { const resolve = paths.resolveApp /** * 生成路径 * @name remoteFile 远程文件 * @name tmpFile 本地文件 * @name tmpDir 本地临时文件夹 `tmpFile`存在的文件夹 * @name tmpBase 本地临时文件夹 到`__tmp__`层 * @param {string} remoteFilePath 远程文件相对`opts.remotePath`地址 */ function getLocalAndRemoteFilePath(remoteFilePath: string, opts: any) { const splited = remoteFilePath.split('/') const fileName = splited[splited.length - 1] const tmpDirName = `__tmp_${Math.random().toString(16).slice(2)}__` const tmpBase = resolve(opts.localPath, tmpDirName) const tmpDir = resolve(opts.localPath, tmpDirName, ...splited.slice(0, -1)) const tmpFile = resolve(tmpDir, fileName) const remoteFile = [opts.remotePath, remoteFilePath].join('/') return { tmpDir, tmpFile, remoteFile, tmpBase } } function dateTime() { let date = new Date() date = new Date(date.getTime() - date.getTimezoneOffset() * 60000) return date.toISOString().replace(/T/, ' ').replace(/\..+/, '') } function updateLogContent(content: string, info: any) { const lines = content.trim().split('\n') lines.push(`[${dateTime()}] ${JSON.stringify(info)}\n`) return lines.slice(-50).join('\n') } async function upload(opts: SshSftpOptions) { await sshSftp({ ...opts, onAfterUploaded: async ({ client, options, spinner }) => { // 上传dist文件 spinner.start('自动更新 ver.js 版本配置') // 指定远程需要修改的文件 const fileName = 'js/ver.js' // 生成各种路径 const { remoteFile, tmpDir, tmpFile, tmpBase } = getLocalAndRemoteFilePath( fileName, options ) try { // 创建本地临时文件夹,位置在 `opts.localPath` 下的 `__tmp__` 文件夹 fs.mkdirSync(tmpDir, { recursive: true }) const info = { name: appPkg.name, version: appPkg.version, grayscale: appConfig.grayscale, } { // 拉取远程文件到本地 await client.fastGet(remoteFile, tmpFile) // 读取本地文件内容 let code = await fs.readFile(tmpFile, { encoding: 'utf-8' }) code = changeDeployVersion(code, info) code = await format(code, { parser: 'babel', printWidth: 120 }) await client.fastPut(tmpFile, remoteFile + '.bak') await fs.writeFile(tmpFile, code) // 将修改完的内容传回服务器 await client.fastPut(tmpFile, remoteFile) } { const remoteLogFile = remoteFile + '.log' const tmpLogFile = tmpFile + '.log' let content = '' try { await client.fastGet(remoteLogFile, tmpLogFile) content = await fs.readFile(tmpLogFile, 'utf-8') } catch {} content = updateLogContent(content, info) await fs.writeFile(tmpLogFile, content) await client.fastPut(tmpLogFile, remoteLogFile) } spinner.succeed('已更新 ver.js 版本配置') } catch (e: any) { spinner.fail(`自动修改 ver.js 失败,请手动修改`) console.log(chalk.bgRed(e.message)) } finally { fs.removeSync(tmpBase) } }, }) } const uploadSftpConfigs: SshSftpOptions[] = [] if (args.bureau) { uploadSftpConfigs.push({ preset: { context: 'eduwebngv1', folder: 'bureaupc' } }) } if (args.school) { uploadSftpConfigs.push({ preset: { context: 'eduwebngv1', folder: 'userportal' } }) } if (args.documentshelves) { uploadSftpConfigs.push({ preset: { context: 'eduwebngv1', folder: 'documentshelves' } }) } if (args.compositionshelves) { uploadSftpConfigs.push({ preset: { context: 'eduwebngv1', folder: 'compositionshelves' } }) } if (args.compositionshelvesDingtalk) { uploadSftpConfigs.push({ preset: { server: '19' }, remotePath: '/erp/edumaven/dingcorrection-page-dev/compositionshelves', }) } if (fs.existsSync(paths.sshSftp)) { const config = fs.readJsonSync(paths.sshSftp) uploadSftpConfigs.push(config) } if (uploadSftpConfigs.length === 0) { console.log( ` ${chalk.red('指定 deploy 部署范围')} 执行 ${chalk.green('npx edu-scripts deploy -h')} 查看具体用法 ` ) process.exit(1) } const uploadConfig = { ...baseConfig, ignore: [...baseConfig.ignore, 'js/ver.js'] } if (!appConfig.mainProject) { uploadConfig.ignore = [...uploadConfig.ignore, '!(js|images)'] } for (const config of uploadSftpConfigs) { await upload({ ...uploadConfig, ...config }) } } async function singleDeploy() { const config = fs.existsSync(paths.sshSftp) ? fs.readJsonSync(paths.sshSftp) : { ...baseConfig, preset: { context: 'qsxxwapdev' }, cleanRemoteFiles: true, securityLock: true, noWarn: false, } sshSftp(config) } interface DeployArgs { school?: boolean bureau?: boolean documentshelves?: boolean compositionshelves?: boolean compositionshelvesDingtalk?: boolean } export default function deploy(args: DeployArgs) { if (appConfig.single) { singleDeploy() } else { normalDeploy(args) } }