nandi 1.0.1 → 2.0.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/README.md +152 -3
- data/lib/generators/nandi/check_constraint/USAGE +9 -0
- data/lib/generators/nandi/check_constraint/check_constraint_generator.rb +2 -4
- data/lib/generators/nandi/compile/USAGE +14 -2
- data/lib/generators/nandi/compile/compile_generator.rb +27 -24
- data/lib/generators/nandi/foreign_key/USAGE +9 -0
- data/lib/generators/nandi/foreign_key/foreign_key_generator.rb +2 -4
- data/lib/generators/nandi/index/USAGE +9 -0
- data/lib/generators/nandi/index/index_generator.rb +2 -4
- data/lib/generators/nandi/migration/USAGE +12 -2
- data/lib/generators/nandi/migration/migration_generator.rb +3 -6
- data/lib/generators/nandi/not_null_check/USAGE +9 -0
- data/lib/generators/nandi/not_null_check/not_null_check_generator.rb +2 -4
- data/lib/nandi/compiled_migration.rb +17 -9
- data/lib/nandi/config.rb +69 -64
- data/lib/nandi/lockfile.rb +74 -54
- data/lib/nandi/migration_violations.rb +110 -0
- data/lib/nandi/multi_database.rb +178 -0
- data/lib/nandi/multi_db_generator.rb +23 -0
- data/lib/nandi/safe_migration_enforcer.rb +78 -82
- data/lib/nandi/version.rb +1 -1
- data/lib/nandi.rb +3 -6
- metadata +5 -2
@@ -7,6 +7,7 @@ require "rails/generators"
|
|
7
7
|
require "nandi/file_diff"
|
8
8
|
require "nandi/file_matcher"
|
9
9
|
require "nandi/lockfile"
|
10
|
+
require "nandi/migration_violations"
|
10
11
|
|
11
12
|
module Nandi
|
12
13
|
class SafeMigrationEnforcer
|
@@ -16,128 +17,123 @@ module Nandi
|
|
16
17
|
DEFAULT_AR_MIGRATION_DIR = "db/migrate"
|
17
18
|
DEFAULT_FILE_SPEC = "all"
|
18
19
|
|
20
|
+
attr_reader :violations
|
21
|
+
|
19
22
|
def initialize(require_path: nil,
|
20
23
|
safe_migration_dir: DEFAULT_SAFE_MIGRATION_DIR,
|
21
24
|
ar_migration_dir: DEFAULT_AR_MIGRATION_DIR,
|
22
25
|
files: DEFAULT_FILE_SPEC)
|
23
|
-
@safe_migration_dir = safe_migration_dir
|
24
|
-
@ar_migration_dir = ar_migration_dir
|
25
26
|
@files = files
|
26
27
|
|
27
28
|
require require_path unless require_path.nil?
|
28
29
|
|
29
|
-
|
30
|
-
|
31
|
-
c.output_directory = @ar_migration_dir
|
32
|
-
end
|
30
|
+
configure_legacy_mode_if_needed(safe_migration_dir, ar_migration_dir)
|
31
|
+
@violations = MigrationViolations.new
|
33
32
|
end
|
34
33
|
|
35
34
|
def run
|
36
|
-
|
37
|
-
ar_migrations = matching_migrations(@ar_migration_dir)
|
35
|
+
collect_violations
|
38
36
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
enforce_no_hand_written_migrations!(safe_migrations, ar_migrations)
|
43
|
-
enforce_no_hand_edited_migrations!(ar_migrations)
|
44
|
-
enforce_no_out_of_date_migrations!(safe_migrations)
|
37
|
+
if violations.any?
|
38
|
+
raise MigrationLintingFailed, violations.to_error_message
|
39
|
+
end
|
45
40
|
|
46
41
|
true
|
47
42
|
end
|
48
43
|
|
49
44
|
private
|
50
45
|
|
51
|
-
def
|
52
|
-
|
53
|
-
|
54
|
-
end
|
55
|
-
|
56
|
-
def enforce_no_ungenerated_migrations!(safe_migrations, ar_migrations)
|
57
|
-
ungenerated_migrations = safe_migrations - ar_migrations
|
58
|
-
if ungenerated_migrations.any?
|
59
|
-
error = <<~ERROR
|
60
|
-
The following migrations are pending generation:
|
46
|
+
def configure_legacy_mode_if_needed(safe_dir, ar_dir)
|
47
|
+
legacy_mode = safe_dir != DEFAULT_SAFE_MIGRATION_DIR ||
|
48
|
+
ar_dir != DEFAULT_AR_MIGRATION_DIR
|
61
49
|
|
62
|
-
|
50
|
+
return unless legacy_mode
|
63
51
|
|
64
|
-
|
65
|
-
|
52
|
+
Nandi.configure do |c|
|
53
|
+
c.migration_directory = safe_dir
|
54
|
+
c.output_directory = ar_dir
|
55
|
+
end
|
56
|
+
end
|
66
57
|
|
67
|
-
|
58
|
+
def collect_violations
|
59
|
+
Nandi.config.databases.each do |_, database|
|
60
|
+
check_database_violations(database)
|
68
61
|
end
|
69
62
|
end
|
70
63
|
|
71
|
-
def
|
72
|
-
|
73
|
-
|
64
|
+
def check_database_violations(database)
|
65
|
+
safe_migrations = matching_migrations(database.migration_directory)
|
66
|
+
ar_migrations = matching_migrations(database.output_directory)
|
74
67
|
|
75
|
-
|
76
|
-
|
77
|
-
|
68
|
+
# Check ungenerated migrations and get remaining files
|
69
|
+
remaining_safe, remaining_ar = check_ungenerated_migrations(
|
70
|
+
safe_migrations, ar_migrations, database
|
71
|
+
)
|
78
72
|
|
79
|
-
|
73
|
+
# Check handwritten migrations and get remaining files
|
74
|
+
remaining_safe, remaining_ar = check_handwritten_migrations(
|
75
|
+
remaining_safe, remaining_ar, database
|
76
|
+
)
|
80
77
|
|
81
|
-
|
82
|
-
|
83
|
-
require additional review that will slow your PR down.
|
84
|
-
ERROR
|
78
|
+
# Check out-of-date migrations
|
79
|
+
check_out_of_date_migrations(remaining_safe, database)
|
85
80
|
|
86
|
-
|
87
|
-
|
81
|
+
# Check hand-edited migrations
|
82
|
+
check_hand_edited_migrations(remaining_ar, database)
|
88
83
|
end
|
89
84
|
|
90
|
-
def
|
91
|
-
|
92
|
-
|
93
|
-
select do |filename, digests|
|
94
|
-
Nandi::FileDiff.new(
|
95
|
-
file_path: File.join(@safe_migration_dir, filename),
|
96
|
-
known_digest: digests[:source_digest],
|
97
|
-
).changed?
|
98
|
-
end
|
99
|
-
|
100
|
-
if out_of_date_migrations.any?
|
101
|
-
error = <<~ERROR
|
102
|
-
The following migrations have changed but not been recompiled:
|
103
|
-
|
104
|
-
- #{out_of_date_migrations.sort.join("\n - ")}
|
85
|
+
def check_ungenerated_migrations(safe_migrations, ar_migrations, database)
|
86
|
+
missing_files = safe_migrations - ar_migrations
|
87
|
+
violations.add_ungenerated(missing_files, database.migration_directory)
|
105
88
|
|
106
|
-
|
107
|
-
|
108
|
-
ERROR
|
109
|
-
|
110
|
-
raise MigrationLintingFailed, error
|
111
|
-
end
|
89
|
+
# Return remaining files after removing processed ones
|
90
|
+
[safe_migrations - missing_files, ar_migrations]
|
112
91
|
end
|
113
92
|
|
114
|
-
def
|
115
|
-
|
116
|
-
|
117
|
-
select do |filename, digests|
|
118
|
-
Nandi::FileDiff.new(
|
119
|
-
file_path: File.join(@ar_migration_dir, filename),
|
120
|
-
known_digest: digests[:compiled_digest],
|
121
|
-
).changed?
|
122
|
-
end
|
93
|
+
def check_handwritten_migrations(safe_migrations, ar_migrations, database)
|
94
|
+
handwritten_files = ar_migrations - safe_migrations
|
95
|
+
violations.add_handwritten(handwritten_files, database.output_directory)
|
123
96
|
|
124
|
-
|
125
|
-
|
126
|
-
|
97
|
+
# Return remaining files after removing processed ones
|
98
|
+
[safe_migrations, ar_migrations - handwritten_files]
|
99
|
+
end
|
127
100
|
|
128
|
-
|
101
|
+
def check_out_of_date_migrations(safe_migrations, database)
|
102
|
+
out_of_date_files = find_changed_files(
|
103
|
+
safe_migrations,
|
104
|
+
database,
|
105
|
+
:source_digest,
|
106
|
+
database.migration_directory,
|
107
|
+
)
|
108
|
+
violations.add_out_of_date(out_of_date_files, database.migration_directory)
|
109
|
+
end
|
129
110
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
111
|
+
def check_hand_edited_migrations(ar_migrations, database)
|
112
|
+
hand_edited_files = find_changed_files(
|
113
|
+
ar_migrations,
|
114
|
+
database,
|
115
|
+
:compiled_digest,
|
116
|
+
database.output_directory,
|
117
|
+
)
|
118
|
+
violations.add_hand_edited(hand_edited_files, database.output_directory)
|
119
|
+
end
|
134
120
|
|
135
|
-
|
121
|
+
def find_changed_files(filenames, database, digest_key, directory)
|
122
|
+
filenames.filter_map do |filename|
|
123
|
+
digests = Nandi::Lockfile.for(database.name).get(filename)
|
124
|
+
file_diff = Nandi::FileDiff.new(
|
125
|
+
file_path: File.join(directory, filename),
|
126
|
+
known_digest: digests[digest_key],
|
127
|
+
)
|
128
|
+
filename if file_diff.changed?
|
136
129
|
end
|
137
130
|
end
|
138
131
|
|
139
|
-
def
|
140
|
-
|
132
|
+
def matching_migrations(directory)
|
133
|
+
return Set.new unless Dir.exist?(directory)
|
134
|
+
|
135
|
+
filenames = Dir.glob(File.join(directory, "*.rb")).map { |path| File.basename(path) }
|
136
|
+
FileMatcher.call(files: filenames, spec: @files)
|
141
137
|
end
|
142
138
|
end
|
143
139
|
end
|
data/lib/nandi/version.rb
CHANGED
data/lib/nandi.rb
CHANGED
@@ -9,15 +9,16 @@ module Nandi
|
|
9
9
|
class Error < StandardError; end
|
10
10
|
|
11
11
|
class << self
|
12
|
-
def compile(files:)
|
12
|
+
def compile(files:, db_name: nil)
|
13
13
|
compiled = files.
|
14
|
-
map { |f| CompiledMigration.build(f) }
|
14
|
+
map { |f| CompiledMigration.build(file_name: f, db_name: db_name) }
|
15
15
|
|
16
16
|
yield compiled
|
17
17
|
end
|
18
18
|
|
19
19
|
def configure
|
20
20
|
yield config
|
21
|
+
config.validate!
|
21
22
|
end
|
22
23
|
|
23
24
|
def validator
|
@@ -27,9 +28,5 @@ module Nandi
|
|
27
28
|
def config
|
28
29
|
@config ||= Config.new
|
29
30
|
end
|
30
|
-
|
31
|
-
def compiled_output_directory
|
32
|
-
Nandi.config.output_directory || "db/migrate"
|
33
|
-
end
|
34
31
|
end
|
35
32
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nandi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GoCardless Engineering
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-09-25 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: activesupport
|
@@ -120,6 +120,9 @@ files:
|
|
120
120
|
- lib/nandi/instructions/validate_constraint.rb
|
121
121
|
- lib/nandi/lockfile.rb
|
122
122
|
- lib/nandi/migration.rb
|
123
|
+
- lib/nandi/migration_violations.rb
|
124
|
+
- lib/nandi/multi_database.rb
|
125
|
+
- lib/nandi/multi_db_generator.rb
|
123
126
|
- lib/nandi/renderers.rb
|
124
127
|
- lib/nandi/renderers/active_record.rb
|
125
128
|
- lib/nandi/renderers/active_record/generate.rb
|