rails_engine_toolkit 0.6.3

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.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +54 -0
  3. data/.github/workflows/release.yml +22 -0
  4. data/Gemfile +11 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +83 -0
  7. data/Rakefile +7 -0
  8. data/docs/ARCHITECTURE.md +18 -0
  9. data/docs/COMPATIBILITY.md +18 -0
  10. data/docs/CONTRIBUTING.md +26 -0
  11. data/docs/END_TO_END.md +26 -0
  12. data/docs/PUBLISHING.md +24 -0
  13. data/docs/RELEASE.md +32 -0
  14. data/docs/RELEASE_CHECKLIST.md +49 -0
  15. data/docs/TESTING.md +21 -0
  16. data/exe/engine-toolkit +6 -0
  17. data/lib/rails_engine_toolkit/actions/delete_engine_migration.rb +46 -0
  18. data/lib/rails_engine_toolkit/actions/init.rb +54 -0
  19. data/lib/rails_engine_toolkit/actions/install_engine_migrations.rb +83 -0
  20. data/lib/rails_engine_toolkit/actions/new_engine.rb +127 -0
  21. data/lib/rails_engine_toolkit/actions/new_engine_migration.rb +30 -0
  22. data/lib/rails_engine_toolkit/actions/new_engine_model.rb +30 -0
  23. data/lib/rails_engine_toolkit/actions/remove_engine.rb +78 -0
  24. data/lib/rails_engine_toolkit/actions/uninstall_engine_migrations.rb +79 -0
  25. data/lib/rails_engine_toolkit/actions/update_engine_readme.rb +60 -0
  26. data/lib/rails_engine_toolkit/cli.rb +110 -0
  27. data/lib/rails_engine_toolkit/config.rb +98 -0
  28. data/lib/rails_engine_toolkit/errors.rb +7 -0
  29. data/lib/rails_engine_toolkit/file_editor.rb +40 -0
  30. data/lib/rails_engine_toolkit/generators/install/install_generator.rb +67 -0
  31. data/lib/rails_engine_toolkit/project.rb +71 -0
  32. data/lib/rails_engine_toolkit/railtie.rb +9 -0
  33. data/lib/rails_engine_toolkit/route_inspector.rb +44 -0
  34. data/lib/rails_engine_toolkit/routes_rewriter.rb +63 -0
  35. data/lib/rails_engine_toolkit/templates/engine_readme.erb +37 -0
  36. data/lib/rails_engine_toolkit/templates/engine_toolkit_yml.erb +35 -0
  37. data/lib/rails_engine_toolkit/templates/gemspec.erb +25 -0
  38. data/lib/rails_engine_toolkit/templates/license.erb +3 -0
  39. data/lib/rails_engine_toolkit/templates.rb +12 -0
  40. data/lib/rails_engine_toolkit/utils.rb +56 -0
  41. data/lib/rails_engine_toolkit/version.rb +5 -0
  42. data/lib/rails_engine_toolkit.rb +33 -0
  43. data/rails_engine_toolkit.gemspec +31 -0
  44. data/spec/rails_engine_toolkit/cli_spec.rb +11 -0
  45. data/spec/rails_engine_toolkit/config_spec.rb +52 -0
  46. data/spec/rails_engine_toolkit/file_editor_spec.rb +26 -0
  47. data/spec/rails_engine_toolkit/install_engine_migrations_spec.rb +36 -0
  48. data/spec/rails_engine_toolkit/install_generator_spec.rb +26 -0
  49. data/spec/rails_engine_toolkit/new_engine_integration_spec.rb +59 -0
  50. data/spec/rails_engine_toolkit/new_engine_spec.rb +54 -0
  51. data/spec/rails_engine_toolkit/project_spec.rb +19 -0
  52. data/spec/rails_engine_toolkit/remove_engine_integration_spec.rb +40 -0
  53. data/spec/rails_engine_toolkit/remove_engine_spec.rb +72 -0
  54. data/spec/rails_engine_toolkit/route_inspector_spec.rb +20 -0
  55. data/spec/rails_engine_toolkit/routes_rewriter_spec.rb +36 -0
  56. data/spec/rails_engine_toolkit/uninstall_engine_migrations_spec.rb +35 -0
  57. data/spec/rails_engine_toolkit/update_engine_readme_spec.rb +32 -0
  58. data/spec/spec_helper.rb +30 -0
  59. data/test/fixtures/host_app/Gemfile +5 -0
  60. data/test/fixtures/host_app/config/application.rb +11 -0
  61. data/test/fixtures/host_app/config/boot.rb +3 -0
  62. data/test/fixtures/host_app/config/environment.rb +3 -0
  63. data/test/fixtures/host_app/config/routes.rb +2 -0
  64. metadata +140 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2daf757686e083799345227ffbacbb0fe9d53915f764a82320049f23202aab1f
4
+ data.tar.gz: d0e663fa492f7f6f2992cd0b2f9ad7ec6e80c1c772baf385f4537410461b407e
5
+ SHA512:
6
+ metadata.gz: b03b80e939f4e62b57471509c80241b4e7ca2157f8db29a6c7ae8b38e79df651633839bd50135546234418076b9d89f3e1c4de17a58b490372b54a81a440915c
7
+ data.tar.gz: c537ed36c0a79ac052eadeb992f38ddb5fcb526d5f057f855708feeee988b30188e704ed95f7a59318fd5742ec70e03b995d4230ac13b899092e8c9ef461e183
@@ -0,0 +1,54 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ rspec:
9
+ runs-on: ubuntu-latest
10
+ strategy:
11
+ fail-fast: false
12
+ matrix:
13
+ ruby: ["3.2", "3.3"]
14
+ rails: ["8.1.2"]
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: ruby/setup-ruby@v1
18
+ with:
19
+ ruby-version: ${{ matrix.ruby }}
20
+ bundler-cache: true
21
+ - run: bundle install
22
+ - run: bundle exec rspec
23
+ - run: bundle exec rubocop
24
+
25
+ host-app-smoke:
26
+ runs-on: ubuntu-latest
27
+ strategy:
28
+ fail-fast: false
29
+ matrix:
30
+ ruby: ["3.2", "3.3"]
31
+ rails: ["8.1.2"]
32
+ steps:
33
+ - uses: actions/checkout@v4
34
+ - uses: ruby/setup-ruby@v1
35
+ with:
36
+ ruby-version: ${{ matrix.ruby }}
37
+ - run: gem install rails -v ${{ matrix.rails }}
38
+ - run: bundle install
39
+ - run: |
40
+ cp -R test/fixtures/host_app /tmp/host_app
41
+ cd /tmp/host_app
42
+ bundle config set local.rails_engine_toolkit "$GITHUB_WORKSPACE"
43
+ bundle install
44
+ bundle exec rails generate engine_toolkit:install <<'EOF'
45
+
46
+
47
+
48
+
49
+
50
+
51
+ EOF
52
+ bundle exec engine-toolkit new_engine auth <<'EOF'
53
+
54
+ EOF
@@ -0,0 +1,22 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: read
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: ruby/setup-ruby@v1
16
+ with:
17
+ ruby-version: "3.3"
18
+ bundler-cache: true
19
+ - run: gem build rails_engine_toolkit.gemspec
20
+ - run: gem push *.gem
21
+ env:
22
+ RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
6
+
7
+ group :development, :test do
8
+ gem 'rake'
9
+ gem 'rspec', '~> 3.13'
10
+ gem 'rubocop', '~> 1.72', require: false
11
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # Rails Engine Toolkit
2
+
3
+ Reusable tooling for Rails projects that use internal engines.
4
+
5
+ ## What this gem provides
6
+
7
+ - `rails generate engine_toolkit:install`
8
+ - `engine-toolkit init`
9
+ - `engine-toolkit new_engine ENGINE_NAME`
10
+ - `engine-toolkit new_engine_model ENGINE_NAME MODEL_NAME [ATTRS...]`
11
+ - `engine-toolkit new_engine_migration ENGINE_NAME MIGRATION_NAME [ATTRS...]`
12
+ - `engine-toolkit install_engine_migrations ENGINE_NAME`
13
+ - `engine-toolkit uninstall_engine_migrations ENGINE_NAME`
14
+ - `engine-toolkit delete_engine_migration ENGINE_NAME PATTERN`
15
+ - `engine-toolkit update_engine_readme ENGINE_NAME`
16
+ - `engine-toolkit remove_engine ENGINE_NAME`
17
+
18
+ ## What is new in v6.3
19
+
20
+ - RuboCop cleanup and sane project-level RuboCop configuration
21
+ - action classes split into smaller private helpers where useful
22
+ - long lines and unused arguments fixed
23
+ - packaging polish for a cleaner public repository
24
+
25
+ ## Installation in a host Rails app
26
+
27
+ ```ruby
28
+ gem "rails_engine_toolkit"
29
+ ```
30
+
31
+ Then:
32
+
33
+ ```bash
34
+ bundle install
35
+ bin/rails generate engine_toolkit:install
36
+ ```
37
+
38
+ The install generator creates:
39
+
40
+ ```text
41
+ config/engine_toolkit.yml
42
+ ```
43
+
44
+ and prints:
45
+
46
+ - the generated default configuration
47
+ - the full path of the file you should edit
48
+
49
+ ## CLI usage
50
+
51
+ ```bash
52
+ bundle exec engine-toolkit new_engine auth
53
+ bundle exec engine-toolkit new_engine_model auth credential email:string
54
+ bundle exec engine-toolkit new_engine_migration auth CreateCredentials
55
+ bundle exec engine-toolkit install_engine_migrations auth
56
+ bundle exec engine-toolkit uninstall_engine_migrations auth
57
+ bundle exec engine-toolkit update_engine_readme auth
58
+ bundle exec engine-toolkit delete_engine_migration auth create_credentials
59
+ bundle exec engine-toolkit remove_engine auth
60
+ ```
61
+
62
+ ## Development
63
+
64
+ ```bash
65
+ bundle install
66
+ bundle exec rspec
67
+ bundle exec rubocop
68
+ ```
69
+
70
+ ## Publishing to RubyGems
71
+
72
+ ```bash
73
+ gem build rails_engine_toolkit.gemspec
74
+ gem push rails_engine_toolkit-0.6.3.gem
75
+ ```
76
+
77
+ After it is published, consumers can install it with:
78
+
79
+ ```ruby
80
+ gem "rails_engine_toolkit"
81
+ ```
82
+
83
+ without `github:` or `path:`.
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task default: :spec
@@ -0,0 +1,18 @@
1
+ # Architecture
2
+
3
+ ## Main components
4
+
5
+ - `CLI`: command entrypoint powered by Thor
6
+ - `Project`: host app path and mutation helper
7
+ - `Config`: validated project configuration loader
8
+ - `FileEditor`: small safe file mutation helpers
9
+ - `Templates`: ERB renderer for generated content
10
+ - `Actions::*`: business operations
11
+
12
+ ## Rails integration
13
+
14
+ The gem ships a Railtie and a generator:
15
+
16
+ - `rails generate engine_toolkit:install`
17
+
18
+ That keeps the install experience close to tools such as `rspec:install`. The install generator creates the configuration file and prints a summary plus the exact path to edit.
@@ -0,0 +1,18 @@
1
+ # Compatibility strategy
2
+
3
+ ## Rails support
4
+
5
+ The gem is intended to support Rails 8.1.x first.
6
+
7
+ ## Verification approach
8
+
9
+ Compatibility is checked at four levels:
10
+
11
+ 1. unit specs
12
+ 2. integration-style specs on temporary directories
13
+ 3. install generator smoke test in a dummy host app fixture
14
+ 4. CI matrix across supported Ruby and Rails versions
15
+
16
+ ## Important limitation
17
+
18
+ A true dynamic matrix that creates full temporary Rails apps at runtime is possible, but is best executed in CI rather than lightweight local scaffolding environments.
@@ -0,0 +1,26 @@
1
+ # Contributing
2
+
3
+ ## Local setup
4
+
5
+ ```bash
6
+ bundle install
7
+ bundle exec rspec
8
+ bundle exec rubocop
9
+ ```
10
+
11
+ ## Main principles
12
+
13
+ - keep project mutations explicit
14
+ - prefer small action classes
15
+ - keep route handling readable and testable
16
+ - avoid hidden side effects during install and engine generation
17
+ - prefer configuration over hardcoded project metadata
18
+
19
+ ## Test expectations
20
+
21
+ Before opening a pull request, run:
22
+
23
+ ```bash
24
+ bundle exec rspec
25
+ bundle exec rubocop
26
+ ```
@@ -0,0 +1,26 @@
1
+ # End-to-end smoke test
2
+
3
+ A lightweight host app fixture is included under `test/fixtures/host_app`.
4
+
5
+ ## Typical smoke workflow
6
+
7
+ From a temporary Rails app or the fixture app:
8
+
9
+ ```bash
10
+ bundle install
11
+ bin/rails generate engine_toolkit:install
12
+ bundle exec engine-toolkit new_engine auth
13
+ bundle exec engine-toolkit new_engine_model auth credential email:string
14
+ bundle exec engine-toolkit install_engine_migrations auth
15
+ bin/rails db:migrate
16
+ ```
17
+
18
+ ## Migration uninstall note
19
+
20
+ If you later run:
21
+
22
+ ```bash
23
+ bundle exec engine-toolkit uninstall_engine_migrations auth
24
+ ```
25
+
26
+ the toolkit removes copied files from `db/migrate`, but it does **not** rollback the database automatically. Run your down/rollback flow manually first if needed.
@@ -0,0 +1,24 @@
1
+ # Publishing
2
+
3
+ ## Publish to RubyGems
4
+
5
+ Create an account on RubyGems.org, then:
6
+
7
+ ```bash
8
+ gem build rails_engine_toolkit.gemspec
9
+ gem push rails_engine_toolkit-0.6.2.gem
10
+ ```
11
+
12
+ Consumers can then use:
13
+
14
+ ```ruby
15
+ gem "rails_engine_toolkit"
16
+ ```
17
+
18
+ instead of `github:` or `path:`.
19
+
20
+ ## Trusted publishing
21
+
22
+ You can also set up GitHub Actions with trusted publishing so that releases push automatically after tagging.
23
+
24
+ See the RubyGems publishing guide and trusted publishing guide.
data/docs/RELEASE.md ADDED
@@ -0,0 +1,32 @@
1
+ # Release workflow
2
+
3
+ ## Local release
4
+
5
+ 1. Update `lib/rails_engine_toolkit/version.rb`
6
+ 2. Commit your changes
7
+ 3. Tag the release
8
+ 4. Build and push the gem
9
+
10
+ ```bash
11
+ gem build rails_engine_toolkit.gemspec
12
+ gem push rails_engine_toolkit-0.3.0.gem
13
+ git tag v0.3.0
14
+ git push origin v0.3.0
15
+ ```
16
+
17
+ ## GitHub Actions release
18
+
19
+ This repository includes a RubyGems release workflow template.
20
+
21
+ ### Required secret
22
+
23
+ - `RUBYGEMS_API_KEY`
24
+
25
+ ### Trigger
26
+
27
+ Push a version tag such as:
28
+
29
+ ```bash
30
+ git tag v0.3.0
31
+ git push origin v0.3.0
32
+ ```
@@ -0,0 +1,49 @@
1
+ # Release checklist
2
+
3
+ ## Before releasing
4
+
5
+ - Run the test suite:
6
+
7
+ ```bash
8
+ bundle exec rspec
9
+ ```
10
+
11
+ - Run the linter:
12
+
13
+ ```bash
14
+ bundle exec rubocop
15
+ ```
16
+
17
+ - Verify the gem builds:
18
+
19
+ ```bash
20
+ gem build rails_engine_toolkit.gemspec
21
+ ```
22
+
23
+ - Review:
24
+ - `README.md`
25
+ - `docs/PUBLISHING.md`
26
+ - `docs/RELEASE.md`
27
+ - `lib/rails_engine_toolkit/version.rb`
28
+
29
+ ## Release steps
30
+
31
+ 1. Bump the version in `lib/rails_engine_toolkit/version.rb`
32
+ 2. Commit the changes
33
+ 3. Tag the release
34
+ 4. Push the tag
35
+ 5. Publish the gem manually or through GitHub Actions
36
+
37
+ ## Manual publish
38
+
39
+ ```bash
40
+ gem build rails_engine_toolkit.gemspec
41
+ gem push rails_engine_toolkit-0.6.2.gem
42
+ ```
43
+
44
+ ## Tag example
45
+
46
+ ```bash
47
+ git tag v0.6.2
48
+ git push origin v0.6.2
49
+ ```
data/docs/TESTING.md ADDED
@@ -0,0 +1,21 @@
1
+ # Testing strategy
2
+
3
+ ## Layers
4
+
5
+ The test suite is split into four layers:
6
+
7
+ 1. focused specs for utility classes and file mutation helpers
8
+ 2. action specs that exercise CLI-facing workflows on temporary directories
9
+ 3. integration-style specs for route insertion, removal, and migration installation/uninstallation
10
+ 4. host-app smoke checks through CI using a Rails 8.1 fixture app
11
+
12
+ ## Main scenarios covered
13
+
14
+ - install generator creates configuration and prints summary
15
+ - broken engine references in Gemfile are detected and can be removed
16
+ - route mounts are inspected with parser-assisted detection
17
+ - route mounts are inserted once and removed across multiple mount syntaxes
18
+ - engine README owned tables are updated from engine migrations
19
+ - engine migration files can be installed into the host app one engine at a time
20
+ - copied root migrations can be uninstalled with explicit confirmation
21
+ - engine removal cleans routes, Gemfile, and engine directory
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'rails_engine_toolkit'
5
+
6
+ exit RailsEngineToolkit::CLI.start(ARGV)
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsEngineToolkit
4
+ module Actions
5
+ class DeleteEngineMigration
6
+ def initialize(argv, stdin:, stdout:, root:, stderr: nil)
7
+ @engine_name = argv[0]
8
+ @pattern = argv[1]
9
+ @stdin = stdin
10
+ @stdout = stdout
11
+ @stderr = stderr
12
+ @root = Pathname(root)
13
+ @project = Project.new(@root)
14
+ end
15
+
16
+ def call
17
+ @project.validate_root!
18
+ validate_args!
19
+
20
+ migrations_dir = @project.engine_path(@engine_name).join('db/migrate')
21
+ raise ValidationError, "Migrations path does not exist: #{migrations_dir}" unless migrations_dir.directory?
22
+
23
+ matches = migrations_dir.glob("*#{@pattern}*.rb").sort
24
+ raise ValidationError, "No migrations found matching '#{@pattern}'." if matches.empty?
25
+
26
+ confirmed = Utils.ask(
27
+ "Type '#{@engine_name}' to confirm deletion",
28
+ input: @stdin,
29
+ output: @stdout
30
+ )
31
+ raise ValidationError, 'Operation aborted.' unless confirmed == @engine_name
32
+
33
+ matches.each(&:delete)
34
+ UpdateEngineReadme.new([@engine_name], stdin: @stdin, stdout: @stdout, stderr: nil, root: @root).call
35
+ @stdout.puts("Deleted #{matches.size} migration(s)")
36
+ end
37
+
38
+ private
39
+
40
+ def validate_args!
41
+ raise ValidationError, 'Engine name is required.' if @engine_name.to_s.empty?
42
+ raise ValidationError, 'Migration match pattern is required.' if @pattern.to_s.empty?
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsEngineToolkit
4
+ module Actions
5
+ class Init
6
+ def initialize(_argv, stdin:, stdout:, root:, stderr: nil)
7
+ @stdin = stdin
8
+ @stdout = stdout
9
+ @stderr = stderr
10
+ @root = Pathname(root)
11
+ @project = Project.new(@root)
12
+ end
13
+
14
+ def call
15
+ @project.validate_root!
16
+ answers = prompt_values
17
+
18
+ FileUtils.mkdir_p(@project.config_file.dirname)
19
+ @project.config_file.write(
20
+ Templates.render('engine_toolkit_yml', answers)
21
+ )
22
+
23
+ @stdout.puts("Created #{@project.config_file}")
24
+ @stdout.puts('Edit this file to customize the toolkit for your project:')
25
+ @stdout.puts(" #{@project.config_file}")
26
+ end
27
+
28
+ private
29
+
30
+ def prompt_values
31
+ slug_default = Utils.repo_slug_from_path(@root)
32
+ name_default = Utils.humanize_slug(slug_default)
33
+
34
+ {
35
+ project_slug: Utils.ask('Project slug', default: slug_default, input: @stdin, output: @stdout),
36
+ project_name: Utils.ask('Project name', default: name_default, input: @stdin, output: @stdout),
37
+ project_url: Utils.ask('Project URL', default: Utils.git_remote_url.to_s, input: @stdin, output: @stdout),
38
+ author_name: Utils.ask('Author name', default: Utils.git_config('user.name').to_s, input: @stdin,
39
+ output: @stdout),
40
+ author_email: Utils.ask('Author email', default: Utils.git_config('user.email').to_s, input: @stdin,
41
+ output: @stdout),
42
+ database: Utils.ask('Default database adapter', default: 'postgresql', input: @stdin, output: @stdout),
43
+ api_only: Utils.ask_yes_no('Use API-only engines by default?', default: true, input: @stdin, output: @stdout),
44
+ skip_asset_pipeline: Utils.ask_yes_no('Skip asset pipeline by default?', default: true, input: @stdin,
45
+ output: @stdout),
46
+ mount_routes: Utils.ask_yes_no('Mount engine routes automatically?', default: true, input: @stdin,
47
+ output: @stdout),
48
+ create_ddd_structure: Utils.ask_yes_no('Create DDD folders by default?', default: true, input: @stdin,
49
+ output: @stdout)
50
+ }
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsEngineToolkit
4
+ module Actions
5
+ class InstallEngineMigrations
6
+ def initialize(argv, stdout:, root:, stdin: nil, stderr: nil)
7
+ @engine_name = argv[0]
8
+ @stdout = stdout
9
+ @stdin = stdin
10
+ @stderr = stderr
11
+ @root = Pathname(root)
12
+ @project = Project.new(@root)
13
+ end
14
+
15
+ def call
16
+ @project.validate_root!
17
+ validate_args!
18
+
19
+ migrations_dir = engine_migrations_dir
20
+ FileUtils.mkdir_p(@project.root_migrations_dir)
21
+
22
+ copied = migrations_to_copy(migrations_dir).map do |source_file|
23
+ copy_migration(source_file)
24
+ end
25
+
26
+ report_result(copied)
27
+ end
28
+
29
+ private
30
+
31
+ def validate_args!
32
+ raise ValidationError, 'Engine name is required.' if @engine_name.to_s.empty?
33
+ raise ValidationError, 'Engine name must be snake_case.' unless Utils.snake_case?(@engine_name)
34
+
35
+ engine_path = @project.engine_path(@engine_name)
36
+ raise ValidationError, "Engine does not exist: #{engine_path}" unless engine_path.directory?
37
+ end
38
+
39
+ def engine_migrations_dir
40
+ migrations_dir = @project.engine_path(@engine_name).join('db/migrate')
41
+ unless migrations_dir.directory?
42
+ raise ValidationError,
43
+ "Engine migrations directory not found: #{migrations_dir}"
44
+ end
45
+
46
+ migrations_dir
47
+ end
48
+
49
+ def migrations_to_copy(migrations_dir)
50
+ existing = @project.root_migrations_dir.glob('*.rb').map do |file|
51
+ file.basename.to_s.sub(/^\d+_/, '')
52
+ end
53
+
54
+ migrations_dir.glob('*.rb').sort.reject do |file|
55
+ existing.include?(file.basename.to_s.sub(/^\d+_/, ''))
56
+ end
57
+ end
58
+
59
+ def copy_migration(source_file)
60
+ basename_without_version = source_file.basename.to_s.sub(/^\d+_/, '')
61
+ destination = @project.root_migrations_dir.join("#{next_timestamp}_#{basename_without_version}")
62
+ FileEditor.copy_file(source_file, destination)
63
+ destination
64
+ end
65
+
66
+ def report_result(copied)
67
+ if copied.empty?
68
+ @stdout.puts("No new migrations to install for engine '#{@engine_name}'.")
69
+ return
70
+ end
71
+
72
+ @stdout.puts("Installed #{copied.size} migration(s) for engine '#{@engine_name}':")
73
+ copied.each { |file| @stdout.puts(" #{file.relative_path_from(@root)}") }
74
+ end
75
+
76
+ def next_timestamp
77
+ @counter ||= 0
78
+ @counter += 1
79
+ (Time.now.utc + @counter).strftime('%Y%m%d%H%M%S')
80
+ end
81
+ end
82
+ end
83
+ end