Skip to content

Commit b178184

Browse files
stevericesds
authored andcommitted
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.
1 parent 5af4021 commit b178184

File tree

5 files changed

+136
-0
lines changed

5 files changed

+136
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
and `post-merge` hooks
77
* Display commit message when `commit-msg` hooks fail
88
* Drop support for JRuby
9+
* Add `YarnCheck` pre-commit hook which checks if `yarn.lock` matches `package.json`
910

1011
## 0.42.0
1112

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,7 @@ issue](https://github.com/brigade/overcommit/issues/238) for more details.
562562
* [XmlSyntax](lib/overcommit/hook/pre_commit/xml_syntax.rb)
563563
* [YamlLint](lib/overcommit/hook/pre_commit/yaml_lint.rb)
564564
* [YamlSyntax](lib/overcommit/hook/pre_commit/yaml_syntax.rb)
565+
* [YarnCheck](lib/overcommit/hook/pre_commit/yarn_check.rb)
565566

566567
### PrePush
567568

config/default.yml

+10
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,16 @@ PreCommit:
785785
- '**/*.yaml'
786786
- '**/*.yml'
787787

788+
YarnCheck:
789+
enabled: false
790+
description: 'Check yarn.lock dependencies'
791+
required_executable: 'yarn'
792+
flags: ['check', '--silent', '--no-progress', '--non-interactive']
793+
install_command: 'npm install --global yarn'
794+
include:
795+
- 'package.json'
796+
- 'yarn.lock'
797+
788798
# Hooks that run after HEAD changes or a file is explicitly checked out.
789799
PostCheckout:
790800
ALL:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# frozen_string_literal: true
2+
3+
module Overcommit::Hook::PreCommit
4+
# Check if local yarn.lock matches package.json when either changes, unless
5+
# yarn.lock is ignored by git.
6+
#
7+
# @see https://yarnpkg.com/en/docs/cli/check
8+
class YarnCheck < Base
9+
LOCK_FILE = 'yarn.lock'.freeze
10+
11+
# A lot of the errors returned by `yarn check` are outside the developer's control
12+
# (are caused by bad package specification, in the hands of the upstream maintainer)
13+
# So limit reporting to errors the developer can do something about
14+
ACTIONABLE_ERRORS = [
15+
'Lockfile does not contain pattern'.freeze,
16+
].freeze
17+
18+
def run
19+
# Ignore if yarn.lock is not tracked by git
20+
ignored_files = execute(%w[git ls-files -o -i --exclude-standard]).stdout.split("\n")
21+
return :pass if ignored_files.include?(LOCK_FILE)
22+
23+
previous_lockfile = File.exist?(LOCK_FILE) ? File.read(LOCK_FILE) : nil
24+
result = execute(command)
25+
new_lockfile = File.exist?(LOCK_FILE) ? File.read(LOCK_FILE) : nil
26+
27+
# `yarn check` also throws many warnings, which should be ignored here
28+
errors_regex = Regexp.new("^error (.*)(#{ACTIONABLE_ERRORS.join('|')})(.*)$")
29+
errors = errors_regex.match(result.stderr)
30+
unless errors.nil? && previous_lockfile == new_lockfile
31+
return :fail, "#{LOCK_FILE} is not up-to-date -- run `yarn install`"
32+
end
33+
34+
:pass
35+
end
36+
end
37+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
require 'spec_helper'
2+
3+
describe Overcommit::Hook::PreCommit::YarnCheck do
4+
let(:config) { Overcommit::ConfigurationLoader.default_configuration }
5+
let(:context) { double('context') }
6+
subject { described_class.new(config, context) }
7+
8+
context 'when yarn.lock is ignored' do
9+
around do |example|
10+
repo do
11+
touch 'yarn.lock'
12+
echo('yarn.lock', '.gitignore')
13+
`git add .gitignore`
14+
`git commit -m "Ignore yarn.lock"`
15+
example.run
16+
end
17+
end
18+
19+
it { should pass }
20+
end
21+
22+
context 'when yarn.lock is not ignored' do
23+
let(:result) { double('result') }
24+
25+
around do |example|
26+
repo do
27+
example.run
28+
end
29+
end
30+
31+
before do
32+
result.stub(stderr: stderr)
33+
subject.stub(:execute).with(%w[git ls-files -o -i --exclude-standard]).
34+
and_return(double(stdout: ''))
35+
subject.stub(:execute).with(%w[yarn check --silent --no-progress --non-interactive]).
36+
and_return(result)
37+
end
38+
39+
context 'and yarn check reports no errors' do
40+
let(:stderr) { '' }
41+
42+
it { should pass }
43+
44+
context 'and there was a change to the yarn.lock' do
45+
before do
46+
subject.stub(:execute).with(%w[yarn check --silent --no-progress --non-interactive]) do
47+
echo('stuff', 'yarn.lock')
48+
double(stderr: '')
49+
end
50+
end
51+
52+
it { should fail_hook }
53+
end
54+
end
55+
56+
context 'and yarn check contains only warnings' do
57+
let(:stderr) do
58+
<<STDERR
59+
warning "parent-package#child-package@version" could be deduped from "one version" to "another version"
60+
STDERR
61+
end
62+
63+
it { should pass }
64+
end
65+
66+
context 'and yarn check contains unactionable errors' do
67+
let(:stderr) do
68+
<<STDERR
69+
error "peer-dependency#peer@a || list || of || versions" doesn't satisfy found match of "peer@different-version"
70+
error "bad-maintainer#bad-package" is wrong version: expected "something normal", got "something crazy"
71+
STDERR
72+
end
73+
74+
it { should pass }
75+
end
76+
77+
context 'and yarn check contains actionable errors' do
78+
let(:stderr) do
79+
<<STDERR
80+
error Lockfile does not contain pattern: "thing-i-updated-in-package.json@new-version"
81+
STDERR
82+
end
83+
84+
it { should fail_hook }
85+
end
86+
end
87+
end

0 commit comments

Comments
 (0)