├── spec ├── dummy │ ├── lib │ │ └── dummy.rb │ ├── components │ │ ├── a-file │ │ ├── invalid-sub-dependencies │ │ │ ├── Gemfile │ │ │ ├── lib │ │ │ │ └── invalid │ │ │ │ │ └── sub │ │ │ │ │ ├── dependencies │ │ │ │ │ └── version.rb │ │ │ │ │ └── dependencies.rb │ │ │ ├── Gemfile.lock │ │ │ └── invalid-sub-dependencies.gemspec │ │ ├── obviously-valid-dependencies │ │ │ ├── Gemfile │ │ │ ├── lib │ │ │ │ └── obviously │ │ │ │ │ └── valid │ │ │ │ │ ├── dependencies │ │ │ │ │ └── version.rb │ │ │ │ │ └── dependencies.rb │ │ │ ├── Gemfile.lock │ │ │ └── obviously-valid-dependencies.gemspec │ │ └── obviously-invalid-dependencies │ │ │ ├── Gemfile │ │ │ ├── lib │ │ │ └── obviously │ │ │ │ └── invalid │ │ │ │ ├── dependencies │ │ │ │ └── version.rb │ │ │ │ └── dependencies.rb │ │ │ ├── Gemfile.lock │ │ │ └── obviously-invalid-dependencies.gemspec │ ├── Gemfile │ └── Gemfile.lock ├── spec_helper.rb └── transdeps │ ├── cli_spec.rb │ ├── spec_list_factory_spec.rb │ ├── tasks_spec.rb │ ├── default_component_discoverer_spec.rb │ ├── reconciler_spec.rb │ ├── spec_list_spec.rb │ └── specification_spec.rb ├── .rspec ├── lib ├── transdeps │ ├── version.rb │ ├── spec_list_factory.rb │ ├── default_component_discoverer.rb │ ├── inconsistency.rb │ ├── cli.rb │ ├── specification.rb │ ├── tasks.rb │ ├── spec_list.rb │ └── reconciler.rb └── transdeps.rb ├── .travis.yml ├── Gemfile ├── Rakefile ├── .gitignore ├── transdeps.gemspec ├── LICENSE.txt └── README.md /spec/dummy/lib/dummy.rb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/dummy/components/a-file: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /lib/transdeps/version.rb: -------------------------------------------------------------------------------- 1 | module Transdeps 2 | VERSION = "2.0.0" 3 | end 4 | -------------------------------------------------------------------------------- /lib/transdeps.rb: -------------------------------------------------------------------------------- 1 | require "transdeps/version" 2 | 3 | module Transdeps 4 | end 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.2.4 4 | before_install: gem install bundler -v 1.17.3 5 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in transdeps.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | require 'transdeps/tasks' 4 | 5 | require 'rspec/core/rake_task' 6 | RSpec::Core::RakeTask.new(:spec) 7 | 8 | task default: [:spec] 9 | -------------------------------------------------------------------------------- /spec/dummy/components/invalid-sub-dependencies/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in invalid-sub-dependencies.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /spec/dummy/components/obviously-valid-dependencies/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in obviously-valid-dependencies.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /spec/dummy/components/obviously-invalid-dependencies/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in obviously-invalid-dependencies.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | *.bundle 11 | *.so 12 | *.o 13 | *.a 14 | mkmf.log 15 | -------------------------------------------------------------------------------- /spec/dummy/components/invalid-sub-dependencies/lib/invalid/sub/dependencies/version.rb: -------------------------------------------------------------------------------- 1 | module Invalid 2 | module Sub 3 | module Dependencies 4 | VERSION = "0.0.1" 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/dummy/components/obviously-valid-dependencies/lib/obviously/valid/dependencies/version.rb: -------------------------------------------------------------------------------- 1 | module Obviously 2 | module Valid 3 | module Dependencies 4 | VERSION = "0.0.1" 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/dummy/components/obviously-invalid-dependencies/lib/obviously/invalid/dependencies/version.rb: -------------------------------------------------------------------------------- 1 | module Obviously 2 | module Invalid 3 | module Dependencies 4 | VERSION = "0.0.1" 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/transdeps/spec_list_factory.rb: -------------------------------------------------------------------------------- 1 | require 'transdeps/spec_list' 2 | module Transdeps 3 | class SpecListFactory 4 | def call(dir) 5 | SpecList.new(dir + 'Gemfile.lock', File.read(dir + 'Gemfile.lock')) 6 | end 7 | end 8 | end -------------------------------------------------------------------------------- /spec/dummy/components/invalid-sub-dependencies/lib/invalid/sub/dependencies.rb: -------------------------------------------------------------------------------- 1 | require "invalid/sub/dependencies/version" 2 | 3 | module Invalid 4 | module Sub 5 | module Dependencies 6 | # Your code goes here... 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/transdeps/default_component_discoverer.rb: -------------------------------------------------------------------------------- 1 | module Transdeps 2 | class DefaultComponentDiscoverer 3 | def call(component_dir) 4 | component_dir 5 | .children 6 | .select { |filename| File.directory?(filename) } 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/dummy/components/obviously-valid-dependencies/lib/obviously/valid/dependencies.rb: -------------------------------------------------------------------------------- 1 | require "obviously/valid/dependencies/version" 2 | 3 | module Obviously 4 | module Valid 5 | module Dependencies 6 | # Your code goes here... 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/dummy/components/obviously-invalid-dependencies/lib/obviously/invalid/dependencies.rb: -------------------------------------------------------------------------------- 1 | require "obviously/invalid/dependencies/version" 2 | 3 | module Obviously 4 | module Invalid 5 | module Dependencies 6 | # Your code goes here... 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/transdeps/inconsistency.rb: -------------------------------------------------------------------------------- 1 | module Transdeps 2 | class Inconsistency < Struct.new(:expected, :actual) 3 | def to_s 4 | "%-30s != %-30s (%-30s)" % { 5 | expected: expected, 6 | component: actual.project_path.split('/').last, 7 | actual: actual, 8 | } 9 | end 10 | end 11 | end -------------------------------------------------------------------------------- /spec/dummy/components/obviously-valid-dependencies/Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | obviously-valid-dependencies (0.0.1) 5 | 6 | GEM 7 | remote: https://rubygems.org/ 8 | specs: 9 | rake (10.4.2) 10 | 11 | PLATFORMS 12 | ruby 13 | 14 | DEPENDENCIES 15 | bundler (~> 1.7) 16 | obviously-valid-dependencies! 17 | rake (~> 10.0) 18 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | 3 | DUMMY_APP_PATH = Pathname(File.expand_path('../dummy', __FILE__)) 4 | 5 | RSpec.configure do |config| 6 | config.expect_with :rspec do |expectations| 7 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 8 | end 9 | 10 | config.mock_with :rspec do |mocks| 11 | mocks.verify_partial_doubles = true 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/transdeps/cli_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'transdeps/cli' 3 | 4 | module Transdeps 5 | describe Cli do 6 | it 'uses the reconciles dependencies' do 7 | reconciler = double('Reconciler', call: []) 8 | cli = Cli.new('components', 'root', reconciler) 9 | expect(reconciler).to receive(:call).with(Pathname('components'), Pathname('root')) 10 | cli.run 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/transdeps/cli.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | require 'transdeps/reconciler' 3 | 4 | module Transdeps 5 | class Cli 6 | attr_reader :component_dir, :project_root, :reconciler 7 | def initialize(component_dir, project_root=Pathname('.'), reconciler=Reconciler.new) 8 | @component_dir = Pathname(component_dir) 9 | @project_root = Pathname(project_root) 10 | @reconciler = reconciler 11 | end 12 | 13 | def run 14 | reconciler.call(component_dir, project_root) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/transdeps/specification.rb: -------------------------------------------------------------------------------- 1 | module Transdeps 2 | class Specification < Struct.new(:name, :version, :project_path) 3 | def self.from_lock(lock, path='') 4 | match = lock.match(/^(?[^\s]+) \((?.*)\)/) 5 | new(match[:name], match[:version], path) 6 | end 7 | 8 | def same_gem_as?(other) 9 | name == other.name 10 | end 11 | 12 | def =~(other) 13 | same_gem_as?(other) && version == other.version 14 | end 15 | 16 | def to_s 17 | "#{name} (#{version})" 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/dummy/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | 4 | gem 'rails', '~> 6' 5 | gem 'sqlite3' 6 | gem 'sass-rails' 7 | gem 'uglifier' 8 | gem 'coffee-rails' 9 | 10 | gem 'jquery-rails' 11 | gem 'turbolinks' 12 | gem 'sdoc', '~> 0.4.0', group: :doc 13 | 14 | gem 'spring', group: :development 15 | 16 | path './components' do 17 | gem 'obviously-invalid-dependencies' 18 | gem 'obviously-valid-dependencies' 19 | gem 'invalid-sub-dependencies' 20 | end 21 | 22 | group :development, :test do 23 | gem 'rspec', '~> 3.1' 24 | gem 'capybara', '~> 2.1' 25 | end 26 | -------------------------------------------------------------------------------- /lib/transdeps/tasks.rb: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | require 'transdeps/cli' 3 | 4 | desc 'Print all the dependency inconsistencies. root_dir is optional.' 5 | task :transdeps, [:component_dir, :root_dir] do |_, args| 6 | if args[:root_dir] 7 | result = Transdeps::Cli.new(args[:component_dir], args[:root_dir]).run 8 | puts(result) 9 | exit 1 if !result.nil? && !result.empty? 10 | else 11 | warn 'WARNING: Using PWD as project root_dir' 12 | result = Transdeps::Cli.new(args[:component_dir]).run 13 | puts(result) 14 | exit 1 if !result.nil? && !result.empty? 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/transdeps/spec_list_factory_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'transdeps/spec_list_factory' 3 | 4 | module Transdeps 5 | describe SpecListFactory do 6 | it 'returns a speclist for given dir' do 7 | lockfile = DUMMY_APP_PATH + 'Gemfile.lock' 8 | expect(File).to receive(:read).with(lockfile).and_return('stuff') 9 | 10 | speclist = SpecList.new DUMMY_APP_PATH + 'Gemfile.lock', 'stuff' 11 | result = SpecListFactory.new.call(DUMMY_APP_PATH) 12 | expect(result.lock_file_path).to eq(speclist.lock_file_path) 13 | expect(result.lock_file_contents).to eq(speclist.lock_file_contents) 14 | end 15 | end 16 | end -------------------------------------------------------------------------------- /spec/dummy/components/obviously-invalid-dependencies/Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | obviously-invalid-dependencies (0.0.1) 5 | 6 | GEM 7 | remote: https://rubygems.org/ 8 | specs: 9 | diff-lcs (1.2.5) 10 | rake (10.4.2) 11 | rspec (3.0.0) 12 | rspec-core (~> 3.0.0) 13 | rspec-expectations (~> 3.0.0) 14 | rspec-mocks (~> 3.0.0) 15 | rspec-core (3.0.4) 16 | rspec-support (~> 3.0.0) 17 | rspec-expectations (3.0.4) 18 | diff-lcs (>= 1.2.0, < 2.0) 19 | rspec-support (~> 3.0.0) 20 | rspec-mocks (3.0.4) 21 | rspec-support (~> 3.0.0) 22 | rspec-support (3.0.4) 23 | 24 | PLATFORMS 25 | ruby 26 | 27 | DEPENDENCIES 28 | bundler (~> 1.7) 29 | obviously-invalid-dependencies! 30 | rake (~> 10.0) 31 | rspec (= 3.0) 32 | -------------------------------------------------------------------------------- /spec/dummy/components/invalid-sub-dependencies/Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | invalid-sub-dependencies (0.0.1) 5 | 6 | GEM 7 | remote: https://rubygems.org/ 8 | specs: 9 | capybara (2.1.0) 10 | mime-types (>= 1.16) 11 | nokogiri (>= 1.3.3) 12 | rack (>= 1.0.0) 13 | rack-test (>= 0.5.4) 14 | xpath (~> 2.0) 15 | capybara-webkit (1.1.1) 16 | capybara (>= 2.0.2, < 2.2.0) 17 | json 18 | json (1.8.1) 19 | mime-types (2.4.3) 20 | mini_portile2 (2.4.0) 21 | nokogiri (1.10.7) 22 | mini_portile2 (~> 2.4.0) 23 | rack (2.0.7) 24 | rack-test (0.6.2) 25 | rack (>= 1.0) 26 | rake (10.4.2) 27 | xpath (2.0.0) 28 | nokogiri (~> 1.3) 29 | 30 | PLATFORMS 31 | ruby 32 | 33 | DEPENDENCIES 34 | bundler (~> 1.7) 35 | capybara (= 2.1) 36 | capybara-webkit (~> 1.1.0) 37 | invalid-sub-dependencies! 38 | rake (~> 10.0) 39 | -------------------------------------------------------------------------------- /transdeps.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'transdeps/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "transdeps" 8 | spec.version = Transdeps::VERSION 9 | spec.authors = ["TJ Taylor"] 10 | spec.email = ["dugancathal@gmail.com"] 11 | spec.description = %q{A tool to manage unbuilt gems' dependencies} 12 | spec.summary = %q{} 13 | spec.homepage = "" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files -z`.split("\x0") 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_development_dependency "bundler", "~> 1.7" 22 | spec.add_development_dependency "rake", "~> 10.0" 23 | spec.add_development_dependency "rspec", "~> 3.0" 24 | end 25 | -------------------------------------------------------------------------------- /lib/transdeps/spec_list.rb: -------------------------------------------------------------------------------- 1 | require 'bundler/lockfile_parser' 2 | require 'transdeps/specification' 3 | require 'transdeps/inconsistency' 4 | 5 | module Transdeps 6 | class SpecList < Struct.new(:lock_file_path, :lock_file_contents, :parser) 7 | def initialize(lock_file_path, lock_file_contents, parser=Bundler::LockfileParser.new(lock_file_contents.to_s)) 8 | lock_file_path = Pathname(lock_file_path) 9 | super(lock_file_path, lock_file_contents, parser) 10 | end 11 | 12 | def specs 13 | parser.specs.map do |spec| 14 | Specification.from_lock(spec.to_s, lock_file_path.dirname.to_s) 15 | end 16 | end 17 | 18 | def -(other) 19 | specs.map do |spec| 20 | next unless other[spec.name] 21 | unless spec =~ other[spec.name] 22 | Inconsistency.new(spec, other[spec.name]) 23 | end 24 | end.compact 25 | end 26 | 27 | def [](name) 28 | specs.find{|spec| spec.name == name } 29 | end 30 | end 31 | end -------------------------------------------------------------------------------- /spec/dummy/components/obviously-valid-dependencies/obviously-valid-dependencies.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'obviously/valid/dependencies/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "obviously-valid-dependencies" 8 | spec.version = Obviously::Valid::Dependencies::VERSION 9 | spec.authors = ["TJ Taylor"] 10 | spec.email = ["dugancathal@gmail.com"] 11 | spec.summary = %q{Write a short summary. Required.} 12 | spec.description = %q{Write a longer description. Optional.} 13 | spec.homepage = "" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files -z`.split("\x0") 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_development_dependency "bundler", "~> 1.7" 22 | spec.add_development_dependency "rake", "~> 10.0" 23 | end 24 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 TJ Taylor 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /spec/dummy/components/obviously-invalid-dependencies/obviously-invalid-dependencies.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'obviously/invalid/dependencies/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "obviously-invalid-dependencies" 8 | spec.version = Obviously::Invalid::Dependencies::VERSION 9 | spec.authors = ["TJ Taylor"] 10 | spec.email = ["dugancathal@gmail.com"] 11 | spec.summary = %q{Write a short summary. Required.} 12 | spec.description = %q{Write a longer description. Optional.} 13 | spec.homepage = "" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files -z`.split("\x0") 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_development_dependency "bundler", "~> 1.7" 22 | spec.add_development_dependency "rake", "~> 10.0" 23 | spec.add_development_dependency "rspec", "= 3.0" 24 | end 25 | -------------------------------------------------------------------------------- /lib/transdeps/reconciler.rb: -------------------------------------------------------------------------------- 1 | require 'transdeps/default_component_discoverer' 2 | require 'transdeps/spec_list_factory' 3 | require 'transdeps/inconsistency' 4 | 5 | module Transdeps 6 | class Reconciler < Struct.new(:spec_list_factory, :component_discoverer) 7 | def initialize(spec_list_factory=SpecListFactory.new, component_discoverer=DefaultComponentDiscoverer.new) 8 | super 9 | end 10 | 11 | def call(component_dir, project_dir) 12 | project_specs = specs_for(project_dir) 13 | all_component_specs = all_component_specs(component_dir) 14 | 15 | spec_differences(project_specs, all_component_specs) 16 | end 17 | 18 | private 19 | 20 | def specs_for(dir) 21 | spec_list_factory.call(dir) 22 | end 23 | 24 | def all_component_specs(component_dir) 25 | component_discoverer 26 | .call(component_dir) 27 | .map { |dir| specs_for(dir) } 28 | end 29 | 30 | def spec_differences(project_specs, all_component_specs) 31 | all_component_specs.flat_map do |component_specs| 32 | project_specs - component_specs 33 | end.compact 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/dummy/components/invalid-sub-dependencies/invalid-sub-dependencies.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'invalid/sub/dependencies/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "invalid-sub-dependencies" 8 | spec.version = Invalid::Sub::Dependencies::VERSION 9 | spec.authors = ["TJ Taylor"] 10 | spec.email = ["dugancathal@gmail.com"] 11 | spec.summary = %q{Write a short summary. Required.} 12 | spec.description = %q{Write a longer description. Optional.} 13 | spec.homepage = "" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files -z`.split("\x0") 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_development_dependency "bundler", "~> 1.7" 22 | spec.add_development_dependency "rake", "~> 10.0" 23 | spec.add_development_dependency "capybara-webkit", "~> 1.1.0" 24 | spec.add_development_dependency "capybara", "= 2.1" 25 | end 26 | -------------------------------------------------------------------------------- /spec/transdeps/tasks_spec.rb: -------------------------------------------------------------------------------- 1 | require 'transdeps/tasks' 2 | 3 | describe 'rake' do 4 | describe 'transdeps' do 5 | let(:cli) { double(Transdeps::Cli, run: nil) } 6 | before do 7 | Rake::Task['transdeps'].reenable 8 | allow(Transdeps::Cli).to receive(:new).and_return(cli) 9 | end 10 | 11 | context 'with two arguments' do 12 | it 'calls Cli.run' do 13 | Rake.application.invoke_task 'transdeps[component_dir,root_dir]' 14 | 15 | expect(Transdeps::Cli).to have_received(:new).with 'component_dir', 'root_dir' 16 | expect(cli).to have_received(:run) 17 | end 18 | end 19 | 20 | context 'with one argument' do 21 | it 'calls Cli.run' do 22 | Rake.application.invoke_task 'transdeps[component_dir]' 23 | 24 | expect(Transdeps::Cli).to have_received(:new).with 'component_dir' 25 | expect(cli).to have_received(:run) 26 | end 27 | 28 | it 'prints a warning that the root dir is PWD' do 29 | expect do 30 | Rake.application.invoke_task 'transdeps[component_dir]' 31 | end.to output(/WARNING: Using PWD as project root_dir/).to_stderr 32 | end 33 | end 34 | end 35 | end -------------------------------------------------------------------------------- /spec/transdeps/default_component_discoverer_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'tmpdir' 3 | require 'transdeps/default_component_discoverer' 4 | 5 | module Transdeps 6 | describe DefaultComponentDiscoverer do 7 | subject { Transdeps::DefaultComponentDiscoverer.new } 8 | 9 | around(:each) do |example| 10 | Dir.mktmpdir do |dir| 11 | Dir.chdir(dir) do 12 | Dir.mkdir('components') 13 | Dir.mkdir(component_a) 14 | Dir.mkdir(component_b) 15 | 16 | File.open(dummy_file, 'w') { } 17 | 18 | example.run 19 | end 20 | end 21 | end 22 | 23 | let (:component_a) { File.join('components', 'componentA') } 24 | let (:component_b) { File.join('components', 'componentB') } 25 | let (:dummy_file) { File.join('README.md') } 26 | 27 | it 'returns Pathnames to directories inside a given component_dir' do 28 | component_dir = double( 29 | Dir, 30 | children: [ 31 | Pathname.new(component_a), 32 | Pathname.new(dummy_file), 33 | Pathname.new(component_b) 34 | ] 35 | ) 36 | 37 | components = subject.call(component_dir) 38 | 39 | expect(components).to match_array( 40 | [ 41 | Pathname.new(component_a), 42 | Pathname.new(component_b) 43 | ] 44 | ) 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/transdeps/reconciler_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'transdeps/reconciler' 3 | 4 | module Transdeps 5 | describe Reconciler do 6 | it 'returns the set of specs that are inconsistent between the components' do 7 | factory = SpecListFactory.new 8 | rec = Reconciler.new(factory) 9 | component_dir = double(Dir, children: [DUMMY_APP_PATH + 'components/invalid-sub-dependencies']) 10 | 11 | ten_dot_oh = Specification.from_lock('rake (10.0.0)') 12 | ten_dot_one = Specification.from_lock('rake (10.1.0)') 13 | expect(factory).to receive(:call).with(DUMMY_APP_PATH) do 14 | list = SpecList.new('Gemfile.lock', double) 15 | allow(list).to receive(:specs).and_return([ten_dot_oh]) 16 | list 17 | end 18 | expect(factory).to receive(:call).with(DUMMY_APP_PATH + 'components/invalid-sub-dependencies') do 19 | list = SpecList.new('Gemfile.lock', double) 20 | allow(list).to receive(:specs).and_return([ten_dot_one]) 21 | list 22 | end 23 | 24 | result = rec.call(component_dir, DUMMY_APP_PATH) 25 | 26 | expect(result).to eq [Inconsistency.new(ten_dot_oh, ten_dot_one)] 27 | end 28 | 29 | it 'ignores files in components directory' do 30 | factory = SpecListFactory.new 31 | rec = Reconciler.new(factory) 32 | component_dir = double(Dir, children: [DUMMY_APP_PATH + 'components/a-file']) 33 | 34 | result = rec.call(component_dir, DUMMY_APP_PATH) 35 | 36 | expect(result).to be_empty 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Transdeps 2 | 3 | A gem to find inconsistent dependency versions in component-based 4 | Ruby apps. 5 | 6 | ## Installation 7 | 8 | Add this line to your application's Gemfile: 9 | 10 | ```ruby 11 | gem 'transdeps' 12 | ``` 13 | 14 | And then execute: 15 | 16 | $ bundle 17 | 18 | Or install it yourself as: 19 | 20 | $ gem install transdeps 21 | 22 | ## Usage 23 | 24 | ### Using `rake` 25 | 26 | Add this line to application's Rakefile: 27 | 28 | ```ruby 29 | require 'transdeps/tasks' 30 | ``` 31 | 32 | Then run: 33 | 34 | ```bash 35 | bundle exec rake transdeps[/path/to/my/components,/path/to/my/project] 36 | ``` 37 | 38 | Since you are presumably running this task from the root of your project, 39 | you can leave the second argument to the Rake task off, and the 40 | first argument would be a relative path. Something like: 41 | 42 | ```bash 43 | bundle exec rake transdeps[components] 44 | ``` 45 | 46 | ### Using `rspec` 47 | 48 | Add this spec: 49 | 50 | ```ruby 51 | require "transdeps/cli" 52 | 53 | RSpec.describe "Component" do 54 | it "uses the same version of dependencies as the ones in the container application" do 55 | results = Transdeps::Cli.new("components").run 56 | expect(results).to be_empty, failure_message(results) 57 | end 58 | 59 | def failure_message(results) 60 | (["❌ Dependency inconsistencies found:"] + results).join("\n") 61 | end 62 | end 63 | ``` 64 | 65 | ## Contributing 66 | 67 | 1. Fork it ( https://github.com/dugancathal/transdeps/fork ) 68 | 1. Create your feature branch (`git checkout -b my-new-feature`) 69 | 1. Install gems (`gem install bundler && bundle`) 70 | 1. Run tests (`rspec`) 71 | 1. Commit your changes (`git commit -am 'Add some feature'`) 72 | 1. Push to the branch (`git push origin my-new-feature`) 73 | 1. Create a new Pull Request 74 | -------------------------------------------------------------------------------- /spec/transdeps/spec_list_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'transdeps/spec_list' 3 | 4 | module Transdeps 5 | describe SpecList do 6 | it 'delegates the :specs method to the generator' do 7 | generator = double(Bundler::LockfileParser, new: nil, specs: []) 8 | expect(generator).to receive(:specs) 9 | list = SpecList.new('Gemfile.lock', 'contents', generator) 10 | list.specs 11 | end 12 | 13 | it 'returns a Specification for each spec' do 14 | generator = double(Bundler::LockfileParser, specs: ['rake (10.0.0)']) 15 | list = SpecList.new('Gemfile.lock', 'contents', generator) 16 | allow(list).to receive(:parser).and_return(generator) 17 | expect(list.specs).to eq([Specification.from_lock('rake (10.0.0)', '.')]) 18 | end 19 | 20 | it "sets the specification project_path to the lock_file's directory" do 21 | generator = double(Bundler::LockfileParser, specs: ['rake (10.0.0)']) 22 | list = SpecList.new('/path/to/Gemfile.lock', 'contents', generator) 23 | allow(list).to receive(:parser).and_return(generator) 24 | expect(list.specs).to eq([Specification.from_lock('rake (10.0.0)', '/path/to')]) 25 | end 26 | 27 | it 'allows findingy by gem name' do 28 | generator = double(Bundler::LockfileParser, specs: ['rake (10.0.0)']) 29 | list = SpecList.new('/path/to/Gemfile.lock', 'contents', generator) 30 | allow(list).to receive(:parser).and_return(generator) 31 | expect(list['rake']).to eq(Specification.from_lock('rake (10.0.0)', '/path/to')) 32 | end 33 | 34 | describe '-=' do 35 | it 'returns a list of the inconsistencies' do 36 | ten_dot_oh = Specification.from_lock('rake (10.0.0)', '.') 37 | ten_dot_one = Specification.from_lock('rake (10.1.0)', '.') 38 | project_generator = double(Bundler::LockfileParser, specs: [ten_dot_oh]) 39 | project_list = SpecList.new('Gemfile.lock', 'contents', project_generator) 40 | 41 | component_generator = double(Bundler::LockfileParser, specs: [ten_dot_one]) 42 | component_list = SpecList.new('Gemfile.lock', 'contents', component_generator) 43 | 44 | expect(project_list - component_list).to eq([Inconsistency.new(ten_dot_oh, ten_dot_one)]) 45 | end 46 | 47 | it 'is agnostic to project_path' do 48 | ten_dot_oh = Specification.from_lock('rake (10.0.0)', '/first/path') 49 | ten_dot_one = Specification.from_lock('rake (10.0.0)', '/other/path') 50 | project_generator = double(Bundler::LockfileParser, specs: [ten_dot_oh]) 51 | project_list = SpecList.new('/first/Gemfile.lock', 'contents', project_generator) 52 | 53 | component_generator = double(Bundler::LockfileParser, specs: [ten_dot_one]) 54 | component_list = SpecList.new('/other/Gemfile.lock', 'contents', component_generator) 55 | 56 | expect(project_list - component_list).to eq([]) 57 | end 58 | end 59 | end 60 | end -------------------------------------------------------------------------------- /spec/transdeps/specification_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'transdeps/specification' 3 | 4 | describe Transdeps::Specification do 5 | describe '.from_lock' do 6 | it 'initializes the name and version from the lock string' do 7 | lock = 'rake (10.0.0)' 8 | spec = Transdeps::Specification.from_lock(lock) 9 | expect(spec.name).to eq 'rake' 10 | expect(spec.version).to eq '10.0.0' 11 | end 12 | 13 | it 'works for dasherized spec-names' do 14 | lock = 'rake-lib (10.0.0)' 15 | spec = Transdeps::Specification.from_lock(lock) 16 | expect(spec.name).to eq 'rake-lib' 17 | expect(spec.version).to eq '10.0.0' 18 | end 19 | 20 | it 'works for underscored names' do 21 | lock = 'rake_lib (10.0.0)' 22 | spec = Transdeps::Specification.from_lock(lock) 23 | expect(spec.name).to eq 'rake_lib' 24 | expect(spec.version).to eq '10.0.0' 25 | end 26 | 27 | it 'works for names with dots' do 28 | lock = 'http_parser.rb (0.6.0)' 29 | spec = Transdeps::Specification.from_lock(lock) 30 | expect(spec.name).to eq 'http_parser.rb' 31 | expect(spec.version).to eq '0.6.0' 32 | end 33 | end 34 | 35 | describe 'match-y-ness' do 36 | it 'matches when the name is the same' do 37 | version1 = Transdeps::Specification.new('rake', '10.0.0') 38 | version2 = Transdeps::Specification.new('rake', '10.0.0') 39 | expect(version1 =~ version2).to be_truthy 40 | end 41 | 42 | it 'does not match when the name is the same and the version is different' do 43 | version1 = Transdeps::Specification.new('rake', '10.0.0') 44 | version2 = Transdeps::Specification.new('rake', '10.1.0') 45 | expect(version1 =~ version2).to be_falsey 46 | end 47 | 48 | it 'does not care what the project path is' do 49 | version1 = Transdeps::Specification.new('rake', '10.0.0', 'path1') 50 | version2 = Transdeps::Specification.new('rake', '10.0.0', 'path2') 51 | expect(version1 =~ version2).to be_truthy 52 | end 53 | end 54 | 55 | describe 'equality' do 56 | it 'is equal when the name, version, and project_path are equal' do 57 | version1 = Transdeps::Specification.new('rake', '10.0.0', 'path') 58 | version2 = Transdeps::Specification.new('rake', '10.0.0', 'path') 59 | expect(version1 === version2).to be_truthy 60 | end 61 | end 62 | 63 | describe '#same_gem_as?' do 64 | it 'returns true when the gem names are the same' do 65 | version1 = Transdeps::Specification.new('rake', '10.0.0') 66 | version2 = Transdeps::Specification.new('rake', '10.0.0') 67 | expect(version1).to be_same_gem_as version2 68 | end 69 | 70 | it 'returns true when the gem names are the same and versions are different' do 71 | version1 = Transdeps::Specification.new('rake', '10.1.0') 72 | version2 = Transdeps::Specification.new('rake', '10.0.0') 73 | expect(version1).to be_same_gem_as version2 74 | end 75 | 76 | it 'returns false when the gem names are the different' do 77 | version1 = Transdeps::Specification.new('rake', '10.0.0') 78 | version2 = Transdeps::Specification.new('rack', '10.0.0') 79 | expect(version1).to_not be_same_gem_as version2 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /spec/dummy/Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: components 3 | specs: 4 | invalid-sub-dependencies (0.0.1) 5 | obviously-invalid-dependencies (0.0.1) 6 | obviously-valid-dependencies (0.0.1) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | actioncable (6.0.1) 12 | actionpack (= 6.0.1) 13 | nio4r (~> 2.0) 14 | websocket-driver (>= 0.6.1) 15 | actionmailbox (6.0.1) 16 | actionpack (= 6.0.1) 17 | activejob (= 6.0.1) 18 | activerecord (= 6.0.1) 19 | activestorage (= 6.0.1) 20 | activesupport (= 6.0.1) 21 | mail (>= 2.7.1) 22 | actionmailer (6.0.1) 23 | actionpack (= 6.0.1) 24 | actionview (= 6.0.1) 25 | activejob (= 6.0.1) 26 | mail (~> 2.5, >= 2.5.4) 27 | rails-dom-testing (~> 2.0) 28 | actionpack (6.0.1) 29 | actionview (= 6.0.1) 30 | activesupport (= 6.0.1) 31 | rack (~> 2.0) 32 | rack-test (>= 0.6.3) 33 | rails-dom-testing (~> 2.0) 34 | rails-html-sanitizer (~> 1.0, >= 1.2.0) 35 | actiontext (6.0.1) 36 | actionpack (= 6.0.1) 37 | activerecord (= 6.0.1) 38 | activestorage (= 6.0.1) 39 | activesupport (= 6.0.1) 40 | nokogiri (>= 1.8.5) 41 | actionview (6.0.1) 42 | activesupport (= 6.0.1) 43 | builder (~> 3.1) 44 | erubi (~> 1.4) 45 | rails-dom-testing (~> 2.0) 46 | rails-html-sanitizer (~> 1.1, >= 1.2.0) 47 | activejob (6.0.1) 48 | activesupport (= 6.0.1) 49 | globalid (>= 0.3.6) 50 | activemodel (6.0.1) 51 | activesupport (= 6.0.1) 52 | activerecord (6.0.1) 53 | activemodel (= 6.0.1) 54 | activesupport (= 6.0.1) 55 | activestorage (6.0.1) 56 | actionpack (= 6.0.1) 57 | activejob (= 6.0.1) 58 | activerecord (= 6.0.1) 59 | marcel (~> 0.3.1) 60 | activesupport (6.0.1) 61 | concurrent-ruby (~> 1.0, >= 1.0.2) 62 | i18n (>= 0.7, < 2) 63 | minitest (~> 5.1) 64 | tzinfo (~> 1.1) 65 | zeitwerk (~> 2.2) 66 | addressable (2.7.0) 67 | public_suffix (>= 2.0.2, < 5.0) 68 | builder (3.2.3) 69 | capybara (2.18.0) 70 | addressable 71 | mini_mime (>= 0.1.3) 72 | nokogiri (>= 1.3.3) 73 | rack (>= 1.0.0) 74 | rack-test (>= 0.5.4) 75 | xpath (>= 2.0, < 4.0) 76 | coffee-rails (5.0.0) 77 | coffee-script (>= 2.2.0) 78 | railties (>= 5.2.0) 79 | coffee-script (2.4.1) 80 | coffee-script-source 81 | execjs 82 | coffee-script-source (1.12.2) 83 | concurrent-ruby (1.1.5) 84 | crass (1.0.5) 85 | diff-lcs (1.3) 86 | erubi (1.9.0) 87 | execjs (2.7.0) 88 | ffi (1.11.3) 89 | globalid (0.4.2) 90 | activesupport (>= 4.2.0) 91 | i18n (1.7.0) 92 | concurrent-ruby (~> 1.0) 93 | jquery-rails (4.3.5) 94 | rails-dom-testing (>= 1, < 3) 95 | railties (>= 4.2.0) 96 | thor (>= 0.14, < 2.0) 97 | json (1.8.6) 98 | loofah (2.4.0) 99 | crass (~> 1.0.2) 100 | nokogiri (>= 1.5.9) 101 | mail (2.7.1) 102 | mini_mime (>= 0.1.1) 103 | marcel (0.3.3) 104 | mimemagic (~> 0.3.2) 105 | method_source (0.9.2) 106 | mimemagic (0.3.3) 107 | mini_mime (1.0.2) 108 | mini_portile2 (2.4.0) 109 | minitest (5.13.0) 110 | nio4r (2.5.2) 111 | nokogiri (1.10.7) 112 | mini_portile2 (~> 2.4.0) 113 | public_suffix (4.0.1) 114 | rack (2.0.7) 115 | rack-test (1.1.0) 116 | rack (>= 1.0, < 3) 117 | rails (6.0.1) 118 | actioncable (= 6.0.1) 119 | actionmailbox (= 6.0.1) 120 | actionmailer (= 6.0.1) 121 | actionpack (= 6.0.1) 122 | actiontext (= 6.0.1) 123 | actionview (= 6.0.1) 124 | activejob (= 6.0.1) 125 | activemodel (= 6.0.1) 126 | activerecord (= 6.0.1) 127 | activestorage (= 6.0.1) 128 | activesupport (= 6.0.1) 129 | bundler (>= 1.3.0) 130 | railties (= 6.0.1) 131 | sprockets-rails (>= 2.0.0) 132 | rails-dom-testing (2.0.3) 133 | activesupport (>= 4.2.0) 134 | nokogiri (>= 1.6) 135 | rails-html-sanitizer (1.3.0) 136 | loofah (~> 2.3) 137 | railties (6.0.1) 138 | actionpack (= 6.0.1) 139 | activesupport (= 6.0.1) 140 | method_source 141 | rake (>= 0.8.7) 142 | thor (>= 0.20.3, < 2.0) 143 | rake (13.0.1) 144 | rdoc (4.3.0) 145 | rspec (3.9.0) 146 | rspec-core (~> 3.9.0) 147 | rspec-expectations (~> 3.9.0) 148 | rspec-mocks (~> 3.9.0) 149 | rspec-core (3.9.0) 150 | rspec-support (~> 3.9.0) 151 | rspec-expectations (3.9.0) 152 | diff-lcs (>= 1.2.0, < 2.0) 153 | rspec-support (~> 3.9.0) 154 | rspec-mocks (3.9.0) 155 | diff-lcs (>= 1.2.0, < 2.0) 156 | rspec-support (~> 3.9.0) 157 | rspec-support (3.9.0) 158 | sass-rails (6.0.0) 159 | sassc-rails (~> 2.1, >= 2.1.1) 160 | sassc (2.2.1) 161 | ffi (~> 1.9) 162 | sassc-rails (2.1.2) 163 | railties (>= 4.0.0) 164 | sassc (>= 2.0) 165 | sprockets (> 3.0) 166 | sprockets-rails 167 | tilt 168 | sdoc (0.4.2) 169 | json (~> 1.7, >= 1.7.7) 170 | rdoc (~> 4.0) 171 | spring (2.1.0) 172 | sprockets (4.0.0) 173 | concurrent-ruby (~> 1.0) 174 | rack (> 1, < 3) 175 | sprockets-rails (3.2.1) 176 | actionpack (>= 4.0) 177 | activesupport (>= 4.0) 178 | sprockets (>= 3.0.0) 179 | sqlite3 (1.4.1) 180 | thor (0.20.3) 181 | thread_safe (0.3.6) 182 | tilt (2.0.10) 183 | turbolinks (5.2.1) 184 | turbolinks-source (~> 5.2) 185 | turbolinks-source (5.2.0) 186 | tzinfo (1.2.5) 187 | thread_safe (~> 0.1) 188 | uglifier (4.2.0) 189 | execjs (>= 0.3.0, < 3) 190 | websocket-driver (0.7.1) 191 | websocket-extensions (>= 0.1.0) 192 | websocket-extensions (0.1.4) 193 | xpath (3.2.0) 194 | nokogiri (~> 1.8) 195 | zeitwerk (2.2.2) 196 | 197 | PLATFORMS 198 | ruby 199 | 200 | DEPENDENCIES 201 | capybara (~> 2.1) 202 | coffee-rails 203 | invalid-sub-dependencies! 204 | jquery-rails 205 | obviously-invalid-dependencies! 206 | obviously-valid-dependencies! 207 | rails (~> 6) 208 | rspec (~> 3.1) 209 | sass-rails 210 | sdoc (~> 0.4.0) 211 | spring 212 | sqlite3 213 | turbolinks 214 | uglifier 215 | 216 | BUNDLED WITH 217 | 1.17.2 218 | --------------------------------------------------------------------------------