rails-worktrees 0.3.0 → 0.5.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.
@@ -0,0 +1,326 @@
1
+ module Rails
2
+ module Worktrees
3
+ # Audits and prepares safe file-based maintenance updates for the current checkout.
4
+ # rubocop:disable Metrics/ClassLength, Metrics/MethodLength, Metrics/AbcSize
5
+ class ProjectMaintenance
6
+ # rubocop:disable Style/RedundantStructKeywordInit
7
+ Check = Struct.new(
8
+ :identifier,
9
+ :category,
10
+ :headline,
11
+ :messages,
12
+ :relative_path,
13
+ :status,
14
+ :updated_content,
15
+ :make_executable,
16
+ :apply_messages,
17
+ keyword_init: true
18
+ ) do
19
+ def ok?
20
+ status == :ok
21
+ end
22
+
23
+ def fixable?
24
+ status == :fixable
25
+ end
26
+
27
+ def warning?
28
+ status == :warning
29
+ end
30
+
31
+ def updatable?
32
+ !updated_content.nil?
33
+ end
34
+ end
35
+
36
+ Report = Struct.new(:checks, keyword_init: true) do
37
+ def fixable_checks
38
+ checks.select(&:fixable?)
39
+ end
40
+
41
+ def warning_checks
42
+ checks.select(&:warning?)
43
+ end
44
+
45
+ def ok?
46
+ fixable_checks.empty? && warning_checks.empty?
47
+ end
48
+ end
49
+ # rubocop:enable Style/RedundantStructKeywordInit
50
+
51
+ TEMPLATE_ROOT = File.expand_path('../../generators/rails/worktrees/templates', __dir__)
52
+
53
+ def initialize(root:)
54
+ @root = root
55
+ end
56
+
57
+ def call
58
+ Report.new(checks: maintenance_checks.compact)
59
+ end
60
+
61
+ private
62
+
63
+ attr_reader :root
64
+
65
+ def maintenance_checks
66
+ [
67
+ template_file_check(wrapper_config),
68
+ initializer_check,
69
+ database_check,
70
+ optional_updater_check(procfile_config) { |content| ProcfileUpdater.new(content: content).call },
71
+ optional_updater_check(puma_config) { |content| PumaConfigUpdater.new(content: content).call },
72
+ mise_check,
73
+ template_file_check(browser_wrapper_config)
74
+ ]
75
+ end
76
+
77
+ def wrapper_config
78
+ {
79
+ identifier: :bin_wt,
80
+ category: :install,
81
+ relative_path: 'bin/wt',
82
+ template_path: File.join(TEMPLATE_ROOT, 'bin/wt'),
83
+ make_executable: true,
84
+ optional: false
85
+ }
86
+ end
87
+
88
+ def browser_wrapper_config
89
+ {
90
+ identifier: :bin_ob,
91
+ category: :install,
92
+ relative_path: 'bin/ob',
93
+ template_path: File.join(TEMPLATE_ROOT, 'bin/ob'),
94
+ make_executable: true,
95
+ optional: true
96
+ }
97
+ end
98
+
99
+ def template_file_check(config)
100
+ path = absolute_path(config.fetch(:relative_path))
101
+ return nil if config.fetch(:optional) && !File.exist?(path)
102
+
103
+ desired_content = File.read(config.fetch(:template_path))
104
+ return template_missing_check(config, desired_content) unless File.exist?(path)
105
+
106
+ current_content = File.read(path)
107
+ if current_content == desired_content
108
+ if executable_mode_current?(config, path)
109
+ return ok_check(config, "#{config.fetch(:relative_path)} is up to date.")
110
+ end
111
+
112
+ return executable_permission_check(config, desired_content)
113
+ end
114
+
115
+ fixable_check(
116
+ config,
117
+ "#{config.fetch(:relative_path)} differs from the managed template.",
118
+ updated_content: desired_content,
119
+ apply_messages: ["Updated #{config.fetch(:relative_path)} to match the managed template."],
120
+ make_executable: config.fetch(:make_executable, false)
121
+ )
122
+ end
123
+
124
+ def executable_mode_current?(config, path)
125
+ !config.fetch(:make_executable, false) || File.executable?(path)
126
+ end
127
+
128
+ def executable_permission_check(config, desired_content)
129
+ relative_path = config.fetch(:relative_path)
130
+ fixable_check(
131
+ config,
132
+ "#{relative_path} needs its executable bit restored.",
133
+ updated_content: desired_content,
134
+ apply_messages: ["Restored executable permissions on #{relative_path}."],
135
+ make_executable: true
136
+ )
137
+ end
138
+
139
+ def template_missing_check(config, desired_content)
140
+ fixable_check(
141
+ config,
142
+ "#{config.fetch(:relative_path)} is missing.",
143
+ updated_content: desired_content,
144
+ apply_messages: ["Created #{config.fetch(:relative_path)} from the managed template."],
145
+ make_executable: config.fetch(:make_executable, false)
146
+ )
147
+ end
148
+
149
+ def initializer_check
150
+ config = {
151
+ identifier: :initializer,
152
+ category: :install,
153
+ relative_path: 'config/initializers/rails_worktrees.rb',
154
+ identical_headline: 'config/initializers/rails_worktrees.rb already uses the current safety guard.',
155
+ fixable_headline: 'config/initializers/rails_worktrees.rb can be updated automatically.',
156
+ warning_headline: 'config/initializers/rails_worktrees.rb needs manual review.'
157
+ }
158
+ path = absolute_path(config.fetch(:relative_path))
159
+
160
+ unless File.exist?(path)
161
+ return fixable_check(
162
+ config,
163
+ "#{config.fetch(:relative_path)} is missing.",
164
+ updated_content: InitializerUpdater.default_content,
165
+ apply_messages: ['Created config/initializers/rails_worktrees.rb with the current safety guard.']
166
+ )
167
+ end
168
+
169
+ updater_result_check(config, InitializerUpdater.new(content: File.read(path)).call)
170
+ end
171
+
172
+ def database_check
173
+ config = {
174
+ identifier: :database,
175
+ category: :config,
176
+ relative_path: 'config/database.yml'
177
+ }
178
+ path = absolute_path(config.fetch(:relative_path))
179
+
180
+ unless File.exist?(path)
181
+ return warning_check(
182
+ config,
183
+ 'config/database.yml is missing.',
184
+ ['Add WORKTREE_DATABASE_SUFFIX support manually if this app uses a custom database setup.']
185
+ )
186
+ end
187
+
188
+ result = DatabaseConfigUpdater.new(content: File.read(path)).call
189
+ return updated_database_check(config, result) if result.changed?
190
+
191
+ if database_configured?(result)
192
+ return ok_check(
193
+ config,
194
+ 'config/database.yml already includes WORKTREE_DATABASE_SUFFIX in supported entries.'
195
+ )
196
+ end
197
+
198
+ warning_check(
199
+ config,
200
+ result.messages.first || 'config/database.yml needs manual review.',
201
+ result.messages.drop(1)
202
+ )
203
+ end
204
+
205
+ def updated_database_check(config, result)
206
+ fixable_check(
207
+ config,
208
+ 'config/database.yml can be updated automatically.',
209
+ messages: result.messages.drop(1),
210
+ updated_content: result.content,
211
+ apply_messages: result.messages
212
+ )
213
+ end
214
+
215
+ def database_configured?(result)
216
+ result.messages.first == 'Development/test database names already include WORKTREE_DATABASE_SUFFIX.'
217
+ end
218
+
219
+ def procfile_config
220
+ {
221
+ identifier: :procfile,
222
+ category: :config,
223
+ relative_path: 'Procfile.dev',
224
+ identical_headline: 'Procfile.dev already uses the DEV_PORT-aware web entry.',
225
+ fixable_headline: 'Procfile.dev can be updated automatically.',
226
+ warning_headline: 'Procfile.dev needs manual review.'
227
+ }
228
+ end
229
+
230
+ def puma_config
231
+ {
232
+ identifier: :puma,
233
+ category: :config,
234
+ relative_path: 'config/puma.rb',
235
+ identical_headline: 'config/puma.rb already prefers DEV_PORT.',
236
+ fixable_headline: 'config/puma.rb can be updated automatically.',
237
+ warning_headline: 'config/puma.rb needs manual review.'
238
+ }
239
+ end
240
+
241
+ def optional_updater_check(config)
242
+ path = absolute_path(config.fetch(:relative_path))
243
+ return unless File.exist?(path)
244
+
245
+ updater_result_check(config, yield(File.read(path)))
246
+ end
247
+
248
+ def mise_check
249
+ relative_path = %w[mise.toml .mise.toml].find { |candidate| File.exist?(absolute_path(candidate)) }
250
+ return unless relative_path
251
+
252
+ config = {
253
+ identifier: :mise,
254
+ category: :config,
255
+ relative_path: relative_path,
256
+ identical_headline: "#{relative_path} already loads .env from [env].",
257
+ fixable_headline: "#{relative_path} can be updated automatically.",
258
+ warning_headline: "#{relative_path} needs manual review."
259
+ }
260
+
261
+ updater_result_check(config, mise_updater_result(relative_path))
262
+ end
263
+
264
+ def mise_updater_result(relative_path)
265
+ MiseTomlUpdater.new(
266
+ content: File.read(absolute_path(relative_path)),
267
+ file_name: File.basename(relative_path)
268
+ ).call
269
+ end
270
+
271
+ def updater_result_check(config, result)
272
+ case result.status
273
+ when :identical
274
+ ok_check(config, config.fetch(:identical_headline))
275
+ when :updated
276
+ fixable_check(
277
+ config,
278
+ config.fetch(:fixable_headline),
279
+ updated_content: result.content,
280
+ apply_messages: result.messages
281
+ )
282
+ else
283
+ warning_check(config, config.fetch(:warning_headline), result.messages)
284
+ end
285
+ end
286
+
287
+ def ok_check(config, headline)
288
+ build_check(config, status: :ok, headline: headline)
289
+ end
290
+
291
+ def warning_check(config, headline, messages = [])
292
+ build_check(config, status: :warning, headline: headline, messages: messages)
293
+ end
294
+
295
+ def fixable_check(config, headline, updated_content:, apply_messages:, **attributes)
296
+ build_check(
297
+ config,
298
+ status: :fixable,
299
+ headline: headline,
300
+ updated_content: updated_content,
301
+ apply_messages: apply_messages,
302
+ **attributes
303
+ )
304
+ end
305
+
306
+ def build_check(config, status:, headline:, **attributes)
307
+ Check.new(
308
+ identifier: config.fetch(:identifier),
309
+ category: config.fetch(:category),
310
+ relative_path: config.fetch(:relative_path),
311
+ status: status,
312
+ headline: headline,
313
+ messages: attributes.fetch(:messages, []),
314
+ updated_content: attributes.fetch(:updated_content, nil),
315
+ make_executable: attributes.fetch(:make_executable, false),
316
+ apply_messages: attributes.fetch(:apply_messages, [])
317
+ )
318
+ end
319
+
320
+ def absolute_path(relative_path)
321
+ File.join(root, relative_path)
322
+ end
323
+ end
324
+ # rubocop:enable Metrics/ClassLength, Metrics/MethodLength, Metrics/AbcSize
325
+ end
326
+ end
@@ -1,5 +1,5 @@
1
1
  module Rails
2
2
  module Worktrees
3
- VERSION = '0.3.0'.freeze
3
+ VERSION = '0.5.0'.freeze
4
4
  end
5
5
  end
@@ -7,9 +7,11 @@ require_relative 'worktrees/command'
7
7
  require_relative 'worktrees/cli'
8
8
  require_relative 'worktrees/browser_command'
9
9
  require_relative 'worktrees/database_config_updater'
10
+ require_relative 'worktrees/initializer_updater'
10
11
  require_relative 'worktrees/procfile_updater'
11
12
  require_relative 'worktrees/mise_toml_updater'
12
13
  require_relative 'worktrees/puma_config_updater'
14
+ require_relative 'worktrees/project_maintenance'
13
15
 
14
16
  module Rails
15
17
  # Rails-specific git worktree helpers and installer support.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-worktrees
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Asjer Querido
@@ -66,9 +66,11 @@ files:
66
66
  - lib/rails/worktrees/configuration.rb
67
67
  - lib/rails/worktrees/database_config_updater.rb
68
68
  - lib/rails/worktrees/env_bootstrapper.rb
69
+ - lib/rails/worktrees/initializer_updater.rb
69
70
  - lib/rails/worktrees/mise_toml_updater.rb
70
71
  - lib/rails/worktrees/names/cities.txt
71
72
  - lib/rails/worktrees/procfile_updater.rb
73
+ - lib/rails/worktrees/project_maintenance.rb
72
74
  - lib/rails/worktrees/puma_config_updater.rb
73
75
  - lib/rails/worktrees/railtie.rb
74
76
  - lib/rails/worktrees/version.rb