migration_signature 0.1.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 +7 -0
- data/Gemfile +8 -0
- data/exe/migration_signature +50 -0
- data/lib/migration_signature/config.rb +62 -0
- data/lib/migration_signature/migration_file.rb +96 -0
- data/lib/migration_signature/railtie.rb +34 -0
- data/lib/migration_signature/version.rb +5 -0
- data/lib/migration_signature.rb +40 -0
- data/migration_signature.gemspec +31 -0
- metadata +97 -0
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,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,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: []
|