migration_signature 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 21b9a40066ce68440305cfca161d1f774d80be846ea19ea2938df5612e7c32cc
4
+ data.tar.gz: 03713b084e7381e46b17bcbfd67a7f3ca8647a1b8ef363d7a9862d40e999c3f7
5
+ SHA512:
6
+ metadata.gz: ac6c41cbc16270c4823ff650bfc6b71310bfbf35fc87755fd8acfff2dac71433bec24cd57978f38b4d4a9100ceeff8785c0107f3617c0e0edd1430e9857dd015
7
+ data.tar.gz: 7db7de487a64a60bb53a815d6b00b05690a2673178d9d02c7487a0157a00d695c5b1cba2d574f83dae0d37b08392d7e3ed87beb39f96f8b629e5f3463819ed10
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in migration_signature.gemspec
6
+ gemspec
7
+
8
+ gem 'rubocop'
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'migration_signature'
5
+ require 'optparse'
6
+
7
+ OptionParser.new do |parser|
8
+ parser.banner = <<~BANNER
9
+ Usage: migration_signature {check|buildall|build <file>}
10
+
11
+ check - Check all your migration files for present and correct migration signatures
12
+ buildall - Rebuild all migration signatures for all files (should only be used in setup)
13
+ build <file> - Rebuild a migration signature for a single migration
14
+
15
+ BANNER
16
+
17
+ parser.on('-h', '--help', 'Prints this help') do
18
+ puts parser
19
+ exit
20
+ end
21
+ end.parse!
22
+
23
+ action = ARGV[0]
24
+ unless action
25
+ warn 'You must supply one of {check|buildall|build <file>}'
26
+ exit 1
27
+ end
28
+
29
+ if action == 'check'
30
+ begin
31
+ MigrationSignature.check_all
32
+ rescue MigrationSignature::InvalidSignatureError => e
33
+ warn e.message
34
+ exit 1
35
+ end
36
+ elsif action == 'buildall'
37
+ MigrationSignature.build_all
38
+ elsif action == 'build'
39
+ file = ARGV[1]
40
+
41
+ unless file
42
+ warn 'You must supply a file to build a signature for'
43
+ exit 1
44
+ end
45
+
46
+ MigrationSignature.build_file(file)
47
+ else
48
+ warn 'You must supply one of {check|buildall|build <file>}'
49
+ exit 1
50
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MigrationSignature
4
+ class Config
5
+ DEFAULTS = {
6
+ 'ignore' => [],
7
+ 'rails_dir' => Dir.pwd,
8
+ 'migration_dir' => 'db/migrate'
9
+ }.freeze
10
+
11
+ CONFIG_FILE_PATH = "#{Dir.pwd}/.migration_signature.yml"
12
+
13
+ def self.load
14
+ return new(DEFAULTS) unless File.exist?(CONFIG_FILE_PATH)
15
+
16
+ require 'yaml'
17
+ hash = YAML.safe_load(File.read(CONFIG_FILE_PATH), [Regexp]) || {}
18
+
19
+ new(DEFAULTS.merge(hash))
20
+ end
21
+
22
+ def initialize(opts = DEFAULTS)
23
+ @opts = opts
24
+ end
25
+
26
+ def all_runnable_files
27
+ @all_runnable_files ||= begin
28
+ require 'pathname'
29
+
30
+ Dir["#{migration_dir}/*"].sort.reject do |f|
31
+ ignore?(f)
32
+ end
33
+ end
34
+ end
35
+
36
+ def migration_dir
37
+ "#{@opts['rails_dir']}/#{@opts['migration_dir']}"
38
+ end
39
+
40
+ def ignore?(file)
41
+ rails_root_file =
42
+ Pathname.new(file).relative_path_from(@opts['rails_dir']).to_s
43
+
44
+ return true if string_ignores.include?(file)
45
+ return true if string_ignores.include?(rails_root_file)
46
+ return true if regexp_ignores.any? { |ignore| ignore =~ file }
47
+ return true if regexp_ignores.any? { |ignore| ignore =~ rails_root_file }
48
+
49
+ false
50
+ end
51
+
52
+ private
53
+
54
+ def regexp_ignores
55
+ @regexp_ignores ||= @opts['ignore'].find_all { |i| i.is_a?(Regexp) }
56
+ end
57
+
58
+ def string_ignores
59
+ @string_ignores ||= @opts['ignore'].reject { |i| i.is_a?(Regexp) }
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MigrationSignature
4
+ class MigrationFile
5
+ MIGRATION_SIG_PREFIX = '# migration_signature: '
6
+
7
+ def self.resolve_full_name(path_or_version)
8
+ path =
9
+ if path_or_version =~ /^\d+$/
10
+ Dir["#{MigrationSignature.config.migration_dir}/#{path_or_version}*"]
11
+ .first
12
+ else
13
+ File.expand_path(path_or_version)
14
+ end
15
+
16
+ return path if path && File.exist?(path)
17
+
18
+ bad_basename = path ? path.split('/').last : path_or_version
19
+ raise("Could not find migration #{bad_basename}")
20
+ end
21
+
22
+ def initialize(path_or_version)
23
+ @path = self.class.resolve_full_name(path_or_version)
24
+ end
25
+
26
+ def ignore?
27
+ MigrationSignature.config.ignore?(@path)
28
+ end
29
+
30
+ def update_signature!
31
+ new_lines = lines.dup
32
+ unless signature?
33
+ new_lines.unshift("\n")
34
+ # add blank line between magic comments and content
35
+ if !new_lines[1].empty? && !new_lines[2].start_with?('#')
36
+ new_lines.unshift("\n")
37
+ end
38
+ end
39
+
40
+ new_lines[signature_line_number || 0] = new_source_signature_line
41
+ write_to_file(new_lines)
42
+ end
43
+
44
+ def validate_signature!
45
+ return true if valid_signature?
46
+
47
+ raise MigrationSignature::InvalidSignatureError,
48
+ 'Missing or invalid migration signature in migration: ' \
49
+ "#{basename}. Please re-run your migration to receive an " \
50
+ 'updated signature.'
51
+ end
52
+
53
+ def basename
54
+ File.basename(path)
55
+ end
56
+
57
+ private
58
+
59
+ attr_reader :path
60
+
61
+ def valid_signature?
62
+ return false unless signature?
63
+
64
+ comment_signature = lines[signature_line_number]
65
+ .sub(MIGRATION_SIG_PREFIX, '')
66
+ .strip
67
+ comment_signature == source_signature
68
+ end
69
+
70
+ def new_source_signature_line
71
+ "#{MIGRATION_SIG_PREFIX}#{source_signature}\n"
72
+ end
73
+
74
+ def source_signature
75
+ Digest::MD5.hexdigest(Parser::CurrentRuby.parse(lines.join).inspect)
76
+ end
77
+
78
+ def signature?
79
+ signature_line_number && signature_line_number > -1
80
+ end
81
+
82
+ def signature_line_number
83
+ lines.index { |l| l.start_with?(MIGRATION_SIG_PREFIX) }
84
+ end
85
+
86
+ def lines
87
+ @lines ||= File.read(path).lines
88
+ end
89
+
90
+ def write_to_file(new_lines)
91
+ File.write(path, new_lines.join)
92
+ remove_instance_variable(:@lines) if defined?(@lines)
93
+ true
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MigrationSignature
4
+ class Railtie < Rails::Railtie
5
+ initializer 'migration_signatures.prepend migrator' do
6
+ ActiveRecord::Migrator.prepend(MigrationSignature::MigratorEnhancement)
7
+ end
8
+ end
9
+
10
+ module MigratorEnhancement
11
+ def run
12
+ res = super
13
+ return res unless @direction == :up
14
+
15
+ MigrationSignature.build_file(@target_version.to_s)
16
+
17
+ puts('Migration signature successfully built')
18
+ res
19
+ end
20
+
21
+ def migrate
22
+ runnable = super
23
+
24
+ return runnable if @direction && @direction != :up
25
+
26
+ runnable.each do |migration|
27
+ MigrationSignature.build_file(migration.filename)
28
+ end
29
+
30
+ puts('Migration signatures successfully built')
31
+ runnable
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MigrationSignature
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'parser/current'
4
+ require 'digest/md5'
5
+ require_relative './migration_signature/config'
6
+ require_relative './migration_signature/migration_file'
7
+ require_relative './migration_signature/railtie' if defined?(Rails)
8
+
9
+ module MigrationSignature
10
+ class InvalidSignatureError < StandardError; end
11
+
12
+ def self.config
13
+ @config ||= MigrationSignature::Config.load
14
+ end
15
+
16
+ def self.check_all
17
+ config.all_runnable_files.each do |path|
18
+ MigrationSignature::MigrationFile.new(path).validate_signature!
19
+ end
20
+
21
+ true
22
+ end
23
+
24
+ def self.build_all
25
+ config.all_runnable_files.each do |path|
26
+ MigrationSignature::MigrationFile.new(path).update_signature!
27
+ end
28
+ end
29
+
30
+ def self.build_file(file)
31
+ mf = MigrationSignature::MigrationFile.new(file)
32
+
33
+ if mf.ignore?
34
+ warn "Tried to build signature for #{mf.basename}, but it is ignored."
35
+ return
36
+ end
37
+
38
+ mf.update_signature!
39
+ end
40
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'migration_signature/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'migration_signature'
9
+ spec.version = MigrationSignature::VERSION
10
+ spec.authors = ['Dave Allie']
11
+ spec.email = ['dave@tanda.co']
12
+
13
+ spec.summary = 'Generate signatures for migration files when they ' \
14
+ 'are run and check them in CI.'
15
+ spec.description = 'A small Rails utility to ensure all migration files ' \
16
+ 'have been tested before they are committed and run ' \
17
+ 'in production.'
18
+ spec.homepage = 'https://github.com/TandaHQ/migration_signature'
19
+ spec.license = 'MIT'
20
+
21
+ spec.files = Dir['exe/*'] + Dir['lib/**/*'] +
22
+ %w[Gemfile migration_signature.gemspec]
23
+ spec.bindir = 'exe'
24
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
+ spec.require_paths = ['lib']
26
+
27
+ spec.add_dependency 'parser', '>= 2'
28
+
29
+ spec.add_development_dependency 'bundler', '~> 2.0'
30
+ spec.add_development_dependency 'rake', '~> 10.0'
31
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: migration_signature
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Dave Allie
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-06-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: parser
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ description: A small Rails utility to ensure all migration files have been tested
56
+ before they are committed and run in production.
57
+ email:
58
+ - dave@tanda.co
59
+ executables:
60
+ - migration_signature
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - Gemfile
65
+ - exe/migration_signature
66
+ - lib/migration_signature.rb
67
+ - lib/migration_signature/config.rb
68
+ - lib/migration_signature/migration_file.rb
69
+ - lib/migration_signature/railtie.rb
70
+ - lib/migration_signature/version.rb
71
+ - migration_signature.gemspec
72
+ homepage: https://github.com/TandaHQ/migration_signature
73
+ licenses:
74
+ - MIT
75
+ metadata: {}
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubyforge_project:
92
+ rubygems_version: 2.7.9
93
+ signing_key:
94
+ specification_version: 4
95
+ summary: Generate signatures for migration files when they are run and check them
96
+ in CI.
97
+ test_files: []