devpack 0.3.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +4 -0
- data/README.md +16 -1
- data/devpack.gemspec +3 -2
- data/exe/devpack +32 -0
- data/lib/devpack/gem_spec.rb +59 -10
- data/lib/devpack/gems.rb +13 -11
- data/lib/devpack/messages.rb +37 -9
- data/lib/devpack/version.rb +1 -1
- data/lib/devpack.rb +17 -1
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 88ec4f3a9263a0ce2204ff40a97c9f502897ad2eab40d5ec9356dcab0dccdd10
|
4
|
+
data.tar.gz: 5e6e9173e18339fa091cda8780588b0378c95c9589f8344a328d2e368f6649fa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 99bce1bb4528c5a2da45d6165481a05334688dec0c9a5f8bd6ac8778e4a85ca3dca45331134881a2d57b4bf0a664e1341541158456b48dde40e264dce7badcea
|
7
|
+
data.tar.gz: 693c4a80282a14cc25a4be573d8b3de84823083251e3a8417b6c056edcba1d5e3170700962d86ef5dccb1508b8185fd9f124fc138d46af9c4ac3e175a164edef
|
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
@@ -21,7 +21,7 @@ Add _Devpack_ to any project's `Gemfile`:
|
|
21
21
|
```ruby
|
22
22
|
# Gemfile
|
23
23
|
group :development, :test do
|
24
|
-
gem 'devpack', '~> 0.
|
24
|
+
gem 'devpack', '~> 0.4.0'
|
25
25
|
end
|
26
26
|
```
|
27
27
|
|
@@ -45,6 +45,14 @@ It is recommended to use a [global configuration](#global-configuration).
|
|
45
45
|
|
46
46
|
When using a per-project configuration, `.devpack` files should be added to `.gitignore`.
|
47
47
|
|
48
|
+
### Gem Installation
|
49
|
+
|
50
|
+
A convenience command is provided to install all gems listed in `.devpack` file that are not already installed:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
bundle exec devpack install
|
54
|
+
```
|
55
|
+
|
48
56
|
### Initializers
|
49
57
|
|
50
58
|
Custom initializers can be loaded by creating a directory named `.devpack_initializers` containing a set of `.rb` files.
|
@@ -75,6 +83,13 @@ To configure globally simply save your `.devpack` configuration file to any pare
|
|
75
83
|
|
76
84
|
This strategy also applies to `.devpack_initializers`.
|
77
85
|
|
86
|
+
### Silencing
|
87
|
+
|
88
|
+
To prevent _Devpack_ from displaying messages on load, set the environment variable `DEVPACK_SILENT=1` to any value:
|
89
|
+
```bash
|
90
|
+
DEVPACK_SILENT=1 bundle exec ruby myapp.rb
|
91
|
+
```
|
92
|
+
|
78
93
|
### Disabling
|
79
94
|
|
80
95
|
To disable _Devpack_ set the environment variable `DEVPACK_DISABLE` to any value:
|
data/devpack.gemspec
CHANGED
@@ -17,12 +17,13 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.metadata['homepage_uri'] = spec.homepage
|
18
18
|
spec.metadata['source_code_uri'] = spec.homepage
|
19
19
|
spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/master/CHANGELOG.md"
|
20
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
20
21
|
|
21
22
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
22
23
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
23
24
|
end
|
24
|
-
spec.bindir = '
|
25
|
-
spec.executables = []
|
25
|
+
spec.bindir = 'exe'
|
26
|
+
spec.executables = ['devpack']
|
26
27
|
spec.require_paths = ['lib']
|
27
28
|
|
28
29
|
spec.add_development_dependency 'byebug', '~> 11.1'
|
data/exe/devpack
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
ENV['DEVPACK_DISABLE'] = '1'
|
5
|
+
|
6
|
+
require 'open3'
|
7
|
+
|
8
|
+
require 'devpack'
|
9
|
+
|
10
|
+
command = ARGV[0]
|
11
|
+
|
12
|
+
if command == 'install'
|
13
|
+
missing = Devpack::Gems.new(Devpack.config).tap { |gems| gems.load(silent: true) }.missing
|
14
|
+
install_command = "bundle exec gem install -V #{missing.map(&:required_version).join(' ')}" unless missing.empty?
|
15
|
+
if install_command.nil?
|
16
|
+
warn '[devpack] No gems to install.'
|
17
|
+
else
|
18
|
+
warn "[devpack] [exec] #{install_command}"
|
19
|
+
output, status = Open3.capture2e(install_command)
|
20
|
+
puts output
|
21
|
+
puts status
|
22
|
+
if status.success?
|
23
|
+
warn '[devpack] Installation complete.'
|
24
|
+
else
|
25
|
+
warn "[devpack] Installation failed. Manually verify this command: #{install_command}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
exit 0
|
29
|
+
end
|
30
|
+
|
31
|
+
warn "[devpack] Unknown command: #{command}"
|
32
|
+
exit 1
|
data/lib/devpack/gem_spec.rb
CHANGED
@@ -3,56 +3,104 @@
|
|
3
3
|
module Devpack
|
4
4
|
# Locates relevant gemspec for a given gem and provides a full list of paths
|
5
5
|
# for all `require_paths` listed in gemspec.
|
6
|
+
# rubocop:disable Metrics/ClassLength
|
6
7
|
class GemSpec
|
7
|
-
|
8
|
+
attr_reader :name, :root
|
9
|
+
|
10
|
+
def initialize(glob, name, requirement, root: nil)
|
8
11
|
@name = name
|
9
12
|
@glob = glob
|
10
13
|
@requirement = requirement
|
14
|
+
@root = root || self
|
11
15
|
@dependency = Gem::Dependency.new(@name, @requirement)
|
12
16
|
end
|
13
17
|
|
14
18
|
def require_paths(visited = Set.new)
|
15
|
-
raise GemNotFoundError
|
19
|
+
raise GemNotFoundError.new("Gem not found: #{required_version}", self) if gemspec.nil?
|
16
20
|
|
17
21
|
(immediate_require_paths + dependency_require_paths(visited)).compact.flatten.uniq
|
18
22
|
end
|
19
23
|
|
20
24
|
def gemspec
|
25
|
+
return Gem.loaded_specs[@name] if compatible?(Gem.loaded_specs[@name])
|
26
|
+
|
21
27
|
@gemspec ||= gemspecs.find do |spec|
|
22
28
|
next false if spec.nil?
|
23
29
|
|
24
|
-
|
30
|
+
raise_incompatible(spec) unless compatible?(spec)
|
31
|
+
|
32
|
+
@dependency.requirement.satisfied_by?(spec.version)
|
25
33
|
end
|
26
34
|
end
|
27
35
|
|
36
|
+
def pretty_name
|
37
|
+
return @name.to_s if @requirement.nil?
|
38
|
+
|
39
|
+
"#{@name} #{@requirement}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def root?
|
43
|
+
self == @root
|
44
|
+
end
|
45
|
+
|
46
|
+
def required_version
|
47
|
+
return @name.to_s if compatible_spec.nil? && @requirement.nil?
|
48
|
+
return "#{@name}:#{compatible_version}" if compatible_spec.nil?
|
49
|
+
|
50
|
+
"#{@name}:#{compatible_spec.version}"
|
51
|
+
end
|
52
|
+
|
28
53
|
private
|
29
54
|
|
30
55
|
def compatible?(spec)
|
31
56
|
return false if spec.nil?
|
32
57
|
return false if incompatible_version_loaded?(spec)
|
33
58
|
|
34
|
-
compatible_specs?(
|
59
|
+
compatible_specs?([@dependency] + spec.runtime_dependencies)
|
60
|
+
end
|
61
|
+
|
62
|
+
def compatible_spec
|
63
|
+
@compatible_spec ||= gemspecs.compact
|
64
|
+
.select { |spec| requirements_satisfied_by?(spec.version) }
|
65
|
+
.max_by(&:version)
|
35
66
|
end
|
36
67
|
|
37
68
|
def incompatible_version_loaded?(spec)
|
38
69
|
matched = Gem.loaded_specs[spec.name]
|
39
70
|
return false if matched.nil?
|
40
71
|
|
41
|
-
matched.
|
72
|
+
!matched.satisfies_requirement?(@dependency)
|
42
73
|
end
|
43
74
|
|
44
|
-
def
|
45
|
-
|
75
|
+
def raise_incompatible(spec)
|
76
|
+
raise GemIncompatibilityError.new('Incompatible dependencies', incompatible_dependencies(spec))
|
77
|
+
end
|
78
|
+
|
79
|
+
def compatible_version
|
80
|
+
@requirement.requirements.map(&:last).max_by { |version| @requirement.satisfied_by?(version) }
|
46
81
|
end
|
47
82
|
|
48
|
-
def
|
49
|
-
|
83
|
+
def requirements_satisfied_by?(version)
|
84
|
+
@dependency.requirement.satisfied_by?(version)
|
85
|
+
end
|
86
|
+
|
87
|
+
def compatible_specs?(dependencies)
|
88
|
+
Gem.loaded_specs.values.all? { |spec| compatible_dependencies?(dependencies, spec) }
|
50
89
|
end
|
51
90
|
|
52
91
|
def compatible_dependencies?(dependencies, spec)
|
53
92
|
dependencies.all? { |dependency| compatible_dependency?(dependency, spec) }
|
54
93
|
end
|
55
94
|
|
95
|
+
def incompatible_dependencies(spec)
|
96
|
+
dependencies = [@dependency] + spec.runtime_dependencies
|
97
|
+
Gem.loaded_specs.map do |_name, loaded_spec|
|
98
|
+
next nil if compatible_dependencies?(dependencies, loaded_spec)
|
99
|
+
|
100
|
+
[@root, dependencies.reject { |dependency| compatible_dependency?(dependency, loaded_spec) }]
|
101
|
+
end.compact
|
102
|
+
end
|
103
|
+
|
56
104
|
def compatible_dependency?(dependency, spec)
|
57
105
|
return false if spec.nil?
|
58
106
|
return true unless dependency.name == spec.name
|
@@ -69,7 +117,7 @@ module Devpack
|
|
69
117
|
next nil if visited.include?(dependency)
|
70
118
|
|
71
119
|
visited << dependency
|
72
|
-
GemSpec.new(@glob, dependency.name, dependency.requirement).require_paths(visited)
|
120
|
+
GemSpec.new(@glob, dependency.name, dependency.requirement, root: @root).require_paths(visited)
|
73
121
|
end
|
74
122
|
end
|
75
123
|
|
@@ -101,4 +149,5 @@ module Devpack
|
|
101
149
|
@candidates ||= @glob.find(@name)
|
102
150
|
end
|
103
151
|
end
|
152
|
+
# rubocop:enable Metrics/ClassLength
|
104
153
|
end
|
data/lib/devpack/gems.rb
CHANGED
@@ -5,36 +5,38 @@ module Devpack
|
|
5
5
|
class Gems
|
6
6
|
include Timeable
|
7
7
|
|
8
|
+
attr_reader :missing
|
9
|
+
|
8
10
|
def initialize(config, glob = GemGlob.new)
|
9
11
|
@config = config
|
10
12
|
@gem_glob = glob
|
11
13
|
@failures = []
|
12
14
|
@missing = []
|
15
|
+
@incompatible = []
|
13
16
|
end
|
14
17
|
|
15
|
-
def load
|
18
|
+
def load(silent: false)
|
16
19
|
return [] if @config.requested_gems.nil?
|
17
20
|
|
18
21
|
gems, time = timed { load_devpack }
|
19
22
|
names = gems.map(&:first)
|
20
|
-
summarize(gems, time)
|
23
|
+
summarize(gems, time) unless silent
|
21
24
|
names
|
22
25
|
end
|
23
26
|
|
24
27
|
private
|
25
28
|
|
26
29
|
def summarize(gems, time)
|
27
|
-
@failures.each
|
28
|
-
|
29
|
-
end
|
30
|
-
warn(:success, Messages.loaded(@config.devpack_path, gems, time.round(2)))
|
30
|
+
@failures.each { |failure| warn(:error, Messages.failure(failure[:name], failure[:message])) }
|
31
|
+
warn(:success, Messages.loaded(@config, gems, time.round(2)))
|
31
32
|
warn(:info, Messages.install_missing(@missing)) unless @missing.empty?
|
33
|
+
warn(:info, Messages.alert_incompatible(@incompatible.flatten(1))) unless @incompatible.empty?
|
32
34
|
end
|
33
35
|
|
34
36
|
def load_devpack
|
35
37
|
@config.requested_gems.map do |requested|
|
36
38
|
name, _, version = requested.partition(':')
|
37
|
-
load_gem(name, version.empty? ? nil : Gem::Requirement.new(
|
39
|
+
load_gem(name, version.empty? ? nil : Gem::Requirement.new(version))
|
38
40
|
end.compact
|
39
41
|
end
|
40
42
|
|
@@ -42,11 +44,11 @@ module Devpack
|
|
42
44
|
[name, activate(name, requirement)]
|
43
45
|
rescue LoadError => e
|
44
46
|
deactivate(name)
|
45
|
-
@failures << { name: name, message: load_error_message(e) }
|
46
|
-
nil
|
47
|
+
nil.tap { @failures << { name: name, message: load_error_message(e) } }
|
47
48
|
rescue GemNotFoundError => e
|
48
|
-
@missing <<
|
49
|
-
|
49
|
+
nil.tap { @missing << e.meta }
|
50
|
+
rescue GemIncompatibilityError => e
|
51
|
+
nil.tap { @incompatible << e.meta }
|
50
52
|
end
|
51
53
|
|
52
54
|
def activate(name, version)
|
data/lib/devpack/messages.rb
CHANGED
@@ -13,24 +13,43 @@ module Devpack
|
|
13
13
|
"Failed to load initializer `#{path}`: #{error_message}"
|
14
14
|
end
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
# rubocop:disable Metrics/AbcSize
|
17
|
+
def loaded(config, gems, time)
|
18
|
+
loaded = gems.size - gems.reject { |_, devpack_loaded| devpack_loaded }.size
|
19
|
+
of_total = gems.size == config.requested_gems.size ? nil : " of #{color(:cyan) { config.requested_gems.size }}"
|
20
|
+
path = color(:cyan) { config.devpack_path }
|
21
|
+
base = "Loaded #{color(:green) { loaded }}#{of_total} development gem(s) from #{path} in #{time} seconds"
|
22
|
+
return "#{base}." if loaded == gems.size
|
20
23
|
|
21
|
-
"#{base} (#{gems.size -
|
24
|
+
"#{base} (#{color(:cyan) { gems.size - loaded }} gem(s) were already loaded by environment)."
|
22
25
|
end
|
26
|
+
# rubocop:enable Metrics/AbcSize
|
23
27
|
|
24
28
|
def loaded_initializers(path, initializers, time)
|
25
|
-
"Loaded #{initializers.compact.size} initializer(s) from '#{path}' in #{time} seconds"
|
29
|
+
"Loaded #{color(:green) { initializers.compact.size }} initializer(s) from '#{path}' in #{time} seconds"
|
26
30
|
end
|
27
31
|
|
28
32
|
def install_missing(missing)
|
29
|
-
|
30
|
-
|
33
|
+
command = color(:cyan) { 'bundle exec devpack install' }
|
34
|
+
grouped_missing = missing
|
35
|
+
.group_by(&:root)
|
36
|
+
.map do |root, dependencies|
|
37
|
+
next (color(:cyan) { root.pretty_name }).to_s if dependencies.all?(&:root?)
|
38
|
+
|
39
|
+
formatted_dependencies = dependencies.map { |dependency| color(:yellow) { dependency.pretty_name } }
|
40
|
+
"#{color(:cyan) { root.pretty_name }}: #{formatted_dependencies.join(', ')}"
|
31
41
|
end
|
42
|
+
"Install #{missing.size} missing gem(s): #{command} # [#{grouped_missing.join(', ')}]"
|
43
|
+
end
|
32
44
|
|
33
|
-
|
45
|
+
def alert_incompatible(incompatible)
|
46
|
+
grouped_dependencies = {}
|
47
|
+
incompatible.each do |spec, dependencies|
|
48
|
+
key = spec.root.pretty_name
|
49
|
+
grouped_dependencies[key] ||= []
|
50
|
+
grouped_dependencies[key] << dependencies
|
51
|
+
end
|
52
|
+
alert_incompatible_message(grouped_dependencies)
|
34
53
|
end
|
35
54
|
|
36
55
|
def test
|
@@ -52,6 +71,15 @@ module Devpack
|
|
52
71
|
"bundle exec gem install #{gems.join(' ')}"
|
53
72
|
end
|
54
73
|
|
74
|
+
def alert_incompatible_message(grouped_dependencies)
|
75
|
+
incompatible_dependencies = grouped_dependencies.sort.map do |name, dependencies|
|
76
|
+
"#{color(:cyan) { name }}: "\
|
77
|
+
"#{dependencies.flatten.map { |dependency| color(:yellow) { dependency.to_s } }.join(', ')}"
|
78
|
+
end
|
79
|
+
"Unable to resolve version conflicts for #{color(:yellow) { incompatible_dependencies.size }} "\
|
80
|
+
"dependencies: #{incompatible_dependencies.join(', ')}}"
|
81
|
+
end
|
82
|
+
|
55
83
|
def palette
|
56
84
|
{
|
57
85
|
reset: "\e[39m",
|
data/lib/devpack/version.rb
CHANGED
data/lib/devpack.rb
CHANGED
@@ -15,12 +15,24 @@ require 'devpack/version'
|
|
15
15
|
|
16
16
|
# Provides helper method for writing warning messages.
|
17
17
|
module Devpack
|
18
|
-
class
|
18
|
+
# Base class for all Devpack errors. Accepts additional argument `meta` to store object info.
|
19
|
+
class Error < StandardError
|
20
|
+
attr_reader :message, :meta
|
21
|
+
|
22
|
+
def initialize(message = nil, meta = nil)
|
23
|
+
@message = message
|
24
|
+
@meta = meta
|
25
|
+
super(message)
|
26
|
+
end
|
27
|
+
end
|
19
28
|
|
20
29
|
class GemNotFoundError < Error; end
|
30
|
+
class GemIncompatibilityError < Error; end
|
21
31
|
|
22
32
|
class << self
|
23
33
|
def warn(level, message)
|
34
|
+
return if silent?
|
35
|
+
|
24
36
|
prefixed = message.split("\n").map { |line| "#{prefix(level)} #{line}" }.join("\n")
|
25
37
|
Kernel.warn(prefixed)
|
26
38
|
end
|
@@ -33,6 +45,10 @@ module Devpack
|
|
33
45
|
ENV.key?('DEVPACK_DISABLE')
|
34
46
|
end
|
35
47
|
|
48
|
+
def silent?
|
49
|
+
ENV.key?('DEVPACK_SILENT')
|
50
|
+
end
|
51
|
+
|
36
52
|
def rails?
|
37
53
|
defined?(Rails::Railtie)
|
38
54
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: devpack
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bob Farrell
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-12-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: byebug
|
@@ -98,7 +98,8 @@ description: Allow developers to optionally include a set of development gems wi
|
|
98
98
|
adding to the Gemfile.
|
99
99
|
email:
|
100
100
|
- git@bob.frl
|
101
|
-
executables:
|
101
|
+
executables:
|
102
|
+
- devpack
|
102
103
|
extensions: []
|
103
104
|
extra_rdoc_files: []
|
104
105
|
files:
|
@@ -115,6 +116,7 @@ files:
|
|
115
116
|
- bin/console
|
116
117
|
- bin/setup
|
117
118
|
- devpack.gemspec
|
119
|
+
- exe/devpack
|
118
120
|
- lib/devpack.rb
|
119
121
|
- lib/devpack/config.rb
|
120
122
|
- lib/devpack/gem_glob.rb
|
@@ -132,6 +134,7 @@ metadata:
|
|
132
134
|
homepage_uri: https://github.com/bobf/devpack
|
133
135
|
source_code_uri: https://github.com/bobf/devpack
|
134
136
|
changelog_uri: https://github.com/bobf/devpack/blob/master/CHANGELOG.md
|
137
|
+
rubygems_mfa_required: 'true'
|
135
138
|
post_install_message:
|
136
139
|
rdoc_options: []
|
137
140
|
require_paths:
|