const fs = require('fs') const path = require('path') const ssh = new (require('node-ssh'))() const commander = require('commander') commander .version('1.0', '-v, --version') // 默认是 -V。如果要 -v,就要加 '-v --version' .option('-H, --host ', 'domain name or ip address of the target server') .option('-P, --port ', 'ssh port number of the target server') .option('-r, --root ', 'root directory to deploy on the target server') .option('-d, --dist ', 'dist folder to deploy on the target server') .option('-u, --user ', 'user id to login the target server') .option('-k, --key ', 'user key file to login the target server') .option('-p, --password ', 'user password to login the target server. You may have to enclose the password in ""') .parse(process.argv) const root=commander.root // 本地的项目目录。似乎该目录必须已经存在于服务器上 const dist=commander.dist||'dist' // 新系统将发布在这个目录里。建议为dist,和npm run build产生的目录一致,这样既可以远程自动部署,也可以直接登录服务器手动部署。 console.log(` root = ${root} `) console.log(` dist = ${dist} `) const privateKeyFile=commander.key||`${process.env.HOME}/.ssh/id_rsa` const connection = { host: commander.host, port: commander.port||22, username: commander.user||'tic', privateKey: fs.existsSync(privateKeyFile)?privateKeyFile:undefined, password: commander.password, tryKeyboard: true, onKeyboardInteractive: (name, instructions, instructionsLang, prompts, finish) => { // 不起作用 if (prompts.length > 0 && prompts[0].prompt.toLowerCase().includes('password')) { finish([password]) } }, } console.log(connection) 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 ${dist} ${dist}-backup-${new Date().toISOString()} ... ]`) await ssh.execCommand(`mv ${dist} ${dist}-backup-${new Date().toISOString()}`, { cwd:root }) console.log(`[ mkdir ${dist} ... ]`) await ssh.execCommand(`mkdir ${dist}`, { cwd:root }) const toCreate = necessaryPath('./dist') for (const name of toCreate) { console.log(`[ mkdir ${dist}/${name.join('/')} ... ]`) await ssh.execCommand(`mkdir ${dist}/${name.join('/')}`, { cwd:root }) } let err console.log(`[ Upload to ${root}/${dist} ... ]`) await ssh.putDirectory('./dist', `${root}/${dist}`, { concurrency: 10, recursive: true, validate: itemPath => { const baseName = path.basename(itemPath); return !baseName.endsWith('.map'); }, tick: (localPath, remotePath, error) => { console.log(`Uploading "${localPath}" ===> "${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) })