diff --git a/.nvmrc b/.nvmrc index 87834047..48b14e6b 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -20.12.2 +20.14.0 diff --git a/Gemfile b/Gemfile index 084d6245..2f9dfec4 100644 --- a/Gemfile +++ b/Gemfile @@ -112,7 +112,7 @@ end # We use a constant here so that we can ensure that all of the bullet_train-* # packages are on the same version. -BULLET_TRAIN_VERSION = "1.7.9" +BULLET_TRAIN_VERSION = "1.7.10" # Core packages. gem "bullet_train", BULLET_TRAIN_VERSION diff --git a/Gemfile.lock b/Gemfile.lock index 9ff2f42f..709b4471 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -126,7 +126,7 @@ GEM bootsnap (1.17.1) msgpack (~> 1.2) builder (3.2.4) - bullet_train (1.7.9) + bullet_train (1.7.10) awesome_print bullet_train-has_uuid bullet_train-roles @@ -159,7 +159,7 @@ GEM unicode-emoji valid_email xxhash - bullet_train-api (1.7.9) + bullet_train-api (1.7.10) bullet_train bullet_train-super_scaffolding colorizer @@ -170,61 +170,61 @@ GEM pagy_cursor rack-cors rails (>= 6.0.0) - bullet_train-fields (1.7.9) + bullet_train-fields (1.7.10) chronic cloudinary phonelib rails (>= 6.0.0) - bullet_train-has_uuid (1.7.9) + bullet_train-has_uuid (1.7.10) rails (>= 6.0.0) - bullet_train-incoming_webhooks (1.7.9) + bullet_train-incoming_webhooks (1.7.10) bullet_train bullet_train-api bullet_train-super_scaffolding rails (>= 6.0.0) - bullet_train-integrations (1.7.9) + bullet_train-integrations (1.7.10) rails (>= 6.0.0) - bullet_train-integrations-stripe (1.7.9) + bullet_train-integrations-stripe (1.7.10) omniauth omniauth-rails_csrf_protection omniauth-stripe-connect rails (>= 6.0.0) stripe - bullet_train-obfuscates_id (1.7.9) + bullet_train-obfuscates_id (1.7.10) hashids rails (>= 6.0.0) - bullet_train-outgoing_webhooks (1.7.9) + bullet_train-outgoing_webhooks (1.7.10) public_suffix rails (>= 6.0.0) - bullet_train-roles (1.7.9) + bullet_train-roles (1.7.10) active_hash activesupport cancancan bullet_train-routes (1.0.0) rails (>= 6.0.0) - bullet_train-scope_questions (1.7.9) + bullet_train-scope_questions (1.7.10) rails (>= 6.0.0) - bullet_train-scope_validator (1.7.9) + bullet_train-scope_validator (1.7.10) rails - bullet_train-sortable (1.7.9) + bullet_train-sortable (1.7.10) rails (>= 6.0.0) - bullet_train-super_load_and_authorize_resource (1.7.9) + bullet_train-super_load_and_authorize_resource (1.7.10) cancancan rails (>= 6.0.0) - bullet_train-super_scaffolding (1.7.9) + bullet_train-super_scaffolding (1.7.10) colorizer indefinite_article masamune-ast (~> 2.0.2) rails (>= 6.0.0) - bullet_train-themes (1.7.9) + bullet_train-themes (1.7.10) bullet_train-fields nice_partials (~> 0.9) rails (>= 6.0.0) - bullet_train-themes-light (1.7.9) + bullet_train-themes-light (1.7.10) bullet_train-themes-tailwind_css masamune-ast (~> 2.0.2) rails (>= 6.0.0) - bullet_train-themes-tailwind_css (1.7.9) + bullet_train-themes-tailwind_css (1.7.10) bullet_train-themes rails (>= 6.0.0) cable_ready (5.0.5) @@ -644,7 +644,7 @@ GEM stimulus-rails (1.3.0) railties (>= 6.0.0) stringio (3.0.8) - stripe (11.5.0) + stripe (11.6.0) terser (1.1.20) execjs (>= 0.3.0, < 3) thor (1.3.0) @@ -699,24 +699,24 @@ DEPENDENCIES avo (>= 3.1.7) aws-sdk-s3 bootsnap - bullet_train (= 1.7.9) - bullet_train-api (= 1.7.9) - bullet_train-fields (= 1.7.9) - bullet_train-has_uuid (= 1.7.9) - bullet_train-incoming_webhooks (= 1.7.9) - bullet_train-integrations (= 1.7.9) - bullet_train-integrations-stripe (= 1.7.9) - bullet_train-obfuscates_id (= 1.7.9) - bullet_train-outgoing_webhooks (= 1.7.9) - bullet_train-roles (= 1.7.9) - bullet_train-scope_questions (= 1.7.9) - bullet_train-scope_validator (= 1.7.9) - bullet_train-sortable (= 1.7.9) - bullet_train-super_load_and_authorize_resource (= 1.7.9) - bullet_train-super_scaffolding (= 1.7.9) - bullet_train-themes (= 1.7.9) - bullet_train-themes-light (= 1.7.9) - bullet_train-themes-tailwind_css (= 1.7.9) + bullet_train (= 1.7.10) + bullet_train-api (= 1.7.10) + bullet_train-fields (= 1.7.10) + bullet_train-has_uuid (= 1.7.10) + bullet_train-incoming_webhooks (= 1.7.10) + bullet_train-integrations (= 1.7.10) + bullet_train-integrations-stripe (= 1.7.10) + bullet_train-obfuscates_id (= 1.7.10) + bullet_train-outgoing_webhooks (= 1.7.10) + bullet_train-roles (= 1.7.10) + bullet_train-scope_questions (= 1.7.10) + bullet_train-scope_validator (= 1.7.10) + bullet_train-sortable (= 1.7.10) + bullet_train-super_load_and_authorize_resource (= 1.7.10) + bullet_train-super_scaffolding (= 1.7.10) + bullet_train-themes (= 1.7.10) + bullet_train-themes-light (= 1.7.10) + bullet_train-themes-tailwind_css (= 1.7.10) capybara (~> 3.39) capybara-email capybara-lockstep diff --git a/README.example.md b/README.example.md index 7ff18ff7..822d02c0 100644 --- a/README.example.md +++ b/README.example.md @@ -21,10 +21,3 @@ ## Information about Bullet Train If this is your first time working on a Bullet Train application, be sure to review the [Bullet Train Basic Techniques](https://bullettrain.co/docs/getting-started) and the [Bullet Train Developer Documentation](https://bullettrain.co/docs). -### Render - -[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/bullet-train-co/bullet_train) - -Clicking this button will take you to the first step of a process that, when completed, will provision production-grade infrastructure for your Bullet Train application which will cost about **$30/month**. - -When you're done deploying to Render, you need to go into "Dashboard" > "web", copy the server URL, and then go into "Env Groups" > "settings" and paste the URL into the value for `BASE_URL`. diff --git a/app.json b/app.json index 462d8e7b..7f373a4a 100644 --- a/app.json +++ b/app.json @@ -5,8 +5,8 @@ "logo": "https://bullettrain.co/heroku-logo.png", "keywords": ["ruby", "rails"], "buildpacks": [ - { "url": "heroku/ruby" }, - { "url": "heroku/nodejs" } + { "url": "heroku/nodejs" }, + { "url": "heroku/ruby" } ], "formation": { "web": { diff --git a/bin/configure b/bin/configure index a579e2cf..ed4b9315 100755 --- a/bin/configure +++ b/bin/configure @@ -1,295 +1,48 @@ #!/usr/bin/env ruby -puts "" -puts `gem install colorize` -puts "" - -puts "" -puts `gem install activesupport` -puts "" - -require 'colorize' -require 'active_support' - -def replace_in_file(file, before, after, target_regexp = nil) - puts "Replacing in '#{file}'." - if target_regexp - target_file_content = "" - File.open(file).each_line do |l| - l.gsub!(before, after) if !!l.match(target_regexp) - l if !!l.match(target_regexp) - target_file_content += l - end - else - target_file_content = File.open(file).read - target_file_content.gsub!(before, after) - end - File.open(file, "w+") do |f| - f.write(target_file_content) - end -end - -def stream(command, prefix = " ") - puts "" - IO.popen(command) do |io| - while (line = io.gets) do - puts "#{prefix}#{line}" - end - end - puts "" -end - -def ask(string) - puts string.blue - return gets.strip -end - -def not_installed?(package) - `brew info #{package} | grep "Not installed"`.strip.length > 0 -end - -def check_package(package) - if not_installed?(package) - puts "#{package} is not installed via Homebrew. Try running `brew install #{package}`.".red - input = ask "Try proceeding without #{package}? [y/n]" - if input.downcase[0] == "n" - exit - end - else - puts "#{package} is installed via Homebrew.".green - end -end - - - -# Unless the shell's current version of Ruby is the same as what the application requires, we should flag it. -# rbenv produces strings like "3.1.2" while rvm produces ones like "ruby-3.1.2", so we account for that here. -required_ruby = `cat ./.ruby-version`.strip.gsub(/^ruby-/, "") -actual_ruby = `ruby -v`.strip -message = "Bullet Train requires Ruby #{required_ruby} and `ruby -v` returns #{actual_ruby}." -if actual_ruby.include?(required_ruby) - puts message.green -else - puts message.red - input = ask "Try proceeding with with Ruby #{actual_ruby} anyway? [y/n]" - if input.downcase[0] == "n" - exit - end -end - -if `brew info 2> /dev/null`.length > 0 - puts "Homebrew is installed.".green -else - puts "You don't have Homebrew installed. This isn't necessarily a problem, you might not even be on macOS, but we can't check your dependencies without it.".red - input = ask "Try proceeding without Homebrew? [y/n]" - if input.downcase[0] == "n" - exit - end -end - -case Gem::Platform.local.os -when "darwin" - check_package("postgresql@14") - check_package("redis") - check_package("icu4c") -when "linux" - system_packages = `dpkg -l | grep '^ii'`.split("\n").map do |package_information| - package_information.split("\s")[1] - end +# First we require a util file that will install a few utility gems +# and define some utility methods used by other scirpts. +require "#{__dir__}/configure-scripts/utils" - psql_installed = system_packages.include?("postgresql") - if psql_installed - psql_version = `psql --version`.split("\s")[2] - if psql_version.match?(/^14/) - puts "You have PostgreSQL 14 installed.".green - else - puts "You have PostgreSQL installed, but you're using v#{psql_version} and not v14.".red - input = ask "Try proceeding without PostgreSQL 14? [y/n]" - if input.downcase[0] == "n" - exit - end - end - else - puts "You don't have PostgreSQL installed. Please see the installation instructions at https://ubuntu.com/server/docs/databases-postgresql".red - exit - end +# Then we check that we have the right ruby version +require "#{__dir__}/configure-scripts/check_ruby" - # TODO: system_packages should include `redis`. - # https://github.com/bullet-train-co/bullet_train/issues/1330 - begin - redis_version = `redis-cli --version`.chomp.split("\s").last - puts "Redis #{redis_version} is installed.".green - rescue - puts "You don't have Redis installed. Please see the installation instructions at https://redis.io/docs/getting-started/installation/install-redis-on-linux/ .".red - exit - end +# We rename `origin` to `bullet-train` for consistency with documented upgrade +# procedures. Also to prevent any unintended pushes to the starter repo. +require "#{__dir__}/configure-scripts/rename_origin" - if system_packages.select{|pkg| pkg.match?(/^libicu/)}.any? - puts "You have icu4c installed.".green - else - puts "You don't have icu4c installed. Please run `sudo apt-get install libicu-dev`.".red - exit - end -else - puts "We currently don't support this platform to check if you have the following libraries installed:".red - puts "1. PostgreSQL" - puts "2. Redis" - puts "3. icu4c" - puts "" - puts "Please ensure they are installed before proceeding." - input = ask "Proceed? [y/n]" - if input.downcase[0] == "n" - exit - end -end +# Now we connect your local repo to GitHub if you want +require "#{__dir__}/configure-scripts/setup_github" -required_node = `cat ./.nvmrc`.strip -actual_node = begin - `node -v`.strip.gsub("v", "") - rescue - :not_found - end -message = "Bullet Train requires Node.js #{required_node} and `node -v` returns #{actual_node}." -if actual_node == :not_found - puts "You don't have Node installed. We can't proceed without it. Try `brew install node`.".red - exit -elsif Gem::Version.new(actual_node) >= Gem::Version.new(required_node) - puts message.green -else - puts message.red - input = ask "Try proceeding with Node #{actual_node} anyway? [y/n]" - if input.downcase[0] == "n" - exit - end -end +# Next we change instances of "Untilted Application" and `untitled_application` +# into strings that represent the actual name of the new project being started. +require "#{__dir__}/configure-scripts/update_configs" -if `yarn -v 2> /dev/null`.length > 0 - puts "Yarn is installed.".green -else - puts "You don't have Yarn installed. We can't proceed without it. Try `brew install yarn` or see the installation instructions at https://yarnpkg.com/getting-started/install .".red - exit -end +# Now we ask if you want a `Deploy to Heroku` and/or `Deploy to Render` button +require "#{__dir__}/configure-scripts/deploy_button_heroku" +require "#{__dir__}/configure-scripts/deploy_button_render" -# TODO: Uncomment this when the enable bulk invitations JS is implemented. -# Enable the bulk invitations configuration. -# bt_config_lines = File.open("config/initializers/bullet_train.rb").readlines -# new_lines = bt_config_lines.map do |line| -# if line.match?("config.enable_bulk_invitations") -# line.gsub!(/#\s*/, "") -# end -# line -# end -# File.write("config/initializers/bullet_train.rb", bt_config_lines.join) +# Now that we're finished making changes to the new repo we commit them if you want +require "#{__dir__}/configure-scripts/commit_changes" -puts "Next, let's push your application to GitHub." -puts "If you would like to use another service like Gitlab to manage your repository," -puts "you can opt out of this step and set up the repository manually." -puts "(If you're not sure, we suggest going with GitHub)" -skip_github = ask "Continue setting up with GitHub? [y/n]" -if skip_github.downcase[0] == "y" - if `git remote | grep bullet-train`.strip.length > 0 - puts "Repository already has a \`bullet-train`\ remote.".yellow - else - if `git remote | grep origin`.strip.length > 0 - puts "Renaming repository `origin` remote to `bullet-train`.".green - `git remote rename origin bullet-train` - else - puts "Repository has no `origin` remote, but also no `bullet-train` remote. Did something go wrong?".red - end - end +# Now we can push to `origin` (if we have one, and you want to) +require "#{__dir__}/configure-scripts/push_to_origin" - if `git remote | grep origin`.strip.length > 0 - puts "Repository already has a \`origin`\ remote.".yellow - else - ask "Hit and we'll open a browser to GitHub where you can create a new repository. When you're done, copy the SSH path from the new repository and return here. We'll ask you to paste it to us in the next step." - command = if Gem::Platform.local.os == "linux" - "xdg-open" - else - "open" - end - `#{command} https://github.com/new` +# Now we check that you have base-level system dependencies installed. +require "#{__dir__}/configure-scripts/check_postgres" +require "#{__dir__}/configure-scripts/check_redis" +require "#{__dir__}/configure-scripts/check_icu" +require "#{__dir__}/configure-scripts/check_node" - ssh_path = ask "OK, what was the SSH path? (It should look like `git@github.com:your-account/your-new-repo.git`.)" - while ssh_path == "" - puts "You must provide a path for your new repository.".red - ssh_path = ask "What was the SSH path? (It should look like `git@github.com:your-account/your-new-repo.git`.)" - end - puts "Setting repository's `origin` remote to `#{ssh_path}`.".green - puts `git remote add origin #{ssh_path}`.chomp - end - - local_branch = `git branch | grep "*"`.split.last - - puts "Pushing repository to `origin`.".green - stream "git push origin #{local_branch}:main 2>&1" -end - -puts "Running `bundle install`.".green +# And finally we `bundle install` & `yarn install` +# +announce_section "Running `bundle install`" stream "bundle install" -puts "Running `yarn install`.".green +announce_section "Running `yarn install`." stream "yarn install" -human = ask "What is the name of your new application in title case? (e.g. \"Some Great Application\")" -while human == "" - puts "You must provide a name for your application.".red - human = ask "What is the name of your new application in title case? (e.g. \"Some Great Application\")" -end - -require "active_support/inflector" - -variable = ActiveSupport::Inflector.parameterize(human.gsub("-", " "), separator: '_') -environment_variable = ActiveSupport::Inflector.parameterize(human.gsub("-", " "), separator: '_').upcase -class_name = variable.classify -kebab_case = variable.tr("_", "-") -connected_name = variable.gsub("_", "") # i.e. `bullettrain` as opposed to `bullet_train` - -puts "" -puts "Replacing instances of \"Untitled Application\" with \"#{human}\" throughout the codebase.".green -replace_in_file("./.circleci/config.yml", "untitled_application", variable) -replace_in_file("./config/application.rb", "untitled_application", connected_name) -replace_in_file("./config/database.yml", "untitled_application", variable) -replace_in_file("./config/database.yml", "UNTITLED_APPLICATION", environment_variable) -replace_in_file("./config/cable.yml", "untitled_application", variable) -replace_in_file("./config/initializers/session_store.rb", "untitled_application", variable) -replace_in_file("./config/environments/production.rb", "untitled_application", variable) -replace_in_file("./config/application.rb", "UntitledApplication", class_name) -replace_in_file("./config/locales/en/application.en.yml", "Untitled Application", human, /name/) -replace_in_file("./config/locales/en/application.en.yml", "untitled_application", variable) -replace_in_file("./config/locales/en/application.en.yml", "untitled application", human.downcase, /keywords/) -replace_in_file("./config/locales/en/user_mailer.en.yml", "Untitled Application", human) -replace_in_file("./zapier/package.json", "untitled-application", kebab_case) -replace_in_file("./zapier/package.json", "Untitled Application", human) -replace_in_file("./app/views/api/v1/open_api/index.yaml.erb", "Untitled Application", human) -replace_in_file("./app.json", "Untitled Application", human) -replace_in_file("./.redocly.yaml", "untitled_application", variable) - -puts "" - -unless skip_github - original_repo_link = "https://github.com/bullet-train-co/bullet_train" - new_repo_link = ask "What is the link to your repository? We will use this to enable the one-click deploy to Render button for your application." - replace_in_file("README.example.md", original_repo_link, new_repo_link, /repo=#{original_repo_link}/) -end - -puts "Moving `./README.example.md` to `./README.md`.".green -puts `mv ./README.example.md ./README.md`.chomp - -puts `rm .github/FUNDING.yml`.chomp - -# We can only do this after the README is moved into place. -replace_in_file("./README.md", "Untitled Application", human) - -if skip_github - puts "" - puts "Make sure you save your changes with Git.".yellow -else - puts "Committing all these changes to the repository.".green - stream "git add -A" - stream "git commit -m \"Run configuration script.\"" - stream "git push origin #{local_branch}:main" -end - +announce_section "Configuration complete!" puts "" puts "OK, we're done, but at some point you should edit `config/locales/en/application.en.yml`!".yellow puts "" diff --git a/bin/configure-scripts/check_icu.rb b/bin/configure-scripts/check_icu.rb new file mode 100755 index 00000000..a510990b --- /dev/null +++ b/bin/configure-scripts/check_icu.rb @@ -0,0 +1,95 @@ +#!/usr/bin/env ruby + +require "#{__dir__}/utils" + +announce_section "Checking icu4c" + +def not_installed?(package) + `brew info #{package} | grep "Not installed"`.strip.length > 0 +end + +def check_package(package) + if not_installed?(package) + puts "#{package} is not installed via Homebrew.".red + install_package = ask_boolean "Would you like us to install it? (via: `brew install #{package}`)", "y" + if install_package + puts "brew install #{package}" + stream "brew install #{package}" + puts "#{package} should now be installed.".green + else + continue_anyway = ask_boolean "Try proceeding without #{package}?", "y" + if continue_anyway + puts "You have chosen to continue without `#{package}`.".yellow + else + puts "You have chosen not to continue without `#{package}`. Goodbye".red + exit + end + end + else + puts "#{package} is installed via Homebrew.".green + end +end + +puts "" + +case Gem::Platform.local.os +when "darwin" + if `brew info 2> /dev/null`.length > 0 + puts "Homebrew is installed.".green + puts "" + check_package("icu4c") + else + puts "You don't have Homebrew installed. This isn't necessarily a problem, but we can't check your dependencies without it.".red + continue_anyway = ask_boolean "Try proceeding without Homebrew?", "y" + if continue_anyway + puts "You've chosen to continue without Homebrew.".yellow + puts "This means that we can't tell if you have `icu4c` installed.".yellow + else + puts "You don't have Homebrew and have chosen not to continue. Goodbye.".red + exit + end + end +when "linux" + dpkg_version_info = `dpkg --version` + if dpkg_version_info.match?(/version/) + system_packages = `dpkg -l | grep '^ii'`.split("\n").map do |package_information| + package_information.split("\s")[1] + end + if system_packages.select { |pkg| pkg.match?(/^libicu/) }.any? + puts "You have icu4c installed.".green + else + puts "You don't have icu4c installed.".red + install_package = ask_boolean "Would you like us to install `icu4c`? (vi: Please run `sudo apt-get install libicu-dev`)", "y" + if install_package + puts "sudo apt-get install libicu-dev" + stream "sudo apt-get install libicu-dev" + puts "`icu4c` should now be installed." + else + continue_anyway = ask_boolean "Try proceeding without `icu4c`?", "y" + if continue_anyway + puts "You have chosen to continue without `icu4c`.".yellow + else + puts "You have chosen not to continue without `icu4c`. Goodbye".red + exit + end + exit + end + end + else + puts "You don't have dpkg, so we can't tell if `icu4c` is installed.".red + puts "Please make sure that `icu4c` is installed.".yellow + end +else + puts "We currently don't support this platform to check if you have `icu4c` installed.".red + puts "" + puts "Please ensure it is installed before proceeding." + continue_anyway = ask_boolean "Proceed?", "y" + if continue_anyway + puts "Continuing with unknown status of `icu4c`.".yellow + else + puts "You have chosen not to continue with unknown status of `icu4c`. Goodbye.".red + exit + end +end + +puts "" diff --git a/bin/configure-scripts/check_node.rb b/bin/configure-scripts/check_node.rb new file mode 100755 index 00000000..a4b455e4 --- /dev/null +++ b/bin/configure-scripts/check_node.rb @@ -0,0 +1,28 @@ +#!/usr/bin/env ruby + +require "#{__dir__}/utils" + +announce_section "Checking NodeJS" + +required_node = `cat ./.nvmrc`.strip +actual_node = begin + `node -v`.strip.delete("v") +rescue + :not_found +end +message = "Bullet Train requires Node.js #{required_node} and `node -v` returns #{actual_node}." +if actual_node == :not_found + puts "You don't have Node installed. We can't proceed without it. Try `brew install node`.".red + exit +elsif Gem::Version.new(actual_node) >= Gem::Version.new(required_node) + puts message.green +else + puts message.red + continue_anyway = ask_boolean "Try proceeding with Node #{actual_node} anyway?", "y" + if continue_anyway + puts "You've chosen to continue with Node #{actual_node}.".yellow + else + puts "You've chosen not to continue with Node #{actual_node}. Goodbye.".red + exit + end +end diff --git a/bin/configure-scripts/check_postgres.rb b/bin/configure-scripts/check_postgres.rb new file mode 100755 index 00000000..8578081d --- /dev/null +++ b/bin/configure-scripts/check_postgres.rb @@ -0,0 +1,59 @@ +#!/usr/bin/env ruby + +require "#{__dir__}/utils" + +announce_section "Checking PostgreSQL" + +postgres_version_info = begin + `postgres --version` +rescue + "not found" +end + +psql_version_info = begin + `psql --version` +rescue + "not found" +end + +if /postgres/.match?(postgres_version_info.downcase) + postgres_version = postgres_version_info.split("\s")[2] + + if postgres_version.match?(/^14/) + puts "You have PostgreSQL 14 installed.".green + else + puts "You have PostgreSQL installed, but you're using v#{postgres_version} and not v14.".red + continue_anyway = ask_boolean "Try proceeding without PostgreSQL 14?", "y" + if continue_anyway + puts "Continuing with PostgreSQL v#{postgres_version}. This might cause problems in the next steps.".yellow + else + puts "PostgreSQL v14 is not installed. And you've chosen not to continue. Goodbye.".red + exit + end + end +elsif /postgres/.match?(psql_version_info.downcase) + psql_version = psql_version_info.split("\s")[2] + + if psql_version.match?(/^14/) + puts "You have PostgreSQL 14 installed.".green + else + puts "You have PostgreSQL installed, but you're using v#{psql_version} and not v14.".red + continue_anyway = ask_boolean "Try proceeding without PostgreSQL 14?", "y" + if continue_anyway + puts "Continuing with PostgreSQL v#{psql_version}. This might cause problems in the next steps.".yellow + else + puts "PostgreSQL v14 is not installed. And you've chosen not to continue. Goodbye.".red + exit + end + end +else + puts "You don't seem to have PostgreSQL installed.".red + continue_anyway = ask_boolean "Would you like to try to continue without PostgreSQL?", "n" + if continue_anyway + puts "Continuing without PostgreSQL. This might cause problems in the next steps.".yellow + else + puts "PostgreSQL is not installed. And you've chosen not to continue. Goodbye.".red + exit + end +end +puts "" diff --git a/bin/configure-scripts/check_redis.rb b/bin/configure-scripts/check_redis.rb new file mode 100755 index 00000000..a8c63479 --- /dev/null +++ b/bin/configure-scripts/check_redis.rb @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby + +require "#{__dir__}/utils" + +announce_section "Checking Redis" + +redis_version_info = begin + `redis-cli --version` +rescue + "not found" +end + +puts "" +if /redis/.match?(redis_version_info.downcase) + redis_version = redis_version_info.split("\s")[1] + puts "You have redis #{redis_version} installed.".green +else + puts "You don't seem to have redis installed.".red + continue_anyway = ask_boolan "Would you like to try to continue without redis?", "n" + if continue_anyway + puts "Continuing without redis. This might cause problems in the next steps.".yellow + else + puts "redis is not installed. And you've chosen not to continue. Goodbye.".red + exit + end +end +puts "" diff --git a/bin/configure-scripts/check_ruby.rb b/bin/configure-scripts/check_ruby.rb new file mode 100755 index 00000000..74f8ad60 --- /dev/null +++ b/bin/configure-scripts/check_ruby.rb @@ -0,0 +1,20 @@ +#!/usr/bin/env ruby + +require "#{__dir__}/utils" + +announce_section "Checking Ruby" + +# Unless the shell's current version of Ruby is the same as what the application requires, we should flag it. +# rbenv produces strings like "3.1.2" while rvm produces ones like "ruby-3.1.2", so we account for that here. +required_ruby = `cat ./.ruby-version`.strip.gsub(/^ruby-/, "") +actual_ruby = `ruby -v`.strip +message = "Bullet Train requires Ruby #{required_ruby} and `ruby -v` returns #{actual_ruby}." +if actual_ruby.include?(required_ruby) + puts message.green +else + puts message.red + input = ask "Try proceeding with with Ruby #{actual_ruby} anyway? [y/n]" + if input.downcase[0] == "n" + exit + end +end diff --git a/bin/configure-scripts/check_yarn.rb b/bin/configure-scripts/check_yarn.rb new file mode 100755 index 00000000..e8911bbc --- /dev/null +++ b/bin/configure-scripts/check_yarn.rb @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby + +require "#{__dir__}/utils" +if `yarn -v 2> /dev/null`.length > 0 + puts "Yarn is installed.".green +else + puts "You don't have Yarn installed. We probably can't proceed with out it. Try `brew install yarn` or see the installation instructions at https://yarnpkg.com/getting-started/install .".red + + continue_anyway = ask_boolean "Try proceeding without `yarn`?", "y" + if continue_anyway + puts "You have chosen to continue without `yarn`.".yellow + else + puts "You have chosen not to continue without `yarn`. Goodbye".red + exit + end +end diff --git a/bin/configure-scripts/commit_changes.rb b/bin/configure-scripts/commit_changes.rb new file mode 100755 index 00000000..06246a12 --- /dev/null +++ b/bin/configure-scripts/commit_changes.rb @@ -0,0 +1,15 @@ +#!/usr/bin/env ruby + +require "#{__dir__}/utils" + +announce_section "Commit changes?" + +commit_changes = ask_boolean "Would you like to commit the changes to your project?", "y" +if commit_changes + puts "Committing all these changes to the repository.".green + stream "git add -A" + stream "git commit -m \"Run configuration script.\"" +else + puts "" + puts "Make sure you save your changes with Git.".yellow +end diff --git a/bin/configure-scripts/deploy-buttons/heroku.md b/bin/configure-scripts/deploy-buttons/heroku.md new file mode 100644 index 00000000..4b79c354 --- /dev/null +++ b/bin/configure-scripts/deploy-buttons/heroku.md @@ -0,0 +1,9 @@ + +### Heroku + +[![Deploy to Heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/bullet-train-co/bullet_train) + +Clicking this button will take you to the first step of a process that, when completed, will provision production-grade infrastructure and services for your Bullet Train application which will cost about **$140/month**. + +Once that process has completed, be sure to complete the other steps from the [Deploying to Heroku](https://bullettrain.co/docs/heroku) documentation. + diff --git a/bin/configure-scripts/deploy-buttons/render.md b/bin/configure-scripts/deploy-buttons/render.md new file mode 100644 index 00000000..47778038 --- /dev/null +++ b/bin/configure-scripts/deploy-buttons/render.md @@ -0,0 +1,9 @@ + +### Render + +[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/bullet-train-co/bullet_train) + +Clicking this button will take you to the first step of a process that, when completed, will provision production-grade infrastructure for your Bullet Train application which will cost about **$30/month**. + +When you're done deploying to Render, you need to go into "Dashboard" > "web", copy the server URL, and then go into "Env Groups" > "settings" and paste the URL into the value for `BASE_URL`. + diff --git a/bin/configure-scripts/deploy_button_heroku.rb b/bin/configure-scripts/deploy_button_heroku.rb new file mode 100755 index 00000000..d84cddb7 --- /dev/null +++ b/bin/configure-scripts/deploy_button_heroku.rb @@ -0,0 +1,26 @@ +#!/usr/bin/env ruby + +require "#{__dir__}/utils" + +announce_section "Add a 'Deploy to Heroku' button?" + +button_file = "#{__dir__}/deploy-buttons/heroku.md" + +add_button = ask_boolean "Would you like to add a 'Deploy to Heroku' button to your project.", "y" +if add_button + puts "Adding a 'Deploy to Heroku' button.".green + new_repo_link = ask "What is the https variant of your repo URL? (Something like: https://github.com/your-org/your-repo)" + + File.open("README.md", "a") do |readme| + button = File.read(button_file) + button.each_line do |line| + readme << line + end + end + + original_repo_link = "https://github.com/bullet-train-co/bullet_train" + + replace_in_file("README.md", original_repo_link, new_repo_link, /template=#{original_repo_link}/) +else + puts "Not adding a 'Deploy to Heroku' button.".yellow +end diff --git a/bin/configure-scripts/deploy_button_render.rb b/bin/configure-scripts/deploy_button_render.rb new file mode 100755 index 00000000..e714d282 --- /dev/null +++ b/bin/configure-scripts/deploy_button_render.rb @@ -0,0 +1,26 @@ +#!/usr/bin/env ruby + +require "#{__dir__}/utils" + +announce_section "Add a 'Deploy to Render' button?" + +button_file = "#{__dir__}/deploy-buttons/render.md" + +add_button = ask_boolean "Would you like to add a 'Deploy to Render' button to your project.", "y" +if add_button + puts "Adding a 'Deploy to Render' button.".green + new_repo_link = ask "What is the https variant of your repo URL? (Something like: https://github.com/your-org/your-repo)" + + File.open("README.md", "a") do |readme| + button = File.read(button_file) + button.each_line do |line| + readme << line + end + end + + original_repo_link = "https://github.com/bullet-train-co/bullet_train" + + replace_in_file("README.md", original_repo_link, new_repo_link, /repo=#{original_repo_link}/) +else + puts "Not adding a 'Deploy to Render' button.".yellow +end diff --git a/bin/configure-scripts/push_to_origin.rb b/bin/configure-scripts/push_to_origin.rb new file mode 100755 index 00000000..4c6f207a --- /dev/null +++ b/bin/configure-scripts/push_to_origin.rb @@ -0,0 +1,25 @@ +#!/usr/bin/env ruby + +require "#{__dir__}/utils" + +announce_section "Push to origin" + +has_origin_remote = `git remote | grep origin`.strip.length > 0 +if has_origin_remote + push_to_origin = ask_boolean "Should we push this repo to `origin`?", "y" + if push_to_origin + puts "Pushing repository to `origin`.".green + # TODO: We used to do this to push whatever the current branch is to `main`: + # local_branch = `git branch | grep "*"`.split.last + # stream "git push origin #{local_branch}:main 2>&1" + # + # I'm not sure that's a great thing to do, so for now I'm just doing a bare + # push. Are there reasons that would be inadequate? + + stream "git push origin 2>&1" + else + puts "Skipping pushing to origin.".yellow + end +else + puts "This repo doesn't appear to have an `origin` remote configured. Skipping pushing to `origin`.".red +end diff --git a/bin/configure-scripts/rename_origin.rb b/bin/configure-scripts/rename_origin.rb new file mode 100755 index 00000000..c5c333b2 --- /dev/null +++ b/bin/configure-scripts/rename_origin.rb @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby + +require "#{__dir__}/utils" + +announce_section "Rename origin remote" + +has_bt_remote = `git remote | grep bullet-train`.strip.length > 0 +has_origin_remote = `git remote | grep origin`.strip.length > 0 + +puts "" +if has_bt_remote && has_origin_remote + puts "Repository already has a `bullet-train` remote and an `origin` remote.".yellow +elsif has_bt_remote + puts "Repository already has a `bullet-train` remote.".yellow +elsif has_origin_remote + puts "We recommend renaming the `origin` remote to `bullet-train`.".green + rename_origin = ask_boolean "Should we rename your `origin` remote to `bullet-train`?", "y" + if rename_origin + puts "Renaming `origin` remote to `bullet-train`".green + `git remote rename origin bullet-train` + else + puts "Skipping renaming `origin` remote to `bullet-train`.".yellow + end +else + puts "Repository has no `origin` remote, but also no `bullet-train` remote. Did something go wrong?".red +end +puts "" diff --git a/bin/configure-scripts/setup_github.rb b/bin/configure-scripts/setup_github.rb new file mode 100755 index 00000000..231d6561 --- /dev/null +++ b/bin/configure-scripts/setup_github.rb @@ -0,0 +1,38 @@ +#!/usr/bin/env ruby + +require "#{__dir__}/utils" + +announce_section "Setup GitHub" + +puts "Next, let's push your application to GitHub." +puts "If you would like to use another service like Gitlab to manage your repository," +puts "you can opt out of this step and set up the repository manually." +puts "(If you're not sure, we suggest going with GitHub)" +setup_github = ask_boolean "Continue setting up with GitHub?", "y" + +puts "setup_github = #{setup_github}" +if setup_github + if `git remote | grep origin`.strip.length > 0 + puts "Repository already has a `origin` remote.".yellow + else + ask "Hit and we'll open a browser to GitHub where you can create a new repository. When you're done, copy the SSH path from the new repository and return here. We'll ask you to paste it to us in the next step." + command = if Gem::Platform.local.os == "linux" + "xdg-open" + else + "open" + end + `#{command} https://github.com/new` + + ssh_path = ask "OK, what was the SSH path? (It should look like `git@github.com:your-account/your-new-repo.git`. You can enter `skip` to bail out of GitHub setup.)" + while ssh_path == "" + puts "You must provide a path for your new repository.".red + ssh_path = ask "What was the SSH path? (It should look like `git@github.com:your-account/your-new-repo.git`. you can enter `skip` to bail out of GitHub setup.)" + end + if ssh_path == "skip" + puts "Bailing out of GitHub setup.".yellow + return + end + puts "Setting repository's `origin` remote to `#{ssh_path}`.".green + puts `git remote add origin #{ssh_path}`.chomp + end +end diff --git a/bin/configure-scripts/update_configs.rb b/bin/configure-scripts/update_configs.rb new file mode 100755 index 00000000..a26ea689 --- /dev/null +++ b/bin/configure-scripts/update_configs.rb @@ -0,0 +1,59 @@ +#!/usr/bin/env ruby + +require "#{__dir__}/utils" + +announce_section "Update project configs" + +human = ask "What is the name of your new application in title case? (e.g. \"Some Great Application\")" +while human == "" + puts "You must provide a name for your application.".red + human = ask "What is the name of your new application in title case? (e.g. \"Some Great Application\")" +end + +require "active_support/inflector" + +variable = ActiveSupport::Inflector.parameterize(human.tr("-", " "), separator: "_") +environment_variable = ActiveSupport::Inflector.parameterize(human.tr("-", " "), separator: "_").upcase +class_name = variable.classify +kebab_case = variable.tr("_", "-") +connected_name = variable.delete("_") # i.e. `bullettrain` as opposed to `bullet_train` + +puts "" +puts "Replacing instances of \"Untitled Application\" with \"#{human}\" throughout the codebase.".green +replace_in_file("./.circleci/config.yml", "untitled_application", variable) +replace_in_file("./config/application.rb", "untitled_application", connected_name) +replace_in_file("./config/database.yml", "untitled_application", variable) +replace_in_file("./config/database.yml", "UNTITLED_APPLICATION", environment_variable) +replace_in_file("./config/cable.yml", "untitled_application", variable) +replace_in_file("./config/initializers/session_store.rb", "untitled_application", variable) +replace_in_file("./config/environments/production.rb", "untitled_application", variable) +replace_in_file("./config/application.rb", "UntitledApplication", class_name) +replace_in_file("./config/locales/en/application.en.yml", "Untitled Application", human, /name/) +replace_in_file("./config/locales/en/application.en.yml", "untitled_application", variable) +replace_in_file("./config/locales/en/application.en.yml", "untitled application", human.downcase, /keywords/) +replace_in_file("./config/locales/en/user_mailer.en.yml", "Untitled Application", human) +replace_in_file("./zapier/package.json", "untitled-application", kebab_case) +replace_in_file("./zapier/package.json", "Untitled Application", human) +replace_in_file("./app/views/api/v1/open_api/index.yaml.erb", "Untitled Application", human) +replace_in_file("./app.json", "Untitled Application", human) +replace_in_file("./.redocly.yaml", "untitled_application", variable) + +replace_in_file("./README.example.md", "Untitled Application", human) + +puts "" + +puts "Moving `./README.example.md` to `./README.md`.".green +puts `mv ./README.example.md ./README.md`.chomp + +puts `rm .github/FUNDING.yml`.chomp + +# TODO: Uncomment this when the enable bulk invitations JS is implemented. +# Enable the bulk invitations configuration. +# bt_config_lines = File.open("config/initializers/bullet_train.rb").readlines +# new_lines = bt_config_lines.map do |line| +# if line.match?("config.enable_bulk_invitations") +# line.gsub!(/#\s*/, "") +# end +# line +# end +# File.write("config/initializers/bullet_train.rb", bt_config_lines.join) diff --git a/bin/configure-scripts/utils.rb b/bin/configure-scripts/utils.rb new file mode 100755 index 00000000..f2c8b49f --- /dev/null +++ b/bin/configure-scripts/utils.rb @@ -0,0 +1,56 @@ +#!/usr/bin/env ruby + +require "bundler/inline" + +gemfile do + gem "colorize" + gem "activesupport", require: "active_support" +end + +def ask(string) + puts string.blue + gets.strip +end + +def ask_boolean(question, default = "y") + choices = (default.downcase[0] == "y") ? "[Y/n]" : "[y/N]" + puts "#{question} #{choices}".blue + answer = gets.strip.downcase[0] + if !answer + answer = default.downcase + end + answer == "y" +end + +def stream(command, prefix = " ") + puts "" + IO.popen(command) do |io| + while (line = io.gets) + puts "#{prefix}#{line}" + end + end + puts "" +end + +def replace_in_file(file, before, after, target_regexp = nil) + puts "Replacing in '#{file}'." + if target_regexp + target_file_content = "" + File.open(file).each_line do |l| + l.gsub!(before, after) if !!l.match(target_regexp) + l if !!l.match(target_regexp) + target_file_content += l + end + else + target_file_content = File.read(file) + target_file_content.gsub!(before, after) + end + File.write(file, target_file_content) +end + +def announce_section(section_name) + puts "" + puts "".ljust(80, "-").cyan + puts section_name.cyan + puts "" +end diff --git a/package.json b/package.json index 7765e12f..c2145c81 100644 --- a/package.json +++ b/package.json @@ -2,9 +2,9 @@ "name": "app", "private": true, "dependencies": { - "@bullet-train/bullet-train": "1.7.9", - "@bullet-train/bullet-train-sortable": "1.7.9", - "@bullet-train/fields": "1.7.9", + "@bullet-train/bullet-train": "1.7.10", + "@bullet-train/bullet-train-sortable": "1.7.10", + "@bullet-train/fields": "1.7.10", "@esbuild-plugins/node-globals-polyfill": "^0.2.3", "@fullhuman/postcss-purgecss": "6.0.0", "@hotwired/turbo-rails": "^8.0.4", @@ -34,6 +34,7 @@ "yalc": "^1.0.0-pre.53" }, "scripts": { + "heroku-postbuild": "echo 'This step prevents Heroku from trying to run `yarn build` before ruby and bundler are available. The asset pipeline will call it as part of asset precompilation.'", "build": "THEME=\"light\" node esbuild.config.js", "build:css": "bin/link; THEME=\"light\" yarn build; yarn light:build:css; yarn light:build:mailer:css", "light:build:css": "THEME=\"light\" NODE_PATH=./node_modules tailwindcss -c tailwind.config.js -i ./app/assets/stylesheets/application.css -o ./app/assets/builds/application.light.css --postcss ./postcss.config.js", diff --git a/yarn.lock b/yarn.lock index b6a01e44..6a83a6df 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1495,30 +1495,30 @@ __metadata: languageName: node linkType: hard -"@bullet-train/bullet-train-sortable@npm:1.7.9": - version: 1.7.9 - resolution: "@bullet-train/bullet-train-sortable@npm:1.7.9" +"@bullet-train/bullet-train-sortable@npm:1.7.10": + version: 1.7.10 + resolution: "@bullet-train/bullet-train-sortable@npm:1.7.10" dependencies: "@hotwired/stimulus": "npm:^3.0.1" "@rails/request.js": "npm:^0.0.6" dragula: "npm:^3.7.3" jquery: "npm:^3.7.1" - checksum: 10c0/d26ea1ad0e78381987a5bc4299d8182e5de466a3a1b7c1cd6c3bc54bec508dccf4564abcb420e1ef3a98a5929b1bab701ad39ea2bc832c1abe212178d66787ae + checksum: 10c0/5552302641f7a6bb59dac4004ecc9786ec533f997a72142d50641d9c4465c023cb80feabc4ed3f4d894fab3a7c63222ced2b17badba3ef685b17f0a60a9c973e languageName: node linkType: hard -"@bullet-train/bullet-train@npm:1.7.9": - version: 1.7.9 - resolution: "@bullet-train/bullet-train@npm:1.7.9" +"@bullet-train/bullet-train@npm:1.7.10": + version: 1.7.10 + resolution: "@bullet-train/bullet-train@npm:1.7.10" dependencies: "@hotwired/stimulus": "npm:^3.0.1" - checksum: 10c0/76ce4aafdd186f23f80bc8a69e87b07712c8b24b4a9c622f16f53406a6274e46017cb9cb167500d75c82b1da844eb420fca06348f98078945b90cac7fb32d9ac + checksum: 10c0/08a48ffec5600ea89cedd2d07a8f1d89beef75bdc7b8218c538b6dfd0149642f86a3494a6e25331333d6494d2dd6f6aaa9849be4dcde48cc81f3c4e7e6c499ff languageName: node linkType: hard -"@bullet-train/fields@npm:1.7.9": - version: 1.7.9 - resolution: "@bullet-train/fields@npm:1.7.9" +"@bullet-train/fields@npm:1.7.10": + version: 1.7.10 + resolution: "@bullet-train/fields@npm:1.7.10" dependencies: "@hotwired/stimulus": "npm:^3.0.1" "@simonwep/pickr": "npm:^1.8.1" @@ -1531,7 +1531,7 @@ __metadata: tributejs: "npm:^5.1.3" trix: "npm:^2.0.1" zxcvbn: "npm:^4.4.2" - checksum: 10c0/c446f6f8fda018f47bf8d89f21a0090a0ca360a5dcc9c1c9d93a712098d7559d785b2ca5f1fe4cce3ff01a70c1b0e0286088da21ff6541140bacd28f302cd600 + checksum: 10c0/120e4870f054a1f5cbf7c7e5c379282e475ec4ad17b0adc99f12efbaffc7e3a99b5ae53ece6c058a2d4b8360699063afee0cedf6d7534f8f7bbb7b177dc3468a languageName: node linkType: hard @@ -2172,9 +2172,9 @@ __metadata: version: 0.0.0-use.local resolution: "app@workspace:." dependencies: - "@bullet-train/bullet-train": "npm:1.7.9" - "@bullet-train/bullet-train-sortable": "npm:1.7.9" - "@bullet-train/fields": "npm:1.7.9" + "@bullet-train/bullet-train": "npm:1.7.10" + "@bullet-train/bullet-train-sortable": "npm:1.7.10" + "@bullet-train/fields": "npm:1.7.10" "@esbuild-plugins/node-globals-polyfill": "npm:^0.2.3" "@fullhuman/postcss-purgecss": "npm:6.0.0" "@hotwired/turbo-rails": "npm:^8.0.4"