├── src ├── api │ └── .keep ├── esbuild_tailwind │ └── .keep ├── importmap_tailwind │ └── .keep ├── shared │ ├── ci.rb │ ├── packages.rb │ ├── run_rubocop.rb │ ├── gems_active_decorator.rb │ ├── gems_active_interaction.rb │ ├── docs.rb │ ├── gems_pagy.rb │ ├── gems_i18n_tasks.rb │ ├── gems_lockbox.rb │ ├── migrations_uuid.rb │ ├── init_db_cli.rb │ ├── git_init.rb │ ├── init_i18n.rb │ ├── init_generators.rb │ ├── env_rubocop.rb │ ├── yarnconfig.rb │ ├── solid_queue_setup.rb │ ├── general.rb │ ├── gems_rspec.rb │ ├── gems_anyway_config.rb │ ├── staging_env.rb │ └── gems_shrine.rb ├── classic_shared │ ├── gems_erblint.rb │ ├── helpers.rb │ ├── routes.rb │ ├── gems_better_html.rb │ ├── app_static_pages.rb │ ├── gems_view_component.rb │ └── custom_error_pages.rb └── inertia_shared │ ├── init_generators.rb │ ├── rspec_inertia.rb │ ├── general.rb │ ├── custom_error_pages.rb │ ├── cleanup.rb │ ├── gems_inertia.rb │ └── finalize_layouts.rb ├── variants ├── shared │ ├── .rubocop_todo.yml │ ├── app │ │ ├── decorators │ │ │ └── .keep │ │ ├── interactions │ │ │ └── .keep │ │ ├── uploaders │ │ │ └── base_uploader.rb │ │ ├── controllers │ │ │ └── errors_controller.rb │ │ ├── helpers │ │ │ ├── view_component_helper.rb │ │ │ └── active_link_to_helper.rb │ │ └── views │ │ │ └── errors │ │ │ ├── unprocessable.html.erb │ │ │ ├── not_found.html.erb │ │ │ ├── unacceptable.html.erb │ │ │ └── internal_server_error.html.erb │ ├── config │ │ ├── locales │ │ │ ├── en.yml │ │ │ └── ru.yml │ │ ├── settings │ │ │ ├── aws_config.rb │ │ │ └── lockbox_config.rb │ │ ├── aws.yml │ │ ├── routes │ │ │ └── errors.rb │ │ ├── ci.rb │ │ └── database.yml.tt │ ├── .erdconfig │ ├── .rspec │ ├── spec │ │ ├── support │ │ │ ├── factory_bot.rb │ │ │ ├── shoulda.rb │ │ │ ├── time_helpers.rb │ │ │ └── database_cleaner.rb │ │ ├── spec_helper.rb │ │ ├── requests │ │ │ └── health_spec.rb │ │ └── rails_helper.rb │ ├── migrations │ │ └── uuid.rb │ ├── .rubocop_rails.yml │ ├── Brewfile │ ├── .rubocop_rspec.yml │ ├── lib │ │ └── tasks │ │ │ └── factory_bot.rake │ ├── .rubocop_strict.yml │ ├── .editorconfig │ └── .erb_lint.yml ├── esbuild_tailwind │ ├── spec │ │ ├── components │ │ │ └── previews │ │ │ │ └── .keep │ │ └── better_html_spec.rb │ ├── .yarnrc.yml │ ├── config │ │ ├── routes │ │ │ ├── landing.rb │ │ │ ├── pages.rb │ │ │ ├── dev.rb │ │ │ └── support.rb │ │ └── routes.rb │ ├── app │ │ ├── helpers │ │ │ └── application_helper.rb │ │ └── views │ │ │ ├── landing │ │ │ ├── about.html.erb │ │ │ └── home.html.erb │ │ │ ├── pages │ │ │ ├── privacy.html.erb │ │ │ └── terms.html.erb │ │ │ └── layouts │ │ │ ├── component_preview.html.erb │ │ │ ├── plain.html.erb │ │ │ ├── landing.html.erb │ │ │ └── application.html.erb │ ├── README.md.tt │ ├── .rubocop.yml │ ├── lefthook.yml │ ├── .gitignore │ ├── mise.toml │ └── Gemfile.tt ├── importmap_tailwind │ ├── spec │ │ ├── components │ │ │ └── previews │ │ │ │ └── .keep │ │ └── better_html_spec.rb │ ├── config │ │ ├── routes │ │ │ ├── landing.rb │ │ │ ├── pages.rb │ │ │ ├── dev.rb │ │ │ └── support.rb │ │ └── routes.rb │ ├── app │ │ ├── helpers │ │ │ └── application_helper.rb │ │ └── views │ │ │ ├── landing │ │ │ ├── about.html.erb │ │ │ └── home.html.erb │ │ │ ├── pages │ │ │ ├── privacy.html.erb │ │ │ └── terms.html.erb │ │ │ └── layouts │ │ │ ├── component_preview.html.erb │ │ │ ├── plain.html.erb │ │ │ ├── landing.html.erb │ │ │ └── application.html.erb │ ├── README.md.tt │ ├── .rubocop.yml │ ├── lefthook.yml │ ├── .gitignore │ ├── mise.toml │ └── Gemfile.tt ├── inertia_react │ ├── .yarnrc.yml │ ├── config │ │ ├── routes │ │ │ ├── landing.rb │ │ │ ├── pages.rb │ │ │ └── support.rb │ │ └── routes.rb │ ├── app │ │ ├── helpers │ │ │ └── application_helper.rb │ │ ├── frontend │ │ │ ├── layouts │ │ │ │ └── Layout.tsx │ │ │ ├── pages │ │ │ │ ├── About.tsx │ │ │ │ ├── Home.tsx │ │ │ │ ├── Terms.tsx │ │ │ │ └── Privacy.tsx │ │ │ └── lib │ │ │ │ └── components │ │ │ │ └── Flash.tsx │ │ └── views │ │ │ └── layouts │ │ │ └── application.html.erb │ ├── README.md.tt │ ├── lefthook.yml │ ├── .rubocop.yml │ ├── .gitignore │ ├── mise.toml │ └── Gemfile.tt ├── inertia_vue │ ├── .yarnrc.yml │ ├── config │ │ ├── routes │ │ │ ├── landing.rb │ │ │ ├── pages.rb │ │ │ └── support.rb │ │ └── routes.rb │ ├── app │ │ ├── helpers │ │ │ └── application_helper.rb │ │ ├── frontend │ │ │ ├── layouts │ │ │ │ └── Layout.vue │ │ │ ├── pages │ │ │ │ ├── About.vue │ │ │ │ ├── Home.vue │ │ │ │ ├── Terms.vue │ │ │ │ └── Privacy.vue │ │ │ └── lib │ │ │ │ └── components │ │ │ │ └── Flash.vue │ │ └── views │ │ │ └── layouts │ │ │ └── application.html.erb │ ├── README.md.tt │ ├── lefthook.yml │ ├── .rubocop.yml │ ├── .gitignore │ ├── mise.toml │ └── Gemfile.tt ├── inertia_svelte │ ├── .yarnrc.yml │ ├── config │ │ ├── routes │ │ │ ├── landing.rb │ │ │ ├── pages.rb │ │ │ └── support.rb │ │ └── routes.rb │ ├── app │ │ ├── helpers │ │ │ └── application_helper.rb │ │ ├── frontend │ │ │ ├── layouts │ │ │ │ └── Layout.svelte │ │ │ ├── pages │ │ │ │ ├── About.svelte │ │ │ │ ├── Home.svelte │ │ │ │ ├── Terms.svelte │ │ │ │ └── Privacy.svelte │ │ │ └── lib │ │ │ │ └── components │ │ │ │ └── Flash.svelte │ │ └── views │ │ │ └── layouts │ │ │ └── application.html.erb │ ├── README.md.tt │ ├── lefthook.yml │ ├── .rubocop.yml │ ├── .gitignore │ ├── mise.toml │ └── Gemfile.tt ├── classic_shared │ ├── config │ │ └── routes │ │ │ ├── landing.rb │ │ │ ├── pages.rb │ │ │ ├── dev.rb │ │ │ └── support.rb │ ├── app │ │ ├── helpers │ │ │ └── application_helper.rb │ │ ├── controllers │ │ │ ├── pages_controller.rb │ │ │ └── landing_controller.rb │ │ └── components │ │ │ └── ui │ │ │ ├── alert_component │ │ │ └── alert_component.html.erb │ │ │ └── alert_component.rb │ └── spec │ │ ├── support │ │ ├── capybara.rb │ │ └── view_component.rb │ │ ├── components │ │ ├── previews │ │ │ └── ui │ │ │ │ └── alert_component_preview.rb │ │ └── ui │ │ │ └── alert_component_spec.rb │ │ └── requests │ │ ├── landing_spec.rb │ │ └── pages_spec.rb ├── api │ ├── README.md.tt │ ├── lefthook.yml │ ├── .rubocop.yml │ ├── .gitignore │ ├── mise.toml │ └── Gemfile.tt └── inertia_shared │ ├── config │ ├── routes │ │ ├── dev.rb │ │ └── errors.rb │ └── vite.json │ ├── app │ ├── controllers │ │ └── errors_controller.rb │ └── views │ │ └── errors │ │ ├── unprocessable.html.erb │ │ ├── not_found.html.erb │ │ ├── unacceptable.html.erb │ │ └── internal_server_error.html.erb │ └── spec │ └── requests │ ├── landing_spec.rb │ └── pages_spec.rb ├── .rubocop.yml ├── .gitignore ├── LICENSE.md ├── api.rb ├── inertia_vue.rb ├── inertia_react.rb ├── inertia_svelte.rb ├── importmap_tailwind.rb ├── esbuild_tailwind.rb └── TEMPLATES.md /src/api/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/esbuild_tailwind/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/importmap_tailwind/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /variants/shared/.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /variants/shared/app/decorators/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /variants/shared/app/interactions/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /variants/shared/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | -------------------------------------------------------------------------------- /variants/shared/config/locales/ru.yml: -------------------------------------------------------------------------------- 1 | ru: 2 | -------------------------------------------------------------------------------- /variants/shared/.erdconfig: -------------------------------------------------------------------------------- 1 | filename: docs/erd 2 | -------------------------------------------------------------------------------- /src/shared/ci.rb: -------------------------------------------------------------------------------- 1 | copy_file "config/ci.rb", force: true 2 | -------------------------------------------------------------------------------- /variants/esbuild_tailwind/spec/components/previews/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /variants/importmap_tailwind/spec/components/previews/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/shared/packages.rb: -------------------------------------------------------------------------------- 1 | template "Gemfile.tt", force: true 2 | -------------------------------------------------------------------------------- /src/shared/run_rubocop.rb: -------------------------------------------------------------------------------- 1 | system("bundle exec rubocop -A") 2 | -------------------------------------------------------------------------------- /variants/inertia_react/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /variants/inertia_vue/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /variants/esbuild_tailwind/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /variants/inertia_svelte/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /src/classic_shared/gems_erblint.rb: -------------------------------------------------------------------------------- 1 | copy_file ".erb_lint.yml", force: true 2 | -------------------------------------------------------------------------------- /src/shared/gems_active_decorator.rb: -------------------------------------------------------------------------------- 1 | directory "app/decorators", force: true 2 | -------------------------------------------------------------------------------- /src/shared/gems_active_interaction.rb: -------------------------------------------------------------------------------- 1 | directory "app/interactions", force: true 2 | -------------------------------------------------------------------------------- /variants/inertia_vue/config/routes/landing.rb: -------------------------------------------------------------------------------- 1 | get "/about", to: "landing#about" 2 | -------------------------------------------------------------------------------- /variants/classic_shared/config/routes/landing.rb: -------------------------------------------------------------------------------- 1 | get "/about", to: "landing#about" 2 | -------------------------------------------------------------------------------- /variants/esbuild_tailwind/config/routes/landing.rb: -------------------------------------------------------------------------------- 1 | get "/about", to: "landing#about" 2 | -------------------------------------------------------------------------------- /variants/inertia_react/config/routes/landing.rb: -------------------------------------------------------------------------------- 1 | get "/about", to: "landing#about" 2 | -------------------------------------------------------------------------------- /variants/inertia_svelte/config/routes/landing.rb: -------------------------------------------------------------------------------- 1 | get "/about", to: "landing#about" 2 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | Style/StringLiterals: 2 | Enabled: true 3 | EnforcedStyle: double_quotes 4 | -------------------------------------------------------------------------------- /variants/importmap_tailwind/config/routes/landing.rb: -------------------------------------------------------------------------------- 1 | get "/about", to: "landing#about" 2 | -------------------------------------------------------------------------------- /src/classic_shared/helpers.rb: -------------------------------------------------------------------------------- 1 | copy_file "app/helpers/active_link_to_helper.rb", force: true 2 | -------------------------------------------------------------------------------- /src/shared/docs.rb: -------------------------------------------------------------------------------- 1 | empty_directory "docs", force: true 2 | copy_file ".erdconfig", force: true 3 | -------------------------------------------------------------------------------- /variants/shared/.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | --color 3 | --format documentation 4 | --fail-fast 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # IDE specific files 4 | .idea 5 | .ruby-lsp 6 | *.swp 7 | .vscode/settings.json 8 | -------------------------------------------------------------------------------- /variants/classic_shared/config/routes/pages.rb: -------------------------------------------------------------------------------- 1 | get "/terms", to: "pages#terms" 2 | get "/privacy", to: "pages#privacy" 3 | -------------------------------------------------------------------------------- /variants/inertia_react/config/routes/pages.rb: -------------------------------------------------------------------------------- 1 | get "/terms", to: "pages#terms" 2 | get "/privacy", to: "pages#privacy" 3 | -------------------------------------------------------------------------------- /variants/inertia_svelte/config/routes/pages.rb: -------------------------------------------------------------------------------- 1 | get "/terms", to: "pages#terms" 2 | get "/privacy", to: "pages#privacy" 3 | -------------------------------------------------------------------------------- /variants/inertia_vue/config/routes/pages.rb: -------------------------------------------------------------------------------- 1 | get "/terms", to: "pages#terms" 2 | get "/privacy", to: "pages#privacy" 3 | -------------------------------------------------------------------------------- /variants/classic_shared/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | include BetterHtml::Helpers 3 | end 4 | -------------------------------------------------------------------------------- /variants/esbuild_tailwind/config/routes/pages.rb: -------------------------------------------------------------------------------- 1 | get "/terms", to: "pages#terms" 2 | get "/privacy", to: "pages#privacy" 3 | -------------------------------------------------------------------------------- /variants/importmap_tailwind/config/routes/pages.rb: -------------------------------------------------------------------------------- 1 | get "/terms", to: "pages#terms" 2 | get "/privacy", to: "pages#privacy" 3 | -------------------------------------------------------------------------------- /variants/inertia_react/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | include BetterHtml::Helpers 3 | end 4 | -------------------------------------------------------------------------------- /variants/inertia_svelte/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | include BetterHtml::Helpers 3 | end 4 | -------------------------------------------------------------------------------- /variants/inertia_vue/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | include BetterHtml::Helpers 3 | end 4 | -------------------------------------------------------------------------------- /variants/esbuild_tailwind/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | include BetterHtml::Helpers 3 | end 4 | -------------------------------------------------------------------------------- /variants/importmap_tailwind/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | include BetterHtml::Helpers 3 | end 4 | -------------------------------------------------------------------------------- /variants/shared/spec/support/factory_bot.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.include FactoryBot::Syntax::Methods 3 | end 4 | -------------------------------------------------------------------------------- /variants/esbuild_tailwind/app/views/landing/about.html.erb: -------------------------------------------------------------------------------- 1 |

About us

2 |

Find me in app/views/landing/about.html.erb

3 | -------------------------------------------------------------------------------- /variants/importmap_tailwind/app/views/landing/about.html.erb: -------------------------------------------------------------------------------- 1 |

About us

2 |

Find me in app/views/landing/about.html.erb

3 | -------------------------------------------------------------------------------- /variants/esbuild_tailwind/app/views/pages/privacy.html.erb: -------------------------------------------------------------------------------- 1 |

Privacy policy

2 |

Find me in app/views/pages/privacy.html.erb

3 | -------------------------------------------------------------------------------- /variants/esbuild_tailwind/app/views/pages/terms.html.erb: -------------------------------------------------------------------------------- 1 |

Terms of service

2 |

Find me in app/views/pages/terms.html.erb

3 | -------------------------------------------------------------------------------- /variants/importmap_tailwind/app/views/pages/privacy.html.erb: -------------------------------------------------------------------------------- 1 |

Privacy policy

2 |

Find me in app/views/pages/privacy.html.erb

3 | -------------------------------------------------------------------------------- /variants/importmap_tailwind/app/views/pages/terms.html.erb: -------------------------------------------------------------------------------- 1 |

Terms of service

2 |

Find me in app/views/pages/terms.html.erb

3 | -------------------------------------------------------------------------------- /src/shared/gems_pagy.rb: -------------------------------------------------------------------------------- 1 | # Download and install Pagy initializer 2 | get "https://ddnexus.github.io/pagy/gem/config/pagy.rb", "config/initializers/pagy.rb" 3 | -------------------------------------------------------------------------------- /src/shared/gems_i18n_tasks.rb: -------------------------------------------------------------------------------- 1 | system("cp $(i18n-tasks gem-path)/templates/config/i18n-tasks.yml config/") 2 | system("cp $(i18n-tasks gem-path)/templates/rspec/i18n_spec.rb spec/") 3 | -------------------------------------------------------------------------------- /variants/classic_shared/spec/support/capybara.rb: -------------------------------------------------------------------------------- 1 | require "capybara/rspec" 2 | 3 | RSpec.configure do |config| 4 | config.include Capybara::RSpecMatchers, type: :component 5 | end 6 | -------------------------------------------------------------------------------- /src/shared/gems_lockbox.rb: -------------------------------------------------------------------------------- 1 | copy_file "config/settings/lockbox_config.rb", force: true 2 | 3 | initializer "lockbox.rb", <<-CODE 4 | Lockbox.master_key = LockboxConfig.master_key 5 | CODE 6 | -------------------------------------------------------------------------------- /variants/shared/spec/support/shoulda.rb: -------------------------------------------------------------------------------- 1 | Shoulda::Matchers.configure do |config| 2 | config.integrate do |with| 3 | with.test_framework :rspec 4 | with.library :rails 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /variants/shared/migrations/uuid.rb: -------------------------------------------------------------------------------- 1 | class EnableUuidPsqlExtension < ActiveRecord::Migration[8.1] 2 | def change 3 | enable_extension "pgcrypto" 4 | enable_extension "uuid-ossp" 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /variants/classic_shared/app/controllers/pages_controller.rb: -------------------------------------------------------------------------------- 1 | class PagesController < ApplicationController 2 | layout "plain" 3 | 4 | def terms 5 | end 6 | 7 | def privacy 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /variants/shared/config/settings/aws_config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AwsConfig < ApplicationConfig 4 | attr_config :access_key_id, :secret_access_key, :s3_region, :s3_bucket 5 | end 6 | -------------------------------------------------------------------------------- /variants/classic_shared/app/controllers/landing_controller.rb: -------------------------------------------------------------------------------- 1 | class LandingController < ApplicationController 2 | layout "landing" 3 | 4 | def home 5 | end 6 | 7 | def about 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /variants/inertia_react/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | draw :pages 3 | draw :landing 4 | draw :errors 5 | draw :dev 6 | draw :support 7 | 8 | root "landing#home" 9 | end 10 | -------------------------------------------------------------------------------- /variants/inertia_vue/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | draw :pages 3 | draw :landing 4 | draw :errors 5 | draw :dev 6 | draw :support 7 | 8 | root "landing#home" 9 | end 10 | -------------------------------------------------------------------------------- /src/classic_shared/routes.rb: -------------------------------------------------------------------------------- 1 | empty_directory "config/routes" 2 | 3 | copy_file "config/routes/dev.rb", force: true 4 | copy_file "config/routes/support.rb", force: true 5 | copy_file "config/routes.rb", force: true 6 | -------------------------------------------------------------------------------- /variants/esbuild_tailwind/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | draw :pages 3 | draw :landing 4 | draw :errors 5 | draw :dev 6 | draw :support 7 | 8 | root "landing#home" 9 | end 10 | -------------------------------------------------------------------------------- /variants/importmap_tailwind/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | draw :pages 3 | draw :landing 4 | draw :errors 5 | draw :dev 6 | draw :support 7 | 8 | root "landing#home" 9 | end 10 | -------------------------------------------------------------------------------- /variants/inertia_svelte/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | draw :pages 3 | draw :landing 4 | draw :errors 5 | draw :dev 6 | draw :support 7 | 8 | root "landing#home" 9 | end 10 | -------------------------------------------------------------------------------- /variants/api/README.md.tt: -------------------------------------------------------------------------------- 1 | # <%= app_name %> 2 | 3 | ## Description 4 | 5 | To be defined 6 | 7 | ## Setup 8 | 9 | 1. Install [homebrew](https://brew.sh/) and install all required packages using `brew bundle` 10 | -------------------------------------------------------------------------------- /variants/shared/spec/support/time_helpers.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | # https://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html 3 | config.include ActiveSupport::Testing::TimeHelpers 4 | end 5 | -------------------------------------------------------------------------------- /src/shared/migrations_uuid.rb: -------------------------------------------------------------------------------- 1 | generate "migration EnableUuidPsqlExtension" 2 | uuid_migration_file = (Dir["db/migrate/*_enable_uuid_psql_extension.rb"]).first 3 | copy_file "migrations/uuid.rb", uuid_migration_file, force: true 4 | -------------------------------------------------------------------------------- /variants/inertia_shared/config/routes/dev.rb: -------------------------------------------------------------------------------- 1 | if Rails.env.development? 2 | namespace :dev do 3 | mount LetterOpenerWeb::Engine, at: "/letter_opener" 4 | mount MissionControl::Jobs::Engine, at: "/jobs" 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /variants/inertia_vue/README.md.tt: -------------------------------------------------------------------------------- 1 | # <%= app_name %> 2 | 3 | ## Description 4 | 5 | To be defined 6 | 7 | ## Setup 8 | 9 | 1. Install [homebrew](https://brew.sh/) and install all required packages using `brew bundle` 10 | -------------------------------------------------------------------------------- /variants/esbuild_tailwind/README.md.tt: -------------------------------------------------------------------------------- 1 | # <%= app_name %> 2 | 3 | ## Description 4 | 5 | To be defined 6 | 7 | ## Setup 8 | 9 | 1. Install [homebrew](https://brew.sh/) and install all required packages using `brew bundle` 10 | -------------------------------------------------------------------------------- /variants/inertia_react/README.md.tt: -------------------------------------------------------------------------------- 1 | # <%= app_name %> 2 | 3 | ## Description 4 | 5 | To be defined 6 | 7 | ## Setup 8 | 9 | 1. Install [homebrew](https://brew.sh/) and install all required packages using `brew bundle` 10 | -------------------------------------------------------------------------------- /variants/inertia_svelte/README.md.tt: -------------------------------------------------------------------------------- 1 | # <%= app_name %> 2 | 3 | ## Description 4 | 5 | To be defined 6 | 7 | ## Setup 8 | 9 | 1. Install [homebrew](https://brew.sh/) and install all required packages using `brew bundle` 10 | -------------------------------------------------------------------------------- /variants/shared/.rubocop_rails.yml: -------------------------------------------------------------------------------- 1 | Rails/LexicallyScopedActionFilter: 2 | Enabled: false # we use BaseController 3 | Rails/UnknownEnv: 4 | Environments: 5 | - production 6 | - development 7 | - test 8 | - staging 9 | -------------------------------------------------------------------------------- /variants/importmap_tailwind/README.md.tt: -------------------------------------------------------------------------------- 1 | # <%= app_name %> 2 | 3 | ## Description 4 | 5 | To be defined 6 | 7 | ## Setup 8 | 9 | 1. Install [homebrew](https://brew.sh/) and install all required packages using `brew bundle` 10 | -------------------------------------------------------------------------------- /src/shared/init_db_cli.rb: -------------------------------------------------------------------------------- 1 | initializer "database_cli.rb", <<-CODE 2 | Rails.application.configure do 3 | if Rails.env.local? 4 | config.active_record.database_cli = {postgresql: "pgcli"} 5 | end 6 | end 7 | CODE 8 | -------------------------------------------------------------------------------- /src/shared/git_init.rb: -------------------------------------------------------------------------------- 1 | return if ARGV.include?("--skip-git") 2 | 3 | unless system("git init") 4 | puts "Failed to initialize git repository" 5 | exit 1 6 | end 7 | 8 | system("git add .") 9 | system('git commit -m "Initial commit"') 10 | -------------------------------------------------------------------------------- /src/shared/init_i18n.rb: -------------------------------------------------------------------------------- 1 | initializer "i18n.rb", <<-CODE 2 | I18n.load_path += Rails.root.glob("config/locales/**/*.{rb,yml}") 3 | I18n.default_locale = :en 4 | I18n.available_locales = %i[en ru] 5 | CODE 6 | 7 | directory "config/locales", force: true 8 | -------------------------------------------------------------------------------- /src/shared/init_generators.rb: -------------------------------------------------------------------------------- 1 | initializer "generators.rb", <<-CODE 2 | Rails.application.config.generators do |g| 3 | g.orm :active_record, primary_key_type: :uuid 4 | g.helper false 5 | g.test_framework :rspec, view_specs: false 6 | end 7 | CODE 8 | -------------------------------------------------------------------------------- /variants/classic_shared/spec/components/previews/ui/alert_component_preview.rb: -------------------------------------------------------------------------------- 1 | class UI::AlertComponentPreview < Lookbook::Preview 2 | def alert 3 | flash = {notice: "Notification message!"} 4 | render UI::AlertComponent.new(flash:) 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /variants/classic_shared/config/routes/dev.rb: -------------------------------------------------------------------------------- 1 | if Rails.env.development? 2 | namespace :dev do 3 | mount LetterOpenerWeb::Engine, at: "/letter_opener" 4 | mount MissionControl::Jobs::Engine, at: "/jobs" 5 | mount Lookbook::Engine, at: "/lookbook" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /variants/classic_shared/spec/support/view_component.rb: -------------------------------------------------------------------------------- 1 | require "view_component/test_helpers" 2 | 3 | RSpec.configure do |config| 4 | config.include ViewComponent::TestHelpers, type: :component 5 | config.include ViewComponent::SystemTestHelpers, type: :component 6 | end 7 | -------------------------------------------------------------------------------- /variants/esbuild_tailwind/config/routes/dev.rb: -------------------------------------------------------------------------------- 1 | if Rails.env.development? 2 | namespace :dev do 3 | mount LetterOpenerWeb::Engine, at: "/letter_opener" 4 | mount MissionControl::Jobs::Engine, at: "/jobs" 5 | mount Lookbook::Engine, at: "/lookbook" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /variants/importmap_tailwind/config/routes/dev.rb: -------------------------------------------------------------------------------- 1 | if Rails.env.development? 2 | namespace :dev do 3 | mount LetterOpenerWeb::Engine, at: "/letter_opener" 4 | mount MissionControl::Jobs::Engine, at: "/jobs" 5 | mount Lookbook::Engine, at: "/lookbook" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /variants/inertia_vue/app/frontend/layouts/Layout.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | -------------------------------------------------------------------------------- /variants/shared/config/aws.yml: -------------------------------------------------------------------------------- 1 | default: &default 2 | access_key_id: "000" 3 | secret_access_key: "000" 4 | s3_region: "local" 5 | s3_bucket: "uploads" 6 | 7 | development: 8 | <<: *default 9 | 10 | test: 11 | <<: *default 12 | 13 | production: 14 | <<: *default 15 | -------------------------------------------------------------------------------- /src/shared/env_rubocop.rb: -------------------------------------------------------------------------------- 1 | # Uncomment the RuboCop autocorrection line in development.rb 2 | gsub_file "config/environments/development.rb", 3 | "# config.generators.apply_rubocop_autocorrect_after_generate!", 4 | "config.generators.apply_rubocop_autocorrect_after_generate!" 5 | -------------------------------------------------------------------------------- /variants/inertia_react/config/routes/support.rb: -------------------------------------------------------------------------------- 1 | get "up", to: "rails/health#show", as: :rails_health_check 2 | 3 | # Render dynamic PWA files from app/views/pwa/* 4 | get "service-worker", to: "rails/pwa#service_worker", as: :pwa_service_worker 5 | get "manifest", to: "rails/pwa#manifest", as: :pwa_manifest 6 | -------------------------------------------------------------------------------- /variants/inertia_vue/config/routes/support.rb: -------------------------------------------------------------------------------- 1 | get "up", to: "rails/health#show", as: :rails_health_check 2 | 3 | # Render dynamic PWA files from app/views/pwa/* 4 | get "service-worker", to: "rails/pwa#service_worker", as: :pwa_service_worker 5 | get "manifest", to: "rails/pwa#manifest", as: :pwa_manifest 6 | -------------------------------------------------------------------------------- /variants/classic_shared/config/routes/support.rb: -------------------------------------------------------------------------------- 1 | get "up", to: "rails/health#show", as: :rails_health_check 2 | 3 | # Render dynamic PWA files from app/views/pwa/* 4 | get "service-worker", to: "rails/pwa#service_worker", as: :pwa_service_worker 5 | get "manifest", to: "rails/pwa#manifest", as: :pwa_manifest 6 | -------------------------------------------------------------------------------- /variants/esbuild_tailwind/config/routes/support.rb: -------------------------------------------------------------------------------- 1 | get "up", to: "rails/health#show", as: :rails_health_check 2 | 3 | # Render dynamic PWA files from app/views/pwa/* 4 | get "service-worker", to: "rails/pwa#service_worker", as: :pwa_service_worker 5 | get "manifest", to: "rails/pwa#manifest", as: :pwa_manifest 6 | -------------------------------------------------------------------------------- /variants/importmap_tailwind/config/routes/support.rb: -------------------------------------------------------------------------------- 1 | get "up", to: "rails/health#show", as: :rails_health_check 2 | 3 | # Render dynamic PWA files from app/views/pwa/* 4 | get "service-worker", to: "rails/pwa#service_worker", as: :pwa_service_worker 5 | get "manifest", to: "rails/pwa#manifest", as: :pwa_manifest 6 | -------------------------------------------------------------------------------- /variants/inertia_svelte/config/routes/support.rb: -------------------------------------------------------------------------------- 1 | get "up", to: "rails/health#show", as: :rails_health_check 2 | 3 | # Render dynamic PWA files from app/views/pwa/* 4 | get "service-worker", to: "rails/pwa#service_worker", as: :pwa_service_worker 5 | get "manifest", to: "rails/pwa#manifest", as: :pwa_manifest 6 | -------------------------------------------------------------------------------- /variants/inertia_react/app/frontend/layouts/Layout.tsx: -------------------------------------------------------------------------------- 1 | import Flash from '$lib/components/Flash'; 2 | 3 | export default function Layout({ children }: { children: React.ReactNode }) { 4 | return ( 5 |
6 | 7 | {children} 8 |
9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /variants/shared/config/routes/errors.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Custom error pages routes 4 | match "/404", to: "errors#not_found", via: :all 5 | match "/406", to: "errors#unacceptable", via: :all 6 | match "/422", to: "errors#unprocessable", via: :all 7 | match "/500", to: "errors#internal_server_error", via: :all 8 | -------------------------------------------------------------------------------- /variants/inertia_shared/config/routes/errors.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Custom error pages routes 4 | match "/404", to: "errors#not_found", via: :all 5 | match "/406", to: "errors#unacceptable", via: :all 6 | match "/422", to: "errors#unprocessable", via: :all 7 | match "/500", to: "errors#internal_server_error", via: :all 8 | -------------------------------------------------------------------------------- /variants/shared/Brewfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | brew "act" 4 | brew "gh" 5 | brew "imagemagick" 6 | brew "jq" 7 | brew "lefthook" 8 | brew "mise" 9 | brew "neovim" 10 | brew "osv-scanner" 11 | brew "pgcli" 12 | brew "vips" 13 | brew "xh" 14 | brew "zellij" 15 | 16 | cask "ghostty" 17 | cask "postgres-unofficial" 18 | -------------------------------------------------------------------------------- /variants/shared/spec/support/database_cleaner.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.before(:suite) do 3 | DatabaseCleaner.strategy = :transaction 4 | DatabaseCleaner.clean_with(:truncation) 5 | end 6 | 7 | config.around do |example| 8 | DatabaseCleaner.cleaning do 9 | example.run 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /variants/shared/.rubocop_rspec.yml: -------------------------------------------------------------------------------- 1 | RSpec/ImplicitExpect: 2 | Enabled: false 3 | RSpec/ContextWording: 4 | Enabled: false 5 | RSpec/ExampleWording: 6 | Enabled: false 7 | RSpec/NestedGroups: 8 | AllowedGroups: [get, post, patch, delete, path, response] 9 | RSpec/ExampleLength: 10 | Max: 20 11 | RSpec/MultipleExpectations: 12 | Max: 5 13 | -------------------------------------------------------------------------------- /variants/inertia_svelte/app/frontend/layouts/Layout.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 13 | {@render children()} 14 |
15 | -------------------------------------------------------------------------------- /variants/shared/app/uploaders/base_uploader.rb: -------------------------------------------------------------------------------- 1 | class BaseUploader < Shrine 2 | def generate_location(io, record: nil, derivative: nil, **) 3 | return super unless record 4 | 5 | table = record.class.table_name 6 | id = record.id 7 | prefix = derivative || "original" 8 | 9 | "uploads/#{table}/#{id}/#{prefix}-#{super}" 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /variants/inertia_shared/config/vite.json: -------------------------------------------------------------------------------- 1 | { 2 | "all": { 3 | "sourceCodeDir": "app/frontend", 4 | "watchAdditionalPaths": [] 5 | }, 6 | "development": { 7 | "autoBuild": true, 8 | "publicOutputDir": "vite-dev", 9 | "port": 3036 10 | }, 11 | "test": { 12 | "autoBuild": true, 13 | "publicOutputDir": "vite-test", 14 | "port": 3037 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /variants/shared/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.expect_with :rspec do |expectations| 3 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 4 | end 5 | 6 | config.mock_with :rspec do |mocks| 7 | mocks.verify_partial_doubles = true 8 | end 9 | 10 | config.shared_context_metadata_behavior = :apply_to_host_groups 11 | end 12 | -------------------------------------------------------------------------------- /variants/shared/config/settings/lockbox_config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class LockboxConfig < ApplicationConfig 4 | attr_config :master_key 5 | 6 | def master_key 7 | if Rails.env.production? 8 | Rails.application.credentials.lockbox[:master_key] 9 | else 10 | "0000000000000000000000000000000000000000000000000000000000000000" 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /variants/classic_shared/spec/components/ui/alert_component_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe UI::AlertComponent, type: :component do 4 | before do 5 | flash = {notice: "Notification message!"} 6 | render_inline(described_class.new(flash:)) 7 | end 8 | 9 | it "renders flash notification" do 10 | expect(page).to have_text("Notification message!") 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /src/classic_shared/gems_better_html.rb: -------------------------------------------------------------------------------- 1 | copy_file "spec/better_html_spec.rb", force: true 2 | copy_file "app/helpers/application_helper.rb", force: true 3 | 4 | initializer "better_html.rb", <<-CODE 5 | BetterHtml.configure do |config| 6 | config.template_exclusion_filter = proc { |filename| !filename.start_with?(Rails.root.to_s) } 7 | config.partial_tag_name_pattern = /\\A[a-zA-Z0-9\\-:]+\\z/ 8 | end 9 | CODE 10 | -------------------------------------------------------------------------------- /variants/shared/lib/tasks/factory_bot.rake: -------------------------------------------------------------------------------- 1 | namespace :factory_bot do 2 | desc "Verify that all FactoryBot factories are valid" 3 | task lint: :environment do 4 | if Rails.env.test? 5 | DatabaseCleaner.cleaning do 6 | FactoryBot.lint 7 | end 8 | else 9 | system("bundle exec rake factory_bot:lint RAILS_ENV='test'") 10 | raise if $?.exitstatus.nonzero? 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /src/inertia_shared/init_generators.rb: -------------------------------------------------------------------------------- 1 | # Configure generators for Inertia - skip JavaScript, assets, and helpers 2 | application do 3 | <<-RUBY 4 | config.generators do |g| 5 | g.orm :active_record, primary_key_type: :uuid 6 | g.test_framework :rspec, view_specs: false 7 | g.assets false 8 | g.helper false 9 | g.javascripts false 10 | g.stylesheets false 11 | end 12 | RUBY 13 | end 14 | -------------------------------------------------------------------------------- /variants/classic_shared/app/components/ui/alert_component/alert_component.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% unless @flash.empty? %> 3 | <% @flash.each do |key, value| %> 4 | <% next unless value.is_a? String %> 5 | 8 | <% end %> 9 | <% end %> 10 |
11 | -------------------------------------------------------------------------------- /variants/shared/.rubocop_strict.yml: -------------------------------------------------------------------------------- 1 | Lint/Debugger: # don't leave binding.pry or debugger 2 | Enabled: true 3 | Exclude: [] 4 | 5 | Rails/Output: # Don't leave puts-debugging 6 | Enabled: true 7 | Exclude: [] 8 | 9 | Rails/FindEach: # each could severely affect the performance, use find_each 10 | Enabled: true 11 | Exclude: [] 12 | 13 | Rails/UniqBeforePluck: # uniq.pluck and not pluck.uniq 14 | Enabled: true 15 | Exclude: [] 16 | -------------------------------------------------------------------------------- /variants/shared/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{rb,rake}] 15 | indent_size = 2 16 | 17 | [*.{js,ts,jsx,tsx,vue,svelte}] 18 | indent_size = 2 19 | 20 | [*.{css,scss,sass,less}] 21 | indent_size = 2 22 | 23 | [*.{json,yml,yaml}] 24 | indent_size = 2 -------------------------------------------------------------------------------- /variants/shared/spec/requests/health_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe "Health", type: :request do 4 | describe "GET /up" do 5 | it "returns http success" do 6 | get "/up" 7 | expect(response).to have_http_status(:success) 8 | end 9 | 10 | it "returns ok status" do 11 | get "/up" 12 | expect(response.body).to eq('') 13 | end 14 | end 15 | end -------------------------------------------------------------------------------- /variants/classic_shared/spec/requests/landing_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe "Landing", type: :request do 4 | describe "GET /home" do 5 | it "returns http success" do 6 | get "/" 7 | expect(response).to have_http_status(:success) 8 | end 9 | end 10 | 11 | describe "GET /about" do 12 | it "returns http success" do 13 | get "/about" 14 | expect(response).to have_http_status(:success) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /src/classic_shared/app_static_pages.rb: -------------------------------------------------------------------------------- 1 | copy_file "app/controllers/pages_controller.rb", force: true 2 | copy_file "app/controllers/landing_controller.rb", force: true 3 | 4 | copy_file "config/routes/pages.rb", force: true 5 | copy_file "config/routes/landing.rb", force: true 6 | copy_file "spec/requests/pages_spec.rb", force: true 7 | copy_file "spec/requests/landing_spec.rb", force: true 8 | directory "app/views/pages", force: true 9 | directory "app/views/landing", force: true 10 | -------------------------------------------------------------------------------- /variants/classic_shared/spec/requests/pages_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe "Pages", type: :request do 4 | describe "GET /terms" do 5 | it "returns http success" do 6 | get "/terms" 7 | expect(response).to have_http_status(:success) 8 | end 9 | end 10 | 11 | describe "GET /privacy" do 12 | it "returns http success" do 13 | get "/privacy" 14 | expect(response).to have_http_status(:success) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /variants/shared/.erb_lint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | EnableDefaultLinters: true 3 | linters: 4 | TrailingWhitespace: 5 | enabled: false 6 | SelfClosingTag: 7 | enabled: false 8 | ParserErrors: 9 | enabled: true 10 | SpaceInHtmlTag: 11 | enabled: true 12 | AllowedScriptType: 13 | enabled: true 14 | allowed_types: 15 | - "text/javascript" 16 | - "text/x-tmpl" 17 | - "application/ld+json" 18 | allow_blank: true 19 | disallow_inline_scripts: false 20 | -------------------------------------------------------------------------------- /variants/shared/app/controllers/errors_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ErrorsController < ApplicationController 4 | layout "application" 5 | 6 | def not_found 7 | render status: :not_found 8 | end 9 | 10 | def unprocessable 11 | render status: :unprocessable_entity 12 | end 13 | 14 | def unacceptable 15 | render status: :not_acceptable 16 | end 17 | 18 | def internal_server_error 19 | render status: :internal_server_error 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /variants/inertia_shared/app/controllers/errors_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ErrorsController < ApplicationController 4 | layout "application" 5 | 6 | def not_found 7 | render status: :not_found 8 | end 9 | 10 | def unprocessable 11 | render status: :unprocessable_entity 12 | end 13 | 14 | def unacceptable 15 | render status: :not_acceptable 16 | end 17 | 18 | def internal_server_error 19 | render status: :internal_server_error 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /variants/importmap_tailwind/app/views/layouts/component_preview.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= csrf_meta_tags %> 7 | <%= csp_meta_tag %> 8 | 9 | <%= stylesheet_link_tag :app, "data-turbo-track": "reload" %> 10 | <%= javascript_importmap_tags %> 11 | 12 | 13 | 14 | <%= yield %> 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/shared/yarnconfig.rb: -------------------------------------------------------------------------------- 1 | # Configure Yarn with corepack 2 | copy_file ".yarnrc.yml", force: true 3 | system("yarn set version stable") 4 | 5 | # Configure devcontainer to use corepack instead of APT for Yarn installation 6 | dc_file = ".devcontainer/devcontainer.json" 7 | if File.exist?(dc_file) 8 | gsub_file dc_file, 9 | '"ghcr.io/devcontainers/features/node:1": {},', 10 | <<-CODE 11 | "ghcr.io/devcontainers/features/node:1": { 12 | "installYarnUsingApt": false 13 | }, 14 | CODE 15 | end 16 | -------------------------------------------------------------------------------- /variants/esbuild_tailwind/app/views/layouts/component_preview.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= csrf_meta_tags %> 7 | <%= csp_meta_tag %> 8 | 9 | <%= stylesheet_link_tag :app, "data-turbo-track": "reload" %> 10 | <%= javascript_include_tag "application", "data-turbo-track": "reload", type: "module" %> 11 | 12 | 13 | 14 | <%= yield %> 15 | 16 | 17 | -------------------------------------------------------------------------------- /variants/classic_shared/app/components/ui/alert_component.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class UI::AlertComponent < ViewComponent::Base 4 | def initialize(flash:) 5 | @flash = flash 6 | @flash_class = { 7 | notice: "text-blue-800 bg-blue-50 dark:bg-gray-800 dark:text-blue-400", 8 | success: "text-green-800 bg-green-50 dark:bg-gray-800 dark:text-green-400", 9 | error: "text-red-800 bg-red-50 dark:bg-gray-800 dark:text-red-400", 10 | alert: "text-yellow-800 bg-yellow-50 dark:bg-gray-800 dark:text-yellow-300" 11 | } 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /variants/api/lefthook.yml: -------------------------------------------------------------------------------- 1 | pre-push: 2 | parallel: true 3 | commands: 4 | gems-audit: 5 | tags: backend security 6 | run: bundle-audit check --update 7 | 8 | pre-commit: 9 | parallel: true 10 | commands: 11 | rspec: 12 | tags: backend frontend test 13 | run: bundle exec rspec 14 | rubocop: 15 | tags: backend codestyle 16 | run: bundle exec rubocop -A 17 | brakeman: 18 | tags: backend security 19 | run: bundle exec brakeman --no-pager 20 | zeitwerk: 21 | tags: backend 22 | run: bundle exec rails zeitwerk:check 23 | -------------------------------------------------------------------------------- /variants/shared/app/helpers/view_component_helper.rb: -------------------------------------------------------------------------------- 1 | module ViewComponentHelper 2 | # Helper for view_component gem 3 | # can be used with 4 | # <%= component "example", title: "Hello World!" %> 5 | # or for namespaced components 6 | # <%= component "way_down/we_go/example", title: "Hello World!" %> 7 | # taken with small improvements from 8 | # https://evilmartians.com/chronicles/viewcomponent-in-the-wild-supercharging-your-components 9 | def component(name, *, **, &) 10 | component = "#{name.to_s.camelize}Component".constantize 11 | render(component.new(*, **), &) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /src/inertia_shared/rspec_inertia.rb: -------------------------------------------------------------------------------- 1 | # Configure RSpec for Inertia Rails testing 2 | # Inject the inertia_rails/rspec require into rails_helper.rb 3 | 4 | inject_into_file "spec/rails_helper.rb", after: "require 'rspec/rails'\n" do 5 | <<-RUBY 6 | require 'inertia_rails/rspec' 7 | # Load Vite Ruby helpers for test environment (needed for vite_stylesheet_tag, etc.) 8 | require 'vite_ruby' 9 | RUBY 10 | end 11 | 12 | # Copy Inertia request specs 13 | copy_file "spec/requests/landing_spec.rb", force: true 14 | copy_file "spec/requests/pages_spec.rb", force: true 15 | 16 | say "✓ Inertia RSpec configuration added", :green 17 | -------------------------------------------------------------------------------- /variants/inertia_vue/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= MainConfig.app_name %> 5 | 6 | 7 | <%= csrf_meta_tags %> 8 | <%= csp_meta_tag %> 9 | 10 | <%= yield :head %> 11 | <%= vite_stylesheet_tag "application" %> 12 | <%= vite_client_tag %> 13 | <%= vite_typescript_tag "inertia.ts" %> 14 | <%= inertia_ssr_head %> 15 | 16 | 17 | 18 | <%= yield %> 19 | 20 | 21 | -------------------------------------------------------------------------------- /variants/esbuild_tailwind/spec/better_html_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | RSpec.describe "BetterHtml" do 6 | it "does assert that all .html.erb templates are parseable" do # rubocop:disable RSpec/ExampleLength 7 | erb_glob = Rails.root.join( 8 | "app/views/**/{*.htm,*.html,*.htm.erb,*.html.erb,*.html+*.erb}" 9 | ) 10 | 11 | Dir[erb_glob].each do |filename| 12 | data = File.read(filename) 13 | expect { 14 | BetterHtml::BetterErb::ErubiImplementation.new(data, filename:).validate! 15 | }.not_to raise_exception 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /variants/importmap_tailwind/spec/better_html_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | RSpec.describe "BetterHtml" do 6 | it "does assert that all .html.erb templates are parseable" do # rubocop:disable RSpec/ExampleLength 7 | erb_glob = Rails.root.join( 8 | "app/views/**/{*.htm,*.html,*.htm.erb,*.html.erb,*.html+*.erb}" 9 | ) 10 | 11 | Dir[erb_glob].each do |filename| 12 | data = File.read(filename) 13 | expect { 14 | BetterHtml::BetterErb::ErubiImplementation.new(data, filename:).validate! 15 | }.not_to raise_exception 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /variants/inertia_svelte/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= MainConfig.app_name %> 5 | 6 | 7 | <%= csrf_meta_tags %> 8 | <%= csp_meta_tag %> 9 | 10 | <%= yield :head %> 11 | <%= vite_stylesheet_tag "application" %> 12 | <%= vite_client_tag %> 13 | <%= vite_typescript_tag "inertia.ts" %> 14 | <%= inertia_ssr_head %> 15 | 16 | 17 | 18 | <%= yield %> 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/shared/solid_queue_setup.rb: -------------------------------------------------------------------------------- 1 | # Replace default database.yml with multi-database configuration 2 | template "config/database.yml.tt", "config/database.yml", force: true 3 | 4 | # Add solid_queue configuration to development.rb 5 | inject_into_file "config/environments/development.rb", before: /^end\s*$/ do 6 | <<-RUBY 7 | 8 | config.active_job.queue_adapter = :solid_queue 9 | config.solid_queue.connects_to = {database: {writing: :queue}} 10 | RUBY 11 | end 12 | 13 | # Add jobs process to Procfile.dev if it exists 14 | if File.exist?("Procfile.dev") 15 | append_to_file "Procfile.dev" do 16 | "jobs: bin/jobs start\n" 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /variants/shared/spec/rails_helper.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | ENV['RAILS_ENV'] ||= 'test' 3 | require_relative '../config/environment' 4 | 5 | abort("The Rails environment is running in production mode!") if Rails.env.production? 6 | 7 | require 'rspec/rails' 8 | 9 | Rails.root.glob('spec/support/**/*.rb').sort_by(&:to_s).each { |f| require f } 10 | 11 | begin 12 | ActiveRecord::Migration.maintain_test_schema! 13 | rescue ActiveRecord::PendingMigrationError => e 14 | abort e.to_s.strip 15 | end 16 | 17 | RSpec.configure do |config| 18 | config.filter_run_excluding skip: true # filter pending specs output 19 | config.filter_rails_from_backtrace! 20 | end 21 | -------------------------------------------------------------------------------- /variants/inertia_react/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= MainConfig.app_name %> 5 | 6 | 7 | <%= csrf_meta_tags %> 8 | <%= csp_meta_tag %> 9 | 10 | <%= yield :head %> 11 | <%= vite_stylesheet_tag "application" %> 12 | <%= vite_react_refresh_tag %> 13 | <%= vite_client_tag %> 14 | <%= vite_typescript_tag "inertia.tsx" %> 15 | <%= inertia_ssr_head %> 16 | 17 | 18 | 19 | <%= yield %> 20 | 21 | 22 | -------------------------------------------------------------------------------- /variants/api/.rubocop.yml: -------------------------------------------------------------------------------- 1 | require: 2 | - standard 3 | 4 | plugins: 5 | - rubocop-rails 6 | - rubocop-rspec 7 | - rubocop-performance 8 | - rubocop-thread_safety 9 | 10 | inherit_from: 11 | - .rubocop_todo.yml 12 | - .rubocop_strict.yml 13 | - .rubocop_rspec.yml 14 | - .rubocop_rails.yml 15 | 16 | inherit_gem: 17 | standard: config/base.yml 18 | 19 | AllCops: 20 | NewCops: enable 21 | SuggestExtensions: false 22 | Exclude: 23 | - node_modules/**/* 24 | - public/**/* 25 | - vendor/**/* 26 | - bin/**/* 27 | - db/schema.rb 28 | 29 | Rails: 30 | Enabled: true 31 | RSpec: 32 | Enabled: true 33 | Performance: 34 | Enabled: true 35 | -------------------------------------------------------------------------------- /variants/inertia_react/lefthook.yml: -------------------------------------------------------------------------------- 1 | pre-push: 2 | parallel: true 3 | commands: 4 | gems-audit: 5 | tags: backend security 6 | run: bundle-audit check --update 7 | node-audit: 8 | tags: frontend security 9 | run: yarn npm audit 10 | 11 | pre-commit: 12 | parallel: true 13 | commands: 14 | rspec: 15 | tags: backend frontend test 16 | run: bundle exec rspec 17 | rubocop: 18 | tags: backend codestyle 19 | run: bundle exec rubocop -A 20 | brakeman: 21 | tags: backend security 22 | run: bundle exec brakeman --no-pager 23 | zeitwerk: 24 | tags: backend 25 | run: bundle exec rails zeitwerk:check 26 | -------------------------------------------------------------------------------- /variants/inertia_vue/lefthook.yml: -------------------------------------------------------------------------------- 1 | pre-push: 2 | parallel: true 3 | commands: 4 | gems-audit: 5 | tags: backend security 6 | run: bundle-audit check --update 7 | node-audit: 8 | tags: frontend security 9 | run: yarn npm audit 10 | 11 | pre-commit: 12 | parallel: true 13 | commands: 14 | rspec: 15 | tags: backend frontend test 16 | run: bundle exec rspec 17 | rubocop: 18 | tags: backend codestyle 19 | run: bundle exec rubocop -A 20 | brakeman: 21 | tags: backend security 22 | run: bundle exec brakeman --no-pager 23 | zeitwerk: 24 | tags: backend 25 | run: bundle exec rails zeitwerk:check 26 | -------------------------------------------------------------------------------- /variants/inertia_svelte/lefthook.yml: -------------------------------------------------------------------------------- 1 | pre-push: 2 | parallel: true 3 | commands: 4 | gems-audit: 5 | tags: backend security 6 | run: bundle-audit check --update 7 | node-audit: 8 | tags: frontend security 9 | run: yarn npm audit 10 | 11 | pre-commit: 12 | parallel: true 13 | commands: 14 | rspec: 15 | tags: backend frontend test 16 | run: bundle exec rspec 17 | rubocop: 18 | tags: backend codestyle 19 | run: bundle exec rubocop -A 20 | brakeman: 21 | tags: backend security 22 | run: bundle exec brakeman --no-pager 23 | zeitwerk: 24 | tags: backend 25 | run: bundle exec rails zeitwerk:check 26 | -------------------------------------------------------------------------------- /src/inertia_shared/general.rb: -------------------------------------------------------------------------------- 1 | copy_file ".editorconfig", force: true 2 | template "README.md", force: true 3 | copy_file ".gitignore", force: true 4 | copy_file "mise.toml", force: true 5 | copy_file "lefthook.yml", force: true 6 | 7 | copy_file ".rubocop.yml", force: true 8 | copy_file ".rubocop_rails.yml", force: true 9 | copy_file ".rubocop_rspec.yml", force: true 10 | copy_file ".rubocop_strict.yml", force: true 11 | copy_file ".rubocop_todo.yml", force: true 12 | 13 | # Copy vite.json early to prevent "file not found" errors during bundle install 14 | copy_file "config/vite.json", force: true 15 | 16 | # For Inertia templates, layouts will be copied at the end after Inertia generator runs 17 | -------------------------------------------------------------------------------- /variants/inertia_vue/.rubocop.yml: -------------------------------------------------------------------------------- 1 | require: 2 | - standard 3 | 4 | plugins: 5 | - rubocop-rails 6 | - rubocop-rspec 7 | - rubocop-performance 8 | - rubocop-thread_safety 9 | - rubocop-capybara 10 | 11 | inherit_from: 12 | - .rubocop_todo.yml 13 | - .rubocop_strict.yml 14 | - .rubocop_rspec.yml 15 | - .rubocop_rails.yml 16 | 17 | inherit_gem: 18 | standard: config/base.yml 19 | 20 | AllCops: 21 | NewCops: enable 22 | SuggestExtensions: false 23 | Exclude: 24 | - node_modules/**/* 25 | - public/**/* 26 | - vendor/**/* 27 | - bin/**/* 28 | - db/schema.rb 29 | 30 | Rails: 31 | Enabled: true 32 | RSpec: 33 | Enabled: true 34 | Performance: 35 | Enabled: true 36 | -------------------------------------------------------------------------------- /variants/esbuild_tailwind/.rubocop.yml: -------------------------------------------------------------------------------- 1 | require: 2 | - standard 3 | 4 | plugins: 5 | - rubocop-rails 6 | - rubocop-rspec 7 | - rubocop-performance 8 | - rubocop-thread_safety 9 | - rubocop-capybara 10 | 11 | inherit_from: 12 | - .rubocop_todo.yml 13 | - .rubocop_strict.yml 14 | - .rubocop_rspec.yml 15 | - .rubocop_rails.yml 16 | 17 | inherit_gem: 18 | standard: config/base.yml 19 | 20 | AllCops: 21 | NewCops: enable 22 | SuggestExtensions: false 23 | Exclude: 24 | - node_modules/**/* 25 | - public/**/* 26 | - vendor/**/* 27 | - bin/**/* 28 | - db/schema.rb 29 | 30 | Rails: 31 | Enabled: true 32 | RSpec: 33 | Enabled: true 34 | Performance: 35 | Enabled: true 36 | -------------------------------------------------------------------------------- /variants/inertia_react/.rubocop.yml: -------------------------------------------------------------------------------- 1 | require: 2 | - standard 3 | 4 | plugins: 5 | - rubocop-rails 6 | - rubocop-rspec 7 | - rubocop-performance 8 | - rubocop-thread_safety 9 | - rubocop-capybara 10 | 11 | inherit_from: 12 | - .rubocop_todo.yml 13 | - .rubocop_strict.yml 14 | - .rubocop_rspec.yml 15 | - .rubocop_rails.yml 16 | 17 | inherit_gem: 18 | standard: config/base.yml 19 | 20 | AllCops: 21 | NewCops: enable 22 | SuggestExtensions: false 23 | Exclude: 24 | - node_modules/**/* 25 | - public/**/* 26 | - vendor/**/* 27 | - bin/**/* 28 | - db/schema.rb 29 | 30 | Rails: 31 | Enabled: true 32 | RSpec: 33 | Enabled: true 34 | Performance: 35 | Enabled: true 36 | -------------------------------------------------------------------------------- /variants/inertia_svelte/.rubocop.yml: -------------------------------------------------------------------------------- 1 | require: 2 | - standard 3 | 4 | plugins: 5 | - rubocop-rails 6 | - rubocop-rspec 7 | - rubocop-performance 8 | - rubocop-thread_safety 9 | - rubocop-capybara 10 | 11 | inherit_from: 12 | - .rubocop_todo.yml 13 | - .rubocop_strict.yml 14 | - .rubocop_rspec.yml 15 | - .rubocop_rails.yml 16 | 17 | inherit_gem: 18 | standard: config/base.yml 19 | 20 | AllCops: 21 | NewCops: enable 22 | SuggestExtensions: false 23 | Exclude: 24 | - node_modules/**/* 25 | - public/**/* 26 | - vendor/**/* 27 | - bin/**/* 28 | - db/schema.rb 29 | 30 | Rails: 31 | Enabled: true 32 | RSpec: 33 | Enabled: true 34 | Performance: 35 | Enabled: true 36 | -------------------------------------------------------------------------------- /variants/importmap_tailwind/.rubocop.yml: -------------------------------------------------------------------------------- 1 | require: 2 | - standard 3 | 4 | plugins: 5 | - rubocop-rails 6 | - rubocop-rspec 7 | - rubocop-performance 8 | - rubocop-thread_safety 9 | - rubocop-capybara 10 | 11 | inherit_from: 12 | - .rubocop_todo.yml 13 | - .rubocop_strict.yml 14 | - .rubocop_rspec.yml 15 | - .rubocop_rails.yml 16 | 17 | inherit_gem: 18 | standard: config/base.yml 19 | 20 | AllCops: 21 | NewCops: enable 22 | SuggestExtensions: false 23 | Exclude: 24 | - node_modules/**/* 25 | - public/**/* 26 | - vendor/**/* 27 | - bin/**/* 28 | - db/schema.rb 29 | 30 | Rails: 31 | Enabled: true 32 | RSpec: 33 | Enabled: true 34 | Performance: 35 | Enabled: true 36 | -------------------------------------------------------------------------------- /src/shared/general.rb: -------------------------------------------------------------------------------- 1 | copy_file ".editorconfig", force: true 2 | template "README.md", force: true 3 | copy_file ".gitignore", force: true 4 | copy_file "mise.toml", force: true 5 | copy_file "lefthook.yml", force: true 6 | 7 | copy_file ".rubocop.yml", force: true 8 | copy_file ".rubocop_rails.yml", force: true 9 | copy_file ".rubocop_rspec.yml", force: true 10 | copy_file ".rubocop_strict.yml", force: true 11 | copy_file ".rubocop_todo.yml", force: true 12 | 13 | if File.exist?("app/views/layouts/application.html.erb") 14 | copy_file "app/views/layouts/application.html.erb", force: true 15 | copy_file "app/views/layouts/landing.html.erb", force: true 16 | copy_file "app/views/layouts/plain.html.erb", force: true 17 | end 18 | -------------------------------------------------------------------------------- /variants/inertia_shared/spec/requests/landing_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe "Landing", type: :request, inertia: true do 4 | describe "GET /" do 5 | it "returns http success" do 6 | get "/" 7 | expect(response).to have_http_status(:success) 8 | end 9 | 10 | it "renders the Home component" do 11 | get "/" 12 | expect(inertia.component).to eq("Home") 13 | end 14 | end 15 | 16 | describe "GET /about" do 17 | it "returns http success" do 18 | get "/about" 19 | expect(response).to have_http_status(:success) 20 | end 21 | 22 | it "renders the About component" do 23 | get "/about" 24 | expect(inertia.component).to eq("About") 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /variants/inertia_shared/spec/requests/pages_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe "Pages", type: :request, inertia: true do 4 | describe "GET /terms" do 5 | it "returns http success" do 6 | get "/terms" 7 | expect(response).to have_http_status(:success) 8 | end 9 | 10 | it "renders the Terms component" do 11 | get "/terms" 12 | expect(inertia.component).to eq("Terms") 13 | end 14 | end 15 | 16 | describe "GET /privacy" do 17 | it "returns http success" do 18 | get "/privacy" 19 | expect(response).to have_http_status(:success) 20 | end 21 | 22 | it "renders the Privacy component" do 23 | get "/privacy" 24 | expect(inertia.component).to eq("Privacy") 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /variants/esbuild_tailwind/lefthook.yml: -------------------------------------------------------------------------------- 1 | pre-push: 2 | parallel: true 3 | commands: 4 | gems-audit: 5 | tags: backend security 6 | run: bundle-audit check --update 7 | node-audit: 8 | tags: frontend security 9 | run: yarn npm audit 10 | 11 | pre-commit: 12 | parallel: true 13 | commands: 14 | rspec: 15 | tags: backend frontend test 16 | run: bundle exec rspec 17 | rubocop: 18 | tags: backend codestyle 19 | run: bundle exec rubocop -A 20 | brakeman: 21 | tags: backend security 22 | run: bundle exec brakeman --no-pager 23 | zeitwerk: 24 | tags: backend 25 | run: bundle exec rails zeitwerk:check 26 | erb-lint: 27 | tags: erb-lint frontend 28 | run: bundle exec erb_lint --lint-all 29 | -------------------------------------------------------------------------------- /variants/importmap_tailwind/lefthook.yml: -------------------------------------------------------------------------------- 1 | pre-push: 2 | parallel: true 3 | commands: 4 | gems-audit: 5 | tags: backend security 6 | run: bundle-audit check --update 7 | node-audit: 8 | tags: frontend security 9 | run: yarn npm audit 10 | 11 | pre-commit: 12 | parallel: true 13 | commands: 14 | rspec: 15 | tags: backend frontend test 16 | run: bundle exec rspec 17 | rubocop: 18 | tags: backend codestyle 19 | run: bundle exec rubocop -A 20 | brakeman: 21 | tags: backend security 22 | run: bundle exec brakeman --no-pager 23 | zeitwerk: 24 | tags: backend 25 | run: bundle exec rails zeitwerk:check 26 | erb-lint: 27 | tags: erb-lint frontend 28 | run: bundle exec erb_lint --lint-all 29 | -------------------------------------------------------------------------------- /src/shared/gems_rspec.rb: -------------------------------------------------------------------------------- 1 | generate "rspec:install" 2 | 3 | # Copy only common spec files (not spec/components and spec/requests) 4 | copy_file ".rspec", force: true 5 | copy_file "spec/rails_helper.rb", force: true 6 | copy_file "spec/spec_helper.rb", force: true 7 | copy_file "spec/support/database_cleaner.rb", force: true 8 | copy_file "spec/support/factory_bot.rb", force: true 9 | copy_file "spec/support/shoulda.rb", force: true 10 | copy_file "spec/support/time_helpers.rb", force: true 11 | copy_file "spec/requests/health_spec.rb", force: true 12 | copy_file "lib/tasks/factory_bot.rake", force: true 13 | 14 | # Enable parallel_tests support in database.yml 15 | gsub_file "config/database.yml", 16 | /(test:.*?database: \w+)(_test)/m, 17 | "\\1\\2<%= ENV['TEST_ENV_NUMBER'] %>" 18 | -------------------------------------------------------------------------------- /variants/api/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore bundler config. 2 | /.bundle 3 | 4 | # Ignore all environment files. 5 | /.env* 6 | 7 | # Ignore all logfiles and tempfiles. 8 | /log/* 9 | /tmp/* 10 | !/log/.keep 11 | !/tmp/.keep 12 | 13 | # Ignore pidfiles, but keep the directory. 14 | /tmp/pids/* 15 | !/tmp/pids/ 16 | !/tmp/pids/.keep 17 | 18 | # Ignore storage (uploaded files in development and any SQLite databases). 19 | /storage/* 20 | !/storage/.keep 21 | /tmp/storage/* 22 | !/tmp/storage/ 23 | !/tmp/storage/.keep 24 | 25 | /public/uploads 26 | 27 | # Ignore keys for decrypting credentials and more. 28 | /config/master.key 29 | /config/credentials/development.key 30 | /config/credentials/test.key 31 | 32 | .DS_Store 33 | 34 | # IDE specific files 35 | .idea 36 | .ruby-lsp 37 | *.swp 38 | .vscode/settings.json 39 | .vscode/rdbg_autoattach.json 40 | -------------------------------------------------------------------------------- /variants/shared/config/ci.rb: -------------------------------------------------------------------------------- 1 | # Run using bin/ci 2 | 3 | CI.run do 4 | step "Setup", "bin/setup --skip-server" 5 | 6 | step "Style: Ruby", "bin/rubocop" 7 | 8 | step "Security: Gem audit", "bin/bundler-audit" 9 | step "Security: Brakeman code analysis", "bin/brakeman --quiet --no-pager --exit-on-warn --exit-on-error" 10 | 11 | step "Test: Run rspec tests", "bin/rails spec" 12 | step "Test: Test code loader", "bin/rails zeitwerk:check" 13 | 14 | # Optional: set a green GitHub commit status to unblock PR merge. 15 | # Requires the `gh` CLI and `gh extension install basecamp/gh-signoff`. 16 | # if success? 17 | # step "Signoff: All systems go. Ready for merge and deploy.", "gh signoff" 18 | # else 19 | # failure "Signoff: CI failed. Do not merge or deploy.", "Fix the issues and try again." 20 | # end 21 | end 22 | -------------------------------------------------------------------------------- /variants/shared/app/views/errors/unprocessable.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

422

5 |

6 | Unprocessable entity 7 |

8 |

9 | The change you wanted was rejected. 10 |

11 |
12 |
13 | <%= link_to "Go back home", root_path, class: "inline-flex items-center px-4 py-2 border border-transparent text-base font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" %> 14 |
15 |
16 |
17 | -------------------------------------------------------------------------------- /variants/shared/app/views/errors/not_found.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

404

5 |

6 | Page not found 7 |

8 |

9 | Sorry, we couldn't find the page you're looking for. 10 |

11 |
12 |
13 | <%= link_to "Go back home", root_path, class: "inline-flex items-center px-4 py-2 border border-transparent text-base font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" %> 14 |
15 |
16 |
17 | -------------------------------------------------------------------------------- /variants/inertia_shared/app/views/errors/unprocessable.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

422

5 |

6 | Unprocessable entity 7 |

8 |

9 | The change you wanted was rejected. 10 |

11 |
12 |
13 | <%= link_to "Go back home", root_path, class: "inline-flex items-center px-4 py-2 border border-transparent text-base font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" %> 14 |
15 |
16 |
17 | -------------------------------------------------------------------------------- /variants/inertia_shared/app/views/errors/not_found.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

404

5 |

6 | Page not found 7 |

8 |

9 | Sorry, we couldn't find the page you're looking for. 10 |

11 |
12 |
13 | <%= link_to "Go back home", root_path, class: "inline-flex items-center px-4 py-2 border border-transparent text-base font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" %> 14 |
15 |
16 |
17 | -------------------------------------------------------------------------------- /variants/shared/app/views/errors/unacceptable.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

406

5 |

6 | Not acceptable 7 |

8 |

9 | The requested resource is not available in the requested format. 10 |

11 |
12 |
13 | <%= link_to "Go back home", root_path, class: "inline-flex items-center px-4 py-2 border border-transparent text-base font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" %> 14 |
15 |
16 |
17 | -------------------------------------------------------------------------------- /variants/inertia_shared/app/views/errors/unacceptable.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

406

5 |

6 | Not acceptable 7 |

8 |

9 | The requested resource is not available in the requested format. 10 |

11 |
12 |
13 | <%= link_to "Go back home", root_path, class: "inline-flex items-center px-4 py-2 border border-transparent text-base font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" %> 14 |
15 |
16 |
17 | -------------------------------------------------------------------------------- /variants/shared/app/views/errors/internal_server_error.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

500

5 |

6 | Internal server error 7 |

8 |

9 | Something went wrong on our end. We're working on fixing it. 10 |

11 |
12 |
13 | <%= link_to "Go back home", root_path, class: "inline-flex items-center px-4 py-2 border border-transparent text-base font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" %> 14 |
15 |
16 |
17 | -------------------------------------------------------------------------------- /variants/inertia_shared/app/views/errors/internal_server_error.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

500

5 |

6 | Internal server error 7 |

8 |

9 | Something went wrong on our end. We're working on fixing it. 10 |

11 |
12 |
13 | <%= link_to "Go back home", root_path, class: "inline-flex items-center px-4 py-2 border border-transparent text-base font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" %> 14 |
15 |
16 |
17 | -------------------------------------------------------------------------------- /src/shared/gems_anyway_config.rb: -------------------------------------------------------------------------------- 1 | generate "anyway:install --configs-path=config/settings", force: true 2 | generate "anyway:config main app_name base_url --yml", force: true 3 | generate "anyway:config mailer email_from --yml", force: true 4 | 5 | gsub_file "config/settings/application_config.rb", 6 | /(@instance \|\|= new)/, 7 | '\1 # rubocop:disable ThreadSafety/ClassInstanceVariable' 8 | 9 | gsub_file "config/main.yml", 10 | /# app_name: ".*"/, 11 | " app_name: \"#{app_name}\"" 12 | 13 | gsub_file "config/main.yml", 14 | /# base_url: ".*"/, 15 | ' base_url: "http://localhost:3000"' 16 | 17 | gsub_file "config/mailer.yml", 18 | /# email_from: ".*"/, 19 | ' email_from: "no-reply@example.com"' 20 | 21 | gsub_file "app/mailers/application_mailer.rb", 22 | /default from: ".*"/, 23 | 'default from: MailerConfig.email_from' 24 | -------------------------------------------------------------------------------- /variants/inertia_vue/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore bundler config. 2 | /.bundle 3 | 4 | # Ignore all environment files. 5 | /.env* 6 | 7 | # Ignore all logfiles and tempfiles. 8 | /log/* 9 | /tmp/* 10 | !/log/.keep 11 | !/tmp/.keep 12 | 13 | # Ignore pidfiles, but keep the directory. 14 | /tmp/pids/* 15 | !/tmp/pids/ 16 | !/tmp/pids/.keep 17 | 18 | # Ignore storage (uploaded files in development and any SQLite databases). 19 | /storage/* 20 | !/storage/.keep 21 | /tmp/storage/* 22 | !/tmp/storage/ 23 | !/tmp/storage/.keep 24 | 25 | /public/assets 26 | /public/uploads 27 | 28 | # Ignore keys for decrypting credentials and more. 29 | /config/master.key 30 | /config/credentials/development.key 31 | /config/credentials/test.key 32 | 33 | /node_modules 34 | .pnp.* 35 | .yarn/* 36 | !.yarn/patches 37 | !.yarn/plugins 38 | !.yarn/releases 39 | !.yarn/sdks 40 | !.yarn/versions 41 | 42 | .DS_Store 43 | 44 | # IDE specific files 45 | .idea 46 | .ruby-lsp 47 | *.swp 48 | .vscode/settings.json 49 | .vscode/rdbg_autoattach.json 50 | -------------------------------------------------------------------------------- /variants/esbuild_tailwind/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore bundler config. 2 | /.bundle 3 | 4 | # Ignore all environment files. 5 | /.env* 6 | 7 | # Ignore all logfiles and tempfiles. 8 | /log/* 9 | /tmp/* 10 | !/log/.keep 11 | !/tmp/.keep 12 | 13 | # Ignore pidfiles, but keep the directory. 14 | /tmp/pids/* 15 | !/tmp/pids/ 16 | !/tmp/pids/.keep 17 | 18 | # Ignore storage (uploaded files in development and any SQLite databases). 19 | /storage/* 20 | !/storage/.keep 21 | /tmp/storage/* 22 | !/tmp/storage/ 23 | !/tmp/storage/.keep 24 | 25 | /public/assets 26 | /public/uploads 27 | 28 | # Ignore keys for decrypting credentials and more. 29 | /config/master.key 30 | /config/credentials/development.key 31 | /config/credentials/test.key 32 | 33 | /node_modules 34 | .pnp.* 35 | .yarn/* 36 | !.yarn/patches 37 | !.yarn/plugins 38 | !.yarn/releases 39 | !.yarn/sdks 40 | !.yarn/versions 41 | 42 | .DS_Store 43 | 44 | # IDE specific files 45 | .idea 46 | .ruby-lsp 47 | *.swp 48 | .vscode/settings.json 49 | .vscode/rdbg_autoattach.json 50 | -------------------------------------------------------------------------------- /variants/inertia_react/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore bundler config. 2 | /.bundle 3 | 4 | # Ignore all environment files. 5 | /.env* 6 | 7 | # Ignore all logfiles and tempfiles. 8 | /log/* 9 | /tmp/* 10 | !/log/.keep 11 | !/tmp/.keep 12 | 13 | # Ignore pidfiles, but keep the directory. 14 | /tmp/pids/* 15 | !/tmp/pids/ 16 | !/tmp/pids/.keep 17 | 18 | # Ignore storage (uploaded files in development and any SQLite databases). 19 | /storage/* 20 | !/storage/.keep 21 | /tmp/storage/* 22 | !/tmp/storage/ 23 | !/tmp/storage/.keep 24 | 25 | /public/assets 26 | /public/uploads 27 | 28 | # Ignore keys for decrypting credentials and more. 29 | /config/master.key 30 | /config/credentials/development.key 31 | /config/credentials/test.key 32 | 33 | /node_modules 34 | .pnp.* 35 | .yarn/* 36 | !.yarn/patches 37 | !.yarn/plugins 38 | !.yarn/releases 39 | !.yarn/sdks 40 | !.yarn/versions 41 | 42 | .DS_Store 43 | 44 | # IDE specific files 45 | .idea 46 | .ruby-lsp 47 | *.swp 48 | .vscode/settings.json 49 | .vscode/rdbg_autoattach.json 50 | -------------------------------------------------------------------------------- /variants/inertia_svelte/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore bundler config. 2 | /.bundle 3 | 4 | # Ignore all environment files. 5 | /.env* 6 | 7 | # Ignore all logfiles and tempfiles. 8 | /log/* 9 | /tmp/* 10 | !/log/.keep 11 | !/tmp/.keep 12 | 13 | # Ignore pidfiles, but keep the directory. 14 | /tmp/pids/* 15 | !/tmp/pids/ 16 | !/tmp/pids/.keep 17 | 18 | # Ignore storage (uploaded files in development and any SQLite databases). 19 | /storage/* 20 | !/storage/.keep 21 | /tmp/storage/* 22 | !/tmp/storage/ 23 | !/tmp/storage/.keep 24 | 25 | /public/assets 26 | /public/uploads 27 | 28 | # Ignore keys for decrypting credentials and more. 29 | /config/master.key 30 | /config/credentials/development.key 31 | /config/credentials/test.key 32 | 33 | /node_modules 34 | .pnp.* 35 | .yarn/* 36 | !.yarn/patches 37 | !.yarn/plugins 38 | !.yarn/releases 39 | !.yarn/sdks 40 | !.yarn/versions 41 | 42 | .DS_Store 43 | 44 | # IDE specific files 45 | .idea 46 | .ruby-lsp 47 | *.swp 48 | .vscode/settings.json 49 | .vscode/rdbg_autoattach.json 50 | -------------------------------------------------------------------------------- /variants/importmap_tailwind/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore bundler config. 2 | /.bundle 3 | 4 | # Ignore all environment files. 5 | /.env* 6 | 7 | # Ignore all logfiles and tempfiles. 8 | /log/* 9 | /tmp/* 10 | !/log/.keep 11 | !/tmp/.keep 12 | 13 | # Ignore pidfiles, but keep the directory. 14 | /tmp/pids/* 15 | !/tmp/pids/ 16 | !/tmp/pids/.keep 17 | 18 | # Ignore storage (uploaded files in development and any SQLite databases). 19 | /storage/* 20 | !/storage/.keep 21 | /tmp/storage/* 22 | !/tmp/storage/ 23 | !/tmp/storage/.keep 24 | 25 | /public/assets 26 | /public/uploads 27 | 28 | # Ignore keys for decrypting credentials and more. 29 | /config/master.key 30 | /config/credentials/development.key 31 | /config/credentials/test.key 32 | 33 | /node_modules 34 | .pnp.* 35 | .yarn/* 36 | !.yarn/patches 37 | !.yarn/plugins 38 | !.yarn/releases 39 | !.yarn/sdks 40 | !.yarn/versions 41 | 42 | .DS_Store 43 | 44 | # IDE specific files 45 | .idea 46 | .ruby-lsp 47 | *.swp 48 | .vscode/settings.json 49 | .vscode/rdbg_autoattach.json 50 | -------------------------------------------------------------------------------- /src/classic_shared/gems_view_component.rb: -------------------------------------------------------------------------------- 1 | initializer "view_component.rb", <<-CODE 2 | Rails.application.config.view_component.previews.paths << Rails.root.join("spec/components/previews").to_s 3 | Rails.application.config.view_component.default_preview_layout = "component_preview" 4 | Rails.application.config.view_component.generate.sidecar = true 5 | CODE 6 | 7 | # Create or overwrite inflections.rb initializer 8 | inflections_file = "config/initializers/inflections.rb" 9 | remove_file inflections_file if File.exist?(inflections_file) 10 | initializer "inflections.rb", <<-CODE 11 | ActiveSupport::Inflector.inflections(:en) do |inflect| 12 | inflect.acronym "UI" 13 | end 14 | CODE 15 | 16 | copy_file "app/helpers/view_component_helper.rb", force: true 17 | copy_file "app/views/layouts/component_preview.html.erb", force: true 18 | 19 | directory "app/components", force: true 20 | directory "spec/components", force: true 21 | 22 | # Copy view component support files for RSpec 23 | copy_file "spec/support/view_component.rb", force: true 24 | copy_file "spec/support/capybara.rb", force: true 25 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Alexey Poimtsev 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. 22 | -------------------------------------------------------------------------------- /variants/importmap_tailwind/app/views/layouts/plain.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= content_for(:title) || MainConfig.app_name %> 6 | 7 | 8 | 9 | 10 | <%= csrf_meta_tags %> 11 | <%= csp_meta_tag %> 12 | 13 | <%= yield :head %> 14 | 15 | <%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %> 16 | <%= tag.link rel: "manifest", href: pwa_manifest_path(format: :json) %> 17 | 18 | 19 | 20 | 21 | 22 | <%# Includes all stylesheet files in app/assets/stylesheets %> 23 | <%= stylesheet_link_tag :app, "data-turbo-track": "reload" %> 24 | <%= javascript_importmap_tags %> 25 | 26 | 27 | 28 | <%= yield %> 29 | 30 | 31 | -------------------------------------------------------------------------------- /variants/inertia_svelte/app/frontend/pages/About.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | About - {$page.props.app_name} 8 | 9 | 10 | 11 |
12 |
13 |

14 | About {$page.props.app_name} 15 |

16 |

17 | This is a modern web application built with Rails, Inertia.js, and Svelte 5. 18 | It combines the power of server-side rendering with the flexibility of a 19 | single-page application. 20 |

21 | 29 |
30 |
31 |
32 | -------------------------------------------------------------------------------- /variants/importmap_tailwind/app/views/layouts/landing.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= content_for(:title) || MainConfig.app_name %> 6 | 7 | 8 | 9 | 10 | <%= csrf_meta_tags %> 11 | <%= csp_meta_tag %> 12 | 13 | <%= yield :head %> 14 | 15 | <%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %> 16 | <%= tag.link rel: "manifest", href: pwa_manifest_path(format: :json) %> 17 | 18 | 19 | 20 | 21 | 22 | <%# Includes all stylesheet files in app/assets/stylesheets %> 23 | <%= stylesheet_link_tag :app, "data-turbo-track": "reload" %> 24 | <%= javascript_importmap_tags %> 25 | 26 | 27 | 28 | <%= yield %> 29 | 30 | 31 | -------------------------------------------------------------------------------- /variants/esbuild_tailwind/app/views/landing/home.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | <%= MainConfig.app_name %> 6 |

7 |

8 | Built with Rails, ESBuild, and Tailwind CSS 9 |

10 |
11 | <%= link_to "Rails Guides", "https://guides.rubyonrails.org", 12 | target: "_blank", 13 | rel: "noopener noreferrer", 14 | class: "rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" %> 15 | <%= link_to "https://tailwindcss.com", 16 | target: "_blank", 17 | rel: "noopener noreferrer", 18 | class: "text-sm font-semibold leading-6 text-gray-900" do %> 19 | Tailwind CSS Docs 20 | <% end %> 21 |
22 |
23 |
24 |
25 | -------------------------------------------------------------------------------- /variants/importmap_tailwind/app/views/landing/home.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | <%= MainConfig.app_name %> 6 |

7 |

8 | Built with Rails, Importmap, and Tailwind CSS 9 |

10 |
11 | <%= link_to "Rails Guides", "https://guides.rubyonrails.org", 12 | target: "_blank", 13 | rel: "noopener noreferrer", 14 | class: "rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" %> 15 | <%= link_to "https://tailwindcss.com", 16 | target: "_blank", 17 | rel: "noopener noreferrer", 18 | class: "text-sm font-semibold leading-6 text-gray-900" do %> 19 | Tailwind CSS Docs 20 | <% end %> 21 |
22 |
23 |
24 |
25 | -------------------------------------------------------------------------------- /variants/esbuild_tailwind/app/views/layouts/plain.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= content_for(:title) || MainConfig.app_name %> 6 | 7 | 8 | 9 | 10 | <%= csrf_meta_tags %> 11 | <%= csp_meta_tag %> 12 | 13 | <%= yield :head %> 14 | 15 | <%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %> 16 | <%= tag.link rel: "manifest", href: pwa_manifest_path(format: :json) %> 17 | 18 | 19 | 20 | 21 | 22 | <%# Includes all stylesheet files in app/assets/stylesheets %> 23 | <%= stylesheet_link_tag :app, "data-turbo-track": "reload" %> 24 | <%= javascript_include_tag "application", "data-turbo-track": "reload", type: "module" %> 25 | 26 | 27 | 28 | <%= yield %> 29 | 30 | 31 | -------------------------------------------------------------------------------- /variants/esbuild_tailwind/app/views/layouts/landing.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= content_for(:title) || MainConfig.app_name %> 6 | 7 | 8 | 9 | 10 | <%= csrf_meta_tags %> 11 | <%= csp_meta_tag %> 12 | 13 | <%= yield :head %> 14 | 15 | <%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %> 16 | <%= tag.link rel: "manifest", href: pwa_manifest_path(format: :json) %> 17 | 18 | 19 | 20 | 21 | 22 | <%# Includes all stylesheet files in app/assets/stylesheets %> 23 | <%= stylesheet_link_tag :app, "data-turbo-track": "reload" %> 24 | <%= javascript_include_tag "application", "data-turbo-track": "reload", type: "module" %> 25 | 26 | 27 | 28 | <%= yield %> 29 | 30 | 31 | -------------------------------------------------------------------------------- /variants/importmap_tailwind/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= content_for(:title) || MainConfig.app_name %> 6 | 7 | 8 | 9 | 10 | <%= csrf_meta_tags %> 11 | <%= csp_meta_tag %> 12 | 13 | <%= yield :head %> 14 | 15 | <%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %> 16 | <%= tag.link rel: "manifest", href: pwa_manifest_path(format: :json) %> 17 | 18 | 19 | 20 | 21 | 22 | <%# Includes all stylesheet files in app/assets/stylesheets %> 23 | <%= stylesheet_link_tag :app, "data-turbo-track": "reload" %> 24 | <%= javascript_importmap_tags %> 25 | 26 | 27 | 28 | <%= component "ui/alert", flash: flash %> 29 | <%= yield %> 30 | 31 | 32 | -------------------------------------------------------------------------------- /variants/inertia_vue/app/frontend/pages/About.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 34 | -------------------------------------------------------------------------------- /src/classic_shared/custom_error_pages.rb: -------------------------------------------------------------------------------- 1 | copy_file "app/controllers/errors_controller.rb", force: true 2 | directory "app/views/errors", force: true 3 | copy_file "config/routes/errors.rb", force: true 4 | 5 | inject_into_file "config/application.rb", after: "config.generators.system_tests = nil\n" do 6 | <<-RUBY 7 | 8 | # Use custom error pages 9 | config.exceptions_app = routes 10 | RUBY 11 | end 12 | 13 | # Configure development environment to optionally use custom error pages 14 | gsub_file "config/environments/development.rb", 15 | /config\.consider_all_requests_local\s*=\s*.+$/, 16 | 'config.consider_all_requests_local = !Rails.root.join("tmp/errors-dev.txt").exist? # Toggle with: rails dev:errors' 17 | 18 | # Create rake task to toggle error pages in development 19 | create_file "lib/tasks/dev.rake", <<~RUBY 20 | # frozen_string_literal: true 21 | 22 | namespace :dev do 23 | desc "Toggle custom error pages in development" 24 | task :errors do 25 | error_file = Rails.root.join("tmp/errors-dev.txt") 26 | 27 | if error_file.exist? 28 | error_file.delete 29 | puts "Custom error pages disabled. Restart the server." 30 | else 31 | error_file.write("") 32 | puts "Custom error pages enabled. Restart the server." 33 | end 34 | end 35 | end 36 | RUBY 37 | -------------------------------------------------------------------------------- /variants/esbuild_tailwind/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= content_for(:title) || MainConfig.app_name %> 6 | 7 | 8 | 9 | 10 | <%= csrf_meta_tags %> 11 | <%= csp_meta_tag %> 12 | 13 | <%= yield :head %> 14 | 15 | <%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %> 16 | <%= tag.link rel: "manifest", href: pwa_manifest_path(format: :json) %> 17 | 18 | 19 | 20 | 21 | 22 | <%# Includes all stylesheet files in app/assets/stylesheets %> 23 | <%= stylesheet_link_tag :app, "data-turbo-track": "reload" %> 24 | <%= javascript_include_tag "application", "data-turbo-track": "reload", type: "module" %> 25 | 26 | 27 | 28 | <%= component "ui/alert", flash: flash %> 29 | <%= yield %> 30 | 31 | 32 | -------------------------------------------------------------------------------- /variants/inertia_react/app/frontend/pages/About.tsx: -------------------------------------------------------------------------------- 1 | import { usePage, Head } from '@inertiajs/react'; 2 | import Layout from '../layouts/Layout'; 3 | 4 | interface PageProps { 5 | app_name: string; 6 | [key: string]: any; 7 | } 8 | 9 | export default function About() { 10 | const { app_name } = usePage().props; 11 | 12 | return ( 13 | 14 | 15 |
16 |
17 |

18 | About {app_name} 19 |

20 |

21 | This is a modern web application built with Rails, Inertia.js, and React. 22 | It combines the power of server-side rendering with the flexibility of a 23 | single-page application. 24 |

25 |
26 | 30 | Back to Home 31 | 32 |
33 |
34 |
35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /variants/inertia_svelte/app/frontend/pages/Home.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | Home - {$page.props.app_name} 8 | 9 | 10 | 11 |
12 |
13 |

14 | {$page.props.app_name} 15 |

16 |

17 | Built with Rails, Inertia.js, and Svelte 5 18 |

19 | 37 |
38 |
39 |
40 | -------------------------------------------------------------------------------- /src/inertia_shared/custom_error_pages.rb: -------------------------------------------------------------------------------- 1 | # Configure custom error pages for Inertia templates 2 | # Note: Files from inertia_shared need to be copied explicitly due to source_paths priority 3 | 4 | # Copy errors controller from inertia_shared 5 | copy_file "app/controllers/errors_controller.rb", force: true 6 | 7 | # Copy error views from inertia_shared 8 | directory "app/views/errors", force: true 9 | 10 | inject_into_file "config/application.rb", after: "config.generators.system_tests = nil\n" do 11 | <<-RUBY 12 | 13 | # Use custom error pages 14 | config.exceptions_app = routes 15 | RUBY 16 | end 17 | 18 | # Configure development environment to optionally use custom error pages 19 | gsub_file "config/environments/development.rb", 20 | /config\.consider_all_requests_local\s*=\s*.+$/, 21 | 'config.consider_all_requests_local = !Rails.root.join("tmp/errors-dev.txt").exist? # Toggle with: rails dev:errors' 22 | 23 | # Create rake task to toggle error pages in development 24 | create_file "lib/tasks/dev.rake", <<~RUBY 25 | # frozen_string_literal: true 26 | 27 | namespace :dev do 28 | desc "Toggle custom error pages in development" 29 | task :errors do 30 | error_file = Rails.root.join("tmp/errors-dev.txt") 31 | 32 | if error_file.exist? 33 | error_file.delete 34 | puts "Custom error pages disabled. Restart the server." 35 | else 36 | error_file.write("") 37 | puts "Custom error pages enabled. Restart the server." 38 | end 39 | end 40 | end 41 | RUBY 42 | -------------------------------------------------------------------------------- /variants/inertia_vue/app/frontend/pages/Home.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 42 | -------------------------------------------------------------------------------- /variants/api/mise.toml: -------------------------------------------------------------------------------- 1 | [tools] 2 | ruby = "latest" 3 | 4 | [tasks.update] 5 | description = "Update packages" 6 | run = ["bundle update", "touch tmp/restart.txt"] 7 | 8 | [tasks.install] 9 | description = "Install packages" 10 | run = ["bundle install"] 11 | 12 | [tasks.lint] 13 | description = "Run linters" 14 | run = ["bundle exec rubocop -A", "bundle exec i18n-tasks normalize"] 15 | 16 | [tasks.test] 17 | description = "Run tests in parallel (fast)" 18 | run = ["bundle exec parallel_rspec spec/"] 19 | 20 | [tasks."test:serial"] 21 | description = "Run tests serially (for debugging)" 22 | run = ["bundle exec rspec"] 23 | 24 | [tasks.dev] 25 | description = "Run development server" 26 | run = ["./bin/dev"] 27 | 28 | [tasks.debug] 29 | description = "Run debug server" 30 | run = ["rdbg -n --open=vscode -c -- bin/rails s "] 31 | 32 | [tasks.audit] 33 | description = "Run security audit" 34 | run = ["bundle audit", "osv-scanner scan source -r ."] 35 | 36 | [tasks.db-create] 37 | description = "Prepare development database" 38 | run = [ 39 | "bundle exec rails db:create", 40 | "bundle exec rails db:prepare", 41 | "bin/rails db:schema:load:cache", 42 | "bin/rails db:schema:load:queue", 43 | "bin/rails db:schema:load:cable", 44 | "bundle exec rails parallel:create", 45 | ] 46 | 47 | [tasks.db-drop] 48 | description = "Destroy development database" 49 | run = ["bundle exec rails db:drop", "bundle exec rails parallel:drop"] 50 | 51 | [tasks.db-reset] 52 | description = "Reset development database" 53 | run = ["mise run db-drop", "mise run db-create"] 54 | 55 | [tasks.restart] 56 | description = "Restart server" 57 | run = "touch tmp/restart.txt" 58 | -------------------------------------------------------------------------------- /variants/inertia_react/app/frontend/pages/Home.tsx: -------------------------------------------------------------------------------- 1 | import { usePage, Head } from '@inertiajs/react'; 2 | import Layout from '../layouts/Layout'; 3 | 4 | interface PageProps { 5 | app_name: string; 6 | [key: string]: any; 7 | } 8 | 9 | export default function Home() { 10 | const { app_name } = usePage().props; 11 | 12 | return ( 13 | 14 | 15 |
16 |
17 |

18 | {app_name} 19 |

20 |

21 | Built with Rails, Inertia.js, and React 22 |

23 | 41 |
42 |
43 |
44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /variants/inertia_svelte/app/frontend/pages/Terms.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | Terms of Service - {$page.props.app_name} 8 | 9 | 10 | 11 |
12 |
13 |

14 | Terms of Service 15 |

16 |
17 |

18 | Welcome to {$page.props.app_name}. By using our service, you agree to these terms. 19 |

20 |

1. Use of Service

21 |

22 | You may use our service in accordance with these terms and applicable laws. 23 |

24 |

2. User Accounts

25 |

26 | You are responsible for maintaining the security of your account. 27 |

28 |

3. Content

29 |

30 | You retain ownership of content you submit, but grant us a license to use it. 31 |

32 |
33 | 41 |
42 |
43 |
44 | -------------------------------------------------------------------------------- /variants/importmap_tailwind/mise.toml: -------------------------------------------------------------------------------- 1 | [tools] 2 | ruby = "latest" 3 | node = "latest" 4 | 5 | [env] 6 | COREPACK_ENABLE_STRICT = "0" 7 | 8 | [tasks.update] 9 | description = "Update packages" 10 | run = ["bundle update", "touch tmp/restart.txt"] 11 | 12 | [tasks.install] 13 | description = "Install packages" 14 | run = ["bundle install"] 15 | 16 | [tasks.lint] 17 | description = "Run linters" 18 | run = [ 19 | "bundle exec rubocop -A", 20 | "bundle exec i18n-tasks normalize", 21 | "bundle exec erb_lint --lint-all --autocorrect", 22 | ] 23 | 24 | [tasks.test] 25 | description = "Run tests in parallel (fast)" 26 | run = ["bundle exec parallel_rspec spec/"] 27 | 28 | [tasks."test:serial"] 29 | description = "Run tests serially (for debugging)" 30 | run = ["bundle exec rspec"] 31 | 32 | [tasks.dev] 33 | description = "Run development server" 34 | run = ["./bin/dev"] 35 | 36 | [tasks.debug] 37 | description = "Run debug server" 38 | run = ["rdbg -n --open=vscode -c -- bin/rails s "] 39 | 40 | [tasks.audit] 41 | description = "Run security audit" 42 | run = ["bundle audit", "osv-scanner scan source -r ."] 43 | 44 | [tasks.db-create] 45 | description = "Prepare development database" 46 | run = [ 47 | "bundle exec rails db:create", 48 | "bundle exec rails db:prepare", 49 | "bin/rails db:schema:load:cache", 50 | "bin/rails db:schema:load:queue", 51 | "bin/rails db:schema:load:cable", 52 | "bundle exec rails parallel:create", 53 | ] 54 | 55 | [tasks.db-drop] 56 | description = "Destroy development database" 57 | run = ["bundle exec rails db:drop", "bundle exec rails parallel:drop"] 58 | 59 | [tasks.db-reset] 60 | description = "Reset development database" 61 | run = ["mise run db-drop", "mise run db-create"] 62 | 63 | [tasks.restart] 64 | description = "Restart server" 65 | run = "touch tmp/restart.txt" 66 | -------------------------------------------------------------------------------- /variants/api/Gemfile.tt: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | ### Application 4 | gem "rails", "~> <%= Rails.version %>" 5 | gem "puma"<%= gemfile_requirement("puma") %> 6 | 7 | ### Database 8 | gem "pg"<%= gemfile_requirement("pg") %> 9 | 10 | ### Solid Stack 11 | gem "solid_cache" 12 | gem "solid_queue" 13 | gem "solid_cable" 14 | 15 | ### Encryption and Security 16 | gem "blind_index" 17 | gem "lockbox" 18 | 19 | ### Deployment 20 | gem "kamal", require: false 21 | gem "thruster", require: false 22 | 23 | ### I18n 24 | gem "i18n-tasks" 25 | gem "rails-i18n" 26 | 27 | ### File uploads 28 | gem "shrine", "~> 3.0" 29 | gem "aws-sdk-s3" 30 | 31 | ### Image processing 32 | gem "image_processing", "~> 1.2" 33 | gem "fastimage" 34 | gem "marcel", "~> 1.0", ">= 1.0.2" 35 | 36 | ### Code structure 37 | gem "anyway_config", "~> 2.0" 38 | gem "active_interaction", "~> 5.5" 39 | 40 | ### Tools 41 | gem "tzinfo-data", platforms: %i[ windows jruby ] 42 | gem "bootsnap", require: false 43 | gem "jbuilder" 44 | gem "rack-cors" 45 | 46 | group :development, :test do 47 | gem "debug", platforms: %i[ mri windows ], require: "debug/prelude" 48 | 49 | # security 50 | gem "brakeman", require: false 51 | gem "bundler-audit" 52 | 53 | # linters 54 | gem "standard", require: false, github: "standardrb/standard" 55 | gem "rubocop-rails", require: false 56 | gem "rubocop-rspec", require: false 57 | gem "rubocop-performance", require: false 58 | gem "rubocop-thread_safety", require: false 59 | 60 | # testing 61 | gem "rspec-rails" 62 | gem "parallel_tests" 63 | gem "ffaker" 64 | gem "database_cleaner-active_record" 65 | gem "factory_bot_rails" 66 | gem "shoulda-context", "~> 3.0.0.rc1" 67 | gem "shoulda-matchers", "~> 6.5.0" 68 | end 69 | 70 | group :development do 71 | ## dev tools 72 | gem "amazing_print" 73 | gem "rails-erd" 74 | end 75 | -------------------------------------------------------------------------------- /variants/esbuild_tailwind/mise.toml: -------------------------------------------------------------------------------- 1 | [tools] 2 | ruby = "latest" 3 | node = "latest" 4 | 5 | [env] 6 | COREPACK_ENABLE_STRICT = "0" 7 | 8 | [tasks.update] 9 | description = "Update packages" 10 | run = [ 11 | "bundle update", 12 | "yarn up", 13 | "touch tmp/restart.txt", 14 | ] 15 | 16 | [tasks.install] 17 | description = "Install packages" 18 | run = ["corepack enable", "bundle install", "yarn install"] 19 | 20 | [tasks.lint] 21 | description = "Run linters" 22 | run = [ 23 | "bundle exec rubocop -A", 24 | "bundle exec i18n-tasks normalize", 25 | "bundle exec erb_lint --lint-all --autocorrect", 26 | ] 27 | 28 | [tasks.test] 29 | description = "Run tests in parallel (fast)" 30 | run = ["bundle exec parallel_rspec spec/"] 31 | 32 | [tasks."test:serial"] 33 | description = "Run tests serially (for debugging)" 34 | run = ["bundle exec rspec"] 35 | 36 | [tasks.dev] 37 | description = "Run development server" 38 | run = ["./bin/dev"] 39 | 40 | [tasks.debug] 41 | description = "Run debug server" 42 | run = ["rdbg -n --open=vscode -c -- bin/rails s "] 43 | 44 | [tasks.audit] 45 | description = "Run security audit" 46 | run = ["bundle audit", "yarn audit", "osv-scanner scan source -r ."] 47 | 48 | [tasks.db-create] 49 | description = "Prepare development database" 50 | run = [ 51 | "bundle exec rails db:create", 52 | "bundle exec rails db:prepare", 53 | "bin/rails db:schema:load:cache", 54 | "bin/rails db:schema:load:queue", 55 | "bin/rails db:schema:load:cable", 56 | "bundle exec rails parallel:create", 57 | ] 58 | 59 | [tasks.db-drop] 60 | description = "Destroy development database" 61 | run = ["bundle exec rails db:drop", "bundle exec rails parallel:drop"] 62 | 63 | [tasks.db-reset] 64 | description = "Reset development database" 65 | run = ["mise run db-drop", "mise run db-create"] 66 | 67 | [tasks.restart] 68 | description = "Restart server" 69 | run = "touch tmp/restart.txt" 70 | -------------------------------------------------------------------------------- /variants/inertia_vue/app/frontend/pages/Terms.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 46 | -------------------------------------------------------------------------------- /variants/inertia_react/mise.toml: -------------------------------------------------------------------------------- 1 | [tools] 2 | ruby = "latest" 3 | node = "latest" 4 | 5 | [env] 6 | COREPACK_ENABLE_STRICT = "0" 7 | 8 | [tasks.update] 9 | description = "Update packages" 10 | run = [ 11 | "bundle update", 12 | "yarn up", 13 | "touch tmp/restart.txt", 14 | ] 15 | 16 | [tasks.install] 17 | description = "Install packages" 18 | run = ["corepack enable", "bundle install", "yarn install"] 19 | 20 | [tasks.lint] 21 | description = "Run linters" 22 | run = [ 23 | "bundle exec rubocop -A", 24 | "bundle exec i18n-tasks normalize", 25 | "yarn run check" 26 | ] 27 | 28 | [tasks.test] 29 | description = "Run tests in parallel (fast)" 30 | run = [ 31 | "bundle exec parallel_rspec spec/", 32 | ] 33 | 34 | [tasks."test:serial"] 35 | description = "Run tests serially (for debugging)" 36 | run = [ 37 | "bundle exec rspec", 38 | ] 39 | 40 | [tasks.dev] 41 | description = "Run development server" 42 | run = ["./bin/dev"] 43 | 44 | [tasks.debug] 45 | description = "Run debug server" 46 | run = ["rdbg -n --open=vscode -c -- bin/rails s "] 47 | 48 | [tasks.audit] 49 | description = "Run security audit" 50 | run = ["bundle audit", "yarn audit", "osv-scanner scan source -r ."] 51 | 52 | [tasks.db-create] 53 | description = "Prepare development database" 54 | run = [ 55 | "bundle exec rails db:create", 56 | "bundle exec rails db:prepare", 57 | "bin/rails db:schema:load:cache", 58 | "bin/rails db:schema:load:queue", 59 | "bin/rails db:schema:load:cable", 60 | "bundle exec rails parallel:create" 61 | ] 62 | 63 | [tasks.db-drop] 64 | description = "Destroy development database" 65 | run = [ 66 | "bundle exec rails db:drop", 67 | "bundle exec rails parallel:drop" 68 | ] 69 | 70 | [tasks.db-reset] 71 | description = "Reset development database" 72 | run = [ 73 | "mise run db-drop", 74 | "mise run db-create" 75 | ] 76 | 77 | [tasks.restart] 78 | description = "Restart server" 79 | run = "touch tmp/restart.txt" 80 | -------------------------------------------------------------------------------- /variants/inertia_vue/mise.toml: -------------------------------------------------------------------------------- 1 | [tools] 2 | ruby = "latest" 3 | node = "latest" 4 | 5 | [env] 6 | COREPACK_ENABLE_STRICT = "0" 7 | 8 | [tasks.update] 9 | description = "Update packages" 10 | run = [ 11 | "bundle update", 12 | "yarn up", 13 | "touch tmp/restart.txt", 14 | ] 15 | 16 | [tasks.install] 17 | description = "Install packages" 18 | run = ["corepack enable", "bundle install", "yarn install"] 19 | 20 | [tasks.lint] 21 | description = "Run linters" 22 | run = [ 23 | "bundle exec rubocop -A", 24 | "bundle exec i18n-tasks normalize", 25 | "yarn run check" 26 | ] 27 | 28 | [tasks.test] 29 | description = "Run tests in parallel (fast)" 30 | run = [ 31 | "bundle exec parallel_rspec spec/", 32 | ] 33 | 34 | [tasks."test:serial"] 35 | description = "Run tests serially (for debugging)" 36 | run = [ 37 | "bundle exec rspec", 38 | ] 39 | 40 | [tasks.dev] 41 | description = "Run development server" 42 | run = ["./bin/dev"] 43 | 44 | [tasks.debug] 45 | description = "Run debug server" 46 | run = ["rdbg -n --open=vscode -c -- bin/rails s "] 47 | 48 | [tasks.audit] 49 | description = "Run security audit" 50 | run = ["bundle audit", "yarn audit", "osv-scanner scan source -r ."] 51 | 52 | [tasks.db-create] 53 | description = "Prepare development database" 54 | run = [ 55 | "bundle exec rails db:create", 56 | "bundle exec rails db:prepare", 57 | "bin/rails db:schema:load:cache", 58 | "bin/rails db:schema:load:queue", 59 | "bin/rails db:schema:load:cable", 60 | "bundle exec rails parallel:create" 61 | ] 62 | 63 | [tasks.db-drop] 64 | description = "Destroy development database" 65 | run = [ 66 | "bundle exec rails db:drop", 67 | "bundle exec rails parallel:drop" 68 | ] 69 | 70 | [tasks.db-reset] 71 | description = "Reset development database" 72 | run = [ 73 | "mise run db-drop", 74 | "mise run db-create" 75 | ] 76 | 77 | [tasks.restart] 78 | description = "Restart server" 79 | run = "touch tmp/restart.txt" 80 | -------------------------------------------------------------------------------- /variants/inertia_svelte/mise.toml: -------------------------------------------------------------------------------- 1 | [tools] 2 | ruby = "latest" 3 | node = "latest" 4 | 5 | [env] 6 | COREPACK_ENABLE_STRICT = "0" 7 | 8 | [tasks.update] 9 | description = "Update packages" 10 | run = [ 11 | "bundle update", 12 | "yarn up", 13 | "touch tmp/restart.txt", 14 | ] 15 | 16 | [tasks.install] 17 | description = "Install packages" 18 | run = ["corepack enable", "bundle install", "yarn install"] 19 | 20 | [tasks.lint] 21 | description = "Run linters" 22 | run = [ 23 | "bundle exec rubocop -A", 24 | "bundle exec i18n-tasks normalize", 25 | "yarn run check" 26 | ] 27 | 28 | [tasks.test] 29 | description = "Run tests in parallel (fast)" 30 | run = [ 31 | "bundle exec parallel_rspec spec/", 32 | ] 33 | 34 | [tasks."test:serial"] 35 | description = "Run tests serially (for debugging)" 36 | run = [ 37 | "bundle exec rspec", 38 | ] 39 | 40 | [tasks.dev] 41 | description = "Run development server" 42 | run = ["./bin/dev"] 43 | 44 | [tasks.debug] 45 | description = "Run debug server" 46 | run = ["rdbg -n --open=vscode -c -- bin/rails s "] 47 | 48 | [tasks.audit] 49 | description = "Run security audit" 50 | run = [ 51 | "bundle audit", 52 | "yarn audit", 53 | "osv-scanner scan source -r ." 54 | ] 55 | 56 | [tasks.db-create] 57 | description = "Prepare development database" 58 | run = [ 59 | "bundle exec rails db:create", 60 | "bundle exec rails db:prepare", 61 | "bin/rails db:schema:load:cache", 62 | "bin/rails db:schema:load:queue", 63 | "bin/rails db:schema:load:cable", 64 | "bundle exec rails parallel:create" 65 | ] 66 | 67 | [tasks.db-drop] 68 | description = "Destroy development database" 69 | run = [ 70 | "bundle exec rails db:drop", 71 | "bundle exec rails parallel:drop" 72 | ] 73 | 74 | [tasks.db-reset] 75 | description = "Reset development database" 76 | run = [ 77 | "mise run db-drop", 78 | "mise run db-create" 79 | ] 80 | 81 | [tasks.restart] 82 | description = "Restart server" 83 | run = "touch tmp/restart.txt" 84 | -------------------------------------------------------------------------------- /src/shared/staging_env.rb: -------------------------------------------------------------------------------- 1 | # Create staging environment based on production 2 | run "cp config/environments/production.rb config/environments/staging.rb" 3 | 4 | # Update staging environment configuration 5 | gsub_file "config/environments/staging.rb", 6 | /Rails\.application\.configure do/, 7 | "# Staging environment configuration (based on production)\nRails.application.configure do" 8 | 9 | gsub_file "config/environments/staging.rb", 10 | /config\.log_level = :info/, 11 | "config.log_level = :debug" 12 | 13 | # Add staging to database.yml 14 | append_to_file "config/database.yml" do 15 | <<-YAML 16 | 17 | staging: 18 | primary: &primary_staging 19 | <<: *default 20 | database: <%= ENV.fetch("DATABASE_NAME") { "#{app_name}_staging" } %> 21 | username: <%= ENV.fetch("DATABASE_USERNAME") { "#{app_name}" } %> 22 | password: <%= ENV["DATABASE_PASSWORD"] %> 23 | cache: 24 | <<: *primary_staging 25 | database: <%= ENV.fetch("DATABASE_NAME") { "#{app_name}_staging_cache" } %> 26 | migrations_paths: db/cache_migrate 27 | queue: 28 | <<: *primary_staging 29 | database: <%= ENV.fetch("DATABASE_NAME") { "#{app_name}_staging_queue" } %> 30 | migrations_paths: db/queue_migrate 31 | cable: 32 | <<: *primary_staging 33 | database: <%= ENV.fetch("DATABASE_NAME") { "#{app_name}_staging_cable" } %> 34 | migrations_paths: db/cable_migrate 35 | YAML 36 | end 37 | 38 | # Add staging to cable.yml if it exists 39 | if File.exist?("config/cable.yml") 40 | append_to_file "config/cable.yml" do 41 | <<-YAML 42 | 43 | staging: 44 | adapter: solid_cable 45 | connects_to: 46 | database: 47 | writing: cable 48 | polling_interval: 0.1.seconds 49 | message_retention: 1.day 50 | YAML 51 | end 52 | end 53 | 54 | # Add staging to aws.yml 55 | gsub_file "config/aws.yml", 56 | /production:\n <<: \*default/, 57 | "staging:\n <<: *default\n\nproduction:\n <<: *default" 58 | -------------------------------------------------------------------------------- /variants/inertia_react/app/frontend/pages/Terms.tsx: -------------------------------------------------------------------------------- 1 | import { usePage, Head } from '@inertiajs/react'; 2 | import Layout from '../layouts/Layout'; 3 | 4 | interface PageProps { 5 | app_name: string; 6 | [key: string]: any; 7 | } 8 | 9 | export default function Terms() { 10 | const { app_name } = usePage().props; 11 | 12 | return ( 13 | 14 | 15 |
16 |
17 |

18 | Terms of Service 19 |

20 |
21 |

22 | Welcome to {app_name}. By using our service, you agree to these terms. 23 |

24 |

1. Use of Service

25 |

26 | You may use our service in accordance with these terms and applicable laws. 27 |

28 |

2. User Accounts

29 |

30 | You are responsible for maintaining the security of your account. 31 |

32 |

3. Content

33 |

34 | You retain ownership of content you submit, but grant us a license to use it. 35 |

36 |
37 | 45 |
46 |
47 |
48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /variants/inertia_svelte/app/frontend/pages/Privacy.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | Privacy Policy - {$page.props.app_name} 8 | 9 | 10 | 11 |
12 |
13 |

14 | Privacy Policy 15 |

16 |
17 |

18 | At {$page.props.app_name}, we take your privacy seriously. This policy describes how we collect, 19 | use, and protect your personal information. 20 |

21 |

1. Information We Collect

22 |

23 | We collect information you provide directly to us, such as when you create an account. 24 |

25 |

2. How We Use Your Information

26 |

27 | We use your information to provide, maintain, and improve our services. 28 |

29 |

3. Information Sharing

30 |

31 | We do not share your personal information with third parties except as described in this policy. 32 |

33 |

4. Data Security

34 |

35 | We implement appropriate security measures to protect your information. 36 |

37 |
38 | 46 |
47 |
48 |
49 | -------------------------------------------------------------------------------- /variants/inertia_vue/Gemfile.tt: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | ### Application 4 | gem "rails", "~> <%= Rails.version %>" 5 | gem "puma"<%= gemfile_requirement("puma") %> 6 | 7 | ### Database 8 | gem "pg"<%= gemfile_requirement("pg") %> 9 | 10 | ### Solid Stack 11 | gem "solid_cache" 12 | gem "solid_queue" 13 | gem "solid_cable" 14 | gem "mission_control-jobs" 15 | 16 | ### Frontend 17 | gem "propshaft" 18 | gem "inertia_rails", "~> 3.0" 19 | gem "vite_rails", "~> 3.0" 20 | 21 | ### Encryption and Security 22 | gem "blind_index" 23 | gem "lockbox" 24 | 25 | ### Deployment 26 | gem "kamal", require: false 27 | gem "thruster", require: false 28 | 29 | ### I18n 30 | gem "i18n-tasks" 31 | gem "rails-i18n" 32 | 33 | ### File uploads 34 | gem "shrine", "~> 3.0" 35 | gem "aws-sdk-s3" 36 | 37 | ### Image processing 38 | gem "image_processing", "~> 1.2" 39 | gem "fastimage" 40 | gem "marcel", "~> 1.0", ">= 1.0.2" 41 | 42 | ### Code structure 43 | gem "anyway_config", "~> 2.0" 44 | gem "active_interaction", "~> 5.5" 45 | gem "active_decorator" 46 | gem "madmin" 47 | gem "pagy", "~> 9.0" 48 | 49 | ### Tools 50 | gem "jbuilder" 51 | gem "tzinfo-data", platforms: %i[ windows jruby ] 52 | gem "bootsnap", require: false 53 | 54 | group :development, :test do 55 | gem "debug", platforms: %i[ mri windows ], require: "debug/prelude" 56 | 57 | # security 58 | gem "brakeman", require: false 59 | gem "bundler-audit" 60 | 61 | # linters 62 | gem "standard", require: false, github: "standardrb/standard" 63 | gem "rubocop-rails", require: false 64 | gem "rubocop-rspec", require: false 65 | gem "rubocop-performance", require: false 66 | gem "rubocop-thread_safety", require: false 67 | gem "rubocop-capybara", require: false 68 | 69 | # testing 70 | gem "rspec-rails" 71 | gem "parallel_tests" 72 | gem "capybara" 73 | gem "email_spec" 74 | gem "ffaker" 75 | gem "database_cleaner-active_record" 76 | gem "factory_bot_rails" 77 | gem "shoulda-context", "~> 3.0.0.rc1" 78 | gem "shoulda-matchers", "~> 6.5.0" 79 | end 80 | 81 | group :development do 82 | ## dev tools 83 | gem "web-console" 84 | gem "amazing_print" 85 | gem "letter_opener_web", "~> 3.0" 86 | gem "rails-erd" 87 | end 88 | -------------------------------------------------------------------------------- /variants/inertia_react/Gemfile.tt: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | ### Application 4 | gem "rails", "~> <%= Rails.version %>" 5 | gem "puma"<%= gemfile_requirement("puma") %> 6 | 7 | ### Database 8 | gem "pg"<%= gemfile_requirement("pg") %> 9 | 10 | ### Solid Stack 11 | gem "solid_cache" 12 | gem "solid_queue" 13 | gem "solid_cable" 14 | gem "mission_control-jobs" 15 | 16 | ### Frontend 17 | gem "propshaft" 18 | gem "inertia_rails", "~> 3.0" 19 | gem "vite_rails", "~> 3.0" 20 | 21 | ### Encryption and Security 22 | gem "blind_index" 23 | gem "lockbox" 24 | 25 | ### Deployment 26 | gem "kamal", require: false 27 | gem "thruster", require: false 28 | 29 | ### I18n 30 | gem "i18n-tasks" 31 | gem "rails-i18n" 32 | 33 | ### File uploads 34 | gem "shrine", "~> 3.0" 35 | gem "aws-sdk-s3" 36 | 37 | ### Image processing 38 | gem "image_processing", "~> 1.2" 39 | gem "fastimage" 40 | gem "marcel", "~> 1.0", ">= 1.0.2" 41 | 42 | ### Code structure 43 | gem "anyway_config", "~> 2.0" 44 | gem "active_interaction", "~> 5.5" 45 | gem "active_decorator" 46 | gem "madmin" 47 | gem "pagy", "~> 9.0" 48 | 49 | ### Tools 50 | gem "jbuilder" 51 | gem "tzinfo-data", platforms: %i[ windows jruby ] 52 | gem "bootsnap", require: false 53 | 54 | group :development, :test do 55 | gem "debug", platforms: %i[ mri windows ], require: "debug/prelude" 56 | 57 | # security 58 | gem "brakeman", require: false 59 | gem "bundler-audit" 60 | 61 | # linters 62 | gem "standard", require: false, github: "standardrb/standard" 63 | gem "rubocop-rails", require: false 64 | gem "rubocop-rspec", require: false 65 | gem "rubocop-performance", require: false 66 | gem "rubocop-thread_safety", require: false 67 | gem "rubocop-capybara", require: false 68 | 69 | # testing 70 | gem "rspec-rails" 71 | gem "parallel_tests" 72 | gem "capybara" 73 | gem "email_spec" 74 | gem "ffaker" 75 | gem "database_cleaner-active_record" 76 | gem "factory_bot_rails" 77 | gem "shoulda-context", "~> 3.0.0.rc1" 78 | gem "shoulda-matchers", "~> 6.5.0" 79 | end 80 | 81 | group :development do 82 | ## dev tools 83 | gem "web-console" 84 | gem "amazing_print" 85 | gem "letter_opener_web", "~> 3.0" 86 | gem "rails-erd" 87 | end 88 | -------------------------------------------------------------------------------- /variants/inertia_svelte/Gemfile.tt: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | ### Application 4 | gem "rails", "~> <%= Rails.version %>" 5 | gem "puma"<%= gemfile_requirement("puma") %> 6 | 7 | ### Database 8 | gem "pg"<%= gemfile_requirement("pg") %> 9 | 10 | ### Solid Stack 11 | gem "solid_cache" 12 | gem "solid_queue" 13 | gem "solid_cable" 14 | gem "mission_control-jobs" 15 | 16 | ### Frontend 17 | gem "propshaft" 18 | gem "inertia_rails", "~> 3.0" 19 | gem "vite_rails", "~> 3.0" 20 | 21 | ### Encryption and Security 22 | gem "blind_index" 23 | gem "lockbox" 24 | 25 | ### Deployment 26 | gem "kamal", require: false 27 | gem "thruster", require: false 28 | 29 | ### I18n 30 | gem "i18n-tasks" 31 | gem "rails-i18n" 32 | 33 | ### File uploads 34 | gem "shrine", "~> 3.0" 35 | gem "aws-sdk-s3" 36 | 37 | ### Image processing 38 | gem "image_processing", "~> 1.2" 39 | gem "fastimage" 40 | gem "marcel", "~> 1.0", ">= 1.0.2" 41 | 42 | ### Code structure 43 | gem "anyway_config", "~> 2.0" 44 | gem "active_interaction", "~> 5.5" 45 | gem "active_decorator" 46 | gem "madmin" 47 | gem "pagy", "~> 9.0" 48 | 49 | ### Tools 50 | gem "jbuilder" 51 | gem "tzinfo-data", platforms: %i[ windows jruby ] 52 | gem "bootsnap", require: false 53 | 54 | group :development, :test do 55 | gem "debug", platforms: %i[ mri windows ], require: "debug/prelude" 56 | 57 | # security 58 | gem "brakeman", require: false 59 | gem "bundler-audit" 60 | 61 | # linters 62 | gem "standard", require: false, github: "standardrb/standard" 63 | gem "rubocop-rails", require: false 64 | gem "rubocop-rspec", require: false 65 | gem "rubocop-performance", require: false 66 | gem "rubocop-thread_safety", require: false 67 | gem "rubocop-capybara", require: false 68 | 69 | # testing 70 | gem "rspec-rails" 71 | gem "parallel_tests" 72 | gem "capybara" 73 | gem "email_spec" 74 | gem "ffaker" 75 | gem "database_cleaner-active_record" 76 | gem "factory_bot_rails" 77 | gem "shoulda-context", "~> 3.0.0.rc1" 78 | gem "shoulda-matchers", "~> 6.5.0" 79 | end 80 | 81 | group :development do 82 | ## dev tools 83 | gem "web-console" 84 | gem "amazing_print" 85 | gem "letter_opener_web", "~> 3.0" 86 | gem "rails-erd" 87 | end 88 | -------------------------------------------------------------------------------- /variants/inertia_vue/app/frontend/pages/Privacy.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 51 | -------------------------------------------------------------------------------- /variants/esbuild_tailwind/Gemfile.tt: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | ### Application 4 | gem "rails", "~> <%= Rails.version %>" 5 | gem "puma"<%= gemfile_requirement("puma") %> 6 | 7 | ### Database 8 | gem "pg"<%= gemfile_requirement("pg") %> 9 | 10 | ### Solid Stack 11 | gem "solid_cache" 12 | gem "solid_queue" 13 | gem "solid_cable" 14 | gem "mission_control-jobs" 15 | 16 | ### Frontend 17 | gem "propshaft" 18 | gem "jsbundling-rails" 19 | gem "cssbundling-rails" 20 | gem "turbo-rails" 21 | gem "stimulus-rails" 22 | gem "better_html" 23 | gem "inline_svg" 24 | gem "lookbook" 25 | gem "view_component" 26 | 27 | ### Encryption and Security 28 | gem "blind_index" 29 | gem "lockbox" 30 | 31 | ### Deployment 32 | gem "kamal", require: false 33 | gem "thruster", require: false 34 | 35 | ### I18n 36 | gem "i18n-tasks" 37 | gem "rails-i18n" 38 | 39 | ### File uploads 40 | gem "shrine", "~> 3.0" 41 | gem "aws-sdk-s3" 42 | 43 | ### Image processing 44 | gem "image_processing", "~> 1.2" 45 | gem "fastimage" 46 | gem "marcel", "~> 1.0", ">= 1.0.2" 47 | 48 | ### Code structure 49 | gem "anyway_config", "~> 2.0" 50 | gem "active_interaction", "~> 5.5" 51 | gem "active_decorator" 52 | gem "madmin" 53 | gem "pagy", "~> 9.0" 54 | 55 | ### Tools 56 | gem "jbuilder" 57 | gem "tzinfo-data", platforms: %i[ windows jruby ] 58 | gem "bootsnap", require: false 59 | 60 | group :development, :test do 61 | gem "debug", platforms: %i[ mri windows ], require: "debug/prelude" 62 | 63 | # security 64 | gem "brakeman", require: false 65 | gem "bundler-audit" 66 | 67 | # linters 68 | gem "standard", require: false, github: "standardrb/standard" 69 | gem "rubocop-rails", require: false 70 | gem "rubocop-rspec", require: false 71 | gem "rubocop-performance", require: false 72 | gem "rubocop-thread_safety", require: false 73 | gem "rubocop-capybara", require: false 74 | gem "erb_lint", require: false 75 | 76 | # testing 77 | gem "rspec-rails" 78 | gem "parallel_tests" 79 | gem "capybara" 80 | gem "email_spec" 81 | gem "ffaker" 82 | gem "database_cleaner-active_record" 83 | gem "factory_bot_rails" 84 | gem "shoulda-context", "~> 3.0.0.rc1" 85 | gem "shoulda-matchers", "~> 6.5.0" 86 | end 87 | 88 | group :development do 89 | ## dev tools 90 | gem "web-console" 91 | gem "amazing_print" 92 | gem "letter_opener_web", "~> 3.0" 93 | gem "rails-erd" 94 | end 95 | -------------------------------------------------------------------------------- /variants/importmap_tailwind/Gemfile.tt: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | ### Application 4 | gem "rails", "~> <%= Rails.version %>" 5 | gem "puma"<%= gemfile_requirement("puma") %> 6 | 7 | ### Database 8 | gem "pg"<%= gemfile_requirement("pg") %> 9 | 10 | ### Solid Stack 11 | gem "solid_cache" 12 | gem "solid_queue" 13 | gem "solid_cable" 14 | gem "mission_control-jobs" 15 | 16 | ### Frontend 17 | gem "propshaft" 18 | gem "importmap-rails" 19 | gem "tailwindcss-rails" 20 | gem "turbo-rails" 21 | gem "stimulus-rails" 22 | gem "better_html" 23 | gem "inline_svg" 24 | gem "lookbook" 25 | gem "view_component" 26 | 27 | ### Encryption and Security 28 | gem "blind_index" 29 | gem "lockbox" 30 | 31 | ### Deployment 32 | gem "kamal", require: false 33 | gem "thruster", require: false 34 | 35 | ### I18n 36 | gem "i18n-tasks" 37 | gem "rails-i18n" 38 | 39 | ### File uploads 40 | gem "shrine", "~> 3.0" 41 | gem "aws-sdk-s3" 42 | 43 | ### Image processing 44 | gem "image_processing", "~> 1.2" 45 | gem "fastimage" 46 | gem "marcel", "~> 1.0", ">= 1.0.2" 47 | 48 | ### Code structure 49 | gem "anyway_config", "~> 2.0" 50 | gem "active_interaction", "~> 5.5" 51 | gem "active_decorator" 52 | gem "madmin" 53 | gem "pagy", "~> 9.0" 54 | 55 | ### Tools 56 | gem "jbuilder" 57 | gem "tzinfo-data", platforms: %i[ windows jruby ] 58 | gem "bootsnap", require: false 59 | 60 | group :development, :test do 61 | gem "debug", platforms: %i[ mri windows ], require: "debug/prelude" 62 | 63 | # security 64 | gem "brakeman", require: false 65 | gem "bundler-audit" 66 | 67 | # linters 68 | gem "standard", require: false, github: "standardrb/standard" 69 | gem "rubocop-rails", require: false 70 | gem "rubocop-rspec", require: false 71 | gem "rubocop-performance", require: false 72 | gem "rubocop-thread_safety", require: false 73 | gem "rubocop-capybara", require: false 74 | gem "erb_lint", require: false 75 | 76 | # testing 77 | gem "rspec-rails" 78 | gem "parallel_tests" 79 | gem "capybara" 80 | gem "email_spec" 81 | gem "ffaker" 82 | gem "database_cleaner-active_record" 83 | gem "factory_bot_rails" 84 | gem "shoulda-context", "~> 3.0.0.rc1" 85 | gem "shoulda-matchers", "~> 6.5.0" 86 | end 87 | 88 | group :development do 89 | ## dev tools 90 | gem "web-console" 91 | gem "amazing_print" 92 | gem "letter_opener_web", "~> 3.0" 93 | gem "rails-erd" 94 | end 95 | -------------------------------------------------------------------------------- /variants/inertia_svelte/app/frontend/lib/components/Flash.svelte: -------------------------------------------------------------------------------- 1 | 49 | 50 | {#if visible && getFlashType()} 51 |
52 |
53 |
54 |

{getFlashMessage()}

55 | 68 |
69 |
70 |
71 | {/if} 72 | -------------------------------------------------------------------------------- /variants/inertia_react/app/frontend/pages/Privacy.tsx: -------------------------------------------------------------------------------- 1 | import { usePage, Head } from '@inertiajs/react'; 2 | import Layout from '../layouts/Layout'; 3 | 4 | interface PageProps { 5 | app_name: string; 6 | [key: string]: any; 7 | } 8 | 9 | export default function Privacy() { 10 | const { app_name } = usePage().props; 11 | 12 | return ( 13 | 14 | 15 |
16 |
17 |

18 | Privacy Policy 19 |

20 |
21 |

22 | At {app_name}, we take your privacy seriously. This policy describes how we collect, 23 | use, and protect your personal information. 24 |

25 |

1. Information We Collect

26 |

27 | We collect information you provide directly to us, such as when you create an account. 28 |

29 |

2. How We Use Your Information

30 |

31 | We use your information to provide, maintain, and improve our services. 32 |

33 |

3. Information Sharing

34 |

35 | We do not share your personal information with third parties except as described in this policy. 36 |

37 |

4. Data Security

38 |

39 | We implement appropriate security measures to protect your information. 40 |

41 |
42 | 50 |
51 |
52 |
53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /src/shared/gems_shrine.rb: -------------------------------------------------------------------------------- 1 | copy_file "config/settings/aws_config.rb", force: true 2 | copy_file "config/aws.yml", force: true 3 | 4 | initializer "shrine.rb", <<-CODE 5 | require "shrine" 6 | require "shrine/storage/file_system" 7 | require "shrine/storage/memory" 8 | require "shrine/storage/s3" 9 | 10 | Shrine.plugin :activerecord 11 | Shrine.plugin :backgrounding 12 | Shrine.plugin :cached_attachment_data 13 | Shrine.plugin :derivatives 14 | Shrine.plugin :determine_mime_type, analyzer: :marcel 15 | Shrine.plugin :download_endpoint, prefix: "downloads" 16 | Shrine.plugin :instrumentation, notifications: ActiveSupport::Notifications unless Rails.env.test? 17 | Shrine.plugin :pretty_location 18 | Shrine.plugin :remove_attachment 19 | Shrine.plugin :restore_cached_data 20 | Shrine.plugin :store_dimensions, analyzer: :fastimage 21 | Shrine.plugin :validation 22 | Shrine.plugin :validation_helpers 23 | 24 | ### Storages 25 | s3_options = { 26 | access_key_id: AwsConfig.access_key_id, 27 | secret_access_key: AwsConfig.secret_access_key, 28 | region: AwsConfig.s3_region, 29 | bucket: AwsConfig.s3_bucket 30 | } 31 | 32 | Shrine.storages = 33 | if Rails.env.production? 34 | { 35 | cache: Shrine::Storage::S3.new(prefix: "cache", **s3_options), 36 | store: Shrine::Storage::S3.new(prefix: "store", **s3_options) 37 | } 38 | elsif Rails.env.test? 39 | { 40 | cache: Shrine::Storage::Memory.new, 41 | store: Shrine::Storage::Memory.new 42 | } 43 | else # development and staging 44 | { 45 | cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"), 46 | store: Shrine::Storage::FileSystem.new("public", prefix: "uploads") 47 | } 48 | end 49 | 50 | ### Backgrounding 51 | Shrine::Attacher.promote_block do 52 | job_class = Shrine::PromoteAttachmentJob 53 | 54 | if Rails.env.production? 55 | job_class.perform_later(self.class.name, record.class.name, record.id, name, file_data) 56 | else 57 | job_class.perform_now(self.class.name, record.class.name, record.id, name, file_data) 58 | end 59 | end 60 | 61 | Shrine::Attacher.destroy_block do 62 | job_class = Shrine::DestroyAttachmentJob 63 | 64 | if Rails.env.production? 65 | job_class.perform_later(self.class.name, data) 66 | else 67 | job_class.perform_now(self.class.name, data) 68 | end 69 | end 70 | CODE 71 | 72 | directory "app/uploaders", force: true 73 | -------------------------------------------------------------------------------- /variants/inertia_vue/app/frontend/lib/components/Flash.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 77 | -------------------------------------------------------------------------------- /src/inertia_shared/cleanup.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Cleanup unnecessary files created by the Inertia Rails generator 4 | # 5 | # The inertia:install generator creates some files that are not needed 6 | # for our Kickstart configuration: 7 | # 1. package-lock.json - we use Yarn, not npm 8 | # 2. app/frontend/entrypoints/application.js - not used with our Vite setup 9 | # 3. app/assets/stylesheets/ - we use Tailwind via Vite, not the asset pipeline 10 | 11 | say "Cleaning up unnecessary files created by Inertia generator..." 12 | 13 | # Remove package-lock.json if it exists (we use Yarn) 14 | if File.exist?("package-lock.json") 15 | remove_file "package-lock.json" 16 | say " ✓ Removed package-lock.json (using Yarn)", :green 17 | end 18 | 19 | # Remove app/frontend/entrypoints/application.js if it exists 20 | # Inertia uses app/frontend/entrypoints/inertia.{js,ts,tsx} instead 21 | application_js_path = "app/frontend/entrypoints/application.js" 22 | if File.exist?(application_js_path) 23 | remove_file application_js_path 24 | say " ✓ Removed #{application_js_path} (not used)", :green 25 | end 26 | 27 | # Remove app/assets/stylesheets directory if it exists and is empty or only contains application.css 28 | stylesheets_dir = "app/assets/stylesheets" 29 | if Dir.exist?(stylesheets_dir) 30 | # Check if directory only contains application.css or is empty 31 | files = Dir.glob("#{stylesheets_dir}/**/*").select { |f| File.file?(f) } 32 | 33 | if files.empty? 34 | FileUtils.rm_rf(stylesheets_dir) 35 | say " ✓ Removed empty #{stylesheets_dir} directory", :green 36 | elsif files.size == 1 && files.first.end_with?("application.css") 37 | # Remove application.css and then the directory 38 | remove_file files.first 39 | FileUtils.rm_rf(stylesheets_dir) 40 | say " ✓ Removed #{stylesheets_dir} (using Tailwind via Vite)", :green 41 | end 42 | end 43 | 44 | # Fix tsconfig.node.json to reference vite.config.mts instead of vite.config.ts 45 | # The inertia:install generator creates tsconfig.node.json with "vite.config.ts" 46 | # but the actual file created is "vite.config.mts" 47 | tsconfig_node_path = "tsconfig.node.json" 48 | if File.exist?(tsconfig_node_path) 49 | tsconfig_content = File.read(tsconfig_node_path) 50 | if tsconfig_content.include?('"include": ["vite.config.ts"]') 51 | gsub_file tsconfig_node_path, '"include": ["vite.config.ts"]', '"include": ["vite.config.mts"]' 52 | say " ✓ Fixed #{tsconfig_node_path} to reference vite.config.mts", :green 53 | end 54 | end 55 | 56 | say "Cleanup complete!", :green 57 | -------------------------------------------------------------------------------- /variants/inertia_react/app/frontend/lib/components/Flash.tsx: -------------------------------------------------------------------------------- 1 | import { usePage } from '@inertiajs/react'; 2 | import { useEffect, useState } from 'react'; 3 | 4 | interface PageProps { 5 | flash: { 6 | notice?: string; 7 | alert?: string; 8 | error?: string; 9 | }; 10 | [key: string]: any; 11 | } 12 | 13 | export default function Flash() { 14 | const { flash } = usePage().props; 15 | const [visible, setVisible] = useState(false); 16 | 17 | useEffect(() => { 18 | if (flash?.notice || flash?.alert || flash?.error) { 19 | setVisible(true); 20 | const timer = setTimeout(() => { 21 | setVisible(false); 22 | }, 5000); 23 | return () => clearTimeout(timer); 24 | } 25 | }, [flash]); 26 | 27 | const getFlashType = (): 'notice' | 'alert' | 'error' | null => { 28 | if (flash?.notice) return 'notice'; 29 | if (flash?.alert) return 'alert'; 30 | if (flash?.error) return 'error'; 31 | return null; 32 | }; 33 | 34 | const getFlashMessage = (): string => { 35 | const type = getFlashType(); 36 | if (!type) return ''; 37 | return flash[type] as string; 38 | }; 39 | 40 | const getFlashStyles = (): string => { 41 | const type = getFlashType(); 42 | switch (type) { 43 | case 'notice': 44 | return 'bg-green-50 text-green-800 border-green-200'; 45 | case 'error': 46 | return 'bg-red-50 text-red-800 border-red-200'; 47 | case 'alert': 48 | return 'bg-yellow-50 text-yellow-800 border-yellow-200'; 49 | default: 50 | return ''; 51 | } 52 | }; 53 | 54 | if (!visible || !getFlashType()) { 55 | return null; 56 | } 57 | 58 | return ( 59 |
60 |
61 |
62 |

{getFlashMessage()}

63 | 76 |
77 |
78 |
79 | ); 80 | } 81 | -------------------------------------------------------------------------------- /variants/shared/app/helpers/active_link_to_helper.rb: -------------------------------------------------------------------------------- 1 | module ActiveLinkToHelper 2 | # Creates a link with 'active' class when current page matches the target 3 | # Simplified version of active_link_to gem with only essential functionality 4 | # 5 | # Examples: 6 | # active_link_to "Home", root_path 7 | # active_link_to "Users", users_path, match: :exact 8 | # active_link_to "Dashboard", dashboard_path, active_class: "current" 9 | # 10 | # @param name [String] Link text 11 | # @param target [String, Hash] Path or URL for the link 12 | # @param options [Hash] Additional options 13 | # @option options [Symbol] :match (:inclusive) Match type - :exact, :inclusive 14 | # @option options [String] :active_class ("active") CSS class for active state 15 | # @option options [Hash] :html_options ({}) Standard link_to HTML options 16 | def active_link_to(name, target = nil, options = {}, &block) 17 | # Handle block syntax like standard link_to 18 | if block_given? 19 | options = target || {} 20 | target = name 21 | name = capture(&block) 22 | end 23 | 24 | # Set default options 25 | match_type = options.delete(:match) || :inclusive 26 | active_class = options.delete(:active_class) || "active" 27 | 28 | # Support legacy html_options parameter 29 | if options[:html_options] 30 | html_options = options.delete(:html_options) 31 | options = options.merge(html_options) 32 | end 33 | 34 | # Determine if link should be active 35 | is_active = link_active?(target, match_type) 36 | 37 | # Add active class if needed 38 | if is_active 39 | current_classes = options[:class].to_s 40 | css_classes = [current_classes, active_class].compact.reject(&:empty?).join(" ") 41 | options[:class] = css_classes unless css_classes.empty? 42 | end 43 | 44 | # Generate the link 45 | link_to(name, target, options) 46 | end 47 | 48 | private 49 | 50 | # Check if the link should be considered active 51 | # @param target [String, Hash] The link target 52 | # @param match_type [Symbol] How to match - :exact or :inclusive 53 | # @return [Boolean] Whether the link is active 54 | def link_active?(target, match_type) 55 | target_path = normalize_path(target) 56 | current_path = request.path 57 | 58 | case match_type 59 | when :exact 60 | current_path == target_path 61 | when :inclusive 62 | return true if current_path == target_path 63 | return false if target_path == "/" 64 | current_path.start_with?(target_path + "/") || current_path.start_with?(target_path + "?") || current_path.start_with?(target_path + "#") 65 | else 66 | false 67 | end 68 | rescue 69 | # Return false if path generation fails 70 | false 71 | end 72 | 73 | # Convert target to normalized path string 74 | # @param target [String, Hash] Link target 75 | # @return [String] Normalized path 76 | def normalize_path(target) 77 | case target 78 | when String 79 | target 80 | when Hash 81 | url_for(target) 82 | else 83 | polymorphic_path(target) 84 | end 85 | rescue 86 | # Return empty string if path generation fails 87 | "" 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /src/inertia_shared/gems_inertia.rb: -------------------------------------------------------------------------------- 1 | # Determine framework from template name 2 | framework = case TEMPLATE_NAME 3 | when "inertia_react" then "react" 4 | when "inertia_vue" then "vue" 5 | when "inertia_svelte" then "svelte" 6 | else 7 | raise "Unknown Inertia template: #{TEMPLATE_NAME}" 8 | end 9 | 10 | # Run Inertia Rails generator with the appropriate framework 11 | # Generator will install Vite automatically and modify application.html.erb 12 | # Options: 13 | # --framework - Use React/Vue/Svelte for UI 14 | # --typescript - Enable TypeScript support 15 | # --no-interactive - Skip interactive prompts 16 | # --tailwind - Install and configure Tailwind CSS 17 | # --vite - Use Vite for bundling 18 | # --no-example-page - Skip creating example page (we'll create our own) 19 | # --force - Force overwrite bin/dev and other files 20 | rails_command "generate inertia:install --framework=#{framework} --typescript --no-interactive --tailwind --vite --no-example-page --force" 21 | 22 | # Fix bin/vite creation - vite_ruby's install command uses deprecated bundler --path flag 23 | # Create the binstub manually using modern bundler syntax 24 | run "bundle binstub vite_ruby" 25 | 26 | # Create Inertia controller concern for shared data 27 | create_file "app/controllers/concerns/inertia_share.rb", <<-RUBY 28 | module InertiaShare 29 | extend ActiveSupport::Concern 30 | 31 | included do 32 | inertia_share do 33 | { 34 | app_name: MainConfig.app_name, 35 | flash: { 36 | notice: flash[:notice], 37 | alert: flash[:alert], 38 | error: flash[:error] 39 | }.compact 40 | } 41 | end 42 | end 43 | end 44 | RUBY 45 | 46 | # Update ApplicationController to include InertiaShare 47 | inject_into_file "app/controllers/application_controller.rb", after: "class ApplicationController < ActionController::Base\n" do 48 | " include InertiaShare\n" 49 | end 50 | 51 | # Copy base layouts, lib (with components), and pages 52 | directory "app/frontend/layouts", "app/frontend/layouts" 53 | directory "app/frontend/lib", "app/frontend/lib" 54 | directory "app/frontend/pages", "app/frontend/pages" 55 | 56 | # Create landing controller and route for home page 57 | create_file "app/controllers/landing_controller.rb", <<-RUBY 58 | class LandingController < ApplicationController 59 | def home 60 | render inertia: "Home" 61 | end 62 | 63 | def about 64 | render inertia: "About" 65 | end 66 | end 67 | RUBY 68 | 69 | # Create pages controller for static pages 70 | create_file "app/controllers/pages_controller.rb", <<-RUBY 71 | class PagesController < ApplicationController 72 | def terms 73 | render inertia: "Terms" 74 | end 75 | 76 | def privacy 77 | render inertia: "Privacy" 78 | end 79 | end 80 | RUBY 81 | 82 | # Copy routes.rb and route partials (after Inertia generator to avoid overwriting) 83 | copy_file "config/routes.rb", force: true 84 | 85 | # Copy individual route files from variants 86 | # Note: directory command doesn't work well with source_paths priority 87 | empty_directory "config/routes" 88 | copy_file "config/routes/landing.rb", force: true 89 | copy_file "config/routes/pages.rb", force: true 90 | copy_file "config/routes/support.rb", force: true 91 | 92 | # These files are in inertia_shared (lower priority in source_paths) 93 | # so we need to copy them explicitly from the template source 94 | inside "config/routes" do 95 | create_file "dev.rb", File.read(File.join(source_paths[1], "config/routes/dev.rb")) 96 | create_file "errors.rb", File.read(File.join(source_paths[1], "config/routes/errors.rb")) 97 | end 98 | 99 | # Configure Inertia Rails 100 | create_file "config/initializers/inertia.rb", <<~RUBY 101 | # frozen_string_literal: true 102 | 103 | # Inertia Rails Configuration 104 | # https://inertia-rails.dev 105 | 106 | InertiaRails.configure do |config| 107 | # Always include errors hash for Inertia protocol compliance (InertiaRails 4.0+) 108 | config.always_include_errors_hash = true 109 | end 110 | RUBY 111 | 112 | say "✓ Inertia Rails configured with #{framework}", :green 113 | -------------------------------------------------------------------------------- /src/inertia_shared/finalize_layouts.rb: -------------------------------------------------------------------------------- 1 | # Finalize layouts after Inertia generator has run 2 | # This ensures we have our custom layout with all necessary tags 3 | 4 | say "Finalizing Inertia layouts..." 5 | 6 | # Copy our final application layout (with proper Inertia and Vite tags) 7 | copy_file "app/views/layouts/application.html.erb", force: true 8 | 9 | # Update tsconfig to add $lib/* path mapping 10 | # React/Vue use tsconfig.app.json, Svelte uses tsconfig.json 11 | tsconfig_file = if File.exist?("tsconfig.app.json") 12 | "tsconfig.app.json" 13 | elsif File.exist?("tsconfig.json") 14 | "tsconfig.json" 15 | end 16 | 17 | if tsconfig_file 18 | inject_into_file tsconfig_file, after: '"paths": {' do 19 | "\n \"$lib/*\": [\"app/frontend/lib/*\"]," 20 | end 21 | say "✓ Added $lib/* path mapping to #{tsconfig_file}", :green 22 | else 23 | say "⚠ Warning: tsconfig file not found, skipping $lib/* path mapping", :yellow 24 | end 25 | 26 | # Install @types/node for TypeScript support in vite.config 27 | # This provides type definitions for path, __dirname, etc. 28 | run "yarn add -D @types/node" 29 | 30 | # Set package.json type to module for ES modules support 31 | # Svelte generator doesn't add these fields, so we add them after yarn install 32 | if File.exist?("package.json") 33 | require "json" 34 | package_json_path = "package.json" 35 | package_data = JSON.parse(File.read(package_json_path)) 36 | 37 | needs_update = false 38 | 39 | # Add "private": true if not present 40 | unless package_data.key?("private") 41 | package_data["private"] = true 42 | needs_update = true 43 | say "✓ Adding private: true to package.json", :green 44 | end 45 | 46 | # Add "type": "module" if not present 47 | unless package_data.key?("type") 48 | package_data["type"] = "module" 49 | needs_update = true 50 | say "✓ Adding type: module to package.json", :green 51 | end 52 | 53 | if needs_update 54 | # Write back to file, preserving JSON format 55 | File.write(package_json_path, JSON.pretty_generate(package_data) + "\n") 56 | say "✓ Updated package.json", :green 57 | end 58 | end 59 | 60 | # Update tsconfig.node.json to include node types 61 | # This allows TypeScript to recognize node modules in vite.config 62 | if File.exist?("tsconfig.node.json") 63 | inject_into_file "tsconfig.node.json", after: '"noEmit": true' do 64 | ', 65 | "types": ["node"]' 66 | end 67 | say "✓ Added node types to tsconfig.node.json", :green 68 | end 69 | 70 | # Update vite.config to add resolve.alias for runtime resolution 71 | # This is needed for Vite to resolve the paths correctly 72 | vite_config = if File.exist?("vite.config.ts") 73 | "vite.config.ts" 74 | elsif File.exist?("vite.config.mts") 75 | "vite.config.mts" 76 | end 77 | 78 | if vite_config 79 | is_mts = vite_config.end_with?(".mts") 80 | 81 | # Add imports at the top 82 | if is_mts 83 | # For ES modules (.mts), need to define __dirname 84 | inject_into_file vite_config, after: "import RubyPlugin from 'vite-plugin-ruby'\n" do 85 | <<~JS 86 | import path from "path"; 87 | import { fileURLToPath } from "url"; 88 | 89 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 90 | JS 91 | end 92 | else 93 | # For regular .ts files 94 | inject_into_file vite_config, after: "import RubyPlugin from 'vite-plugin-ruby'\n" do 95 | 'import path from "path"\n' 96 | end 97 | end 98 | 99 | # Add resolve.alias config 100 | inject_into_file vite_config, after: "export default defineConfig({\n" do 101 | <<~JS 102 | resolve: { 103 | alias: { 104 | "@": path.resolve(__dirname, "./app/frontend"), 105 | "~": path.resolve(__dirname, "./app/frontend"), 106 | "$lib": path.resolve(__dirname, "./app/frontend/lib"), 107 | }, 108 | }, 109 | JS 110 | end 111 | say "✓ Added resolve.alias to #{vite_config}", :green 112 | else 113 | say "⚠ Warning: vite.config not found, skipping alias setup", :yellow 114 | end 115 | 116 | say "Layouts finalized with Inertia and Vite configuration" 117 | -------------------------------------------------------------------------------- /api.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | #============================================================================== 4 | # CONSTANTS - Template configuration and shared values 5 | #============================================================================== 6 | 7 | REPO_LINK = "https://github.com/alec-c4/kickstart.git" 8 | AVAILABLE_TEMPLATE_NAMES = %w[api importmap_tailwind esbuild_tailwind inertia_svelte inertia_react inertia_vue].freeze 9 | TEMPLATE_NAME = "api" 10 | RAILS_REQUIREMENT = ">= 8.1.0" 11 | 12 | TEMPLATE_METADATA = { 13 | name: "api", 14 | description: "Rails API-only application with essential setup", 15 | features: %w[postgresql devcontainer rspec rubocop uuid i18n kamal solid_queue solid_cache solid_cable], 16 | rails_version: RAILS_REQUIREMENT 17 | }.freeze 18 | 19 | #============================================================================== 20 | # SHARED CODE - Embedded functions (auto-generated, do not edit manually) 21 | #============================================================================== 22 | 23 | require "rails/all" 24 | 25 | def assert_minimum_rails_version 26 | requirement = Gem::Requirement.new(RAILS_REQUIREMENT) 27 | rails_version = Gem::Version.new(Rails::VERSION::STRING) 28 | return if requirement.satisfied_by?(rails_version) 29 | 30 | prompt = "This template requires Rails #{RAILS_REQUIREMENT}. "\ 31 | "You are using #{rails_version}. Continue anyway?" 32 | exit 1 if no?(prompt) 33 | end 34 | 35 | def gemfile_requirement(name) 36 | @original_gemfile ||= IO.read("Gemfile") 37 | req = @original_gemfile[/gem\s+['"]#{name}['"]\s*(,[><~= \t\d.\w'"]*)?.*$/, 1] 38 | req && req.tr("'", %(")).strip.sub(/^,\s*"/, ', "') 39 | end 40 | 41 | def add_template_repository_to_source_path 42 | if __FILE__.match?(%r{\Ahttps?://}) 43 | require "tmpdir" 44 | source_paths.unshift(tempdir = Dir.mktmpdir("kickstart-tmp")) 45 | at_exit { FileUtils.remove_entry(tempdir) } 46 | git clone: [ 47 | "--quiet", 48 | REPO_LINK, 49 | tempdir 50 | ].map(&:shellescape).join(" ") 51 | 52 | # Check for specific branch in URL path (e.g., /kickstart/branch_name/template.rb) 53 | template_pattern = "(?:#{AVAILABLE_TEMPLATE_NAMES.join('|')})" 54 | if (branch = __FILE__[%r{kickstart/(.+)/#{template_pattern}\.rb}, 1]) 55 | Dir.chdir(tempdir) { git checkout: branch } 56 | end 57 | else 58 | # For local files, add the template root directory 59 | template_root = File.dirname(__FILE__) 60 | source_paths.unshift(template_root) 61 | end 62 | end 63 | 64 | def set_variant_source_path(variant_name = nil) 65 | template_root = if __FILE__.match?(%r{\Ahttps?://}) 66 | source_paths.first 67 | else 68 | File.dirname(__FILE__) 69 | end 70 | 71 | if variant_name 72 | variant_path = File.join(template_root, "variants", variant_name) 73 | source_paths.unshift(variant_path) if File.directory?(variant_path) 74 | end 75 | 76 | shared_path = File.join(template_root, "variants", "shared") 77 | source_paths.unshift(shared_path) if File.directory?(shared_path) 78 | end 79 | 80 | def show_post_install_message 81 | say "\n 82 | ######################################################################################### 83 | 84 | Rails application '#{app_name}' created successfully! 85 | 86 | Next steps: 87 | $ cd #{app_name} 88 | $ bundle install 89 | $ rails db:create db:migrate 90 | $ rails parallel:create # Creates parallel test databases (ignore 'already exists' message) 91 | $ bin/dev 92 | 93 | #########################################################################################\n", :green 94 | end 95 | 96 | #============================================================================== 97 | # TEMPLATE LOGIC - Template-specific configuration and workflow 98 | #============================================================================== 99 | 100 | assert_minimum_rails_version 101 | add_template_repository_to_source_path 102 | set_variant_source_path(TEMPLATE_NAME) 103 | 104 | apply "src/shared/general.rb" 105 | apply "src/shared/packages.rb" 106 | 107 | after_bundle do 108 | apply "src/shared/init_generators.rb" 109 | apply "src/shared/init_db_cli.rb" 110 | apply "src/shared/solid_queue_setup.rb" 111 | apply "src/shared/init_i18n.rb" 112 | 113 | apply "src/shared/env_rubocop.rb" 114 | apply "src/shared/migrations_uuid.rb" 115 | 116 | apply "src/shared/gems_anyway_config.rb" 117 | apply "src/shared/gems_pagy.rb" 118 | apply "src/shared/gems_active_interaction.rb" 119 | apply "src/shared/gems_rspec.rb" 120 | apply "src/shared/gems_i18n_tasks.rb" 121 | 122 | apply "src/shared/gems_lockbox.rb" 123 | apply "src/shared/gems_shrine.rb" 124 | 125 | apply "src/shared/ci.rb" 126 | 127 | apply "src/shared/docs.rb" 128 | apply "src/shared/staging_env.rb" 129 | apply "src/shared/run_rubocop.rb" 130 | apply "src/shared/git_init.rb" 131 | 132 | show_post_install_message 133 | end 134 | -------------------------------------------------------------------------------- /variants/shared/config/database.yml.tt: -------------------------------------------------------------------------------- 1 | # PostgreSQL. Versions 9.3 and up are supported. 2 | # 3 | # Install the pg driver: 4 | # gem install pg 5 | # On macOS with Homebrew: 6 | # gem install pg -- --with-pg-config=/opt/homebrew/bin/pg_config 7 | # On Windows: 8 | # gem install pg 9 | # Choose the win32 build. 10 | # Install PostgreSQL and put its /bin directory on your path. 11 | # 12 | # Configure Using Gemfile 13 | # gem "pg" 14 | # 15 | default: &default 16 | adapter: postgresql 17 | encoding: unicode 18 | # For details on connection pooling, see Rails configuration guide 19 | # https://guides.rubyonrails.org/configuring.html#database-pooling 20 | max_connections: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 21 | <%% if ENV["DB_HOST"] %> 22 | host: <%%= ENV["DB_HOST"] %> 23 | username: postgres 24 | password: postgres 25 | <%% end %> 26 | 27 | 28 | development: 29 | primary: &primary_development 30 | <<: *default 31 | database: <%= app_name %>_development 32 | cache: 33 | <<: *primary_development 34 | database: <%= app_name %>_development_cache 35 | migrations_paths: db/cache_migrate 36 | queue: 37 | <<: *primary_development 38 | database: <%= app_name %>_development_queue 39 | migrations_paths: db/queue_migrate 40 | cable: 41 | <<: *primary_development 42 | database: <%= app_name %>_development_cable 43 | migrations_paths: db/cable_migrate 44 | 45 | # The specified database role being used to connect to PostgreSQL. 46 | # To create additional roles in PostgreSQL see `$ createuser --help`. 47 | # When left blank, PostgreSQL will use the default role. This is 48 | # the same name as the operating system user running Rails. 49 | #username: <%= app_name %> 50 | 51 | # The password associated with the PostgreSQL role (username). 52 | #password: 53 | 54 | # Connect on a TCP socket. Omitted by default since the client uses a 55 | # domain socket that doesn't need configuration. Windows does not have 56 | # domain sockets, so uncomment these lines. 57 | #host: localhost 58 | 59 | # The TCP port the server listens on. Defaults to 5432. 60 | # If your server runs on a different port number, change accordingly. 61 | #port: 5432 62 | 63 | # Schema search path. The server defaults to $user,public 64 | #schema_search_path: myapp,sharedapp,public 65 | 66 | # Minimum log levels, in increasing order: 67 | # debug5, debug4, debug3, debug2, debug1, 68 | # log, notice, warning, error, fatal, and panic 69 | # Defaults to warning. 70 | #min_messages: notice 71 | 72 | # Warning: The database defined as "test" will be erased and 73 | # re-generated from your development database when you run "rake". 74 | # Do not set this db to the same as development or production. 75 | test: 76 | <<: *default 77 | database: <%= app_name %>_test<%%= ENV['TEST_ENV_NUMBER'] %> 78 | 79 | # As with config/credentials.yml, you never want to store sensitive information, 80 | # like your database password, in your source code. If your source code is 81 | # ever seen by anyone, they now have access to your database. 82 | # 83 | # Instead, provide the password or a full connection URL as an environment 84 | # variable when you boot the app. For example: 85 | # 86 | # DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" 87 | # 88 | # If the connection URL is provided in the special DATABASE_URL environment 89 | # variable, Rails will automatically merge its configuration values on top of 90 | # the values provided in this file. Alternatively, you can specify a connection 91 | # URL environment variable explicitly: 92 | # 93 | # production: 94 | # url: <%%= ENV["MY_APP_DATABASE_URL"] %> 95 | # 96 | # Connection URLs for non-primary databases can also be configured using 97 | # environment variables. The variable name is formed by concatenating the 98 | # connection name with `_DATABASE_URL`. For example: 99 | # 100 | # CACHE_DATABASE_URL="postgres://cacheuser:cachepass@localhost/cachedatabase" 101 | # 102 | # Read https://guides.rubyonrails.org/configuring.html#configuring-a-database 103 | # for a full overview on how database connection configuration can be specified. 104 | # 105 | production: 106 | primary: &primary_production 107 | <<: *default 108 | database: <%= app_name %>_production 109 | username: <%= app_name %> 110 | password: <%%= ENV["<%= app_name.upcase %>_DATABASE_PASSWORD"] %> 111 | cache: 112 | <<: *primary_production 113 | database: <%= app_name %>_production_cache 114 | migrations_paths: db/cache_migrate 115 | queue: 116 | <<: *primary_production 117 | database: <%= app_name %>_production_queue 118 | migrations_paths: db/queue_migrate 119 | cable: 120 | <<: *primary_production 121 | database: <%= app_name %>_production_cable 122 | migrations_paths: db/cable_migrate 123 | 124 | staging: 125 | primary: &primary_staging 126 | <<: *default 127 | database: <%%= ENV.fetch("DATABASE_NAME") { "<%= app_name %>_staging" } %> 128 | username: <%%= ENV.fetch("DATABASE_USERNAME") { "<%= app_name %>" } %> 129 | password: <%%= ENV["DATABASE_PASSWORD"] %> 130 | cache: 131 | <<: *primary_staging 132 | database: <%%= ENV.fetch("DATABASE_NAME") { "<%= app_name %>_staging_cache" } %> 133 | migrations_paths: db/cache_migrate 134 | queue: 135 | <<: *primary_staging 136 | database: <%%= ENV.fetch("DATABASE_NAME") { "<%= app_name %>_staging_queue" } %> 137 | migrations_paths: db/queue_migrate 138 | cable: 139 | <<: *primary_staging 140 | database: <%%= ENV.fetch("DATABASE_NAME") { "<%= app_name %>_staging_cable" } %> 141 | migrations_paths: db/cable_migrate 142 | -------------------------------------------------------------------------------- /inertia_vue.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | #============================================================================== 4 | # CONSTANTS - Template configuration and shared values 5 | #============================================================================== 6 | 7 | REPO_LINK = "https://github.com/alec-c4/kickstart.git" 8 | AVAILABLE_TEMPLATE_NAMES = %w[api importmap_tailwind esbuild_tailwind inertia_svelte inertia_react inertia_vue].freeze 9 | TEMPLATE_NAME = "inertia_vue" 10 | RAILS_REQUIREMENT = ">= 8.1.0" 11 | 12 | TEMPLATE_METADATA = { 13 | name: "inertia_vue", 14 | description: "Rails app with Inertia.js and Vue for modern SPA development", 15 | features: %w[postgresql devcontainer rspec rubocop uuid i18n tailwind vite inertia vue kamal solid_queue 16 | solid_cache solid_cable], 17 | rails_version: RAILS_REQUIREMENT 18 | }.freeze 19 | 20 | #============================================================================== 21 | # SHARED CODE - Embedded functions (auto-generated, do not edit manually) 22 | #============================================================================== 23 | 24 | require "rails/all" 25 | 26 | def assert_minimum_rails_version 27 | requirement = Gem::Requirement.new(RAILS_REQUIREMENT) 28 | rails_version = Gem::Version.new(Rails::VERSION::STRING) 29 | return if requirement.satisfied_by?(rails_version) 30 | 31 | prompt = "This template requires Rails #{RAILS_REQUIREMENT}. "\ 32 | "You are using #{rails_version}. Continue anyway?" 33 | exit 1 if no?(prompt) 34 | end 35 | 36 | def gemfile_requirement(name) 37 | @original_gemfile ||= IO.read("Gemfile") 38 | req = @original_gemfile[/gem\s+['"]#{name}['"]\s*(,[><~= \t\d.\w'"]*)?.*$/, 1] 39 | req && req.tr("'", %(")).strip.sub(/^,\s*"/, ', "') 40 | end 41 | 42 | def add_template_repository_to_source_path 43 | if __FILE__.match?(%r{\Ahttps?://}) 44 | require "tmpdir" 45 | source_paths.unshift(tempdir = Dir.mktmpdir("kickstart-tmp")) 46 | at_exit { FileUtils.remove_entry(tempdir) } 47 | git clone: [ 48 | "--quiet", 49 | REPO_LINK, 50 | tempdir 51 | ].map(&:shellescape).join(" ") 52 | 53 | # Check for specific branch in URL path (e.g., /kickstart/branch_name/template.rb) 54 | template_pattern = "(?:#{AVAILABLE_TEMPLATE_NAMES.join('|')})" 55 | if (branch = __FILE__[%r{kickstart/(.+)/#{template_pattern}\.rb}, 1]) 56 | Dir.chdir(tempdir) { git checkout: branch } 57 | end 58 | else 59 | # For local files, add the template root directory 60 | template_root = File.dirname(__FILE__) 61 | source_paths.unshift(template_root) 62 | end 63 | end 64 | 65 | def set_variant_source_path(variant_name = nil) 66 | template_root = if __FILE__.match?(%r{\Ahttps?://}) 67 | source_paths.first 68 | else 69 | File.dirname(__FILE__) 70 | end 71 | 72 | # 1. Universal shared (lowest priority) 73 | shared_path = File.join(template_root, "variants", "shared") 74 | source_paths.unshift(shared_path) if File.directory?(shared_path) 75 | 76 | # 2. Inertia shared (medium priority) 77 | inertia_shared_path = File.join(template_root, "variants", "inertia_shared") 78 | source_paths.unshift(inertia_shared_path) if File.directory?(inertia_shared_path) 79 | 80 | # 3. Variant-specific (highest priority) 81 | if variant_name 82 | variant_path = File.join(template_root, "variants", variant_name) 83 | source_paths.unshift(variant_path) if File.directory?(variant_path) 84 | end 85 | end 86 | 87 | def show_post_install_message 88 | say "\n 89 | ######################################################################################### 90 | 91 | Rails application '#{app_name}' created successfully with Inertia.js + Vue! 92 | 93 | Next steps: 94 | $ cd #{app_name} 95 | $ bundle install 96 | $ yarn install 97 | $ rails db:create db:migrate 98 | $ rails parallel:create # Creates parallel test databases (ignore 'already exists' message) 99 | $ bin/dev 100 | 101 | #########################################################################################\n", :green 102 | end 103 | 104 | #============================================================================== 105 | # TEMPLATE LOGIC - Template-specific configuration and workflow 106 | #============================================================================== 107 | 108 | assert_minimum_rails_version 109 | add_template_repository_to_source_path 110 | set_variant_source_path(TEMPLATE_NAME) 111 | 112 | apply "src/inertia_shared/general.rb" 113 | apply "src/shared/yarnconfig.rb" 114 | apply "src/shared/packages.rb" 115 | 116 | after_bundle do 117 | apply "src/inertia_shared/gems_inertia.rb" 118 | apply "src/inertia_shared/cleanup.rb" 119 | 120 | apply "src/inertia_shared/init_generators.rb" 121 | apply "src/shared/init_db_cli.rb" 122 | apply "src/shared/solid_queue_setup.rb" 123 | apply "src/shared/init_i18n.rb" 124 | 125 | apply "src/shared/env_rubocop.rb" 126 | apply "src/shared/migrations_uuid.rb" 127 | 128 | apply "src/shared/gems_anyway_config.rb" 129 | apply "src/shared/gems_pagy.rb" 130 | apply "src/shared/gems_active_interaction.rb" 131 | apply "src/shared/gems_active_decorator.rb" 132 | 133 | apply "src/shared/gems_rspec.rb" 134 | apply "src/inertia_shared/rspec_inertia.rb" 135 | apply "src/shared/gems_i18n_tasks.rb" 136 | apply "src/shared/gems_lockbox.rb" 137 | apply "src/shared/gems_shrine.rb" 138 | 139 | apply "src/inertia_shared/custom_error_pages.rb" 140 | 141 | apply "src/shared/ci.rb" 142 | 143 | apply "src/shared/docs.rb" 144 | apply "src/shared/staging_env.rb" 145 | apply "src/inertia_shared/finalize_layouts.rb" 146 | apply "src/shared/run_rubocop.rb" 147 | apply "src/shared/git_init.rb" 148 | 149 | show_post_install_message 150 | end 151 | -------------------------------------------------------------------------------- /inertia_react.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | #============================================================================== 4 | # CONSTANTS - Template configuration and shared values 5 | #============================================================================== 6 | 7 | REPO_LINK = "https://github.com/alec-c4/kickstart.git" 8 | AVAILABLE_TEMPLATE_NAMES = %w[api importmap_tailwind esbuild_tailwind inertia_svelte inertia_react inertia_vue].freeze 9 | TEMPLATE_NAME = "inertia_react" 10 | RAILS_REQUIREMENT = ">= 8.1.0" 11 | 12 | TEMPLATE_METADATA = { 13 | name: "inertia_react", 14 | description: "Rails app with Inertia.js and React for modern SPA development", 15 | features: %w[postgresql devcontainer rspec rubocop uuid i18n tailwind vite inertia react kamal solid_queue 16 | solid_cache solid_cable], 17 | rails_version: RAILS_REQUIREMENT 18 | }.freeze 19 | 20 | #============================================================================== 21 | # SHARED CODE - Embedded functions (auto-generated, do not edit manually) 22 | #============================================================================== 23 | 24 | require "rails/all" 25 | 26 | def assert_minimum_rails_version 27 | requirement = Gem::Requirement.new(RAILS_REQUIREMENT) 28 | rails_version = Gem::Version.new(Rails::VERSION::STRING) 29 | return if requirement.satisfied_by?(rails_version) 30 | 31 | prompt = "This template requires Rails #{RAILS_REQUIREMENT}. "\ 32 | "You are using #{rails_version}. Continue anyway?" 33 | exit 1 if no?(prompt) 34 | end 35 | 36 | def gemfile_requirement(name) 37 | @original_gemfile ||= IO.read("Gemfile") 38 | req = @original_gemfile[/gem\s+['"]#{name}['"]\s*(,[><~= \t\d.\w'"]*)?.*$/, 1] 39 | req && req.tr("'", %(")).strip.sub(/^,\s*"/, ', "') 40 | end 41 | 42 | def add_template_repository_to_source_path 43 | if __FILE__.match?(%r{\Ahttps?://}) 44 | require "tmpdir" 45 | source_paths.unshift(tempdir = Dir.mktmpdir("kickstart-tmp")) 46 | at_exit { FileUtils.remove_entry(tempdir) } 47 | git clone: [ 48 | "--quiet", 49 | REPO_LINK, 50 | tempdir 51 | ].map(&:shellescape).join(" ") 52 | 53 | # Check for specific branch in URL path (e.g., /kickstart/branch_name/template.rb) 54 | template_pattern = "(?:#{AVAILABLE_TEMPLATE_NAMES.join('|')})" 55 | if (branch = __FILE__[%r{kickstart/(.+)/#{template_pattern}\.rb}, 1]) 56 | Dir.chdir(tempdir) { git checkout: branch } 57 | end 58 | else 59 | # For local files, add the template root directory 60 | template_root = File.dirname(__FILE__) 61 | source_paths.unshift(template_root) 62 | end 63 | end 64 | 65 | def set_variant_source_path(variant_name = nil) 66 | template_root = if __FILE__.match?(%r{\Ahttps?://}) 67 | source_paths.first 68 | else 69 | File.dirname(__FILE__) 70 | end 71 | 72 | # 1. Universal shared (lowest priority) 73 | shared_path = File.join(template_root, "variants", "shared") 74 | source_paths.unshift(shared_path) if File.directory?(shared_path) 75 | 76 | # 2. Inertia shared (medium priority) 77 | inertia_shared_path = File.join(template_root, "variants", "inertia_shared") 78 | source_paths.unshift(inertia_shared_path) if File.directory?(inertia_shared_path) 79 | 80 | # 3. Variant-specific (highest priority) 81 | if variant_name 82 | variant_path = File.join(template_root, "variants", variant_name) 83 | source_paths.unshift(variant_path) if File.directory?(variant_path) 84 | end 85 | end 86 | 87 | def show_post_install_message 88 | say "\n 89 | ######################################################################################### 90 | 91 | Rails application '#{app_name}' created successfully with Inertia.js + React! 92 | 93 | Next steps: 94 | $ cd #{app_name} 95 | $ bundle install 96 | $ yarn install 97 | $ rails db:create db:migrate 98 | $ rails parallel:create # Creates parallel test databases (ignore 'already exists' message) 99 | $ bin/dev 100 | 101 | #########################################################################################\n", :green 102 | end 103 | 104 | #============================================================================== 105 | # TEMPLATE LOGIC - Template-specific configuration and workflow 106 | #============================================================================== 107 | 108 | assert_minimum_rails_version 109 | add_template_repository_to_source_path 110 | set_variant_source_path(TEMPLATE_NAME) 111 | 112 | apply "src/inertia_shared/general.rb" 113 | apply "src/shared/yarnconfig.rb" 114 | apply "src/shared/packages.rb" 115 | 116 | after_bundle do 117 | apply "src/inertia_shared/gems_inertia.rb" 118 | apply "src/inertia_shared/cleanup.rb" 119 | 120 | apply "src/inertia_shared/init_generators.rb" 121 | apply "src/shared/init_db_cli.rb" 122 | apply "src/shared/solid_queue_setup.rb" 123 | apply "src/shared/init_i18n.rb" 124 | 125 | apply "src/shared/env_rubocop.rb" 126 | apply "src/shared/migrations_uuid.rb" 127 | 128 | apply "src/shared/gems_anyway_config.rb" 129 | apply "src/shared/gems_pagy.rb" 130 | apply "src/shared/gems_active_interaction.rb" 131 | apply "src/shared/gems_active_decorator.rb" 132 | 133 | apply "src/shared/gems_rspec.rb" 134 | apply "src/inertia_shared/rspec_inertia.rb" 135 | apply "src/shared/gems_i18n_tasks.rb" 136 | apply "src/shared/gems_lockbox.rb" 137 | apply "src/shared/gems_shrine.rb" 138 | 139 | apply "src/inertia_shared/custom_error_pages.rb" 140 | 141 | apply "src/shared/ci.rb" 142 | 143 | apply "src/shared/docs.rb" 144 | apply "src/shared/staging_env.rb" 145 | apply "src/inertia_shared/finalize_layouts.rb" 146 | apply "src/shared/run_rubocop.rb" 147 | apply "src/shared/git_init.rb" 148 | 149 | show_post_install_message 150 | end 151 | -------------------------------------------------------------------------------- /inertia_svelte.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | #============================================================================== 4 | # CONSTANTS - Template configuration and shared values 5 | #============================================================================== 6 | 7 | REPO_LINK = "https://github.com/alec-c4/kickstart.git" 8 | AVAILABLE_TEMPLATE_NAMES = %w[api importmap_tailwind esbuild_tailwind inertia_svelte inertia_react inertia_vue].freeze 9 | TEMPLATE_NAME = "inertia_svelte" 10 | RAILS_REQUIREMENT = ">= 8.1.0" 11 | 12 | TEMPLATE_METADATA = { 13 | name: "inertia_svelte", 14 | description: "Rails app with Inertia.js and Svelte 5 for modern SPA development", 15 | features: %w[postgresql devcontainer rspec rubocop uuid i18n tailwind vite inertia svelte5 kamal solid_queue 16 | solid_cache solid_cable], 17 | rails_version: RAILS_REQUIREMENT 18 | }.freeze 19 | 20 | #============================================================================== 21 | # SHARED CODE - Embedded functions (auto-generated, do not edit manually) 22 | #============================================================================== 23 | 24 | require "rails/all" 25 | 26 | def assert_minimum_rails_version 27 | requirement = Gem::Requirement.new(RAILS_REQUIREMENT) 28 | rails_version = Gem::Version.new(Rails::VERSION::STRING) 29 | return if requirement.satisfied_by?(rails_version) 30 | 31 | prompt = "This template requires Rails #{RAILS_REQUIREMENT}. "\ 32 | "You are using #{rails_version}. Continue anyway?" 33 | exit 1 if no?(prompt) 34 | end 35 | 36 | def gemfile_requirement(name) 37 | @original_gemfile ||= IO.read("Gemfile") 38 | req = @original_gemfile[/gem\s+['"]#{name}['"]\s*(,[><~= \t\d.\w'"]*)?.*$/, 1] 39 | req && req.tr("'", %(")).strip.sub(/^,\s*"/, ', "') 40 | end 41 | 42 | def add_template_repository_to_source_path 43 | if __FILE__.match?(%r{\Ahttps?://}) 44 | require "tmpdir" 45 | source_paths.unshift(tempdir = Dir.mktmpdir("kickstart-tmp")) 46 | at_exit { FileUtils.remove_entry(tempdir) } 47 | git clone: [ 48 | "--quiet", 49 | REPO_LINK, 50 | tempdir 51 | ].map(&:shellescape).join(" ") 52 | 53 | # Check for specific branch in URL path (e.g., /kickstart/branch_name/template.rb) 54 | template_pattern = "(?:#{AVAILABLE_TEMPLATE_NAMES.join('|')})" 55 | if (branch = __FILE__[%r{kickstart/(.+)/#{template_pattern}\.rb}, 1]) 56 | Dir.chdir(tempdir) { git checkout: branch } 57 | end 58 | else 59 | # For local files, add the template root directory 60 | template_root = File.dirname(__FILE__) 61 | source_paths.unshift(template_root) 62 | end 63 | end 64 | 65 | def set_variant_source_path(variant_name = nil) 66 | template_root = if __FILE__.match?(%r{\Ahttps?://}) 67 | source_paths.first 68 | else 69 | File.dirname(__FILE__) 70 | end 71 | 72 | # 1. Universal shared (lowest priority) 73 | shared_path = File.join(template_root, "variants", "shared") 74 | source_paths.unshift(shared_path) if File.directory?(shared_path) 75 | 76 | # 2. Inertia shared (medium priority) 77 | inertia_shared_path = File.join(template_root, "variants", "inertia_shared") 78 | source_paths.unshift(inertia_shared_path) if File.directory?(inertia_shared_path) 79 | 80 | # 3. Variant-specific (highest priority) 81 | if variant_name 82 | variant_path = File.join(template_root, "variants", variant_name) 83 | source_paths.unshift(variant_path) if File.directory?(variant_path) 84 | end 85 | end 86 | 87 | def show_post_install_message 88 | say "\n 89 | ######################################################################################### 90 | 91 | Rails application '#{app_name}' created successfully with Inertia.js + Svelte! 92 | 93 | Next steps: 94 | $ cd #{app_name} 95 | $ bundle install 96 | $ yarn install 97 | $ rails db:create db:migrate 98 | $ rails parallel:create # Creates parallel test databases (ignore 'already exists' message) 99 | $ bin/dev 100 | 101 | #########################################################################################\n", :green 102 | end 103 | 104 | #============================================================================== 105 | # TEMPLATE LOGIC - Template-specific configuration and workflow 106 | #============================================================================== 107 | 108 | assert_minimum_rails_version 109 | add_template_repository_to_source_path 110 | set_variant_source_path(TEMPLATE_NAME) 111 | 112 | apply "src/inertia_shared/general.rb" 113 | apply "src/shared/yarnconfig.rb" 114 | apply "src/shared/packages.rb" 115 | 116 | after_bundle do 117 | apply "src/inertia_shared/gems_inertia.rb" 118 | apply "src/inertia_shared/cleanup.rb" 119 | 120 | apply "src/inertia_shared/init_generators.rb" 121 | apply "src/shared/init_db_cli.rb" 122 | apply "src/shared/solid_queue_setup.rb" 123 | apply "src/shared/init_i18n.rb" 124 | 125 | apply "src/shared/env_rubocop.rb" 126 | apply "src/shared/migrations_uuid.rb" 127 | 128 | apply "src/shared/gems_anyway_config.rb" 129 | apply "src/shared/gems_pagy.rb" 130 | apply "src/shared/gems_active_interaction.rb" 131 | apply "src/shared/gems_active_decorator.rb" 132 | 133 | apply "src/shared/gems_rspec.rb" 134 | apply "src/inertia_shared/rspec_inertia.rb" 135 | apply "src/shared/gems_i18n_tasks.rb" 136 | apply "src/shared/gems_lockbox.rb" 137 | apply "src/shared/gems_shrine.rb" 138 | 139 | apply "src/inertia_shared/custom_error_pages.rb" 140 | 141 | apply "src/shared/ci.rb" 142 | 143 | apply "src/shared/docs.rb" 144 | apply "src/shared/staging_env.rb" 145 | apply "src/inertia_shared/finalize_layouts.rb" 146 | apply "src/shared/run_rubocop.rb" 147 | apply "src/shared/git_init.rb" 148 | 149 | show_post_install_message 150 | end 151 | -------------------------------------------------------------------------------- /importmap_tailwind.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | #============================================================================== 4 | # CONSTANTS - Template configuration and shared values 5 | #============================================================================== 6 | 7 | REPO_LINK = "https://github.com/alec-c4/kickstart.git" 8 | AVAILABLE_TEMPLATE_NAMES = %w[api importmap_tailwind esbuild_tailwind inertia_svelte inertia_react inertia_vue].freeze 9 | TEMPLATE_NAME = "importmap_tailwind" 10 | RAILS_REQUIREMENT = ">= 8.1.0" 11 | 12 | TEMPLATE_METADATA = { 13 | name: "importmap_tailwind", 14 | description: "Rails app with Importmap and Tailwind CSS for modern frontend development", 15 | features: %w[postgresql devcontainer rspec rubocop uuid i18n tailwind importmap turbo stimulus kamal solid_queue 16 | solid_cache solid_cable], 17 | rails_version: RAILS_REQUIREMENT 18 | }.freeze 19 | 20 | #============================================================================== 21 | # SHARED CODE - Embedded functions (auto-generated, do not edit manually) 22 | #============================================================================== 23 | 24 | require "rails/all" 25 | 26 | def assert_minimum_rails_version 27 | requirement = Gem::Requirement.new(RAILS_REQUIREMENT) 28 | rails_version = Gem::Version.new(Rails::VERSION::STRING) 29 | return if requirement.satisfied_by?(rails_version) 30 | 31 | prompt = "This template requires Rails #{RAILS_REQUIREMENT}. "\ 32 | "You are using #{rails_version}. Continue anyway?" 33 | exit 1 if no?(prompt) 34 | end 35 | 36 | def gemfile_requirement(name) 37 | @original_gemfile ||= IO.read("Gemfile") 38 | req = @original_gemfile[/gem\s+['"]#{name}['"]\s*(,[><~= \t\d.\w'"]*)?.*$/, 1] 39 | req && req.tr("'", %(")).strip.sub(/^,\s*"/, ', "') 40 | end 41 | 42 | def add_template_repository_to_source_path 43 | if __FILE__.match?(%r{\Ahttps?://}) 44 | require "tmpdir" 45 | source_paths.unshift(tempdir = Dir.mktmpdir("kickstart-tmp")) 46 | at_exit { FileUtils.remove_entry(tempdir) } 47 | git clone: [ 48 | "--quiet", 49 | REPO_LINK, 50 | tempdir 51 | ].map(&:shellescape).join(" ") 52 | 53 | # Check for specific branch in URL path (e.g., /kickstart/branch_name/template.rb) 54 | template_pattern = "(?:#{AVAILABLE_TEMPLATE_NAMES.join('|')})" 55 | if (branch = __FILE__[%r{kickstart/(.+)/#{template_pattern}\.rb}, 1]) 56 | Dir.chdir(tempdir) { git checkout: branch } 57 | end 58 | else 59 | # For local files, add the template root directory 60 | template_root = File.dirname(__FILE__) 61 | source_paths.unshift(template_root) 62 | end 63 | end 64 | 65 | def set_variant_source_path(variant_name = nil) 66 | template_root = if __FILE__.match?(%r{\Ahttps?://}) 67 | source_paths.first 68 | else 69 | File.dirname(__FILE__) 70 | end 71 | 72 | # 1. Universal shared (lowest priority) 73 | shared_path = File.join(template_root, "variants", "shared") 74 | source_paths.unshift(shared_path) if File.directory?(shared_path) 75 | 76 | # 2. Classic shared (medium priority) 77 | classic_shared_path = File.join(template_root, "variants", "classic_shared") 78 | source_paths.unshift(classic_shared_path) if File.directory?(classic_shared_path) 79 | 80 | # 3. Variant-specific (highest priority) 81 | if variant_name 82 | variant_path = File.join(template_root, "variants", variant_name) 83 | source_paths.unshift(variant_path) if File.directory?(variant_path) 84 | end 85 | end 86 | 87 | def show_post_install_message 88 | say "\n 89 | ######################################################################################### 90 | 91 | Rails application '#{app_name}' created successfully! 92 | 93 | Next steps: 94 | $ cd #{app_name} 95 | $ bundle install 96 | $ rails db:create db:migrate 97 | $ rails parallel:create # Creates parallel test databases (ignore 'already exists' message) 98 | $ bin/dev 99 | 100 | #########################################################################################\n", :green 101 | end 102 | 103 | #============================================================================== 104 | # TEMPLATE LOGIC - Template-specific configuration and workflow 105 | #============================================================================== 106 | 107 | assert_minimum_rails_version 108 | add_template_repository_to_source_path 109 | set_variant_source_path(TEMPLATE_NAME) 110 | 111 | apply "src/shared/general.rb" 112 | apply "src/shared/packages.rb" 113 | 114 | after_bundle do 115 | apply "src/shared/init_generators.rb" 116 | apply "src/shared/init_db_cli.rb" 117 | apply "src/shared/solid_queue_setup.rb" 118 | apply "src/shared/init_i18n.rb" 119 | 120 | apply "src/classic_shared/routes.rb" 121 | apply "src/classic_shared/app_static_pages.rb" 122 | apply "src/classic_shared/custom_error_pages.rb" 123 | 124 | apply "src/shared/env_rubocop.rb" 125 | apply "src/shared/migrations_uuid.rb" 126 | 127 | apply "src/shared/gems_anyway_config.rb" 128 | apply "src/shared/gems_pagy.rb" 129 | apply "src/shared/gems_active_interaction.rb" 130 | apply "src/shared/gems_active_decorator.rb" 131 | apply "src/shared/gems_rspec.rb" 132 | apply "src/shared/gems_i18n_tasks.rb" 133 | apply "src/classic_shared/gems_better_html.rb" 134 | apply "src/classic_shared/gems_erblint.rb" 135 | apply "src/shared/gems_lockbox.rb" 136 | apply "src/shared/gems_shrine.rb" 137 | apply "src/classic_shared/gems_view_component.rb" 138 | apply "src/classic_shared/helpers.rb" 139 | 140 | apply "src/shared/ci.rb" 141 | 142 | apply "src/shared/docs.rb" 143 | apply "src/shared/staging_env.rb" 144 | apply "src/shared/run_rubocop.rb" 145 | apply "src/shared/git_init.rb" 146 | 147 | show_post_install_message 148 | end 149 | -------------------------------------------------------------------------------- /esbuild_tailwind.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | #============================================================================== 4 | # CONSTANTS - Template configuration and shared values 5 | #============================================================================== 6 | 7 | REPO_LINK = "https://github.com/alec-c4/kickstart.git" 8 | AVAILABLE_TEMPLATE_NAMES = %w[api importmap_tailwind esbuild_tailwind inertia_svelte inertia_react inertia_vue].freeze 9 | TEMPLATE_NAME = "esbuild_tailwind" 10 | RAILS_REQUIREMENT = ">= 8.1.0" 11 | 12 | TEMPLATE_METADATA = { 13 | name: "esbuild_tailwind", 14 | description: "Rails app with ESBuild and Tailwind CSS for modern frontend development", 15 | features: %w[postgresql devcontainer rspec rubocop uuid i18n tailwind esbuild turbo stimulus kamal solid_queue 16 | solid_cache solid_cable], 17 | rails_version: RAILS_REQUIREMENT 18 | }.freeze 19 | 20 | #============================================================================== 21 | # SHARED CODE - Embedded functions (auto-generated, do not edit manually) 22 | #============================================================================== 23 | 24 | require "rails/all" 25 | 26 | def assert_minimum_rails_version 27 | requirement = Gem::Requirement.new(RAILS_REQUIREMENT) 28 | rails_version = Gem::Version.new(Rails::VERSION::STRING) 29 | return if requirement.satisfied_by?(rails_version) 30 | 31 | prompt = "This template requires Rails #{RAILS_REQUIREMENT}. "\ 32 | "You are using #{rails_version}. Continue anyway?" 33 | exit 1 if no?(prompt) 34 | end 35 | 36 | def gemfile_requirement(name) 37 | @original_gemfile ||= IO.read("Gemfile") 38 | req = @original_gemfile[/gem\s+['"]#{name}['"]\s*(,[><~= \t\d.\w'"]*)?.*$/, 1] 39 | req && req.tr("'", %(")).strip.sub(/^,\s*"/, ', "') 40 | end 41 | 42 | def add_template_repository_to_source_path 43 | if __FILE__.match?(%r{\Ahttps?://}) 44 | require "tmpdir" 45 | source_paths.unshift(tempdir = Dir.mktmpdir("kickstart-tmp")) 46 | at_exit { FileUtils.remove_entry(tempdir) } 47 | git clone: [ 48 | "--quiet", 49 | REPO_LINK, 50 | tempdir 51 | ].map(&:shellescape).join(" ") 52 | 53 | # Check for specific branch in URL path (e.g., /kickstart/branch_name/template.rb) 54 | template_pattern = "(?:#{AVAILABLE_TEMPLATE_NAMES.join('|')})" 55 | if (branch = __FILE__[%r{kickstart/(.+)/#{template_pattern}\.rb}, 1]) 56 | Dir.chdir(tempdir) { git checkout: branch } 57 | end 58 | else 59 | # For local files, add the template root directory 60 | template_root = File.dirname(__FILE__) 61 | source_paths.unshift(template_root) 62 | end 63 | end 64 | 65 | def set_variant_source_path(variant_name = nil) 66 | template_root = if __FILE__.match?(%r{\Ahttps?://}) 67 | source_paths.first 68 | else 69 | File.dirname(__FILE__) 70 | end 71 | 72 | # 1. Universal shared (lowest priority) 73 | shared_path = File.join(template_root, "variants", "shared") 74 | source_paths.unshift(shared_path) if File.directory?(shared_path) 75 | 76 | # 2. Classic shared (medium priority) 77 | classic_shared_path = File.join(template_root, "variants", "classic_shared") 78 | source_paths.unshift(classic_shared_path) if File.directory?(classic_shared_path) 79 | 80 | # 3. Variant-specific (highest priority) 81 | if variant_name 82 | variant_path = File.join(template_root, "variants", variant_name) 83 | source_paths.unshift(variant_path) if File.directory?(variant_path) 84 | end 85 | end 86 | 87 | def show_post_install_message 88 | say "\n 89 | ######################################################################################### 90 | 91 | Rails application '#{app_name}' created successfully! 92 | 93 | Next steps: 94 | $ cd #{app_name} 95 | $ bundle install 96 | $ rails db:create db:migrate 97 | $ rails parallel:create # Creates parallel test databases (ignore 'already exists' message) 98 | $ bin/dev 99 | 100 | #########################################################################################\n", :green 101 | end 102 | 103 | #============================================================================== 104 | # TEMPLATE LOGIC - Template-specific configuration and workflow 105 | #============================================================================== 106 | 107 | assert_minimum_rails_version 108 | add_template_repository_to_source_path 109 | set_variant_source_path(TEMPLATE_NAME) 110 | 111 | apply "src/shared/general.rb" 112 | apply "src/shared/yarnconfig.rb" 113 | apply "src/shared/packages.rb" 114 | 115 | after_bundle do 116 | apply "src/shared/init_generators.rb" 117 | apply "src/shared/init_db_cli.rb" 118 | apply "src/shared/solid_queue_setup.rb" 119 | apply "src/shared/init_i18n.rb" 120 | 121 | apply "src/classic_shared/routes.rb" 122 | apply "src/classic_shared/app_static_pages.rb" 123 | apply "src/classic_shared/custom_error_pages.rb" 124 | 125 | apply "src/shared/env_rubocop.rb" 126 | apply "src/shared/migrations_uuid.rb" 127 | 128 | apply "src/shared/gems_anyway_config.rb" 129 | apply "src/shared/gems_pagy.rb" 130 | apply "src/shared/gems_active_interaction.rb" 131 | apply "src/shared/gems_active_decorator.rb" 132 | apply "src/shared/gems_rspec.rb" 133 | apply "src/shared/gems_i18n_tasks.rb" 134 | apply "src/classic_shared/gems_better_html.rb" 135 | apply "src/classic_shared/gems_erblint.rb" 136 | apply "src/shared/gems_lockbox.rb" 137 | apply "src/shared/gems_shrine.rb" 138 | apply "src/classic_shared/gems_view_component.rb" 139 | apply "src/classic_shared/helpers.rb" 140 | 141 | apply "src/shared/ci.rb" 142 | 143 | apply "src/shared/docs.rb" 144 | apply "src/shared/staging_env.rb" 145 | apply "src/shared/run_rubocop.rb" 146 | apply "src/shared/git_init.rb" 147 | 148 | show_post_install_message 149 | end 150 | -------------------------------------------------------------------------------- /TEMPLATES.md: -------------------------------------------------------------------------------- 1 | # Template Management System 2 | 3 | This document describes the template management system for Kickstart Rails Templates. 4 | 5 | ## Overview 6 | 7 | The Kickstart project uses a sophisticated template management system that provides: 8 | 9 | - **Embedded shared code**: All templates are self-contained with embedded shared functions 10 | - **Template validation**: Automated consistency checking across all templates 11 | - **Template synchronization**: Tools to keep shared code in sync across templates 12 | - **Metadata system**: Rich metadata for each template including features and descriptions 13 | 14 | ## Template Structure 15 | 16 | Each template follows a consistent structure: 17 | 18 | ```ruby 19 | # frozen_string_literal: true 20 | 21 | #============================================================================== 22 | # CONSTANTS - Template configuration and shared values 23 | #============================================================================== 24 | 25 | REPO_LINK = "https://github.com/alec-c4/kickstart.git" 26 | AVAILABLE_TEMPLATE_NAMES = %w[api importmap_tailwind esbuild_tailwind].freeze 27 | TEMPLATE_NAME = "template_name".freeze 28 | RAILS_REQUIREMENT = ">= 8.1.0.beta.1" 29 | 30 | TEMPLATE_METADATA = { 31 | name: "template_name", 32 | description: "Template description", 33 | features: %w[list of features], 34 | rails_version: RAILS_REQUIREMENT 35 | }.freeze 36 | 37 | #============================================================================== 38 | # SHARED CODE - Embedded functions (auto-generated, do not edit manually) 39 | #============================================================================== 40 | 41 | # All shared functions are embedded here... 42 | 43 | #============================================================================== 44 | # TEMPLATE LOGIC - Template-specific configuration and workflow 45 | #============================================================================== 46 | 47 | # Template-specific logic here... 48 | ``` 49 | 50 | ## Available Templates 51 | 52 | ### API Template (`api.rb`) 53 | 54 | - **Description**: Rails API-only application with essential setup 55 | - **Features**: postgresql, devcontainer, rspec, rubocop, uuid, i18n, kamal, solid_queue, solid_cache, solid_cable 56 | - **Use case**: Backend APIs and microservices 57 | 58 | ### Importmap + Tailwind Template (`importmap_tailwind.rb`) 59 | 60 | - **Description**: Rails app with Importmap and Tailwind CSS for modern frontend development 61 | - **Features**: postgresql, devcontainer, rspec, rubocop, uuid, i18n, tailwind, importmap, turbo, stimulus, kamal, solid_queue, solid_cache, solid_cable 62 | - **Use case**: Simple web applications with modern CSS framework 63 | 64 | ### ESBuild + Tailwind Template (`esbuild_tailwind.rb`) 65 | 66 | - **Description**: Rails app with ESBuild and Tailwind CSS for modern frontend development 67 | - **Features**: postgresql, devcontainer, rspec, rubocop, uuid, i18n, tailwind, esbuild, turbo, stimulus, kamal, solid_queue, solid_cache, solid_cable 68 | - **Use case**: Modern web applications with advanced JavaScript bundling 69 | 70 | ## Rake Tasks 71 | 72 | ### `rake templates:validate` 73 | 74 | Validates that all templates are consistent and properly formatted. 75 | 76 | ```bash 77 | $ rake templates:validate 78 | 🔍 Validating template consistency... 79 | ✅ All templates are valid and consistent! 80 | ``` 81 | 82 | **Checks performed:** 83 | 84 | - All required constants are present 85 | - All required functions are defined 86 | - Shared code sections are identical across templates 87 | - Template files exist and are readable 88 | 89 | ### `rake templates:info` 90 | 91 | Displays detailed information about all templates. 92 | 93 | ```bash 94 | $ rake templates:info 95 | 📊 Kickstart Rails Templates Information 96 | ================================================== 97 | 98 | 🚀 Template: IMPORTMAP_TAILWIND 99 | File: importmap_tailwind.rb 100 | Lines: 118 101 | Description: Rails app with Importmap, Tailwind CSS and basic configuration 102 | Features: postgresql, devcontainer, rspec, rubocop, uuid, i18n, tailwind, importmap, turbo, stimulus, kamal, solid_queue, solid_cache, solid_cable 103 | Apply statements: 11 104 | ``` 105 | 106 | ### `rake templates:sync` 107 | 108 | Synchronizes shared code across all templates to ensure consistency. 109 | 110 | ```bash 111 | $ rake templates:sync 112 | 🔄 Syncing shared code across all templates... 113 | ✅ Updated api.rb 114 | ✅ Updated importmap_tailwind.rb 115 | ✅ Updated esbuild_tailwind.rb 116 | 🎉 Shared code synchronization complete! 117 | ``` 118 | 119 | **⚠️ Important**: This task auto-generates the shared code section. Do not edit the shared code manually. 120 | 121 | ### `rake templates:create[name,source]` 122 | 123 | Creates a new template based on an existing one. 124 | 125 | ```bash 126 | $ rake templates:create[mobile,importmap_tailwind] 127 | ✅ Created new template: mobile.rb 128 | ✅ Created directories: src/mobile/, variants/mobile/ 129 | 💡 Don't forget to update AVAILABLE_TEMPLATES constant! 130 | ``` 131 | 132 | ## Usage Examples 133 | 134 | ### Local Development 135 | 136 | ```bash 137 | # Use a template locally 138 | rails new myapp -m /path/to/kickstart/importmap_tailwind.rb --database=postgresql --devcontainer --css=tailwind 139 | 140 | # Validate templates before committing 141 | rake templates:validate 142 | 143 | # Get template information 144 | rake templates:info 145 | ``` 146 | 147 | ### Remote Installation 148 | 149 | ```bash 150 | # Install via curl (most common usage) 151 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/alec-c4/kickstart/master/install.sh)" -- myapp importmap_tailwind 152 | ``` 153 | 154 | ## Maintenance Workflow 155 | 156 | ### Adding New Shared Functions 157 | 158 | 1. **Add the function to one template** (preferably `importmap_tailwind.rb`) 159 | 2. **Run sync task** to propagate to all templates: 160 | ```bash 161 | rake templates:sync 162 | ``` 163 | 3. **Validate consistency**: 164 | ```bash 165 | rake templates:validate 166 | ``` 167 | 168 | ### Creating New Templates 169 | 170 | 1. **Create from existing template**: 171 | ```bash 172 | rake templates:create[new_template,importmap_tailwind] 173 | ``` 174 | 2. **Update constants** in `Rakefile` and new template 175 | 3. **Customize template-specific logic** 176 | 4. **Validate the new template**: 177 | ```bash 178 | rake templates:validate 179 | ``` 180 | 181 | ### Modifying Existing Templates 182 | 183 | 1. **Edit template-specific sections only** (below `# TEMPLATE LOGIC`) 184 | 2. **Never edit the shared code section directly** (it's auto-generated) 185 | 3. **Always validate after changes**: 186 | ```bash 187 | rake templates:validate 188 | ``` 189 | 190 | ## Architecture Benefits 191 | 192 | ### Self-Contained Templates 193 | 194 | - ✅ Work with remote URLs (curl installation) 195 | - ✅ No external dependencies 196 | - ✅ Easy to debug - everything in one file 197 | - ✅ Fast loading 198 | 199 | ### Automated Management 200 | 201 | - ✅ Consistent shared code across templates 202 | - ✅ Validation prevents configuration drift 203 | - ✅ Easy to add new templates 204 | - ✅ Clear separation of concerns 205 | 206 | ### Rich Metadata 207 | 208 | - ✅ Self-documenting templates 209 | - ✅ Feature tracking 210 | - ✅ Version compatibility information 211 | - ✅ Usage statistics 212 | 213 | ## Contributing 214 | 215 | When contributing to templates: 216 | 217 | 1. **Always run validation** before submitting PRs 218 | 2. **Use the sync task** if you modify shared functions 219 | 3. **Update metadata** when adding/removing features 220 | 4. **Test both local and remote usage** 221 | 5. **Document any new conventions** 222 | 223 | ## Troubleshooting 224 | 225 | ### Template Validation Fails 226 | 227 | ```bash 228 | ❌ Shared code sections are not identical across templates 229 | ``` 230 | 231 | **Solution**: Run `rake templates:sync` to synchronize shared code. 232 | 233 | ### Missing Constants/Functions 234 | 235 | ```bash 236 | ❌ Missing constant TEMPLATE_METADATA in api.rb 237 | ``` 238 | 239 | **Solution**: Add the missing constant following the standard structure. 240 | 241 | ### Template Creation Issues 242 | 243 | ```bash 244 | ❌ Template 'mobile' already exists! 245 | ``` 246 | 247 | **Solution**: Use a different name or remove the existing template first. 248 | 249 | --- 250 | 251 | For more information, see the [main README](README.md) or check the [Rakefile](Rakefile) for implementation details. 252 | --------------------------------------------------------------------------------