wo-base-deployer/deploy.js

293 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const fs = require('fs')
const path = require('path')
/** ******************* 读取命令行以及配置文件里的参数 ******************** **/
const commander = require('commander')
const deepmerge = require('deepmerge')
var Config = { deploy: {} }
// 读取配置文件
try {
let configFile
if (fs.existsSync(configFile=path.join(process.cwd(), 'ConfigBasic.js'))) {
Config = require(configFile)
console.info(`${configFile} loaded`)
}
if (fs.existsSync(configFile=path.join(process.cwd(), 'ConfigCustom.js'))) { // 如果存在,覆盖掉 ConfigBasic 里的默认参数
Config = deepmerge(Config, require(configFile)) // 注意objectMerge后产生了一个新的对象而不是在原来的Config里添加
console.info(`${configFile} loaded`)
}
if (fs.existsSync(configFile=path.join(process.cwd(), 'ConfigSecret.js'))) { // 如果存在,覆盖掉 ConfigBasic 和 ConfigCustom 里的参数
Config = deepmerge(Config, require(pconfigFile))
console.info(`${configFile} loaded`)
}
} catch (err) {
console.error('Loading config files failed: ' + err.message)
}
commander
.version('1.0', '-v, --version') // 默认是 -V。如果要 -v就要加 '-v --version'
.option('-f, --from <from>', `from path to copy from. Default to ${Config.deploy.from}`)
.option('-t, --type <type>', `Deploy to server type, web or git. Default to ${Config.deploy.type}`)
.option('-H, --host <host>', `Host IP or domain name of the target server. Default to ${Config.deploy.host}`)
.option('-P, --port <port>', `Ssh port number of the target server. Default to ${Config.deploy.port}`)
.option('-D, --dir <dir>', `Destination path to deploy on the target server. Default to ${Config.deploy.dir}`)
.option('-d, --dist <dist>', `Destination folder to deploy on the target server. Default to ${Config.deploy.dist}`)
.option('-r, --repo <repo>', `git repo address. Default to ${Config.deploy.repo}`)
.option('-b, --branch <branch', `git repo branch. Default to ${Config.deploy.branch}`)
.option('-n, --gitname <name>', `git user name. Default to ${Config.deploy.gitname}`)
.option('-e, --gitemail <email>', `git user email. Default to ${Config.deploy.gitemail}`)
.option('-u, --user <user>', `User id to login the target server. Default to ${Config.deploy.user}`)
.option('-k, --key <key>', `User private key file to login the target server. Default to ${Config.deploy.key}`)
.option('-p, --password <password>', `User password to login the target server. You may have to enclose it in "". Default to "${Config.deploy.password}"`)
.parse(process.argv)
const privateKeyFile = commander.key || Config.deploy.key || `${process.env.HOME}/.ssh/id_rsa`
const connection = {
from: commander.from || Config.deploy.from || './dist',
type: commander.type || Config.deploy.type || 'web',
host: commander.host || Config.deploy.host,
port: commander.port || Config.deploy.port || 22,
dir: commander.dir || Config.deploy.dir, // 目标服务器上的目录。似乎该目录必须已经存在于服务器上
dist: commander.dist || Config.deploy.dist || 'dist', // 新系统将发布在这个文件夹里。建议为dist和npm run build产生的目录一致这样既可以远程自动部署也可以直接登录服务器手动部署。
repo: commander.repo || Config.deploy.repo,
branch: commander.branch || Config.deploy.branch || 'master',
gitname: commander.gitname || Config.deploy.gitname,
gitemail: commander.gitemail || Config.deploy.gitemail,
username: commander.user || Config.deploy.user,
privateKey: fs.existsSync(privateKeyFile) ? privateKeyFile : undefined,
password: commander.password || Config.deploy.password,
tryKeyboard: true,
onKeyboardInteractive: (name, instructions, instructionsLang, prompts, finish) => { // 不起作用
if (prompts.length > 0 && prompts[0].prompt.toLowerCase().includes('password')) {
finish([password])
}
},
}
console.log(` connection = ${JSON.stringify(connection)}`)
if (connection.type==='web') {
deployToWeb()
}else if (connection.type==='git'){
deployToGit()
}
/** ********************** 连接到 Web主机拷贝文件到指定路径 ************* **/
function deployToWeb(){
const ssh = new (require('node-ssh'))()
function subDirs (path) {
const dirs = [path]
if (fs.statSync(path).isFile()) {
return dirs
}
fs.readdirSync(path).forEach(item => {
const stat = fs.statSync(`${path}/${item}`)
if (stat.isDirectory()) {
dirs.push(...subDirs(`${path}/${item}`))
}
})
return dirs
}
const necessaryPath = (path) => {
return subDirs(path)
.map(it => it.replace(path, ''))
.filter(it => it)
.map(it => it.split('/').filter(it => it))
}
ssh.connect(connection).then(async () => {
console.log(`[ mv ${connection.dist} ${connection.dist}-backup-${new Date().toISOString()} ... ]`)
await ssh.execCommand(`mv ${connection.dist} ${connection.dist}-backup-${new Date().toISOString()}`, { cwd: connection.dir })
console.log(`[ mkdir ${connection.dist} ... ]`)
await ssh.execCommand(`mkdir ${connection.dist}`, { cwd: connection.dir })
const toCreate = necessaryPath(path.join('./', connection.from))
for (const name of toCreate) {
console.log(`[ mkdir ${connection.dist}/${name.join('/')} ... ]`)
await ssh.execCommand(`mkdir ${connection.dist}/${name.join('/')}`, { cwd: connection.dir })
}
let err
console.log(`[ Upload to ${connection.dir}/${connection.dist} ... ]`)
await ssh.putDirectory(path.join('./', connection.from), `${connection.dir}/${connection.dist}`, {
concurrency: 10,
recursive: true,
validate: itemPath => {
const baseName = path.basename(itemPath)
return !baseName.endsWith('.map')
},
tick: (fromPath, remotePath, error) => {
console.log(`Uploading "${fromPath}" ===> "${remotePath}" ${error || 'succeeded!'}`)
err = error
},
})
ssh.dispose()
if (err) {
console.log('[ Uploaded with error! ]')
process.exit(1)
} else {
console.log('[ Uploaded successfully! ]')
}
}).catch(err => {
console.error(err)
ssh.dispose()
process.exit(1)
})
}
/** ********************** 连接到 Git主机拷贝文件到指定路径 ************* **/
function deployToGit(){
const pathFn = require('path')
const fs = require('hexo-fs')
const chalk = require('chalk')
const swig = require('swig-templates')
const moment = require('moment')
const Promise = require('bluebird')
const spawn = require('hexo-util/lib/spawn')
const parseConfig = require('./configGit')
const swigHelpers = {
now: function(format) {
return moment().format(format)
}
}
function exec() {
const baseDir = ''
const deployDir = pathFn.join(baseDir, '.deploy_git')
const publicDir = connection.from
let extendDirs = connection.extend_dirs
const ignoreHidden = connection.ignore_hidden
const ignorePattern = connection.ignore_pattern
const message = commitMessage(connection)
const verbose = !connection.silent
if (!connection.repo && process.env.HEXO_DEPLOYER_REPO) {
connection.repo = process.env.HEXO_DEPLOYER_REPO
}
if (!connection.repo && !connection.repository) {
let help = ''
help += 'You have to configure the deployment settings in config files or command line first!\n\n'
help += 'Example:\n'
help += ' node deploy.js -t git -r https://github.com/log-home/log-home.github.io -b master -l ../../project/public'
console.log(help)
return
}
function git(...connection) {
return spawn('git', connection, {
cwd: deployDir,
verbose: verbose,
stdio: 'inherit'
})
}
function setup() {
const userName = Config.deploy.gitname || ''
const userEmail = Config.deploy.gitemail || ''
// Create a placeholder for the first commit
return fs.writeFile(pathFn.join(deployDir, 'placeholder'), '').then(() => {
return git('init')
}).then(() => {
return userName && git('config', 'user.name', userName)
}).then(() => {
return userEmail && git('config', 'user.email', userEmail)
}).then(() => {
return git('add', '-A')
}).then(() => {
return git('commit', '-m', 'First commit')
})
}
function push(repo) {
return git('add', '-A').then(() => {
return git('commit', '-m', message).catch(() => {
// Do nothing. It's OK if nothing to commit.
})
}).then(() => {
return git('push', '-u', repo.url, 'HEAD:' + repo.branch, '--force')
})
}
return fs.exists(deployDir).then(function(exist) {
if (exist) return
// log.info('Setting up Git deployment...')
return setup()
}).then(() => {
// log.info('Clearing .deploy_git folder...')
return fs.emptyDir(deployDir)
}).then(() => {
const opts = {}
// log.info('Copying files from public folder...')
if (typeof ignoreHidden === 'object') {
opts.ignoreHidden = ignoreHidden.public
} else {
opts.ignoreHidden = ignoreHidden
}
if (typeof ignorePattern === 'string') {
opts.ignorePattern = new RegExp(ignorePattern)
} else if (typeof ignorePattern === 'object' && Reflect.apply(Object.prototype.hasOwnProperty, ignorePattern, ['public'])) {
opts.ignorePattern = new RegExp(ignorePattern.public)
}
return fs.copyDir(publicDir, deployDir, opts)
}).then(() => {
// log.info('Copying files from extend dirs...')
if (!extendDirs) {
return
}
if (typeof extendDirs === 'string') {
extendDirs = [extendDirs]
}
const mapFn = function(dir) {
const opts = {}
const extendPath = pathFn.join(baseDir, dir)
const extendDist = pathFn.join(deployDir, dir)
if (typeof ignoreHidden === 'object') {
opts.ignoreHidden = ignoreHidden[dir]
} else {
opts.ignoreHidden = ignoreHidden
}
if (typeof ignorePattern === 'string') {
opts.ignorePattern = new RegExp(ignorePattern)
} else if (typeof ignorePattern === 'object' && Reflect.apply(Object.prototype.hasOwnProperty, ignorePattern, [dir])) {
opts.ignorePattern = new RegExp(ignorePattern[dir])
}
return fs.copyDir(extendPath, extendDist, opts)
}
return Promise.map(extendDirs, mapFn, {
concurrency: 2
})
}).then(() => {
return parseConfig(connection)
}).each(function(repo) {
console.log('########## repo ###########')
console.log(repo)
return push(repo)
})
}
function commitMessage(connection) {
const message = connection.m || connection.msg || connection.message || 'Site updated: {{ now(\'YYYY-MM-DD HH:mm:ss\') }}'
return swig.compile(message)(swigHelpers)
}
exec()
}