CircleCI上でnpmパッケージを定期アップデート

出口 (@dex1t) です。

現在開発中のサービスでは、Railsを使いつつ、React.jsを導入しています。
React.jsを使うと必然的にBrowserifyなどのnpmのエコシステムを、Railsプロジェクトに取り入れることになります。 そうすると、Railsプロジェクトであっても、npmで管理するライブラリが増えていきます。今回はそのアップデートを自動化してみました。

CircleCIで Gemfile.lock を定期更新する

本題に入る前に、bundle updateの自動化について触れます。
ググると、やり方はいろいろ出てくるのですが、

  1. 開発用のサーバからcronで定期的にCircleCIのAPIを叩く
  2. circle.ymlのdeploymentセクションで、スクリプトを実行する
  3. そのスクリプト内で、bundle updateの実行と差分をプルリクエストする

というのが、基本的な流れです。詳しくは、 CircleCIを使ってbundle updateを定期実行する - Qiita をごらんください。

CircleCIで package.json を定期更新する

npmのpackage.jsonを定期更新する場合も、上記の流れと同様です。 今回は、3を行うスクリプトをgulpで組んでみました。

npm-check-updatesでpackage.jsonを更新する

package.jsonの更新には、npm-check-updates を利用します。

$ npm install -g npm-check-updates
$ ncu --upgradeAll

これだけで、npmパッケージの更新確認とpackage.jsonの書き換えを行ってくれます。便利!!
今回はこれをgulpから呼び出します。

package.jsonを更新してプルリクエストする

gulpからgitとgithubが扱えれば何でもいいのですが、今回は主に以下を使いました。

run-sequenceは、各gulpタスクを直列に実行するために使います。 また、gulpプラグインの読み込みを簡単にするため、gulp-load-pluginsも使っています。

これら組み合わせると、ちょっと長いですがこんな感じで、package.jsonの更新~プルリクができます。

var gulp = require('gulp');
var gulpLoadPlugins = require('gulp-load-plugins');
var $ = gulpLoadPlugins();
var ncu = require('npm-check-updates');
var github = require('octonode');
var branch = 'npm-update';
var runSequence = require('run-sequence');

gulp.task('ncu', function(cb) {
  ncu.run({
    packageFile: 'package.json',
    jsonUpgraded: false,
    upgradeAll: true
  }).then(function(){
    cb();
  });
});

gulp.task('createBranch', function(cb) {
  $.git.checkout(branch, {args:'-b'}, function(){
    cb();
  });
});

gulp.task('addAndCommit', function() {
  return gulp.src('package.json')
             .pipe($.git.add())
             .pipe($.git.commit('Upgrade package.json'));
});

gulp.task('pushBranch', function(cb) {
  $.git.push('origin', branch, function(){
    cb();
  });
});

gulp.task('sendPullRequest', function(cb) {
  var token = process.env.GITHUB_ACCESS_TOKEN;
  github.client(token).repo('bitjourney/journey').pr({
    'title': 'Update npm packages',
    'body' : '@bitjourney/developers Please :eyes:',
    'head' : 'bitjourney:' + branch,
    'base': 'master'
  }, function() {
    cb();
  });
});

gulp.task('autoNpmUpdate', function(){
  runSequence(['ncu', 'createBranch'],
              'addAndCommit',
              'pushBranch',
              'sendPullRequest');
});

注意点としては、プルリクエストを送るユーザの、GithubのPersonal access tokenをCircleCIに登録しておく必要があります。 CircleCI側のEnvironment variablesにて、GITHUB_ACCESS_TOKENという名前で登録しておけばOKです。

circle.ymlのdeploymentセクションで実行する

あとは、circle.ymlのdeploymentセクションで、gulp autoNpmUpdateを実行するようにすればOKです。 参考までに、以下のようにしました。

deployment:
  maintain-libraries:
   branch: master
   commands:
      - >
        if [ -n "${NPM_UPDATE}" ] ; then
          git config --global user.email user@example.com
          git config --global user.name 'username'
          gulp autoNpmUpdate
        fi

git configもgulp内でやってしまってもいいのですが、今回は外出ししています。 実際にCircle CIをAPI経由で叩く方法については、こちらを参考にしてください!

おわり

要点だけかいつまみましたが、これで弊社のbotがプルリクをくれるようになりました 🎉

f:id:dex1t:20150915163102p:plain