编写一个自动推送 Git Page 的 nodejs 脚本
背景
写了一个简单的个人主页,由于使用了一些 CSS3
的属性,考虑到浏览器兼容性问题,就使用了 webpack 的一些 loader
给这些属性加前缀。所以简单的静态单页面用了 webpack,又因为这个页面要放在
Github Pages 上,每次打包后还得手动切换分支推送到 Github
上,干脆趁这个机会写一个自动推送的脚本。
该脚本主要参考了 hexo-deployer-git ,可以查看该项目获取功能更完善的源码。
预备知识
脚本比较简单,主要使用 NodeJs child_process 的 spawn
方法调用子进程执行 git 语句。如果你对 NodeJs
不熟悉可能需要了解以下内容才能完全弄懂脚本中的每一行代码:(如果你只是想大致了解一下代码原理,可不必查看以下知识,本文将做简单的解释)
spawn
用法
NodeJs 的 child_process 模块提供了 spawn
方法,该方法将调用子进程,并执行传入的命令行及参数。该脚本主要利用该方法执行需要的
git 命令。下面是一个简单的 spawn 使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const { spawn } = require ('child_process' );const ls = spawn ('ls' , ['-lh' , '/usr' ]); ls.stdout .on ('data' , (data ) => { console .log (`stdout: ${data} ` ); }); ls.stderr .on ('data' , (data ) => { console .error (`stderr: ${data} ` ); }); ls.on ('close' , (code ) => { console .log (`子进程退出,使用退出码 ${code} ` ); });
实例中向 spawn
传入了 ls
命令,以及
-lh
、/usr
参数,并通过事件监听回调处理命令执行的结果。spawn
方法返回一个 ChildProcess
实例,其中 stdout
和
stderr
是命令执行后的输出流和错误流。我们监听这两个流的事件来打印命令执行后的结果或错误信息。同时监听实例的
close
事件,打印命令执行后的退出码。退出码为 0
时代表命令正常执行完毕,非 0 时代表发生了异常。(如 debug
需要具体代码含义,可查看退出码 )。
封装 spawn
如上所述,spawn 通过回调来处理响应,我们希望使用更方便易读的 Promise
来简单封装一下
spawn。(对于简单的代码来说,这一步不是必要的,但我还是参照 hexo
的做法封装了 spawn。)
spawn.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 'use strict' const { spawn } = require ('child_process' )function promiseSpawn (command, args, options ) { if (!command) throw new TypeError ('command is reuqired' ); if (!options && args && !Array .isArray (args)) { options = args; args = []; } args = args || []; options = options || {}; return new Promise ((resolve, reject ) => { const task = spawn (command, args, options); const encoding = options.hasOwnProperty ('encoding' ) ? options.encoding : 'utf8' ; if (task.stdout ) { task.stdout .pipe (process.stdout ); } if (task.stderr ) { task.stderr .pipe (process.stdout ) } task.on ('close' , code => { if (code) { const e = new Error ('command execute failed' ); e.code = code; return reject (e); } resolve (); }); task.on ('error' , reject) if (!task.stderr && !task.stdout ) { task.on ('exit' , code => { if (code) { const e = new Error ('Spawn failed' ); e.code = code; return reject (e); } }); } }); }module .exports = promiseSpawn;
ChildProcess 的
close
、error
、exit
事件的触发是有区别的,并且也不是简单的包含关系,所以代码中对其分别进行了处理,具体可查看官方文档 。
执行 git 命令
利用上面封装的 spawn
方法,可以很方便的执行 git
的语句。
deployer.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 'use strict' const spawn = require ('./lib/spawn' ); const pathFn = require ('path' );const fs = require ('fs' );const args = { user : { name : 'name' , email : '[email protected] ' , }, baseDir : pathFn.resolve ('./' , 'dist' ), repo : { url : '[email protected] :username/username.github.io.git' , branch : 'master' , }, };deployToGit (args); function deployToGit (args ) { const message = args.message || `Site updated: ${(new Date ()).toDateString()} ` const baseDir = args.baseDir ; const gitDir = pathFn.join (baseDir, '.git' ); if (!args.repo ) { return console .log ('Please check configs of repository!' ) } if (!fs.existsSync (baseDir)) { throw new Error ('Please build before deploy' ) } if (!fs.existsSync (gitDir)) { setup () .then (() => push (args.repo )); } else { push (args.repo ); } function git (...args ) { return spawn ('git' , args, { cwd : baseDir, stdio : 'inherit' }); } function setup ( ) { const userName = args.user && args.user .name || '' ; const userEmail = args.user && args.user .emial || '' ; return git ('init' ). then (() => git ('config' , 'user.name' , userName)). then (() => git ('config' , 'user.email' , userEmail)). then (() => git ('add' , '-A' )). then (() => git ('commit' , '-m' , message)); } function push (repo ) { return git ('add' , '-A' ). then (() => git ('commit' , '-m' , message).catch (() => '' )). then (() => git ('push' , '-u' , repo.url , 'HEAD:' + repo.branch , '--force' )); } }
执行 git
命令的代码相对来说就比较简单了,但是要关注一些错误情况的检查,可能需要多
debug 几遍才能确保命令的执行达到预期的效果。
执行脚本
以上 spawn.js
和 deployer.js
就是推送脚本的全部内容,最后把这些脚本胶乳 package.json
中的 script
中,就能在打包后,一键推送部署到 github pages
上了。
package.json
1 2 3 4 5 6 7 8 { ... "scripts" : { ... "deploy" : "node ./script/deploy" } , ...}
在 shell 中执行 npm run deploy
即可执行脚本一键部署,下面是我执行的示,(显然我在这次部署前没有进行任何修改)
1 2 3 4 5 6 7 8 9 10 Hozen@HOZEN MINGW64 /c/Workspace/localstatic/lei (dev)$ npm run deploy > [email protected] deploy C:\Workspace\localstatic\lei> node ./script/deploy On branch master nothing to commit, working tree clean Everything up-to-date Branch 'master' set up to track remote branch 'master' from '[email protected] :****/****.github.io.git'.
注意
由于不同操作系统和不同 shell
客户端中的命令行和格式可能会有不同,导致同一套代码可能不能在不同环境中执行,要解决这个问题可以使用
cross-spawn
工具替代 NodeJs 中的 spawn。