Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
10 changes: 10 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
37 changes: 37 additions & 0 deletions lib/overcommit/hook/pre_commit/yarn_check.rb
Original file line number Diff line number Diff line change
@@ -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
87 changes: 87 additions & 0 deletions spec/overcommit/hook/pre_commit/yarn_check_spec.rb
Original file line number Diff line number Diff line change
@@ -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
<<STDERR
warning "parent-package#child-package@version" could be deduped from "one version" to "another version"
STDERR
end

it { should pass }
end

context 'and yarn check contains unactionable errors' do
let(:stderr) do
<<STDERR
error "peer-dependency#peer@a || list || of || versions" doesn't satisfy found match of "peer@different-version"
error "bad-maintainer#bad-package" is wrong version: expected "something normal", got "something crazy"
STDERR
end

it { should pass }
end

context 'and yarn check contains actionable errors' do
let(:stderr) do
<<STDERR
error Lockfile does not contain pattern: "thing-i-updated-in-package.json@new-version"
STDERR
end

it { should fail_hook }
end
end
end