From 36be45a31ff9dc5fb4df8e411b773959f0fa0f8c Mon Sep 17 00:00:00 2001 From: Steve Rice Date: Fri, 9 Feb 2018 16:59:10 -0800 Subject: [PATCH] Add YarnCheck pre-commit hook Add a hook that checks that a [`yarn.lock`](https://yarnpkg.com) matches the given `package.json`. This is very similar, and mostly copied from, the existing `BundleCheck` pre-commit hook. The primary difference is that `yarn check`'s output needs a bit more massaging. Since it reports a number of issues that are unrelated, or outside the developer's control, check for certain error statements that do indicate the lockfile is out of date. --- CHANGELOG.md | 1 + README.md | 1 + config/default.yml | 10 +++ lib/overcommit/hook/pre_commit/yarn_check.rb | 37 ++++++++ .../hook/pre_commit/yarn_check_spec.rb | 87 +++++++++++++++++++ 5 files changed, 136 insertions(+) create mode 100644 lib/overcommit/hook/pre_commit/yarn_check.rb create mode 100644 spec/overcommit/hook/pre_commit/yarn_check_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 4317be59..00e26937 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ and `post-merge` hooks * Display commit message when `commit-msg` hooks fail * Drop support for JRuby +* Add `YarnCheck` pre-commit hook which checks if `yarn.lock` matches `package.json` ## 0.42.0 diff --git a/README.md b/README.md index c72c8112..b3ad5ae2 100644 --- a/README.md +++ b/README.md @@ -562,6 +562,7 @@ issue](https://github.com/brigade/overcommit/issues/238) for more details. * [XmlSyntax](lib/overcommit/hook/pre_commit/xml_syntax.rb) * [YamlLint](lib/overcommit/hook/pre_commit/yaml_lint.rb) * [YamlSyntax](lib/overcommit/hook/pre_commit/yaml_syntax.rb) +* [YarnCheck](lib/overcommit/hook/pre_commit/yarn_check.rb) ### PrePush diff --git a/config/default.yml b/config/default.yml index 12c01396..2d9b3514 100644 --- a/config/default.yml +++ b/config/default.yml @@ -785,6 +785,16 @@ PreCommit: - '**/*.yaml' - '**/*.yml' + YarnCheck: + enabled: false + description: 'Check yarn.lock dependencies' + required_executable: 'yarn' + flags: ['check', '--silent', '--no-progress', '--non-interactive'] + install_command: 'npm install --global yarn' + include: + - 'package.json' + - 'yarn.lock' + # Hooks that run after HEAD changes or a file is explicitly checked out. PostCheckout: ALL: diff --git a/lib/overcommit/hook/pre_commit/yarn_check.rb b/lib/overcommit/hook/pre_commit/yarn_check.rb new file mode 100644 index 00000000..78a29cdc --- /dev/null +++ b/lib/overcommit/hook/pre_commit/yarn_check.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Overcommit::Hook::PreCommit + # Check if local yarn.lock matches package.json when either changes, unless + # yarn.lock is ignored by git. + # + # @see https://yarnpkg.com/en/docs/cli/check + class YarnCheck < Base + LOCK_FILE = 'yarn.lock'.freeze + + # A lot of the errors returned by `yarn check` are outside the developer's control + # (are caused by bad package specification, in the hands of the upstream maintainer) + # So limit reporting to errors the developer can do something about + ACTIONABLE_ERRORS = [ + 'Lockfile does not contain pattern'.freeze, + ].freeze + + def run + # Ignore if yarn.lock is not tracked by git + ignored_files = execute(%w[git ls-files -o -i --exclude-standard]).stdout.split("\n") + return :pass if ignored_files.include?(LOCK_FILE) + + previous_lockfile = File.exist?(LOCK_FILE) ? File.read(LOCK_FILE) : nil + result = execute(command) + new_lockfile = File.exist?(LOCK_FILE) ? File.read(LOCK_FILE) : nil + + # `yarn check` also throws many warnings, which should be ignored here + errors_regex = Regexp.new("^error (.*)(#{ACTIONABLE_ERRORS.join('|')})(.*)$") + errors = errors_regex.match(result.stderr) + unless errors.nil? && previous_lockfile == new_lockfile + return :fail, "#{LOCK_FILE} is not up-to-date -- run `yarn install`" + end + + :pass + end + end +end diff --git a/spec/overcommit/hook/pre_commit/yarn_check_spec.rb b/spec/overcommit/hook/pre_commit/yarn_check_spec.rb new file mode 100644 index 00000000..247f37bc --- /dev/null +++ b/spec/overcommit/hook/pre_commit/yarn_check_spec.rb @@ -0,0 +1,87 @@ +require 'spec_helper' + +describe Overcommit::Hook::PreCommit::YarnCheck do + let(:config) { Overcommit::ConfigurationLoader.default_configuration } + let(:context) { double('context') } + subject { described_class.new(config, context) } + + context 'when yarn.lock is ignored' do + around do |example| + repo do + touch 'yarn.lock' + echo('yarn.lock', '.gitignore') + `git add .gitignore` + `git commit -m "Ignore yarn.lock"` + example.run + end + end + + it { should pass } + end + + context 'when yarn.lock is not ignored' do + let(:result) { double('result') } + + around do |example| + repo do + example.run + end + end + + before do + result.stub(stderr: stderr) + subject.stub(:execute).with(%w[git ls-files -o -i --exclude-standard]). + and_return(double(stdout: '')) + subject.stub(:execute).with(%w[yarn check --silent --no-progress --non-interactive]). + and_return(result) + end + + context 'and yarn check reports no errors' do + let(:stderr) { '' } + + it { should pass } + + context 'and there was a change to the yarn.lock' do + before do + subject.stub(:execute).with(%w[yarn check --silent --no-progress --non-interactive]) do + echo('stuff', 'yarn.lock') + double(stderr: '') + end + end + + it { should fail_hook } + end + end + + context 'and yarn check contains only warnings' do + let(:stderr) do + <