danger-wcc 0.0.6 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a19b6ce24d61f5ff2a951f9902122eda8942c8c7
4
- data.tar.gz: e958cfa4740a01893df04a960f902ea5eb541dfc
3
+ metadata.gz: 356af9ca6ebc46af86c402709d820bb30fd94c1e
4
+ data.tar.gz: 6842e1bc5330f0088b2978c58e9c9e85afab2030
5
5
  SHA512:
6
- metadata.gz: 2779c8031e0b5dbcc595c07b9bec606c4daab6e94214fa1808909f80819b2f4c95358448adf93b4ec8462c5e1179c981489cfd4eb5be7f48291a4635189e62d3
7
- data.tar.gz: 353a0fb641903e0ce67fc874114976fcd90070a5c28c9c6d81f59682926e874d1ef67236ebe0de64e75f97572353b86fa4b0398ed91155cb0254a7e8787691f4
6
+ metadata.gz: 5c31ffdd4c4cdc7f4a7c8f1631816d6f1452ba31bc0a03d8a5e1c392265ef8b29decd9e644d2f5b437b4ec2d3a282d7973a2f2d81b8fe1c9d02c84e8c3315523
7
+ data.tar.gz: 9d8fecdeaab1eda89620ac77454c28b9975987fb5a875ce472bae1e8ba2fdac511b54dbfe0647c46ef6c7f3fccdfc9b501a5ca24fbc85c016328e26108445842
@@ -19,6 +19,7 @@ Gem::Specification.new do |spec|
19
19
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
20
  spec.require_paths = ['lib']
21
21
 
22
+ spec.add_runtime_dependency 'activesupport', '> 5'
22
23
  spec.add_runtime_dependency 'brakeman'
23
24
  spec.add_runtime_dependency 'danger-plugin-api', '~> 1.0'
24
25
  spec.add_runtime_dependency 'flay'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DangerWCC
4
- VERSION = '0.0.6'
4
+ VERSION = '0.1.0'
5
5
  end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require_relative 'utils'
5
+
6
+ class Danger::DangerWCC < Danger::Plugin
7
+ class Dependencies
8
+ include Utils
9
+
10
+ def yarn_info
11
+ @yarn_info ||= YarnInfo.new(self) if File.exist?('yarn.lock')
12
+ end
13
+
14
+ def initialize(plugin, options = {})
15
+ @plugin = plugin
16
+ @options = options
17
+ end
18
+
19
+ def perform
20
+ return unless File.exist?('yarn.lock')
21
+
22
+ find_yarn_violations
23
+ end
24
+
25
+ private
26
+
27
+ def issue_yarn_violation(package, versions)
28
+ line_index = yarn_info.find_index_in_lockfile(package, versions[1])
29
+
30
+ plugin.fail "Dangerous change! #{package} was updated "\
31
+ "from #{versions[0]} to #{versions[1]}"\
32
+ ' without a corresponding change to package.json!',
33
+ file: 'yarn.lock', line: line_index
34
+ end
35
+
36
+ def find_yarn_violations # rubocop:disable Metrics/AbcSize
37
+ # if there's a corresponding change in the package.json, ignore
38
+ mods =
39
+ yarn_info.modified_yarn_dependencies
40
+ .except(*yarn_info.package_json_changes)
41
+ .select { |_, versions| dangerous_change?(versions[0], versions[1]) }
42
+
43
+ has_dangerous_top_level_changes = false
44
+ # issue warnings for top level dependencies
45
+ mods.slice(*yarn_info.package_json_dependencies)
46
+ .each do |package, versions|
47
+ has_dangerous_top_level_changes = true
48
+ issue_yarn_violation(package, versions)
49
+ end
50
+ # issue warnings if a sub-dependency changed without a dangerous change in
51
+ # a top level dependency
52
+ return if has_dangerous_top_level_changes
53
+
54
+ mods.except(*yarn_info.package_json_dependencies)
55
+ .each do |package, versions|
56
+ issue_yarn_violation(package, versions)
57
+ end
58
+ end
59
+
60
+ def dangerous_change?(old_version, new_version)
61
+ # the package was deleted
62
+ return true unless new_version
63
+
64
+ old_segments = old_version.segments
65
+ new_segments = new_version.segments
66
+
67
+ # the major or minor version changed.
68
+ new_segments[0] > old_segments[0] ||
69
+ new_segments[1] > old_segments[1]
70
+ end
71
+ end
72
+ end
73
+
74
+ require_relative 'dependencies/yarn_info'
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Danger::DangerWCC::Dependencies
4
+ class YarnInfo
5
+ def yarn_lock
6
+ @yarn_lock ||= File.readlines('yarn.lock')
7
+ end
8
+
9
+ def package_json_dependencies
10
+ @package_json_dependencies ||=
11
+ JSON.parse(File.read('package.json'))['dependencies']&.keys || []
12
+ end
13
+
14
+ def package_json_changes
15
+ @package_json_changes ||= find_package_json_changes
16
+ end
17
+
18
+ def modified_yarn_dependencies
19
+ @modified_yarn_dependencies ||= find_modified_yarn_packages
20
+ end
21
+
22
+ attr_reader :plugin
23
+
24
+ def initialize(plugin)
25
+ @plugin = plugin
26
+ end
27
+
28
+ def find_index_in_lockfile(package, version)
29
+ return 0 unless version
30
+
31
+ re = Regexp.new("^#{Regexp.escape(package)}@", Regexp::IGNORECASE)
32
+ indexes =
33
+ yarn_lock.each_with_index
34
+ .select { |l, _i| re.match(l) }
35
+ .map { |pair| pair[1] }
36
+ idx =
37
+ indexes.find do |i|
38
+ yarn_lock[i + 1].include?("version \"#{version}\"")
39
+ end
40
+ (idx || -1) + 1
41
+ end
42
+
43
+ def parse_yarn_semver(line)
44
+ match = /(?<package>\S+)\@(?<version>\S+)/.match(line)
45
+ [match['package'], Gem::Version.new(match['version'])] if match
46
+ end
47
+
48
+ private
49
+
50
+ def find_package_json_changes
51
+ return [] unless file = plugin.find_file_in_diff('package.json')
52
+
53
+ adds = file.hunks.flat_map { |h| h.lines.select(&:addition?) }
54
+ adds.map { |l| /\"(?<package>\S+)\"\: \"\S+\"/.match(l.content) }
55
+ .compact
56
+ .map { |match| match['package'] }
57
+ end
58
+
59
+ def find_modified_yarn_packages # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
60
+ diff = plugin.run_and_diff(
61
+ 'NODE_ENV=production yarn list --depth 0 2>/dev/null'
62
+ )
63
+ diff = GitDiff.from_string(diff)
64
+
65
+ {}.tap do |modified_packages|
66
+ plugin.each_file_in_diff(diff) do |file, _diff|
67
+ file.hunks.each do |hunk|
68
+ deleted, added =
69
+ %i[deletion? addition?].map do |type|
70
+ Hash[hunk.lines.select { |l| l.public_send(type) }
71
+ .map { |l| parse_yarn_semver(l.content) }
72
+ .compact]
73
+ end
74
+ deleted.each do |(package, version)|
75
+ modified_packages[package] =
76
+ [version, added[package]]
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -7,6 +7,7 @@ require_relative 'rubocop_exceptions'
7
7
  require_relative 'commit_lint'
8
8
  require_relative 'reek'
9
9
  require_relative 'jshint'
10
+ require_relative 'dependencies'
10
11
 
11
12
  class Danger::DangerWCC < Danger::Plugin
12
13
  include Utils
@@ -19,7 +20,8 @@ class Danger::DangerWCC < Danger::Plugin
19
20
  brakeman: true,
20
21
  commit_lint: false,
21
22
  reek: false,
22
- jshint: false
23
+ jshint: false,
24
+ dependencies: true
23
25
  }.freeze
24
26
 
25
27
  # Runs all the included checks in the plugin
@@ -95,6 +97,11 @@ class Danger::DangerWCC < Danger::Plugin
95
97
  Jshint.new(self, options).perform
96
98
  end
97
99
 
100
+ def dependencies(options = {})
101
+ logger.info "dependencies: #{options}"
102
+ Dependencies.new(self, options).perform
103
+ end
104
+
98
105
  private
99
106
 
100
107
  def parse_flay_results
@@ -22,7 +22,7 @@ module Utils
22
22
  # All the diffs in the PR parsed into GitDiff objects
23
23
  def parsed_diffs
24
24
  @parsed_diffs ||=
25
- plugin.git.diff.map do |d|
25
+ plugin.git.diff&.map do |d|
26
26
  begin
27
27
  GitDiff.from_string(d.patch)
28
28
  rescue StandardError
@@ -32,6 +32,13 @@ module Utils
32
32
  end
33
33
  end
34
34
 
35
+ def find_file_in_diff(filename)
36
+ each_file_in_diff do |file, _diff|
37
+ return file if file.b_path == filename
38
+ end
39
+ nil
40
+ end
41
+
35
42
  # Finds lines in the overall diff matching the given regex, and
36
43
  # executes a block for each matched line.
37
44
  # The results of the yield block are returned as an array.
@@ -52,17 +59,22 @@ module Utils
52
59
 
53
60
  def each_file_in_diff(passed_diff = nil)
54
61
  diffs = passed_diff ? [passed_diff] : parsed_diffs
55
- diffs.flat_map do |diff|
62
+ diffs&.flat_map do |diff|
56
63
  diff.files.flat_map do |file|
57
64
  yield(file, diff)
58
65
  end
59
66
  end
60
67
  end
61
68
 
62
- def each_addition_in_diff(passed_diff = nil)
69
+ def each_addition_in_diff(passed_diff = nil, &block)
70
+ each_line_in_diff(passed_diff, type: :addition, &block)
71
+ end
72
+
73
+ def each_line_in_diff(passed_diff = nil, type: nil)
63
74
  each_file_in_diff(passed_diff) do |file, diff|
64
75
  file.hunks.flat_map do |hunk|
65
- lines = hunk.lines.select(&:addition?)
76
+ lines = hunk.lines
77
+ lines = lines.select { |l| l.public_send("#{type}?") } if type
66
78
  lines = lines.map { |l| yield(l, hunk, file, diff) } if block_given?
67
79
  lines
68
80
  end
@@ -0,0 +1,112 @@
1
+ {
2
+ "name": "paper-signs",
3
+ "private": true,
4
+ "workspaces": [
5
+ ".",
6
+ "packages/*"
7
+ ],
8
+ "scripts": {
9
+ "fix-css": "stylelint app/**/*.scss --syntax scss --fix",
10
+ "fix-js": "tslint --project tsconfig.json app/assets/javascripts/**/*.ts?\\(x\\) --fix",
11
+ "fix": "yarn fix-js && yarn fix-css",
12
+ "check-types": "tsc --noemit",
13
+ "lint": "yarn lint-js && yarn lint-css",
14
+ "lint-css": "stylelint app/**/*.scss --syntax scss",
15
+ "lint-js": "tslint --project tsconfig.json app/assets/javascripts/**/*.ts?\\(x\\)",
16
+ "analyze": "RAILS_ENV=production NODE_ENV=production bin/webpack --profile --json > tmp/stats.json && webpack-bundle-analyzer tmp/stats.json public/packs --exclude server_rendering -m static",
17
+ "test": "jest && NODE_ENV=test karma start --",
18
+ "test-css": "mocha app/assets/stylesheets/true.js",
19
+ "test-watch": "NODE_ENV=test karma start --auto-watch --no-single-run",
20
+ "prepare": "contentful-ts-generator --download=false && for d in packages/*/; do (cd $d; yarn prepare) || exit -1; done"
21
+ },
22
+ "engines": {},
23
+ "dependencies": {
24
+ "@babel/core": "^7.8.3",
25
+ "@babel/plugin-proposal-class-properties": "^7.8.3",
26
+ "@babel/preset-env": "^7.8.3",
27
+ "@babel/preset-react": "^7.8.3",
28
+ "@babel/preset-typescript": "^7.8.3",
29
+ "@rails/webpacker": "^4.0.2",
30
+ "@types/algoliasearch": "^3.30.1",
31
+ "@types/algoliasearch-helper": "^2.26.1",
32
+ "@types/bootstrap": "^4.3.1",
33
+ "@types/bugsnag-js": "^3.1.0",
34
+ "@types/chai": "^4.1.7",
35
+ "@types/chai-jquery": "^1.1.38",
36
+ "@types/enzyme": "^3.1.15",
37
+ "@types/enzyme-adapter-react-16": "^1.0.3",
38
+ "@types/fontfaceobserver": "^0.0.6",
39
+ "@types/i18n-js": "^3.0.1",
40
+ "@types/lodash": "^4.14.134",
41
+ "@types/mocha": "^5.2.5",
42
+ "@types/qs": "^6.5.3",
43
+ "@types/react": "^16.9.34",
44
+ "@types/react-dom": "^16.0.9",
45
+ "@types/react-infinite-scroller": "^1.2.1",
46
+ "@types/react-instantsearch": "^5.2.1",
47
+ "@types/sinon": "^7.0.3",
48
+ "@types/webpack-env": "^1.13.9",
49
+ "@watermarkchurch/contentful-migration": "^1.0.9",
50
+ "@watermarkchurch/react-instantsearch-components": "*",
51
+ "async-toolbox": "^0.6.6",
52
+ "babel-preset-react": "^6.24.1",
53
+ "contentful-export": "^7.4.0",
54
+ "contentful-shell": "^0.2.7",
55
+ "contentful-ts-generator": "^0.2.4",
56
+ "core-js": "3",
57
+ "date-fns": "^1.30.1",
58
+ "enzyme": "^3.7.0",
59
+ "enzyme-adapter-react-16": "^1.7.0",
60
+ "fontfaceobserver": "^2.1.0",
61
+ "html-react-parser": "^0.9.1",
62
+ "i18n-js": "^3.5.1",
63
+ "identity-obj-proxy": "^3.0.0",
64
+ "inflection": "^1.12.0",
65
+ "postcss-cssnext": "^3.1.0",
66
+ "prop-types": "^15.6.2",
67
+ "qs": "^6.7.0",
68
+ "react": "^16.12.0",
69
+ "react-calendar": "^2.19.0",
70
+ "react-dom": "^16.12.0",
71
+ "react-infinite-scroller": "^1.2.2",
72
+ "react-instantsearch": "^5.3.2",
73
+ "react-svg-loader": "^3.0.3",
74
+ "react_ujs": "^2.4.4",
75
+ "sinon": "^7.2.2",
76
+ "typescript": "^3.3.3"
77
+ },
78
+ "devDependencies": {
79
+ "@percy/agent": "^0.28.0",
80
+ "@types/jest": "^25.2.1",
81
+ "@watermarkchurch/contentful-check": "*",
82
+ "@watermarkchurch/load-tester": "*",
83
+ "babel-jest": "^25.5.0",
84
+ "chai": "^4.2.0",
85
+ "chai-jquery": "^2.0.0",
86
+ "graphql-schema-diff": "^0.6.3",
87
+ "hard-source-webpack-plugin": "^0.13.1",
88
+ "jest": "^25.5.0",
89
+ "karma": "^3.1.1",
90
+ "karma-chai": "^0.1.0",
91
+ "karma-chai-jquery": "^1.0.0",
92
+ "karma-chrome-launcher": "^2.2.0",
93
+ "karma-jquery": "^0.2.3",
94
+ "karma-junit-reporter": "^1.2.0",
95
+ "karma-mocha": "^1.3.0",
96
+ "karma-mocha-reporter": "^2.2.5",
97
+ "karma-sourcemap-loader": "^0.3.7",
98
+ "karma-webpack": "^3.0.5",
99
+ "lerna": "^3.13.1",
100
+ "mocha": "^5.2.0",
101
+ "react-test-renderer": "^16.12.0",
102
+ "sass-true": "^4.0.0",
103
+ "stylelint": "^9.6.0",
104
+ "stylelint-config-sass-guidelines": "^5.2.0",
105
+ "stylelint-scss": "^3.3.2",
106
+ "ts-node": "^7.0.1",
107
+ "tslint": "^5.11.0",
108
+ "tslint-eslint-rules": "^5.4.0",
109
+ "webpack-bundle-analyzer": "^3.0.3",
110
+ "webpack-dev-server": "^3.3.1"
111
+ }
112
+ }
@@ -0,0 +1,28 @@
1
+ diff --git a/package.json b/package.json
2
+ index 74d7ad8a..ea999d34 100644
3
+ --- a/package.json
4
+ +++ b/package.json
5
+ @@ -41,7 +41,6 @@
6
+ "@types/mocha": "^5.2.5",
7
+ "@types/qs": "^6.5.3",
8
+ "@types/react": "^16.9.34",
9
+ - "@types/react-autosuggest": "^9.3.6",
10
+ "@types/react-dom": "^16.0.9",
11
+ "@types/react-infinite-scroller": "^1.2.1",
12
+ "@types/react-instantsearch": "^5.2.1",
13
+ @@ -67,7 +66,6 @@
14
+ "prop-types": "^15.6.2",
15
+ "qs": "^6.7.0",
16
+ "react": "^16.12.0",
17
+ - "react-autosuggest": "^9.4.3",
18
+ "react-calendar": "^2.19.0",
19
+ "react-dom": "^16.12.0",
20
+ "react-infinite-scroller": "^1.2.2",
21
+ @@ -78,6 +76,7 @@
22
+ "typescript": "^3.3.3"
23
+ },
24
+ "devDependencies": {
25
+ + "@percy/agent": "^0.28.0",
26
+ "@types/jest": "^25.2.1",
27
+ "@watermarkchurch/contentful-check": "*",
28
+ "@watermarkchurch/load-tester": "*",
@@ -0,0 +1,13 @@
1
+ diff --git a/package.json b/package.json
2
+ index ea999d3..efa8440 100644
3
+ --- a/package.json
4
+ +++ b/package.json
5
+ @@ -69,7 +69,7 @@
6
+ "react-calendar": "^2.19.0",
7
+ "react-dom": "^16.12.0",
8
+ "react-infinite-scroller": "^1.2.2",
9
+ - "react-instantsearch": "^5.3.2",
10
+ + "react-instantsearch": "^5.3.3",
11
+ "react-svg-loader": "^3.0.3",
12
+ "react_ujs": "^2.4.4",
13
+ "sinon": "^7.2.2",
@@ -0,0 +1,112 @@
1
+ {
2
+ "name": "paper-signs",
3
+ "private": true,
4
+ "workspaces": [
5
+ ".",
6
+ "packages/*"
7
+ ],
8
+ "scripts": {
9
+ "fix-css": "stylelint app/**/*.scss --syntax scss --fix",
10
+ "fix-js": "tslint --project tsconfig.json app/assets/javascripts/**/*.ts?\\(x\\) --fix",
11
+ "fix": "yarn fix-js && yarn fix-css",
12
+ "check-types": "tsc --noemit",
13
+ "lint": "yarn lint-js && yarn lint-css",
14
+ "lint-css": "stylelint app/**/*.scss --syntax scss",
15
+ "lint-js": "tslint --project tsconfig.json app/assets/javascripts/**/*.ts?\\(x\\)",
16
+ "analyze": "RAILS_ENV=production NODE_ENV=production bin/webpack --profile --json > tmp/stats.json && webpack-bundle-analyzer tmp/stats.json public/packs --exclude server_rendering -m static",
17
+ "test": "jest && NODE_ENV=test karma start --",
18
+ "test-css": "mocha app/assets/stylesheets/true.js",
19
+ "test-watch": "NODE_ENV=test karma start --auto-watch --no-single-run",
20
+ "prepare": "contentful-ts-generator --download=false && for d in packages/*/; do (cd $d; yarn prepare) || exit -1; done"
21
+ },
22
+ "engines": {},
23
+ "dependencies": {
24
+ "@babel/core": "^7.8.3",
25
+ "@babel/plugin-proposal-class-properties": "^7.8.3",
26
+ "@babel/preset-env": "^7.8.3",
27
+ "@babel/preset-react": "^7.8.3",
28
+ "@babel/preset-typescript": "^7.8.3",
29
+ "@rails/webpacker": "^4.0.2",
30
+ "@types/algoliasearch": "^3.30.1",
31
+ "@types/algoliasearch-helper": "^2.26.1",
32
+ "@types/bootstrap": "^4.3.1",
33
+ "@types/bugsnag-js": "^3.1.0",
34
+ "@types/chai": "^4.1.7",
35
+ "@types/chai-jquery": "^1.1.38",
36
+ "@types/enzyme": "^3.1.15",
37
+ "@types/enzyme-adapter-react-16": "^1.0.3",
38
+ "@types/fontfaceobserver": "^0.0.6",
39
+ "@types/i18n-js": "^3.0.1",
40
+ "@types/lodash": "^4.14.134",
41
+ "@types/mocha": "^5.2.5",
42
+ "@types/qs": "^6.5.3",
43
+ "@types/react": "^16.9.34",
44
+ "@types/react-dom": "^16.0.9",
45
+ "@types/react-infinite-scroller": "^1.2.1",
46
+ "@types/react-instantsearch": "^5.2.1",
47
+ "@types/sinon": "^7.0.3",
48
+ "@types/webpack-env": "^1.13.9",
49
+ "@watermarkchurch/contentful-migration": "^1.0.9",
50
+ "@watermarkchurch/react-instantsearch-components": "*",
51
+ "async-toolbox": "^0.6.6",
52
+ "babel-preset-react": "^6.24.1",
53
+ "contentful-export": "^7.4.0",
54
+ "contentful-shell": "^0.2.7",
55
+ "contentful-ts-generator": "^0.2.4",
56
+ "core-js": "3",
57
+ "date-fns": "^1.30.1",
58
+ "enzyme": "^3.7.0",
59
+ "enzyme-adapter-react-16": "^1.7.0",
60
+ "fontfaceobserver": "^2.1.0",
61
+ "html-react-parser": "^0.9.1",
62
+ "i18n-js": "^3.5.1",
63
+ "identity-obj-proxy": "^3.0.0",
64
+ "inflection": "^1.12.0",
65
+ "postcss-cssnext": "^3.1.0",
66
+ "prop-types": "^15.6.2",
67
+ "qs": "^6.7.0",
68
+ "react": "^16.12.0",
69
+ "react-calendar": "^2.19.0",
70
+ "react-dom": "^16.12.0",
71
+ "react-infinite-scroller": "^1.2.2",
72
+ "react-instantsearch": "^5.3.3",
73
+ "react-svg-loader": "^3.0.3",
74
+ "react_ujs": "^2.4.4",
75
+ "sinon": "^7.2.2",
76
+ "typescript": "^3.3.3"
77
+ },
78
+ "devDependencies": {
79
+ "@percy/agent": "^0.28.0",
80
+ "@types/jest": "^25.2.1",
81
+ "@watermarkchurch/contentful-check": "*",
82
+ "@watermarkchurch/load-tester": "*",
83
+ "babel-jest": "^25.5.0",
84
+ "chai": "^4.2.0",
85
+ "chai-jquery": "^2.0.0",
86
+ "graphql-schema-diff": "^0.6.3",
87
+ "hard-source-webpack-plugin": "^0.13.1",
88
+ "jest": "^25.5.0",
89
+ "karma": "^3.1.1",
90
+ "karma-chai": "^0.1.0",
91
+ "karma-chai-jquery": "^1.0.0",
92
+ "karma-chrome-launcher": "^2.2.0",
93
+ "karma-jquery": "^0.2.3",
94
+ "karma-junit-reporter": "^1.2.0",
95
+ "karma-mocha": "^1.3.0",
96
+ "karma-mocha-reporter": "^2.2.5",
97
+ "karma-sourcemap-loader": "^0.3.7",
98
+ "karma-webpack": "^3.0.5",
99
+ "lerna": "^3.13.1",
100
+ "mocha": "^5.2.0",
101
+ "react-test-renderer": "^16.12.0",
102
+ "sass-true": "^4.0.0",
103
+ "stylelint": "^9.6.0",
104
+ "stylelint-config-sass-guidelines": "^5.2.0",
105
+ "stylelint-scss": "^3.3.2",
106
+ "ts-node": "^7.0.1",
107
+ "tslint": "^5.11.0",
108
+ "tslint-eslint-rules": "^5.4.0",
109
+ "webpack-bundle-analyzer": "^3.0.3",
110
+ "webpack-dev-server": "^3.3.1"
111
+ }
112
+ }