From 918408bae6e604bbb240fb49189d87c7189c112c Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Tue, 21 Apr 2020 17:40:58 +0300 Subject: [PATCH 01/62] Split `toys` into external gems --- template/.toys/.preload/example_file.rb | 98 ------------------- template/.toys/.preload/question.rb | 44 --------- template/.toys/.toys.rb | 3 + .../.toys/generate/model/template.rb.erb.erb | 4 +- template/Gemfile | 1 + 5 files changed, 7 insertions(+), 143 deletions(-) delete mode 100644 template/.toys/.preload/example_file.rb delete mode 100644 template/.toys/.preload/question.rb diff --git a/template/.toys/.preload/example_file.rb b/template/.toys/.preload/example_file.rb deleted file mode 100644 index 542dc09..0000000 --- a/template/.toys/.preload/example_file.rb +++ /dev/null @@ -1,98 +0,0 @@ -# frozen_string_literal: true - -require 'diffy' -require 'paint' - -## Class for example file -class ExampleFile - SUFFIX = '.example' - - class << self - def all(directory) - Dir[File.join(directory, '**', "*#{SUFFIX}*")] - .map { |file_name| new file_name } - end - end - - def initialize(file_name) - @file_name = file_name - @regular_file_name = @file_name.sub SUFFIX, '' - - @example_basename = - Paint[File.basename(@file_name), :green, :bold] - @regular_basename = - Paint[File.basename(@regular_file_name), :red, :bold] - end - - def actualize_regular_file - return create_regular_file unless regular_file_exist? - - return unless new? - - return update_regular_file if diff.chomp.empty? - - ask_question_and_get_answer - end - - private - - def new? - File.mtime(@file_name) > File.mtime(@regular_file_name) - end - - def regular_file_exist? - File.exist? @regular_file_name - end - - def create_regular_file - FileUtils.cp @file_name, @regular_file_name - edit_file @regular_file_name - end - - def update_regular_file - FileUtils.touch @regular_file_name - end - - def diff - @diff ||= Diffy::Diff - .new(@regular_file_name, @file_name, source: 'files', context: 3) - .to_s(:color) - end - - def ask_question_and_get_answer - case answer = ask_question.answer - when 'yes' - edit_file @regular_file_name - when 'replace' - rewrite_regular_file - when 'no' - update_regular_file - puts 'File modified time updated' - end - - answer - end - - def ask_question - puts <<~WARN - #{@basename} was modified after #{@regular_basename}. - - ```diff - #{diff} - ``` - - WARN - - Question.new( - "Do you want to edit #{@regular_basename} ?", %w[yes replace no] - ) - end - - def edit_file(filename) - system "eval $EDITOR #{filename}" - end - - def rewrite_regular_file - File.write @regular_file_name, File.read(@file_name) - end -end diff --git a/template/.toys/.preload/question.rb b/template/.toys/.preload/question.rb deleted file mode 100644 index ff5869c..0000000 --- a/template/.toys/.preload/question.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -## Class for questions -class Question - def initialize(text, possible_answers) - @text = text - @possible_answers = Set.new(possible_answers) << 'quit' << 'help' - end - - def answer - while @answer.nil? - ask - @answer = @possible_answers.find do |possible_answer| - possible_answer.start_with? @real_answer - end - print_help if @answer.nil? - end - @answer - end - - private - - def print_question - print "#{@text} [#{@possible_answers.map(&:chr).join(',')}] : " - end - - def print_help - @possible_answers.each do |possible_answer| - puts "#{possible_answer.chr} - #{possible_answer}" - end - end - - def ask - print_question - @real_answer = STDIN.gets.chomp.downcase - case @real_answer - when 'h' - print_help - ask - when 'q' - exit - end - end -end diff --git a/template/.toys/.toys.rb b/template/.toys/.toys.rb index 6c4f014..86b4846 100644 --- a/template/.toys/.toys.rb +++ b/template/.toys/.toys.rb @@ -8,6 +8,9 @@ include :exec, exit_on_nonzero_status: true unless include?(:exec) end +require 'benchmark_toys_template' +expand BenchmarkToysTemplate + alias_tool :db, :database alias_tool :psql, 'database:psql' diff --git a/template/.toys/generate/model/template.rb.erb.erb b/template/.toys/generate/model/template.rb.erb.erb index 2f70a15..e8a8fef 100644 --- a/template/.toys/generate/model/template.rb.erb.erb +++ b/template/.toys/generate/model/template.rb.erb.erb @@ -1,6 +1,8 @@ # frozen_string_literal: true module <%= @module_name %> - class \<\%\= camelized_name \%\> < Sequel::Model + module Models + class \<\%\= camelized_name \%\> < Sequel::Model + end end end diff --git a/template/Gemfile b/template/Gemfile index 019876b..b1611c5 100644 --- a/template/Gemfile +++ b/template/Gemfile @@ -88,6 +88,7 @@ group :others do end group :toys do + gem 'benchmark_toys_template', path: '~/Projects/ruby/benchmark_toys_template' gem 'diffy' gem 'paint' gem 'pry' From 9752e8a38d121401a905ab92a7bcc51fc4047d56 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Sun, 2 Aug 2020 19:31:36 +0300 Subject: [PATCH 02/62] Update to a new gem template Replace `rake` with `toys`, add `remark`, etc. --- .cirrus.yml | 48 ++++++++++++++++++++++++++++++------ .editorconfig | 6 +++++ .gitignore | 15 +++++------ .remarkrc.yaml | 2 ++ .rspec | 1 + .rubocop.yml | 34 +++++++------------------ .toys.rb | 27 ++++++++++++++++++++ CHANGELOG.md | 5 ++++ LICENSE.txt | 21 ++++++++++++++++ README.md | 45 +++++++++++++++++++++++++++++++++ Rakefile | 11 --------- flame-cli.gemspec | 63 +++++++++++++++++++++++++---------------------- package.json | 9 +++++++ 13 files changed, 206 insertions(+), 81 deletions(-) create mode 100644 .remarkrc.yaml create mode 100644 .toys.rb create mode 100644 CHANGELOG.md create mode 100644 LICENSE.txt create mode 100644 README.md delete mode 100644 Rakefile create mode 100644 package.json diff --git a/.cirrus.yml b/.cirrus.yml index d8baee0..67312fb 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -10,16 +10,48 @@ bundle_cache: &bundle_cache - gem install bundler - bundle update -test_task: +remark_task: container: - image: ruby:2.6 - <<: *bundle_cache - environment: - CODECOV_TOKEN: ENCRYPTED[5bfb9cd3d53a72c8423ce1b84b18cf7d30fbdcf3a56a25f9cd79dbdff1c3552542578272db8088ec27d7de7f2c363d72] - test_script: bundle exec rake + image: node + node_modules_cache: + folder: node_modules + fingerprint_script: + - echo $CIRRUS_OS + - node -v + - cat package.json + install_script: npm install + lint_script: npm run remark + only_if: ($CIRRUS_BRANCH == 'master') || + changesInclude( + '.cirrus.yml', '.gitignore', 'package.json', '.remarkrc.yaml', '**.md' + ) rubocop_task: container: - image: ruby:2.6 + image: ruby:latest <<: *bundle_cache - rubocop_script: bundle exec rubocop + lint_script: toys rubocop + only_if: ($CIRRUS_BRANCH == 'master') || + changesInclude( + '.cirrus.yml', '.gitignore', 'Gemfile', 'Rakefile', '.rubocop.yml', '*.gemspec', + '**.rb', '**.ru' + ) + +rspec_task: + depends_on: + - remark + - rubocop + container: + matrix: + image: ruby:2.5 + image: ruby:2.6 + image: ruby:2.7 + <<: *bundle_cache + environment: + CODECOV_TOKEN: ENCRYPTED[5bfb9cd3d53a72c8423ce1b84b18cf7d30fbdcf3a56a25f9cd79dbdff1c3552542578272db8088ec27d7de7f2c363d72] + test_script: toys rspec + only_if: ($CIRRUS_BRANCH == 'master') || + changesInclude( + '.cirrus.yml', '.gitignore', 'Gemfile', 'Rakefile', '.rspec', '*.gemspec', 'lib/**', + 'spec/**' + ) diff --git a/.editorconfig b/.editorconfig index 686e508..9fab75a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -2,11 +2,17 @@ root = true [*] indent_style = tab +indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true +max_line_length = 100 [*.y{a,}ml] indent_style = space indent_size = 2 + +[*.md] +indent_style = space +indent_size = 4 diff --git a/.gitignore b/.gitignore index 6f198b7..2f96481 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,10 @@ -Gemfile.lock -.ruby-version -!template/.ruby-version +/Gemfile.lock +/.ruby-version -# Gem files -*.gem +/node_modules/ +/package-lock.json +/yarn.lock -# Tests coverage -coverage/ +/coverage/ + +/pkg/ diff --git a/.remarkrc.yaml b/.remarkrc.yaml new file mode 100644 index 0000000..755a2b1 --- /dev/null +++ b/.remarkrc.yaml @@ -0,0 +1,2 @@ +plugins: + - remark-preset-lint-recommended diff --git a/.rspec b/.rspec index f308d93..c04af2f 100644 --- a/.rspec +++ b/.rspec @@ -1,2 +1,3 @@ --require spec_helper.rb --warnings +--color diff --git a/.rubocop.yml b/.rubocop.yml index 389bfb9..159dc1f 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -2,23 +2,25 @@ require: - rubocop-performance - rubocop-rspec -Layout/Tab: - Enabled: false +Layout/IndentationStyle: + EnforcedStyle: tabs IndentationWidth: 2 Layout/IndentationWidth: Width: 1 +Layout/LineLength: + Max: 100 Layout/MultilineMethodCallIndentation: EnforcedStyle: indented Layout/MultilineOperationIndentation: EnforcedStyle: indented -Layout/ParameterAlignment: - EnforcedStyle: with_fixed_indentation Layout/ArgumentAlignment: EnforcedStyle: with_fixed_indentation -Layout/FirstParameterIndentation: - EnforcedStyle: consistent +Layout/ParameterAlignment: + EnforcedStyle: with_fixed_indentation Layout/FirstArgumentIndentation: EnforcedStyle: consistent +Layout/FirstParameterIndentation: + EnforcedStyle: consistent Layout/FirstArrayElementIndentation: EnforcedStyle: consistent Layout/FirstHashElementIndentation: @@ -27,31 +29,13 @@ Layout/MultilineArrayBraceLayout: EnforcedStyle: new_line Layout/MultilineHashBraceLayout: EnforcedStyle: new_line -Layout/FirstArrayElementLineBreak: - Enabled: true -Layout/FirstHashElementLineBreak: - Enabled: true -Layout/FirstMethodArgumentLineBreak: - Enabled: true -Layout/FirstMethodParameterLineBreak: - Enabled: true Style/ParenthesesAroundCondition: AllowInMultilineConditions: true -Style/HashEachMethods: - Enabled: true -Style/HashTransformKeys: - Enabled: true -Style/HashTransformValues: - Enabled: true - -Lint/RaiseException: - Enabled: true -Lint/StructNewOverride: - Enabled: true AllCops: TargetRubyVersion: 2.6 + NewCops: enable Metrics/BlockLength: Exclude: diff --git a/.toys.rb b/.toys.rb new file mode 100644 index 0000000..da6a106 --- /dev/null +++ b/.toys.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +include :bundler, static: true + +subtool_apply do + include :exec, exit_on_nonzero_status: true, log_level: Logger::UNKNOWN unless include? :exec +end + +require 'gem_toys' +expand GemToys::Template + +alias_tool :g, :gem + +tool :rspec do + def run + exec 'rspec' + end +end + +alias_tool :spec, :rspec +alias_tool :test, :rspec + +tool :rubocop do + def run + exec 'rubocop' + end +end diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f755226 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## master (unreleased) + +* Initial release. diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..a15107b --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2020 Alexander Popov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ae2d2f2 --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# Flame CLI + +[![Cirrus CI - Base Branch Build Status](https://img.shields.io/cirrus/github/AlexWayfer/flame-cli?style=flat-square)](https://cirrus-ci.com/github/AlexWayfer/flame-cli) +[![Codecov branch](https://img.shields.io/codecov/c/github/AlexWayfer/flame-cli/master.svg?style=flat-square)](https://codecov.io/gh/AlexWayfer/flame-cli) +[![Code Climate](https://img.shields.io/codeclimate/maintainability/AlexWayfer/flame-cli.svg?style=flat-square)](https://codeclimate.com/github/AlexWayfer/flame-cli) +[![Depfu](https://img.shields.io/depfu/AlexWayfer/flame-cli?style=flat-square)](https://depfu.com/repos/github/AlexWayfer/flame-cli) +[![Inline docs](https://inch-ci.org/github/AlexWayfer/flame-cli.svg?branch=master)](https://inch-ci.org/github/AlexWayfer/flame-cli) +[![license](https://img.shields.io/github/license/AlexWayfer/flame-cli.svg?style=flat-square)](https://github.com/AlexWayfer/flame-cli/blob/master/LICENSE.txt) +[![Gem](https://img.shields.io/gem/v/flame-cli.svg?style=flat-square)](https://rubygems.org/gems/flame-cli) + +CLI for [Flame web framework](https://github.com/AlexWayfer/flame). + +## Installation + +Install it globally as: + +```shell +gem install flame-cli +``` + +## Usage + +```ruby +flame --help +``` + +## Development + +After checking out the repo, run `bundle install` to install dependencies. + +Then, run `toys rspec` to run the tests. + +To install this gem onto your local machine, run `toys gem install`. + +To release a new version, run `toys gem release %version%`. +See how it works [here](https://github.com/AlexWayfer/gem_toys#release). + +## Contributing + +Bug reports and pull requests are welcome on [GitHub](https://github.com/AlexWayfer/flame-cli). + +## License + +The gem is available as open source under the terms of the +[MIT License](https://opensource.org/licenses/MIT). diff --git a/Rakefile b/Rakefile deleted file mode 100644 index 4c8a81e..0000000 --- a/Rakefile +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -begin - require 'rspec/core/rake_task' - - RSpec::Core::RakeTask.new(:spec) - - task default: :spec -rescue LoadError - puts 'No RSpec available' -end diff --git a/flame-cli.gemspec b/flame-cli.gemspec index d6634b8..e2898e5 100644 --- a/flame-cli.gemspec +++ b/flame-cli.gemspec @@ -1,43 +1,46 @@ # frozen_string_literal: true -Gem::Specification.new do |s| - s.name = 'flame-cli' - s.version = '0.0.0' +Gem::Specification.new do |spec| + spec.name = 'flame-cli' + spec.version = '0.0.0' - s.summary = 'CLI for Flame Web-framework' - s.description = 'Generate new application and maybe something else.' + spec.summary = 'CLI for Flame web framework' + spec.description = 'Generate new application and maybe something else.' - s.authors = ['Alexander Popov'] - s.email = ['alex.wayfer@gmail.com'] - s.homepage = 'https://github.com/AlexWayfer/flame-cli' - s.license = 'MIT' + spec.authors = ['Alexander Popov'] + spec.email = ['alex.wayfer@gmail.com'] + spec.homepage = 'https://github.com/AlexWayfer/flame-cli' + spec.license = 'MIT' - s.metadata = { + spec.metadata = { 'bug_tracker_uri' => 'https://github.com/AlexWayfer/flame-cli/issues', 'documentation_uri' => - "http://www.rubydoc.info/gems/flame-cli/#{s.version}", + "http://www.rubydoc.info/gems/flame-cli/#{spec.version}", 'homepage_uri' => 'https://github.com/AlexWayfer/flame-cli', 'source_code_uri' => 'https://github.com/AlexWayfer/flame-cli', 'wiki_uri' => 'https://github.com/AlexWayfer/flame-cli/wiki' } - s.required_ruby_version = '>= 2.6' - - s.add_runtime_dependency 'clamp', '~> 1.3' - s.add_runtime_dependency 'gorilla_patch', '~> 3.0' - - s.add_development_dependency 'bundler', '~> 2.1' - s.add_development_dependency 'codecov', '~> 0.1' - s.add_development_dependency 'pry', '~> 0.12' - s.add_development_dependency 'pry-byebug', '~> 3.5' - s.add_development_dependency 'rake', '~> 13.0' - s.add_development_dependency 'rspec', '~> 3.7' - s.add_development_dependency 'rubocop', '~> 0.81.0' - s.add_development_dependency 'rubocop-performance', '~> 1.5' - s.add_development_dependency 'rubocop-rspec', '~> 1.38' - s.add_development_dependency 'simplecov', '~> 0.16' - - s.files = Dir.glob('{lib,template}/**/*', File::FNM_DOTMATCH) - s.bindir = 'exe' - s.executables = ['flame'] + spec.required_ruby_version = '>= 2.6' + + spec.add_runtime_dependency 'clamp', '~> 1.3' + spec.add_runtime_dependency 'gorilla_patch', '~> 4.0' + + spec.add_development_dependency 'pry-byebug', '~> 3.9' + + spec.add_development_dependency 'bundler', '~> 2.0' + spec.add_development_dependency 'gem_toys', '~> 0.3.0' + spec.add_development_dependency 'toys', '~> 0.10.4' + + spec.add_development_dependency 'codecov', '~> 0.2.0' + spec.add_development_dependency 'rspec', '~> 3.9' + spec.add_development_dependency 'simplecov', '~> 0.18.0' + + spec.add_development_dependency 'rubocop', '~> 0.88.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.0' + spec.add_development_dependency 'rubocop-rspec', '~> 1.0' + + spec.files = Dir.glob('{lib,template}/**/*', File::FNM_DOTMATCH) + spec.bindir = 'exe' + spec.executables = ['flame'] end diff --git a/package.json b/package.json new file mode 100644 index 0000000..95f608a --- /dev/null +++ b/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "remark": "remark ." + }, + "devDependencies": { + "remark-cli": "^8.0.0", + "remark-preset-lint-recommended": "^4.0.0" + } +} From f9be9a75a7dfc8262c03eaae9160e8b40a424f81 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Sun, 2 Aug 2020 19:40:42 +0300 Subject: [PATCH 03/62] Update application template, finally split toys into gems --- lib/flame/cli/new/app.rb | 24 +- spec/flame/cli/new/app_spec.rb | 316 +++++++++++++----- template/.editorconfig | 3 + template/.eslintignore | 2 +- template/.eslintrc.yaml | 30 +- template/.eslintrc.yml | 80 ----- template/.rubocop.yml | 39 ++- template/.ruby-version | 1 - template/.toys/.preload/.require.rb | 6 - .../.preload/require_application_config.rb | 7 - template/.toys/.preload/root_dir.rb | 3 - template/.toys/.toys.rb | 23 -- template/.toys/.toys.rb.erb | 72 ++++ template/.toys/benchmark.rb | 10 - template/.toys/config/check.rb.erb | 23 -- template/.toys/console.rb | 13 - template/.toys/database/.preload.rb.erb | 52 --- template/.toys/database/.toys.rb | 11 - template/.toys/database/create.rb | 12 - template/.toys/database/drop.rb | 21 -- .../database/dumps/.preload/dump_file.rb | 121 ------- template/.toys/database/dumps/create.rb | 30 -- template/.toys/database/dumps/list.rb | 7 - template/.toys/database/dumps/restore.rb | 52 --- .../migrations/.preload/migration_file.rb | 189 ----------- template/.toys/database/migrations/.toys.rb | 5 - template/.toys/database/migrations/check.rb | 16 - .../create/.preload/create_migration_file.rb | 7 - .../create/.preload/render_template.rb | 9 - .../.toys/database/migrations/create/.toys.rb | 5 - .../database/migrations/create/regular.rb | 9 - template/.toys/database/migrations/disable.rb | 10 - template/.toys/database/migrations/enable.rb | 10 - template/.toys/database/migrations/list.rb | 8 - .../.toys/database/migrations/reversion.rb | 10 - .../.toys/database/migrations/rollback.rb | 16 - template/.toys/database/migrations/run.rb | 51 --- template/.toys/database/psql.rb | 8 - template/.toys/deploy.rb | 26 -- .../.toys/generate/.preload/base_generator.rb | 38 --- template/.toys/generate/form.rb | 28 -- .../.toys/generate/form/template.rb.erb.erb | 24 -- template/.toys/generate/model.rb | 9 - .../.toys/generate/model/template.rb.erb.erb | 8 - template/.toys/locales/.preload/crowdin.rb | 10 - template/.toys/locales/.preload/locale.rb | 68 ---- template/.toys/locales/check.rb | 26 -- template/.toys/locales/download.rb | 9 - template/.toys/locales/lint.rb | 32 -- template/.toys/locales/upload.rb | 9 - template/.toys/routes.rb.erb | 13 - template/.toys/static.rb | 35 -- template/Gemfile | 76 +++-- template/README.md.erb | 58 ++-- template/application.rb.erb | 75 ++++- template/assets/scripts/.eslintrc.yaml | 13 + template/assets/scripts/.keep | 0 template/assets/scripts/application.js | 78 +++++ template/assets/styles/.keep | 0 template/assets/styles/_colors.scss | 21 ++ template/assets/styles/_fonts.scss | 3 + template/assets/styles/_sizes.scss | 15 + template/assets/styles/components/_flash.scss | 23 ++ .../assets/styles/components/_footer.scss | 3 + template/assets/styles/components/_forms.scss | 83 +++++ .../assets/styles/components/_header.scss | 15 + template/assets/styles/components/_page.scss | 82 +++++ template/assets/styles/lib/_breakpoints.scss | 38 +++ template/assets/styles/lib/_clear_fix.scss | 7 + .../lib/_disable_password_autocomplete.scss | 3 + template/assets/styles/lib/_headings.scss | 7 + .../styles/lib/_input_with_clear_button.scss | 36 ++ .../assets/styles/lib/_inputs_with_types.scss | 16 + .../styles/lib/_small_and_large_elements.scss | 57 ++++ .../assets/styles/lib/_sticky_footer.scss | 18 + template/assets/styles/main.scss | 265 +++++++++++++++ template/benchmark/.rubocop.yml | 18 +- template/benchmark/main.example.rb | 4 +- template/config.ru.erb | 35 +- template/config/base.rb.erb | 110 ++++++ template/config/config.rb.erb | 26 -- template/config/database.example.yaml | 5 - template/config/database.example.yaml.erb | 23 ++ template/config/full.rb.erb | 16 + template/config/mail.example.yaml.erb | 23 ++ template/config/processors/logger.rb.erb | 19 -- template/config/processors/mail.rb.erb | 93 ++++++ template/config/processors/r18n.rb.erb | 28 ++ template/config/processors/sentry.rb.erb | 59 ++++ template/config/processors/sequel.rb.bak.erb | 63 ---- template/config/processors/sequel.rb.erb | 40 +++ template/config/processors/server.rb.erb | 21 ++ template/config/processors/shrine.rb.erb | 43 +++ template/config/puma.rb | 79 ----- template/config/puma.rb.erb | 65 ++++ template/config/sentry.example.yaml.erb | 7 + template/config/server.example.yaml | 4 +- template/constants.rb.erb | 18 - template/controllers/_controller.rb.erb | 29 +- template/controllers/site/_controller.rb.erb | 11 + template/exe/setup.sh | 7 +- template/exe/setup/node.sh | 27 +- template/exe/setup/ruby.sh | 15 +- template/exe/update.sh | 4 +- template/filewatchers.yaml | 16 +- template/forms/.keep | 0 template/forms/_base.rb.erb | 65 ++++ template/lib/.keep | 0 template/lib/flame/raven_context.rb | 14 + template/mailers/_base.rb.erb | 81 +++++ template/mailers/mail/_base.rb.erb | 48 +++ template/mailers/mail/default.rb.erb | 29 ++ template/package.json | 57 ++-- template/rollup.config.js.erb | 6 +- template/server | 213 ------------ template/views/site/errors/400.html.erb.erb | 12 + template/views/site/errors/404.html.erb.erb | 7 + template/views/site/errors/500.html.erb.erb | 28 ++ template/views/site/layout.html.erb.erb | 75 ++++- 119 files changed, 2291 insertions(+), 1890 deletions(-) delete mode 100644 template/.eslintrc.yml delete mode 100644 template/.ruby-version delete mode 100644 template/.toys/.preload/.require.rb delete mode 100644 template/.toys/.preload/require_application_config.rb delete mode 100644 template/.toys/.preload/root_dir.rb delete mode 100644 template/.toys/.toys.rb create mode 100644 template/.toys/.toys.rb.erb delete mode 100644 template/.toys/benchmark.rb delete mode 100644 template/.toys/config/check.rb.erb delete mode 100644 template/.toys/console.rb delete mode 100644 template/.toys/database/.preload.rb.erb delete mode 100644 template/.toys/database/.toys.rb delete mode 100644 template/.toys/database/create.rb delete mode 100644 template/.toys/database/drop.rb delete mode 100644 template/.toys/database/dumps/.preload/dump_file.rb delete mode 100644 template/.toys/database/dumps/create.rb delete mode 100644 template/.toys/database/dumps/list.rb delete mode 100644 template/.toys/database/dumps/restore.rb delete mode 100644 template/.toys/database/migrations/.preload/migration_file.rb delete mode 100644 template/.toys/database/migrations/.toys.rb delete mode 100644 template/.toys/database/migrations/check.rb delete mode 100644 template/.toys/database/migrations/create/.preload/create_migration_file.rb delete mode 100644 template/.toys/database/migrations/create/.preload/render_template.rb delete mode 100644 template/.toys/database/migrations/create/.toys.rb delete mode 100644 template/.toys/database/migrations/create/regular.rb delete mode 100644 template/.toys/database/migrations/disable.rb delete mode 100644 template/.toys/database/migrations/enable.rb delete mode 100644 template/.toys/database/migrations/list.rb delete mode 100644 template/.toys/database/migrations/reversion.rb delete mode 100644 template/.toys/database/migrations/rollback.rb delete mode 100644 template/.toys/database/migrations/run.rb delete mode 100644 template/.toys/database/psql.rb delete mode 100644 template/.toys/deploy.rb delete mode 100644 template/.toys/generate/.preload/base_generator.rb delete mode 100644 template/.toys/generate/form.rb delete mode 100644 template/.toys/generate/form/template.rb.erb.erb delete mode 100644 template/.toys/generate/model.rb delete mode 100644 template/.toys/generate/model/template.rb.erb.erb delete mode 100644 template/.toys/locales/.preload/crowdin.rb delete mode 100644 template/.toys/locales/.preload/locale.rb delete mode 100644 template/.toys/locales/check.rb delete mode 100644 template/.toys/locales/download.rb delete mode 100644 template/.toys/locales/lint.rb delete mode 100644 template/.toys/locales/upload.rb delete mode 100644 template/.toys/routes.rb.erb delete mode 100644 template/.toys/static.rb create mode 100644 template/assets/scripts/.eslintrc.yaml delete mode 100644 template/assets/scripts/.keep create mode 100644 template/assets/scripts/application.js delete mode 100644 template/assets/styles/.keep create mode 100644 template/assets/styles/_colors.scss create mode 100644 template/assets/styles/_fonts.scss create mode 100644 template/assets/styles/_sizes.scss create mode 100644 template/assets/styles/components/_flash.scss create mode 100644 template/assets/styles/components/_footer.scss create mode 100644 template/assets/styles/components/_forms.scss create mode 100644 template/assets/styles/components/_header.scss create mode 100644 template/assets/styles/components/_page.scss create mode 100644 template/assets/styles/lib/_breakpoints.scss create mode 100644 template/assets/styles/lib/_clear_fix.scss create mode 100644 template/assets/styles/lib/_disable_password_autocomplete.scss create mode 100644 template/assets/styles/lib/_headings.scss create mode 100644 template/assets/styles/lib/_input_with_clear_button.scss create mode 100644 template/assets/styles/lib/_inputs_with_types.scss create mode 100644 template/assets/styles/lib/_small_and_large_elements.scss create mode 100644 template/assets/styles/lib/_sticky_footer.scss create mode 100644 template/assets/styles/main.scss create mode 100644 template/config/base.rb.erb delete mode 100644 template/config/config.rb.erb delete mode 100644 template/config/database.example.yaml create mode 100644 template/config/database.example.yaml.erb create mode 100644 template/config/full.rb.erb create mode 100644 template/config/mail.example.yaml.erb delete mode 100644 template/config/processors/logger.rb.erb create mode 100644 template/config/processors/mail.rb.erb create mode 100644 template/config/processors/r18n.rb.erb create mode 100644 template/config/processors/sentry.rb.erb delete mode 100644 template/config/processors/sequel.rb.bak.erb create mode 100644 template/config/processors/sequel.rb.erb create mode 100644 template/config/processors/server.rb.erb create mode 100644 template/config/processors/shrine.rb.erb delete mode 100755 template/config/puma.rb create mode 100644 template/config/puma.rb.erb create mode 100644 template/config/sentry.example.yaml.erb delete mode 100644 template/constants.rb.erb delete mode 100644 template/forms/.keep create mode 100644 template/forms/_base.rb.erb delete mode 100644 template/lib/.keep create mode 100644 template/lib/flame/raven_context.rb create mode 100644 template/mailers/_base.rb.erb create mode 100644 template/mailers/mail/_base.rb.erb create mode 100644 template/mailers/mail/default.rb.erb delete mode 100755 template/server create mode 100644 template/views/site/errors/400.html.erb.erb create mode 100644 template/views/site/errors/404.html.erb.erb create mode 100644 template/views/site/errors/500.html.erb.erb diff --git a/lib/flame/cli/new/app.rb b/lib/flame/cli/new/app.rb index ebe5986..20b3e63 100644 --- a/lib/flame/cli/new/app.rb +++ b/lib/flame/cli/new/app.rb @@ -10,10 +10,7 @@ class App < Clamp::Command parameter 'APP_NAME', 'application name' def execute - @app_name = app_name - @module_name = @app_name.camelize - @short_module_name = @module_name - .split(/([[:upper:]][[:lower:]]*)/).map! { |s| s[0] }.join + initialize_instance_variables make_dir do copy_template @@ -26,6 +23,17 @@ def execute private + def initialize_instance_variables + @app_name = app_name + + @module_name = @app_name.camelize + + @short_module_name = + @module_name.split(/([[:upper:]][[:lower:]]*)/).map! { |s| s[0] }.join + + @domain_name = @module_name.downcase + end + def make_dir(&block) puts "Creating '#{@app_name}' directory..." FileUtils.mkdir @app_name @@ -46,7 +54,7 @@ def clean_dirs def render_templates puts 'Replace module names in template...' - Dir.glob('**/*.erb', File::FNM_DOTMATCH).each do |file| + Dir.glob('**/*.erb', File::FNM_DOTMATCH).sort.each do |file| file_pathname = Pathname.new(file) basename_pathname = file_pathname.sub_ext('') puts "- #{basename_pathname}" @@ -56,9 +64,13 @@ def render_templates end end + PERMISSIONS = {}.freeze + def grant_permissions + return unless PERMISSIONS.any? + puts 'Grant permissions to files...' - File.chmod 0o744, 'server' + PERMISSIONS.each { |file, permissions| File.chmod permissions, file } end end end diff --git a/spec/flame/cli/new/app_spec.rb b/spec/flame/cli/new/app_spec.rb index 0f8cc3f..63708f2 100644 --- a/spec/flame/cli/new/app_spec.rb +++ b/spec/flame/cli/new/app_spec.rb @@ -5,7 +5,7 @@ describe 'Flame::CLI::New::App' do subject(:execute_command) do - Bundler.with_original_env { `#{FLAME_CLI} new app #{app_name}` } + Bundler.with_unbundled_env { `#{FLAME_CLI} new app #{app_name}` } end let(:app_name) { 'foo_bar' } @@ -26,21 +26,35 @@ 'Copy template directories and files...', 'Clean directories...', 'Replace module names in template...', - '- config.ru', - '- constants.rb', + '- .toys/.toys.rb', + '- README.md', '- application.rb', + '- config.ru', + '- config/base.rb', + '- config/database.example.yaml', + '- config/full.rb', + '- config/mail.example.yaml', + '- config/processors/mail.rb', + '- config/processors/r18n.rb', + '- config/processors/sentry.rb', + '- config/processors/server.rb', + '- config/processors/sequel.rb', + '- config/processors/shrine.rb', + '- config/puma.rb', + '- config/sentry.example.yaml', + '- config/site.example.yaml', '- controllers/_controller.rb', '- controllers/site/_controller.rb', '- controllers/site/index_controller.rb', + '- forms/_base.rb', + '- mailers/_base.rb', + '- mailers/mail/_base.rb', + '- mailers/mail/default.rb', + '- rollup.config.js', '- routes.rb', '- views/site/index.html.erb', '- views/site/layout.html.erb', - '- rollup.config.js', - '- config/config.rb', - '- config/site.example.yaml', - '- config/processors/logger.rb', - '- config/processors/sequel.rb.bak', - 'Grant permissions to files...', + # 'Grant permissions to files...', 'Done!' ] end @@ -96,94 +110,113 @@ before { execute_command } - describe '.toys/config/check.rb' do + describe '.toys/.toys.rb' do let(:expected_words) do [ - 'ExampleFile.all(FB::Application.config[:config_dir])' + 'FB::Application', + 'expand FlameGenerateToys::Template, namespace: FooBar', + 'FB::Config::Base.new' ] end it { is_expected.to match_words(*expected_words) } end - describe '.toys/database/.preload.rb' do + describe 'application.rb' do let(:expected_words) do [ - '@db_config = FB::Application.config[:database]', - '@db_connection = FB::Application.db_connection' + 'config = FooBar::Config::Base.new', + 'FooBar.complete_config config', + 'module FooBar', + 'class Application < Flame::Application' ] end it { is_expected.to match_words(*expected_words) } end - describe '.toys/generate/form/template.rb.erb' do + describe 'config.ru' do let(:expected_words) do [ - 'module FooBar' + 'FB::Application.require_dirs FB::APP_DIRS', + 'if FB::Application.config[:session]', + 'use Rack::Session::Cookie, FB::Application.config[:session][:cookie]', + 'use Rack::CommonLogger, FB::Application.logger', + 'FB::App = FB::Application', + 'run FB::Application' ] end it { is_expected.to match_words(*expected_words) } end - describe '.toys/generate/model/template.rb.erb' do + describe 'config/base.rb' do let(:expected_words) do [ - 'module FooBar' + 'module FooBar', + '::FB = ::FooBar', + 'APP_DIRS =' ] end it { is_expected.to match_words(*expected_words) } end - describe '.toys/routes.rb' do + describe 'config/full.rb' do let(:expected_words) do [ - 'puts FB::Application.router.routes' + 'module FooBar', + 'FB::Config::Processors.const_get(processor_name).new config' ] end it { is_expected.to match_words(*expected_words) } end - describe 'application.rb' do + describe 'config/puma.rb' do let(:expected_words) do [ - 'module FooBar' + 'config = FooBar::Config::Base.new' ] end it { is_expected.to match_words(*expected_words) } end - describe 'config.ru' do + describe 'config/database.example.yaml' do let(:expected_words) do [ - 'FB::Application.require_dirs FB::APP_DIRS', - 'if FB::Application.config[:session]', - 'use Rack::Session::Cookie, ' \ - 'FB::Application.config[:session][:cookie]', - 'use Rack::CommonLogger, FB::Application.logger', - 'run FB::Application' + ":database: 'foo_bar'", + ":user: 'foo_bar'" + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'config/mail.example.yaml' do + let(:expected_words) do + [ + ":name: 'FooBar.com'", + ":email: 'info@foobar.com'", + ":user_name: 'info@foobar.com'" ] end it { is_expected.to match_words(*expected_words) } end - describe 'config/config.rb' do + describe 'config/sentry.example.yaml' do let(:expected_words) do [ - 'FB::Application.config.instance_exec do', - 'FB::ConfigProcessors.const_get(processor_name).new self' + ':host: sentry.foobar.com' ] end it { is_expected.to match_words(*expected_words) } end - describe 'config/processors/logger.rb' do + describe 'config/processors/mail.rb' do let(:expected_words) do [ 'module FooBar' @@ -193,25 +226,51 @@ it { is_expected.to match_words(*expected_words) } end - describe 'config/processors/sequel.rb.bak' do + describe 'config/processors/r18n.rb' do let(:expected_words) do [ - 'module FooBar', - 'FB::Application.db_connection.extension extension_name', - 'FB::Application.db_connection.loggers << FB::Application.logger', - "FB::Application.db_connection.freeze unless ENV['RACK_CONSOLE']" + 'module FooBar' ] end it { is_expected.to match_words(*expected_words) } end - describe 'constants.rb' do + describe 'config/processors/sentry.rb' do let(:expected_words) do [ 'module FooBar', - '::FB = ::FooBar', - 'APP_DIRS =' + 'FB::APP_DIRS' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'config/processors/server.rb' do + let(:expected_words) do + [ + 'module FooBar' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'config/processors/sequel.rb' do + let(:expected_words) do + [ + 'module FooBar' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'config/processors/shrine.rb' do + let(:expected_words) do + [ + 'module FooBar' ] end @@ -256,8 +315,7 @@ [ '# FooBar', '`createuser -U postgres foo_bar`', - '`createdb -U postgres foo_bar -O foo_bar`', - '`psql -U postgres -c "CREATE EXTENSION citext" foo_bar`', + 'Run `exe/setup.sh`', 'Add UNIX-user for project: `adduser foo_bar`', 'Make symbolic link of project directory to `/var/www/foo_bar`' ] @@ -286,10 +344,109 @@ it { is_expected.to match_words(*expected_words) } end + describe 'forms/_base.rb' do + let(:expected_words) do + [ + 'module FooBar', + 'FB::Application.db_connection' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'mailers/_base.rb' do + let(:expected_words) do + [ + 'module FooBar', + '@from = FB::Application.config[:mail][:from]', + '@controller = FB::MailController.new', + ## https://github.com/rubocop-hq/rubocop/issues/8416 + # rubocop:disable Lint/InterpolationCheck + 'FB::Application.logger.info "#{mail.log_message} [#{index}/#{count}]..."', + 'File.join(FB::Application.config[:tmp_dir], "mailing_#{object_id}")' + # rubocop:enable Lint/InterpolationCheck + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'mailers/mail/_base.rb' do + let(:expected_words) do + [ + 'module FooBar', + 'FB::Application.logger.error e' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'mailers/mail/default.rb' do + let(:expected_words) do + [ + 'module FooBar' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'views/site/errors/400.html.erb' do + let(:expected_words) do + [ + '

<%= t.error.bad_request.title %>

', + '

<%= t.error.bad_request.subtitle %>

', + '<%= t.error.bad_request.text %>', + '', + '<%= t.button.back %>' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'views/site/errors/404.html.erb' do + let(:expected_words) do + [ + '

<%= t.error.page.itself.not_found %>

', + '
', + '<%= t.button.home %>' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'views/site/errors/500.html.erb' do + let(:expected_words) do + [ + '
', + '

<%= t.error.unexpected_error.title %>

', + '

<%= t.error.unexpected_error.subtitle %>

', + '<%= t.error.unexpected_error.text %>', + "<% if config[:environment] == 'development' %>", + '

<%==', + '%>

<%', + '%>

<%=', + '%>

<% end %><%=', + '%>
', + '<% end %>', + '
', + '<%= t.button.back %>' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + describe 'views/site/layout.html.erb' do let(:expected_words) do [ - '<%= FB::Application.config[:site][:site_name] %>' + '<%= config[:site][:site_name] %>', + '<% if Raven.configuration.environments.include?(config[:environment]) &&', + "environment: '<%= config[:environment] %>'," ] end @@ -299,13 +456,13 @@ describe 'generates RuboCop-satisfying app' do subject do - Bundler.with_original_env do + Bundler.with_unbundled_env do system 'bundle exec rubocop' end end before do - Bundler.with_original_env do + Bundler.with_unbundled_env do execute_command Dir.chdir app_name @@ -323,8 +480,8 @@ describe 'generates working app' do subject do - Bundler.with_original_env do - pid = spawn './server start' + Bundler.with_unbundled_env do + pid = spawn 'toys server start' Process.detach pid @@ -338,29 +495,35 @@ response = Net::HTTP.get URI("http://127.0.0.1:#{port}/") rescue Errno::ECONNREFUSED => e sleep 1 - retry if number_of_attempts < 10 + retry if number_of_attempts < 20 raise e end response ensure - Bundler.with_original_env { `./server stop` } + Bundler.with_unbundled_env { `toys server stop` } Process.wait pid end end - before do - Bundler.with_original_env do - ENV['RACK_ENV'] = 'development' + around do |example| + ## HACK: https://github.com/dazuma/toys/issues/57 + original_toys_file_name = "#{__dir__}/../../../../.toys.rb" + File.rename original_toys_file_name, "#{original_toys_file_name}.bak" + + example.run + File.rename "#{original_toys_file_name}.bak", original_toys_file_name + end + + before do + Bundler.with_unbundled_env do execute_command Dir.chdir app_name - %w[server session site].each do |config| - FileUtils.cp( - "config/#{config}.example.yaml", "config/#{config}.yaml" - ) + Dir['config/*.example.yaml'].each do |config_example_file_name| + FileUtils.cp config_example_file_name, config_example_file_name.sub('.example', '') end ## HACK for testing while some server is running @@ -369,7 +532,7 @@ File.read('config/server.yaml').sub('port: 3000', "port: #{port}") ) - system 'bundle install' + system 'exe/setup.sh' end end @@ -386,38 +549,13 @@ result end - let(:expected_response) do - <<~RESPONSE - - - - - FooBar - - -

Hello, world!

- - - - RESPONSE - end - - it { is_expected.to eq expected_response } - end - - describe 'grants `./server` file execution permissions' do - subject { File.stat('server').mode.to_s(8)[3..5] } - - before do - execute_command - - Dir.chdir app_name - end - - after do - Dir.chdir '..' + let(:expected_response_lines) do + [ + 'FooBar', + '

Hello, world!

' + ] end - it { is_expected.to eq '744' } + it { is_expected.to include(*expected_response_lines) } end end diff --git a/template/.editorconfig b/template/.editorconfig index 5326ee1..9fab75a 100644 --- a/template/.editorconfig +++ b/template/.editorconfig @@ -7,9 +7,12 @@ end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true +max_line_length = 100 [*.y{a,}ml] indent_style = space +indent_size = 2 [*.md] indent_style = space +indent_size = 4 diff --git a/template/.eslintignore b/template/.eslintignore index 9c77fea..45a1997 100644 --- a/template/.eslintignore +++ b/template/.eslintignore @@ -1 +1 @@ -public/scripts/app/compiled/ +public/scripts/application/compiled/ diff --git a/template/.eslintrc.yaml b/template/.eslintrc.yaml index 3921eda..9cc83bb 100644 --- a/template/.eslintrc.yaml +++ b/template/.eslintrc.yaml @@ -13,8 +13,8 @@ rules: - error - unix max-len: - - warn - - code: 80 + - error + - code: 100 tabWidth: 2 ignoreUrls: true quotes: @@ -36,6 +36,9 @@ rules: space-before-function-paren: - warn - never + function-paren-newline: + - warn + - consistent space-before-blocks: - warn - always @@ -58,13 +61,20 @@ rules: - allow: - error - warn + arrow-body-style: + - warn + arrow-parens: + - warn + - as-needed + arrow-spacing: + - warn overrides: - files: - - "*.config.js" - env: - browser: false - node: true - es6: true - parserOptions: - sourceType: module + - files: + - "*.config.js" + env: + browser: false + node: true + es6: true + parserOptions: + sourceType: module diff --git a/template/.eslintrc.yml b/template/.eslintrc.yml deleted file mode 100644 index 765d0a0..0000000 --- a/template/.eslintrc.yml +++ /dev/null @@ -1,80 +0,0 @@ -extends: 'eslint:recommended' -env: - browser: true -rules: - indent: - - error - - tab - - SwitchCase: 1 - no-mixed-spaces-and-tabs: - - error - - smart-tabs - linebreak-style: - - error - - unix - max-len: - - error - - code: 80 - tabWidth: 2 - ignoreUrls: true - quotes: - - warn - - single - - avoidEscape: true - semi: - - error - - always - no-multi-spaces: - - error - keyword-spacing: - - warn - - overrides: - catch: - after: false - brace-style: - - error - space-before-function-paren: - - warn - - never - function-paren-newline: - - warn - - consistent - space-before-blocks: - - warn - - always - block-spacing: - - warn - - always - key-spacing: - - warn - object-curly-spacing: - - warn - - always - space-infix-ops: - - warn - space-in-parens: - - warn - no-unused-vars: - - warn - no-console: - - warn - - allow: - - error - - warn - arrow-body-style: - - warn - arrow-parens: - - warn - - as-needed - arrow-spacing: - - warn - -overrides: - - files: - - "*.config.js" - env: - browser: false - node: true - es6: true - parserOptions: - sourceType: module diff --git a/template/.rubocop.yml b/template/.rubocop.yml index 4d2d8bd..6e41d99 100644 --- a/template/.rubocop.yml +++ b/template/.rubocop.yml @@ -1,27 +1,38 @@ -Layout/Tab: - Enabled: false +require: + - rubocop-performance + - rubocop-rspec + +Layout/IndentationStyle: + EnforcedStyle: tabs IndentationWidth: 2 Layout/IndentationWidth: Width: 1 +Layout/LineLength: + Max: 100 Layout/MultilineMethodCallIndentation: EnforcedStyle: indented +Layout/MultilineOperationIndentation: + EnforcedStyle: indented Layout/ArgumentAlignment: EnforcedStyle: with_fixed_indentation - -Style/HashEachMethods: - Enabled: true -Style/HashTransformKeys: - Enabled: true -Style/HashTransformValues: - Enabled: true - -Lint/RaiseException: - Enabled: true -Lint/StructNewOverride: - Enabled: true +Layout/ParameterAlignment: + EnforcedStyle: with_fixed_indentation +Layout/FirstArgumentIndentation: + EnforcedStyle: consistent +Layout/FirstParameterIndentation: + EnforcedStyle: consistent +Layout/FirstArrayElementIndentation: + EnforcedStyle: consistent +Layout/FirstHashElementIndentation: + EnforcedStyle: consistent +Layout/MultilineArrayBraceLayout: + EnforcedStyle: new_line +Layout/MultilineHashBraceLayout: + EnforcedStyle: new_line AllCops: TargetRubyVersion: 2.6 + NewCops: enable Metrics/BlockLength: Exclude: diff --git a/template/.ruby-version b/template/.ruby-version deleted file mode 100644 index 338a5b5..0000000 --- a/template/.ruby-version +++ /dev/null @@ -1 +0,0 @@ -2.6.6 diff --git a/template/.toys/.preload/.require.rb b/template/.toys/.preload/.require.rb deleted file mode 100644 index d5af821..0000000 --- a/template/.toys/.preload/.require.rb +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true - -require 'bundler/setup' -Bundler.setup :system, :toys, :database - -require 'pry-byebug' diff --git a/template/.toys/.preload/require_application_config.rb b/template/.toys/.preload/require_application_config.rb deleted file mode 100644 index 4972de9..0000000 --- a/template/.toys/.preload/require_application_config.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -private - -def require_application_config - require_relative '../../config/config' -end diff --git a/template/.toys/.preload/root_dir.rb b/template/.toys/.preload/root_dir.rb deleted file mode 100644 index c4056b5..0000000 --- a/template/.toys/.preload/root_dir.rb +++ /dev/null @@ -1,3 +0,0 @@ -# frozen_string_literal: true - -ROOT_DIR = File.expand_path "#{__dir__}/../../" diff --git a/template/.toys/.toys.rb b/template/.toys/.toys.rb deleted file mode 100644 index 86b4846..0000000 --- a/template/.toys/.toys.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -include :exec, exit_on_nonzero_status: true unless include?(:exec) - -subtool_apply do - ## https://github.com/dazuma/toys/issues/23#issuecomment-589031428 - # include :bundler unless include?(:bundler) - include :exec, exit_on_nonzero_status: true unless include?(:exec) -end - -require 'benchmark_toys_template' -expand BenchmarkToysTemplate - -alias_tool :db, :database - -alias_tool :psql, 'database:psql' - -alias_tool :c, :console - -alias_tool :bench, :benchmark -alias_tool :b, :benchmark - -alias_tool :g, :generate diff --git a/template/.toys/.toys.rb.erb b/template/.toys/.toys.rb.erb new file mode 100644 index 0000000..524e03e --- /dev/null +++ b/template/.toys/.toys.rb.erb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +include :bundler, static: true + +config_dir = "#{__dir__}/../config" + +require "#{config_dir}/base" + +require 'benchmark_toys' +expand BenchmarkToys::Template +alias_tool :b, :benchmark + +application_proc = proc do + require_relative '../application' + <%= @short_module_name %>::Application +end + +require 'config_toys' +expand ConfigToys::Template, config_dir: config_dir + +db_connection_proc = proc do + application_proc.call.db_connection +end + +require 'sequel_migrations_toys' +expand SequelMigrationsToys::Template, db_connection_proc: db_connection_proc + +require 'psql_toys' +expand PSQLToys::Template, + db_config_proc: (proc do + ## For full config, not base + application_proc.call.config[:database] + end), + db_connection_proc: db_connection_proc, + db_extensions: %w[citext pgcrypto].freeze + +alias_tool :db, :database + +require 'flame_deploy_toys' +expand FlameDeployToys::Template, config_dir: config_dir + +require 'flame_generate_toys' +expand FlameGenerateToys::Template, namespace: <%= @module_name %> + +require 'flame_routes_toys' +expand FlameRoutesToys::Template, application_proc: application_proc + +require 'flame_server_toys' +expand FlameServerToys::Template, + config_proc: (proc do + <%= @short_module_name %>::Config::Base.new + end) + +require 'locales_toys' +expand LocalesToys::Template + +# tool :locales do +# require 'crowdin_toys' +# expand CrowdinToys::Template +# end + +require 'rack_console_toys' +expand RackConsoleToys::Template + +require 'static_files_toys' +expand StaticFilesToys::Template + +tool :rubocop do + def run + exec 'rubocop' + end +end diff --git a/template/.toys/benchmark.rb b/template/.toys/benchmark.rb deleted file mode 100644 index df67889..0000000 --- a/template/.toys/benchmark.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -desc 'Benchmark code' - -def run - ExampleFile.new("#{ROOT_DIR}/benchmark/main.example.rb") - .actualize_regular_file - - sh 'ruby benchmark/main.rb' -end diff --git a/template/.toys/config/check.rb.erb b/template/.toys/config/check.rb.erb deleted file mode 100644 index 1682a35..0000000 --- a/template/.toys/config/check.rb.erb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -desc 'Check config files' - -def run - check_editor_environment_variable - - require_application_config - - example_files.each(&:actualize_regular_file) -end - -private - -def example_files - ExampleFile.all(<%= @short_module_name %>::Application.config[:config_dir]) -end - -def check_editor_environment_variable - return unless ENV['EDITOR'].to_s.empty? - - abort '`EDITOR` environment variable is empty, see README' -end diff --git a/template/.toys/console.rb b/template/.toys/console.rb deleted file mode 100644 index b8aced9..0000000 --- a/template/.toys/console.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -desc 'Start interactive console' - -optional_arg :environment, default: 'development' - -def run - require 'rack/console' - - ARGV.clear - ENV['RACK_CONSOLE'] = 'true' - Rack::Console.new(environment: environment).start -end diff --git a/template/.toys/database/.preload.rb.erb b/template/.toys/database/.preload.rb.erb deleted file mode 100644 index 1ffae21..0000000 --- a/template/.toys/database/.preload.rb.erb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -def db_config - return @db_config if defined?(@db_config) - - require_application_config - - @db_config = <%= @short_module_name %>::Application.config[:database] -end - -def db_connection - return @db_connection if defined?(@db_connection) - - require_application_config - - @db_connection = <%= @short_module_name %>::Application.db_connection -end - -def db_access - @db_access ||= - { '-U' => db_config[:user], '-h' => db_config[:host] } - .compact.map { |key, value| "#{key} #{value}" }.join(' ') -end - -def pgpass_line - @pgpass_line ||= - db_config - .fetch_values(:host, :port, :database, :user, :password) { |_key| '*' } - .join(':') -end - -## Constants for DB - -DB_DIR = File.join(ROOT_DIR, 'db') -DB_MIGRATIONS_DIR = File.join(DB_DIR, 'migrations') -DB_DUMPS_DIR = File.join(DB_DIR, 'dumps') - -PGPASS_FILE = File.expand_path '~/.pgpass' - -DB_EXTENSIONS = %w[citext pgcrypto].freeze - -def update_pgpass - pgpass_lines = - File.exist?(PGPASS_FILE) ? File.read(PGPASS_FILE).split($RS) : [] - - return if pgpass_lines&.include? pgpass_line - - File.write PGPASS_FILE, pgpass_lines.push(pgpass_line, nil).join($RS) - File.chmod(0o600, PGPASS_FILE) -end - -# db_connection.loggers << Logger.new($stdout) diff --git a/template/.toys/database/.toys.rb b/template/.toys/database/.toys.rb deleted file mode 100644 index 23af514..0000000 --- a/template/.toys/database/.toys.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -alias_tool :migrate, 'migrations:run' - -alias_tool :migrations, 'migrations:list' - -alias_tool :dump, 'dumps:create' - -alias_tool :dumps, 'dumps:list' - -alias_tool :restore, 'dumps:restore' diff --git a/template/.toys/database/create.rb b/template/.toys/database/create.rb deleted file mode 100644 index d83e7a6..0000000 --- a/template/.toys/database/create.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -desc 'Create empty DB' - -def run - sh "createdb -U postgres #{db_config[:database]}" \ - " -O #{db_config[:user]}" - DB_EXTENSIONS.each do |db_extension| - sh "psql -U postgres -c 'CREATE EXTENSION #{db_extension}'" \ - " #{db_config[:database]}" - end -end diff --git a/template/.toys/database/drop.rb b/template/.toys/database/drop.rb deleted file mode 100644 index 89a36c6..0000000 --- a/template/.toys/database/drop.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -desc 'Drop DB' - -flag :force, '-f', '--[no-]force' -flag :question, '-q', '--[no-]question', default: true - -def run - if question - if Question.new( - "Drop #{db_config[:database]} ?", %w[yes no] - ).answer == 'no' - abort 'OK' - end - end - - exec_tool 'db:dump' unless force - - db_connection.disconnect - sh "dropdb --if-exists #{db_access} #{db_config[:database]}" -end diff --git a/template/.toys/database/dumps/.preload/dump_file.rb b/template/.toys/database/dumps/.preload/dump_file.rb deleted file mode 100644 index 1ab0c23..0000000 --- a/template/.toys/database/dumps/.preload/dump_file.rb +++ /dev/null @@ -1,121 +0,0 @@ -# frozen_string_literal: true - -require 'paint' - -## Class for single DB dump file -class DumpFile - DB_DUMP_TIMESTAMP = '%Y-%m-%d_%H-%M' - - DB_DUMP_TIMESTAMP_REGEXP_MAP = { - 'Y' => '\d{4}', - 'm' => '\d{2}', - 'd' => '\d{2}', - 'H' => '\d{2}', - 'M' => '\d{2}' - }.freeze - - missing_keys = - DB_DUMP_TIMESTAMP.scan(/%(\w)/).flatten - - DB_DUMP_TIMESTAMP_REGEXP_MAP.keys - - if missing_keys.any? - raise "`DB_DUMP_TIMESTAMP_REGEXP_MAP` doesn't contain keys" \ - " #{missing_keys} for `DB_DUMP_TIMESTAMP`" - end - - DB_DUMP_TIMESTAMP_REGEXP = - DB_DUMP_TIMESTAMP_REGEXP_MAP - .each_with_object(DB_DUMP_TIMESTAMP.dup) do |(key, value), result| - result.gsub! "%#{key}", value - end - - DB_DUMP_FORMATS = %w[custom plain].freeze - - DB_DUMP_EXTENSIONS = { - 'plain' => '.sql', - 'custom' => '.dump' - }.freeze - - missing_formats = DB_DUMP_FORMATS.reject do |db_dump_format| - DB_DUMP_EXTENSIONS[db_dump_format] - end - - if missing_formats.any? - raise "`DB_DUMP_EXTENSIONS` has no keys for #{missing_formats}" \ - ' from `DB_DUMP_FORMATS`' - end - - class << self - def db_dump_regexp - return unless db_config - return @db_dump_regexp if defined?(@db_dump_regexp) - - regexp_escaped_db_dump_extensions = - DB_DUMP_EXTENSIONS.values.map do |db_dump_extension| - Regexp.escape(db_dump_extension) - end - - @db_dump_regexp = /^ - #{DB_DUMPS_DIR}#{Regexp.escape(File::SEPARATOR)} - #{db_config[:database]}_#{DB_DUMP_TIMESTAMP_REGEXP} - (#{regexp_escaped_db_dump_extensions.join('|')}) - $/xo - end - - def all - Dir[File.join(DB_DUMPS_DIR, '*')] - .select { |file| file.match?(db_dump_regexp) } - .map! { |file| new filename: file } - .sort! - end - end - - attr_reader :version, :timestamp, :format - - def initialize(filename: nil, format: 'custom') - if filename - @extension = File.extname(filename) - @format = DB_DUMP_EXTENSIONS.key(@extension) - self.version = filename[/#{DB_DUMP_TIMESTAMP_REGEXP}/o] - else - @format = format - @extension = DB_DUMP_EXTENSIONS[@format] - self.timestamp = Time.now - end - end - - def <=>(other) - timestamp <=> other.timestamp - end - - def to_s - "#{readable_timestamp} #{format}" - end - - def print - puts to_s - end - - def path - File.join( - DB_DUMPS_DIR, - "#{db_config[:database]}_#{version}#{@extension}" - ) - end - - private - - def version=(value) - @version = value - @timestamp = Time.strptime(version, DB_DUMP_TIMESTAMP) - end - - def timestamp=(value) - @timestamp = value - @version = timestamp.strftime(DB_DUMP_TIMESTAMP) - end - - def readable_timestamp - Paint[timestamp.strftime('%F %R'), :cyan] - end -end diff --git a/template/.toys/database/dumps/create.rb b/template/.toys/database/dumps/create.rb deleted file mode 100644 index 92006dd..0000000 --- a/template/.toys/database/dumps/create.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -desc 'Make DB dump' - -flag :format, '-f', '--format=VALUE', - default: DumpFile::DB_DUMP_FORMATS.first, - handler: (lambda do |value, _previous| - DumpFile::DB_DUMP_FORMATS.find do |db_dump_format| - db_dump_format.start_with? value - end - end) - -def run - require 'benchmark' - - update_pgpass - - sh "mkdir -p #{DB_DUMPS_DIR}" - time = Benchmark.realtime do - sh "pg_dump #{db_access} -F#{format.chr}" \ - " #{db_config[:database]} > #{filename}" - end - puts "Done in #{time.round(2)} s." -end - -private - -def filename - DumpFile.new(format: format).path -end diff --git a/template/.toys/database/dumps/list.rb b/template/.toys/database/dumps/list.rb deleted file mode 100644 index 1ccb978..0000000 --- a/template/.toys/database/dumps/list.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -desc 'List DB dumps' - -def run - DumpFile.all.each(&:print) -end diff --git a/template/.toys/database/dumps/restore.rb b/template/.toys/database/dumps/restore.rb deleted file mode 100644 index ed8f30e..0000000 --- a/template/.toys/database/dumps/restore.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -desc 'Restore DB dump' - -optional_arg :step, accept: Integer, default: -1 - -def run - update_pgpass - - @dump_file = DumpFile.all[step] - - abort 'Dump file not found' unless @dump_file - - if Question.new("Restore #{@dump_file} ?", %w[yes no]).answer == 'no' - abort 'Okay' - end - - drop_if_exists - - exec_tool 'db:create' - - restore -end - -private - -def drop_if_exists - return unless sh( - "psql #{db_access} -l | grep '^\s#{db_config[:database]}\s'" - ) - - exec_tool 'db:dump' - exec_tool 'db:drop' -end - -def restore - case @dump_file.format - when 'custom' - pg_restore - when 'plain' - sh "psql #{db_access}" \ - " #{db_config[:database]} < #{@dump_file.path}" - else - raise 'Unknown DB dump file format' - end -end - -def pg_restore - sh "pg_restore #{db_access} -n public" \ - " -d #{db_config[:database]} #{@dump_file.path}" \ - ' --jobs=4 --clean --if-exists' -end diff --git a/template/.toys/database/migrations/.preload/migration_file.rb b/template/.toys/database/migrations/.preload/migration_file.rb deleted file mode 100644 index 9d1c77b..0000000 --- a/template/.toys/database/migrations/.preload/migration_file.rb +++ /dev/null @@ -1,189 +0,0 @@ -# frozen_string_literal: true - -require 'memery' -require 'paint' -require 'time' - -## Migration file -class MigrationFile # rubocop:disable Metrics/ClassLength - CONTENT = proc do |content| - content ||= +<<~DEFAULT - change do - end - DEFAULT - - ## /^(?!$)/ - searches for a position that starts at the - ## "start of line" position and - ## is not followed by the "end of line" position - - <<~STR - # frozen_string_literal: true - - Sequel.migration do - #{content.gsub!(/^(?!$)/, "\t").strip!} - end - STR - end - - DISABLING_EXT = '.bak' - - class << self - include Memery - - def database_schema_migrations - @database_schema_migrations ||= - db_connection[:schema_migrations].select_map(:filename) - end - - def find(query, only_one: true, enabled: true, disabled: true) - filenames = Dir[File.join(DB_MIGRATIONS_DIR, "*#{query}*")] - filenames.select! { |filename| File.file? filename } - files = filenames.map { |filename| new filename: filename }.sort! - files.reject!(&:disabled) unless disabled - files.select!(&:disabled) unless enabled - - return files unless only_one - return files.first if files.size < 2 - - raise 'More than one file mathes the query' - end - - memoize def applied - database_schema_migrations.map { |one| new filename: one } - end - - memoize def existing - find '*', only_one: false, disabled: false - end - - memoize def applied_not_existing - existing_names = existing.map(&:basename) - - applied.reject do |one| - existing_names.include? one.basename - end - end - - memoize def existing_not_applied - existing.reject do |one| - database_schema_migrations.include? one.basename - end - end - end - - attr_accessor :version, :name, :disabled - - def initialize(filename: nil, name: nil, content: nil) - self.filename = filename - self.name = name if name - @content = content - end - - ## Accessors - - def basename - File.basename(@filename) - end - - def filename=(value) - parse_filename value if value.is_a? String - @filename = value - end - - def name=(value) - @name = value.tr(' ', '_').downcase - end - - def disabled=(value) - @disabled = - case value - when String - [DISABLING_EXT, DISABLING_EXT[1..-1]].include? value - else - value - end - end - - def <=>(other) - version <=> other.version - end - - def applied? - self.class.database_schema_migrations.include?(basename) - end - - ## Behavior - - def print - datetime = Time.parse(version).strftime('%F %R') - - puts [ - Paint["[#{version}]", :white], - Paint[datetime, disabled ? :white : :cyan], - Paint[fullname, disabled ? :white : :default], - (Paint['(not applied)', :red] unless applied?) - ].join(' ') - end - - def generate - self.version = new_version - FileUtils.mkdir_p File.dirname new_filename - File.write new_filename, CONTENT.call(@content) - puts "Migration #{relative_filename} created." - end - - def reversion - rename version: new_version - end - - def disable - abort 'Migration already disabled' if disabled - - rename disabled: true - - puts "Migration #{relative_filename} disabled." - end - - def enable - abort 'Migration already enabled' unless disabled - - rename disabled: false - - puts "Migration #{relative_filename} enabled." - end - - private - - def fullname - result = name.tr('_', ' ').capitalize - disabled ? "- #{result} (disabled)" : result - end - - def parse_filename(value = @filename) - basename = File.basename value - self.version, parts = basename.split('_', 2) - self.name, _ext, self.disabled = parts.split('.') - end - - def new_version - Time.now.strftime('%Y%m%d%H%M%S') - end - - def rename(vars = {}) - vars.each { |key, value| send :"#{key}=", value } - - return unless @filename.is_a? String - - File.rename @filename, new_filename - self.filename = new_filename - end - - def new_filename - new_basename = "#{version}_#{name}.rb#{DISABLING_EXT if disabled}" - File.join DB_MIGRATIONS_DIR, new_basename - end - - def relative_filename - new_filename.gsub("#{ROOT_DIR}/", '') - end -end diff --git a/template/.toys/database/migrations/.toys.rb b/template/.toys/database/migrations/.toys.rb deleted file mode 100644 index c419274..0000000 --- a/template/.toys/database/migrations/.toys.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -alias_tool :create, 'create:regular' - -alias_tool :new, :create diff --git a/template/.toys/database/migrations/check.rb b/template/.toys/database/migrations/check.rb deleted file mode 100644 index 13b229f..0000000 --- a/template/.toys/database/migrations/check.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -desc 'Check applied migrations' - -def run - if MigrationFile.applied_not_existing.any? - puts 'Applied, but not existing' - MigrationFile.applied_not_existing.each(&:print) - puts "\n" if MigrationFile.existing_not_applied.any? - end - - return unless MigrationFile.existing_not_applied.any? - - puts 'Existing, but not applied' - MigrationFile.existing_not_applied.each(&:print) -end diff --git a/template/.toys/database/migrations/create/.preload/create_migration_file.rb b/template/.toys/database/migrations/create/.preload/create_migration_file.rb deleted file mode 100644 index 3e5b2e7..0000000 --- a/template/.toys/database/migrations/create/.preload/create_migration_file.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -def create_migration_file(name, content = nil) - file = MigrationFile.new name: name, content: content - - file.generate -end diff --git a/template/.toys/database/migrations/create/.preload/render_template.rb b/template/.toys/database/migrations/create/.preload/render_template.rb deleted file mode 100644 index a8a5814..0000000 --- a/template/.toys/database/migrations/create/.preload/render_template.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -def render_template(filename) - require 'erb' - filename = File.join DB_MIGRATIONS_DIR, 'templates', "#{filename}.rb.erb" - renderer = ERB.new(File.read(filename)) - renderer.filename = filename - renderer.result(binding) -end diff --git a/template/.toys/database/migrations/create/.toys.rb b/template/.toys/database/migrations/create/.toys.rb deleted file mode 100644 index 9636bf8..0000000 --- a/template/.toys/database/migrations/create/.toys.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -alias_tool :audits, :audits_table - -alias_tool :subscription, :subscription_type diff --git a/template/.toys/database/migrations/create/regular.rb b/template/.toys/database/migrations/create/regular.rb deleted file mode 100644 index 17aa588..0000000 --- a/template/.toys/database/migrations/create/regular.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -desc 'Create regular migration' - -required_arg :name - -def run - create_migration_file name -end diff --git a/template/.toys/database/migrations/disable.rb b/template/.toys/database/migrations/disable.rb deleted file mode 100644 index 7e78ae6..0000000 --- a/template/.toys/database/migrations/disable.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -desc 'Disable migration' - -required_arg :filename - -def run - file = MigrationFile.find filename - file.disable -end diff --git a/template/.toys/database/migrations/enable.rb b/template/.toys/database/migrations/enable.rb deleted file mode 100644 index 037ad91..0000000 --- a/template/.toys/database/migrations/enable.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -desc 'Enable migration' - -required_arg :filename - -def run - file = MigrationFile.find filename - file.enable -end diff --git a/template/.toys/database/migrations/list.rb b/template/.toys/database/migrations/list.rb deleted file mode 100644 index ece8f29..0000000 --- a/template/.toys/database/migrations/list.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -desc 'Show all migrations' - -def run - files = MigrationFile.find '*', only_one: false - files.each(&:print) -end diff --git a/template/.toys/database/migrations/reversion.rb b/template/.toys/database/migrations/reversion.rb deleted file mode 100644 index cd01bd5..0000000 --- a/template/.toys/database/migrations/reversion.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -desc 'Change version of migration to latest' - -required_arg :filename - -def run - file = MigrationFile.find filename - file.reversion -end diff --git a/template/.toys/database/migrations/rollback.rb b/template/.toys/database/migrations/rollback.rb deleted file mode 100644 index 59e2764..0000000 --- a/template/.toys/database/migrations/rollback.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -desc 'Rollback the database N steps' - -optional_arg :step, accept: Integer, default: 1 - -def run - exec_tool 'db:dump' - - file = MigrationFile.find('*', only_one: false)[-1 - step.abs] - - ## https://github.com/dazuma/toys/issues/33 - exec_tool ['db:migrations:run', "--target=#{file.version}"] - - puts "Rolled back to #{file.basename}" -end diff --git a/template/.toys/database/migrations/run.rb b/template/.toys/database/migrations/run.rb deleted file mode 100644 index e4c549d..0000000 --- a/template/.toys/database/migrations/run.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -desc 'Run migrations' - -flag :target, '-t', '--target=VERSION' -flag :current, '-c', '--current=VERSION', default: 'current' -flag :force, '-f', '--force', desc: 'Allow missing migration files' - -def run - exec_tool 'db:dump' unless ENV['SKIP_DB_DUMP'] - - ## https://github.com/jeremyevans/sequel/issues/1182#issuecomment-217696754 - require 'sequel' - %i[migration inflector].each { |extension| Sequel.extension extension } - - require_application_config - - Sequel::Migrator.run db_connection, DB_MIGRATIONS_DIR, options -end - -private - -def options - result = { allow_missing_migration_files: force } - - return result.merge! target_options if target - - puts 'Migrating to latest' - result -end - -def target_options - target_version = - if target == '0' - puts 'Migrating all the way down' - target - else - find_target_file_version - end - - { current: current.to_i, target: target_version.to_i } -end - -def find_target_file_version - file = MigrationFile.find target, disabled: false - - abort 'Migration with this version not found' if file.nil? - - puts "Migrating from #{current} to #{file.basename}" - file.version -end diff --git a/template/.toys/database/psql.rb b/template/.toys/database/psql.rb deleted file mode 100644 index 819b28b..0000000 --- a/template/.toys/database/psql.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -desc 'Start psql' - -def run - update_pgpass - sh "psql #{db_access} #{db_config[:database]}" -end diff --git a/template/.toys/deploy.rb b/template/.toys/deploy.rb deleted file mode 100644 index 36a9087..0000000 --- a/template/.toys/deploy.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -desc 'Deploy to production server' - -long_desc <<~DESC - Command for deploy code from git to server - - Example: - Update from git master branch - toys deploy - Update from git development branch - toys deploy development -DESC - -optional_arg :branch, default: :master - -def run - require 'yaml' - - servers = YAML.load_file File.join(ROOT_DIR, 'config', 'deploy.yml') - - servers.each do |server| - update_command = "cd #{server[:path]} && exe/update.sh #{branch}" - sh "ssh -t #{server[:ssh]} 'bash --login -c \"#{update_command}\"'" - end -end diff --git a/template/.toys/generate/.preload/base_generator.rb b/template/.toys/generate/.preload/base_generator.rb deleted file mode 100644 index 926de23..0000000 --- a/template/.toys/generate/.preload/base_generator.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -require 'gorilla_patch/inflections' - -## Class for common code of generation -class BaseGenerator - class << self - def template(type) - (@templates ||= {})[type] ||= - ERB.new File.read "#{__dir__}/../#{type}/template.rb.erb" - end - end - - attr_reader :camelized_name - - using GorillaPatch::Inflections.from_sequel - - def initialize(type, name) - @template = self.class.template(type) - @camelized_name = name.camelize - @relative_file_path = "#{type.to_s.pluralize}/#{name}.rb" - @file_path = File.join ROOT_DIR, @relative_file_path - - return unless File.exist?(@file_path) - - raise ArgumentError, "File #{name} already exists" - end - - def write(**locals) - FileUtils.mkdir_p File.dirname @file_path - File.write @file_path, @template.result_with_hash( - camelized_name: camelized_name, - **locals - ) - - puts "#{@relative_file_path} created" - end -end diff --git a/template/.toys/generate/form.rb b/template/.toys/generate/form.rb deleted file mode 100644 index 4dfde43..0000000 --- a/template/.toys/generate/form.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -desc 'Generate form' - -required_arg :name - -def run - generator = BaseGenerator.new(:form, name) - - *modules, class_name = generator.camelized_name.split('::') - - generator.write( - modules: modules, - class_name: class_name, - class_indentation: "\t" * modules.size, - parent_form: model_forms.include?(class_name) ? class_name : 'Base' - ) -end - -private - -using GorillaPatch::Inflections - -def model_forms - Dir["#{ROOT_DIR}/forms/_model/*.rb"].map do |file| - File.basename(file, '.rb').sub(/^_/, '').camelize - end -end diff --git a/template/.toys/generate/form/template.rb.erb.erb b/template/.toys/generate/form/template.rb.erb.erb deleted file mode 100644 index 8b001f1..0000000 --- a/template/.toys/generate/form/template.rb.erb.erb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -module <%= @module_name %> - module Forms\<\% - modules.map.with_index do |module_name, index| - \%\> - \<\%= "\t" * index %>module <\%= module_name - \%\>\<\% - end - \%\> - \<\%= - class_indentation - \%\>class \<\%= class_name \%\> < Forms::Model::\<\%= parent_form \%\> - \<\%= - class_indentation - \%\>end\<\% - modules.map.with_index do |_module_name, index| - reverse_index = modules.size - index.next - \%\> - \<\%= "\t" * reverse_index \%\>end\<\% - end -\%\> - end -end diff --git a/template/.toys/generate/model.rb b/template/.toys/generate/model.rb deleted file mode 100644 index 163060d..0000000 --- a/template/.toys/generate/model.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -desc 'Generate model' - -required_arg :name - -def run - BaseGenerator.new(:model, name).write -end diff --git a/template/.toys/generate/model/template.rb.erb.erb b/template/.toys/generate/model/template.rb.erb.erb deleted file mode 100644 index e8a8fef..0000000 --- a/template/.toys/generate/model/template.rb.erb.erb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -module <%= @module_name %> - module Models - class \<\%\= camelized_name \%\> < Sequel::Model - end - end -end diff --git a/template/.toys/locales/.preload/crowdin.rb b/template/.toys/locales/.preload/crowdin.rb deleted file mode 100644 index ad71415..0000000 --- a/template/.toys/locales/.preload/crowdin.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -def crowdin(command, branch) - crowdin_config_file = File.join('config', 'crowdin.yml') - - branch ||= `git branch | grep -e "^*" | cut -d' ' -f 2`.strip - - sh "crowdin --config #{crowdin_config_file} -b #{branch.tr('/', '_')} " + - command -end diff --git a/template/.toys/locales/.preload/locale.rb b/template/.toys/locales/.preload/locale.rb deleted file mode 100644 index c2d7dd9..0000000 --- a/template/.toys/locales/.preload/locale.rb +++ /dev/null @@ -1,68 +0,0 @@ -# frozen_string_literal: true - -require 'yaml' - -## Class for Locale file -class Locale - attr_reader :code, :hash - - EXT = '.yml' - - def self.load - Dir[File.join(ROOT_DIR, 'locales', "*#{EXT}")].map do |file| - new File.basename(file, EXT), YAML.load_file(file) - end - end - - def initialize(code, hash) - @code = code - @hash = hash - end - - ## Compares generic values and returns rich diff - class HashCompare - def initialize(hash, other_hash) - @hash = hash - @other_hash = other_hash - end - - def different_keys - @hash.each_with_object({}) do |(key, value), result| - difference = generic_difference(value, @other_hash[key]) - next if difference.nil? || difference.empty? - - result[key] = difference - end - end - - private - - def generic_difference(value, other_value) - return value if value.class != other_value.class - - case value - when Hash - self.class.new(value, other_value).different_keys - when Array - differences_in_array(value, other_value) - when nil - value - end - end - - def differences_in_array(array, other_array) - return array if array.size != other_array.size - - array.zip(other_array).map do |object, other_object| - next if !object.is_a?(Hash) || !other_object.is_a?(Hash) - - difference = self.class.new(object, other_object).different_keys - difference unless difference.empty? - end.compact - end - end - - def diff(other) - HashCompare.new(hash, other.hash).different_keys - end -end diff --git a/template/.toys/locales/check.rb b/template/.toys/locales/check.rb deleted file mode 100644 index e6613e7..0000000 --- a/template/.toys/locales/check.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -desc 'Check locales' - -def run - require 'yaml' - require 'json' - - locales = Locale.load - - locales.each_with_index do |locale, ind| - locales[ind..-1].each do |other_locale| - next if locale == other_locale - - compare_locales(locale, other_locale) - compare_locales(other_locale, locale) - end - end -end - -private - -def compare_locales(locale, other_locale) - puts "#{locale.code.upcase} -> #{other_locale.code.upcase}:\n\n" - puts locale.diff(other_locale).to_yaml -end diff --git a/template/.toys/locales/download.rb b/template/.toys/locales/download.rb deleted file mode 100644 index db8f7fc..0000000 --- a/template/.toys/locales/download.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -desc 'Download files for translation' - -optional_arg :branch - -def run - crowdin 'download translations', branch -end diff --git a/template/.toys/locales/lint.rb b/template/.toys/locales/lint.rb deleted file mode 100644 index 4ed2806..0000000 --- a/template/.toys/locales/lint.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -desc 'Lint locales files' - -def run - require 'yaml' - - Dir["#{ROOT_DIR}/locales/*.y{a,}ml"].each do |locale_file| - ## `YAML.safe_load` doesn't work with anchors - # rubocop:disable Security/YAMLLoad - next if locale_file.end_with?('zh-CN.yml') - - locale = YAML.load File.read locale_file - # rubocop:enable Security/YAMLLoad - errors = %w[notice warning error].reduce([]) do |result, type| - result.concat deep_lint_periods locale[type] - end - errors.map! { |error| "* `#{error}`" } - raise "There is no period in:\n#{errors.join("\n")}" if errors.any? - end -end - -private - -def deep_lint_periods(value, errors = []) - if value.is_a?(Hash) - value.each_value { |nested_value| send __method__, nested_value, errors } - else - errors << value unless value.end_with?('.') # chinese dot for Chinese - end - errors -end diff --git a/template/.toys/locales/upload.rb b/template/.toys/locales/upload.rb deleted file mode 100644 index 7ac6a69..0000000 --- a/template/.toys/locales/upload.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -desc 'Upload files for translation' - -optional_arg :branch - -def run - crowdin 'upload sources', branch -end diff --git a/template/.toys/routes.rb.erb b/template/.toys/routes.rb.erb deleted file mode 100644 index 05cf9a3..0000000 --- a/template/.toys/routes.rb.erb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -desc 'Print application routes' - -def run - require 'rack' - ENV['RACK_ENV'] ||= 'development' - - ## Properly loads environment - Rack::Builder.parse_file File.join(ROOT_DIR, 'config.ru') - - puts <%= @short_module_name %>::Application.router.routes -end diff --git a/template/.toys/static.rb b/template/.toys/static.rb deleted file mode 100644 index 4a12c09..0000000 --- a/template/.toys/static.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -tool :check do - desc 'Check static files' - - GREP_OPTIONS = '--exclude-dir={\.git,log,node_modules,extra} --color=always' - - def run - Dir[File.join(ROOT_DIR, 'public', '**', '*')].each do |file| - next if File.directory? file - - basename = File.basename(file) - - puts "Looking for #{basename}..." - - found = `grep -ir '#{basename}' #{ROOT_DIR} #{GREP_OPTIONS}` - - next unless found.empty? && File.dirname(file) != @skipping_dir - - ask_question file - end - end - - private - - def ask_question(file) - filename = file.sub(ROOT_DIR, '') - case Question.new("Delete #{filename} ?", %w[yes no skip]).answer - when 'yes' - `git rm #{file.gsub(' ', '\ ')}` - when 'skip' - @skipping_dir = File.dirname(file) - end - end -end diff --git a/template/Gemfile b/template/Gemfile index b1611c5..8a01804 100644 --- a/template/Gemfile +++ b/template/Gemfile @@ -6,27 +6,29 @@ group :system do gem 'bundler' gem 'gorilla_patch' # gem 'httpx' - gem 'memery' + gem 'alt_memery' gem 'pry-byebug' # gem 'rubyzip' end group :server do - gem 'flame', github: 'AlexWayfer/flame' + # gem 'flame', github: 'AlexWayfer/flame' + gem 'flame', path: '~/Projects/ruby/flame' gem 'flame-flash', github: 'AlexWayfer/flame-flash' gem 'puma' gem 'rack-contrib' + gem 'rack_csrf', require: 'rack/csrf' ## https://github.com/mwpastore/rack-protection-maximum_cookie/pull/4 gem 'rack-protection-maximum_cookie', github: 'AlexWayfer/rack-protection-maximum_cookie', branch: 'update_rack_dependency' gem 'rack-slashenforce' gem 'rack-utf8_sanitizer' - gem 'rack_csrf', require: 'rack/csrf' end group :development do gem 'filewatcher' + gem 'letter_opener' end group :linter do @@ -36,31 +38,34 @@ group :linter do gem 'rubocop-rspec' end -# group :database do -# gem 'pg' -# gem 'sequel' -# gem 'sequel-enum_values', require: 'sequel/plugins/enum_values' -# gem 'sequel_pg', require: 'sequel' -# gem 'sequel_secure_password' -# end +group :database do + gem 'pg' + gem 'sequel' + gem 'sequel-enum_values', require: 'sequel/plugins/enum_values' + gem 'sequel_pg', require: 'sequel' + gem 'sequel_secure_password' +end -# group :translations do -# gem 'flame-r18n', github: 'AlexWayfer/flame-r18n' -# gem 'r18n-core', github: 'r18n/r18n' -# end +group :translations do + gem 'flame-r18n', github: 'AlexWayfer/flame-r18n' + gem 'r18n-core', github: 'r18n/r18n' +end -# group :forms do -# gem 'formalism', github: 'AlexWayfer/formalism' -# end +group :forms do + gem 'formalism' + gem 'formalism-model_forms' + gem 'formalism-r18n_errors' + gem 'formalism-sequel_transactions' +end group :views do gem 'erubi', require: 'tilt/erubi' end -# group :mails do -# gem 'email_address' -# gem 'mail' -# end +group :mails do + gem 'email_address' + gem 'mail' +end # group :test do # gem 'database_cleaner' @@ -72,6 +77,7 @@ end group :others do # gem 'faker' + gem 'flame-raven_context' # gem 'kramdown' # gem 'retries', # gem 'money-oxr', @@ -79,25 +85,25 @@ group :others do # github: 'AlexWayfer/money-oxr', branch: 'add_flock_for_cache' # ## https://github.com/ooyala/retries/pull/9 # github: 'AlexWayfer/retries', branch: 'v2' - # gem 'sentry-raven' - # gem 'shrine' + gem 'shrine' # gem 'tzinfo' # gem 'tzinfo-data' ## https://github.com/biola/Voight-Kampff/pull/38 - # gem 'voight_kampff', github: 'AlexWayfer/Voight-Kampff', branch: 'v2' + gem 'voight_kampff', github: 'AlexWayfer/Voight-Kampff', branch: 'v2' end group :toys do - gem 'benchmark_toys_template', path: '~/Projects/ruby/benchmark_toys_template' - gem 'diffy' - gem 'paint' - gem 'pry' - gem 'pry-doc' - gem 'rack-console' gem 'toys' -end -# group :benchmark do -# gem 'benchmark-ips' -# gem 'benchmark-memory' -# end + gem 'benchmark_toys' + gem 'config_toys' + gem 'flame_deploy_toys' + gem 'flame_generate_toys' + gem 'flame_routes_toys' + gem 'flame_server_toys' + gem 'locales_toys' + gem 'psql_toys' + gem 'rack_console_toys' + gem 'sequel_migrations_toys' + gem 'static_files_toys' +end diff --git a/template/README.md.erb b/template/README.md.erb index 6be4c89..ff8ef13 100644 --- a/template/README.md.erb +++ b/template/README.md.erb @@ -2,30 +2,25 @@ ## Tech stack -* [**Flame** framework](https://github.com/AlexWayfer/flame) -* [**Sequel** ORM](https://sequel.jeremyevans.net/) -* [**Puma** web-server](https://puma.io/) +* [**Flame** web framework](https://github.com/AlexWayfer/flame) +* [**Sequel** ORM for relational databases](https://sequel.jeremyevans.net/) +* [**Puma** web server](https://puma.io/) ----- ## Deployment -1. Install PostgreSQL version `11` +1. Install PostgreSQL version `12`. 2. Database setup - 1. Create project user: + 1. Create a project user: `createuser -U postgres <%= @app_name %>` (with `-P` for network-open databases) - 2. Create project database: - `createdb -U postgres <%= @app_name %> -O <%= @app_name %>` - 3. Enable required extensions (like `citext` or `pgcrypto`), like: - `psql -U postgres -c "CREATE EXTENSION citext" <%= @app_name %>` -3. Install [`rbenv`](https://github.com/rbenv/rbenv) -4. Install [`nodenv`](https://github.com/nodenv/nodenv) -5. Install [Yarn version 1](https://classic.yarnpkg.com/) -6. Clone this repository and checkout to directory -7. Set the [`EDITOR` environment variable][1] (`nano`, `vim`, `mcedit`, etc.) -8. Run `exe/setup.sh` to install Ruby (with gems), Node (with modules), - fill configs and run database migrations +3. Install [`rbenv`](https://github.com/rbenv/rbenv). +4. Install [`nodenv`](https://github.com/nodenv/nodenv). +5. Clone this repository and checkout to directory. +6. Set the [`EDITOR` environment variable][1] (`nano`, `vim`, `mcedit`, etc.). +7. Run `exe/setup.sh` to install Ruby (with gems), Node (with modules), + fill configs, create database (if needed) and run database migrations. [1]: https://en.wikibooks.org/wiki/Guide_to_Unix/Environment_Variables#EDITOR @@ -47,10 +42,7 @@ ### Server management -For management server state use `./server` tool (it has usage help). - -`./server devel` is useful on development environment, because application -is restarting on code (or configs) changes. +For management server state use `toys server` command. ### Ruby console @@ -74,12 +66,26 @@ toys psql ## Database migrations +### Create migration + +```shell +toys db migrations new migration_name +``` + +### List migrations + +```shell +toys db migrations +# toys db migrations list +``` + ### Run migrations To latest: ```shell toys db migrate +# toys db migrations run ``` To specific version (forward or backward): @@ -88,10 +94,12 @@ To specific version (forward or backward): toys db migrate --target=part_of_target_migration_name_or_version ``` -### Create migration +### Rollback migrations + +`N` is a number of migrations to rollback relatively to the latest existing. ```shell -toys db migrations new migration_name +toys db migrations rollback N ``` ----- @@ -104,8 +112,8 @@ toys db migrations new migration_name exe/update.sh ``` -It will update `master` branch, update `bundle`, -stop `server`, run `migrations` and start `server`. +It will update `master` branch, update bundle, stop server, run migrations +and start server. ### Remotely @@ -114,4 +122,4 @@ toys deploy ``` It will run [`exe/update.sh` command](#locally) remotely -through `ssh` connection from `deploy.yml` configuration file. +through `ssh` connection from `deploy.yaml` configuration file. diff --git a/template/application.rb.erb b/template/application.rb.erb index e4842f1..e5afc3c 100644 --- a/template/application.rb.erb +++ b/template/application.rb.erb @@ -1,5 +1,36 @@ # frozen_string_literal: true +require_relative 'config/base' +config = <%= @module_name %>::Config::Base.new + +## Require gems +require 'bundler/setup' +Bundler.require( + :system, :server, :database, + :translations, :forms, :views, :assets, :mails, :others, + config[:environment] +) + +require 'erubi/capture_end' +require 'voight_kampff/rack_request' + +## Require libs + +### For $RS constant: +### http://ruby-doc.org/stdlib/libdoc/English/rdoc/English.html +require 'English' + +require 'logger' + +### For PP.pp (differs from `pp` by arguments (width)) +require 'pp' + +# require 'money/bank/google_currency' + +## Load full application config, with dependencies from Bundler +require_relative 'config/full' +<%= @module_name %>.complete_config config + module <%= @module_name %> ## Class for application class Application < Flame::Application @@ -8,30 +39,40 @@ module <%= @module_name %> memoize def logger ::Logger.new( - if (config[:environment] == 'development') || - ENV['RACK_CONSOLE'] || - (File.basename($PROGRAM_NAME) == 'toys') - $stdout - else - File.join(config[:logs_dir], 'stdout.log') - end + development_or_toys ? $stdout : File.join(config[:stdout_file]) ) end def db_connection - ::Sequel.connect config[:database] - rescue Sequel::DatabaseConnectionError => e - logger.error e.full_message - nil + connection = ::Sequel.connect config[:database] + + Config::Processors::Sequel::EXTENSIONS.each do |extension_name| + connection.extension extension_name + end + + connection.loggers << logger if development_or_toys + + ## Freeze DB (not for `toys console`) + connection.freeze unless ENV['RACK_CONSOLE'] + + connection end memoize :db_connection - # memoize def shrine_uploaders - # { - # image: Class.new(::Shrine), - # document: Class.new(::Shrine) - # } - # end + memoize def shrine_uploaders + { + # image: Class.new(::Shrine), + # document: Class.new(::Shrine) + } + end + + private + + memoize def development_or_toys + (config[:environment] == 'development') || (File.basename($PROGRAM_NAME) == 'toys') + end end end end + +<%= @short_module_name %>::Application.config = config diff --git a/template/assets/scripts/.eslintrc.yaml b/template/assets/scripts/.eslintrc.yaml new file mode 100644 index 0000000..4b47c3e --- /dev/null +++ b/template/assets/scripts/.eslintrc.yaml @@ -0,0 +1,13 @@ +extends: '../../.eslintrc.yaml' +env: + es6: true +parserOptions: + sourceType: module +# globals: + ## Libs: + # google: true + # Cccombo: true + +rules: + no-var: + - error diff --git a/template/assets/scripts/.keep b/template/assets/scripts/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/template/assets/scripts/application.js b/template/assets/scripts/application.js new file mode 100644 index 0000000..dfe2e91 --- /dev/null +++ b/template/assets/scripts/application.js @@ -0,0 +1,78 @@ +import 'core-js/stable/string/includes'; + +// https://github.com/zloirock/core-js/issues/618#issuecomment-522618151 +// https://github.com/zloirock/core-js/issues/619#issuecomment-522624271 +import 'core-js/stable/symbol/iterator'; + +import 'core-js/stable/array/from'; +import 'core-js/stable/object/entries'; +import 'core-js/stable/dom-collections/for-each'; + +export class App { + document_ready() { + // Prevent double form submission + document.querySelectorAll('form').forEach( + form => { + form.addEventListener('submit', event => { + if (form.submitting == true) { + event.preventDefault(); + event.stopImmediatePropagation(); + } else { + form.submitting = true; + } + }); + } + ); + + // Prevent `.` typing in number inputs with step = 1 + document.querySelectorAll('input[type="number"][step="1"]').forEach( + input => input.addEventListener('keypress', event => { + if (event.key == '.') { + event.preventDefault(); + return false; + } + }) + ); + + // `input[type="number"]` should allow only numbers + // https://stackoverflow.com/a/469362/2630849 + const number_input_filter = value => /^-?\d*[.,]?\d*$/.test(value); + document.querySelectorAll('input[type="number"]').forEach(number_input => { + [ + 'input', 'keydown', 'keyup', 'mousedown', 'mouseup', 'select', + 'contextmenu', 'drop' + ].forEach(event_type => { + number_input.addEventListener(event_type, () => { + if (number_input_filter(number_input.value)) { + number_input.oldValue = number_input.value; + number_input.oldSelectionStart = number_input.selectionStart; + number_input.oldSelectionEnd = number_input.selectionEnd; + } else if ( + Object.prototype.hasOwnProperty.call(number_input, 'oldValue') + ) { + number_input.value = number_input.oldValue; + number_input.setSelectionRange( + number_input.oldSelectionStart, number_input.oldSelectionEnd + ); + } + }); + }); + }); + + // `button` is not focused while clicked in macOS and iOS + // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus + document.querySelectorAll('button[type="submit"][name]').forEach(button => { + const form = button.form; + const input = form.querySelector(`input[name="${button.name}"][value="${button.value}"]`); + if (input) return; + + button.addEventListener('click', () => { + const input = document.createElement('input'); + input.type = 'hidden'; + input.name = button.name; + input.value = button.value; + form.appendChild(input); + }); + }); + } +} diff --git a/template/assets/styles/.keep b/template/assets/styles/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/template/assets/styles/_colors.scss b/template/assets/styles/_colors.scss new file mode 100644 index 0000000..ea6b05d --- /dev/null +++ b/template/assets/styles/_colors.scss @@ -0,0 +1,21 @@ +$accent_background_color: #f24900; +$accent_text_color: darken($accent_background_color, 15%); + +$footer_background_color: #2a2a2a; +$footer_text_color: white; + +// $tertiary_background_color: #ffffff; + +$danger_background_color: lighten(red, 15%); +$danger_text_color: red; +$error_color: $danger_text_color; +$warning_background_color: orange; +$warning_text_color: #e67e00; // darken(darkorange, 5%); +$success_background_color: #2db92d; // darken(limegreen, 5%); +$success_text_color: green; +$highlight_background_color: #eee; +$disabled_text_color: gray; + +$link_text_color: darken($accent_text_color, 10%); + +$input_border_color: #ccc; diff --git a/template/assets/styles/_fonts.scss b/template/assets/styles/_fonts.scss new file mode 100644 index 0000000..7cb7bb5 --- /dev/null +++ b/template/assets/styles/_fonts.scss @@ -0,0 +1,3 @@ +// Russian Ruble sign somewhy doesn't display on iOS 13. Azerbaijani manat too. + +$main_font_family: -apple-system, BlinkMacSystemFont, sans-serif; diff --git a/template/assets/styles/_sizes.scss b/template/assets/styles/_sizes.scss new file mode 100644 index 0000000..8d8a28d --- /dev/null +++ b/template/assets/styles/_sizes.scss @@ -0,0 +1,15 @@ +$container_max_width: 1200px; + +// $icon_size: 1em; + +$common_border_width: 1px; + +$common_border_radius: 0.5em; + +$input_border_radius: $common_border_radius; +$input_border_width: $common_border_width; +$input_vertical_padding: 0.375em; +$input_padding: $input_vertical_padding 0.75em; +$input_line_height: 1.4em; + +$button_border_radius: $input_border_radius; diff --git a/template/assets/styles/components/_flash.scss b/template/assets/styles/components/_flash.scss new file mode 100644 index 0000000..6efc736 --- /dev/null +++ b/template/assets/styles/components/_flash.scss @@ -0,0 +1,23 @@ +.flash { + margin: 1em 0; + padding: 0 1em; + text-align: center; + font-weight: 600; + line-height: 1.4em; + + &::first-letter { + text-transform: capitalize; + } + + &.notice { + color: black; + } + + &.warning { + color: $warning_text_color; + } + + &.error { + color: $error_color; + } +} diff --git a/template/assets/styles/components/_footer.scss b/template/assets/styles/components/_footer.scss new file mode 100644 index 0000000..546c12f --- /dev/null +++ b/template/assets/styles/components/_footer.scss @@ -0,0 +1,3 @@ +> footer { + background-color: $footer_background_color; +} diff --git a/template/assets/styles/components/_forms.scss b/template/assets/styles/components/_forms.scss new file mode 100644 index 0000000..531a3d4 --- /dev/null +++ b/template/assets/styles/components/_forms.scss @@ -0,0 +1,83 @@ +$form_vertical_label_margin: 0.8em; +$form_vertical_input_margin: 0.4em; + +form { + label { + font-weight: bold; + + > p { + font-weight: normal; + } + } + + input.with_clear_button + button { + margin-bottom: 0; + } + + p.error { + height: 0; + overflow: hidden; + margin: 0; + padding-left: 0.4em; + color: lighten($error_color, 15%); + } + + .invalid { + input { + border: $input_border_width solid lighten($error_color, 15%); + box-shadow: 0 0 3px lighten($error_color, 15%); + } + + & ~ .error { + height: inherit; + margin-top: 0.4em; + } + } + + &.inline { + &, + label { + display: inline-block; + } + } + + &.vertical { + text-align: center; + + label, + ul, + table, + p, + article { + text-align: left; + } + + label, + table { + margin-bottom: $form_vertical_label_margin; + } + + label { + display: block; + // Because of inputs outline + // padding: 5px; + + #{all_horizontal_inputs()}, + textarea, + select, + .cccombo { + display: block; + width: 100%; + margin-top: $form_vertical_input_margin; + } + + > label { + margin-bottom: $form_vertical_input_margin; + + &:first-of-type { + margin-top: $form_vertical_input_margin; + } + } + } + } +} diff --git a/template/assets/styles/components/_header.scss b/template/assets/styles/components/_header.scss new file mode 100644 index 0000000..11e5507 --- /dev/null +++ b/template/assets/styles/components/_header.scss @@ -0,0 +1,15 @@ +$header_logo_height: 66px; +$header_vertical_padding: 15px; +$mobile_header_logo_height: 42px; +$mobile_header_vertical_padding: 7px; + +> header { + background: $accent_background_color; + padding: $mobile_header_vertical_padding 0; + z-index: 7; + + &, + a { + color: black; + } +} diff --git a/template/assets/styles/components/_page.scss b/template/assets/styles/components/_page.scss new file mode 100644 index 0000000..3e9bdfe --- /dev/null +++ b/template/assets/styles/components/_page.scss @@ -0,0 +1,82 @@ +.page { + max-width: 800px; + padding: 20px 15px; + margin: 0 auto; + text-align: justify; + + #{headings()}, + pre { + text-align: left; + } + + &.error { + &, + #{headings()} { + text-align: center; + } + + pre { + #{headings()} { + text-align: left; + } + + padding: 0.8em 1.6em; + max-height: 40em; + overflow: auto; + background: #f5f5f5; + border: $common_border_width solid #ddd; + + .sql { + white-space: normal; + line-height: inherit; + } + } + + /** + * There is a bug in stylelint which marks vw as unsupported when + * only vmax is unsupported + * https://github.com/anandthakker/doiuse/issues/83 + */ + /* stylelint-disable plugin/no-unsupported-browser-features */ + &.development { + max-width: none; + + padding: { + left: 3vw; + right: 3vw; + } + } + /* stylelint-enable plugin/no-unsupported-browser-features */ + } + + table { + border-collapse: collapse; + + td { + border: $common_border_width solid #bbb; + padding: 10px 15px; + } + } + + pre, + code { + background-color: lighten($accent_background_color, 40%); + padding: 0.2em 0.4em; + } + + code { + display: inline-block; + } + + pre { + overflow: auto; + } + + pre code { + padding: 0; + } + + li { + margin: 0.6em 0; + } +} diff --git a/template/assets/styles/lib/_breakpoints.scss b/template/assets/styles/lib/_breakpoints.scss new file mode 100644 index 0000000..5cfa06d --- /dev/null +++ b/template/assets/styles/lib/_breakpoints.scss @@ -0,0 +1,38 @@ +$breakpoints: ( + // Extra small devices (portrait phones, less than 576px) + // No media query since this is the default + // Small devices (landscape phones, 576px and up) + sm: 576px, + // Medium devices (tablets, 768px and up) + md: 768px, + // Large devices (desktops, 992px and up) + lg: 992px, + // Extra large devices (large desktops, 1200px and up) + xl: 1200px +); + +@mixin media-to($breakpoint) { + @media screen and (max-width: map-get($breakpoints, $breakpoint) - 1px) { + @content; + } +} + +@mixin media-from($breakpoint) { + @media screen and (min-width: map-get($breakpoints, $breakpoint)) { + @content; + } +} + +// @each $breakpoint, $size in $breakpoints { +// .show-to-#{$breakpoint} { +// @include media-from($breakpoint) { +// display: none; +// }; +// } +// +// .show-from-#{$breakpoint} { +// @include media-to($breakpoint) { +// display: none; +// }; +// } +// } diff --git a/template/assets/styles/lib/_clear_fix.scss b/template/assets/styles/lib/_clear_fix.scss new file mode 100644 index 0000000..af182f7 --- /dev/null +++ b/template/assets/styles/lib/_clear_fix.scss @@ -0,0 +1,7 @@ +@mixin clear-fix { + &::after { + content: ''; + display: block; + clear: both; + } +} diff --git a/template/assets/styles/lib/_disable_password_autocomplete.scss b/template/assets/styles/lib/_disable_password_autocomplete.scss new file mode 100644 index 0000000..936db8f --- /dev/null +++ b/template/assets/styles/lib/_disable_password_autocomplete.scss @@ -0,0 +1,3 @@ +.disable_password_autocomplete { + display: none; +} diff --git a/template/assets/styles/lib/_headings.scss b/template/assets/styles/lib/_headings.scss new file mode 100644 index 0000000..e97c54b --- /dev/null +++ b/template/assets/styles/lib/_headings.scss @@ -0,0 +1,7 @@ +@function headings($from: 1, $to: 6) { + @if $from == $to { + @return 'h#{$from}'; + } @else { + @return 'h#{$from},' + headings($from + 1, $to); + } +} diff --git a/template/assets/styles/lib/_input_with_clear_button.scss b/template/assets/styles/lib/_input_with_clear_button.scss new file mode 100644 index 0000000..b0c6d77 --- /dev/null +++ b/template/assets/styles/lib/_input_with_clear_button.scss @@ -0,0 +1,36 @@ +$input_with_clear_button_width: 2.4em; + +@mixin padding_for_inputs_with_clear_button { + &.with_clear_button { + padding-right: $input_with_clear_button_width + 0.2em; + + @content; + } +} + +input { + @include padding_for_inputs_with_clear_button { + + button { + $outline_with: 1px; + + position: absolute; + right: $outline_with; + top: $outline_with; + width: $input_with_clear_button_width; + height: calc(100% - #{$outline_with} * 2); + padding: 0.2em; + color: darkgray; + background: none; + border: none; + + &:focus { + background: none; + + outline: { + style: auto; + width: $outline_with; + } + } + } + } +} diff --git a/template/assets/styles/lib/_inputs_with_types.scss b/template/assets/styles/lib/_inputs_with_types.scss new file mode 100644 index 0000000..ea820f5 --- /dev/null +++ b/template/assets/styles/lib/_inputs_with_types.scss @@ -0,0 +1,16 @@ +@function inputs_with_types($types...) { + $result: (); + + @each $type in $types { + $result: append($result, 'input[type="' + $type + '"]', comma); + } + + @return $result; +} + +@function all_horizontal_inputs() { + @return inputs_with_types( + 'text', 'email', 'password', 'number', 'url', 'search', 'date', 'time', + 'file' + ); +} diff --git a/template/assets/styles/lib/_small_and_large_elements.scss b/template/assets/styles/lib/_small_and_large_elements.scss new file mode 100644 index 0000000..6a4dd65 --- /dev/null +++ b/template/assets/styles/lib/_small_and_large_elements.scss @@ -0,0 +1,57 @@ +@mixin small-and-large-elements( + // Probably here should be something like `$large_element_container`, + // for example, for Cccombo input like this: + // https://gitlab.gettransport.com/site/site/merge_requests/80/diffs#28551ac9184a7e08499c6a0b6a17f78126a4defa_124_124 + // Discussion: + // https://gitlab.gettransport.com/site/site/merge_requests/522#note_324681 + $small_element: '> button', $large_element: '> input', + $small_element_on_right: false, $inline: false +) { + display: if($inline, inline-flex, flex); + align-items: stretch; + + #{$large_element}, + #{$small_element} { + border-radius: $input_border_radius; + + // Fix for Safari + margin: { + top: 0; + bottom: 0; + } + + &:focus { + z-index: 2; + } + } + + #{$large_element} { + flex-grow: 2; + + &, + > * { + width: 100%; + } + } + + #{$small_element} { + flex-grow: 0; + } + + // Left element + #{if($small_element_on_right, $large_element, $small_element)} { + margin-right: -$common_border_width; + + border: { + top-right-radius: 0; + bottom-right-radius: 0; + } + } + + #{if($small_element_on_right, $small_element, $large_element)} { + border: { + top-left-radius: 0; + bottom-left-radius: 0; + } + } +} diff --git a/template/assets/styles/lib/_sticky_footer.scss b/template/assets/styles/lib/_sticky_footer.scss new file mode 100644 index 0000000..a872aa5 --- /dev/null +++ b/template/assets/styles/lib/_sticky_footer.scss @@ -0,0 +1,18 @@ +@mixin sticky_footer($main_height: auto) { + @at-root html { + height: 100%; + } + + min-height: 100%; + display: flex; + flex-direction: column; + + > main { + min-height: $main_height; + + flex: { + grow: 1; + shrink: 0; + } + } +} diff --git a/template/assets/styles/main.scss b/template/assets/styles/main.scss new file mode 100644 index 0000000..8e434f6 --- /dev/null +++ b/template/assets/styles/main.scss @@ -0,0 +1,265 @@ +@import 'lib/breakpoints'; +@import 'lib/headings'; +@import 'lib/clear_fix'; +@import 'lib/inputs_with_types'; +@import 'lib/disable_password_autocomplete'; +@import 'lib/sticky_footer'; + +* { + position: relative; + // outline: none; + + &:not(svg) { + box-sizing: border-box; + } +} + +body { + @import 'colors'; + @import 'sizes'; + @import 'fonts'; + + margin: 0; + font-family: $main_font_family; + + .hidden { + display: none !important; + } + + :disabled { + cursor: not-allowed; + } + + .container { + max-width: $container_max_width; + padding: 0 15px; + margin: 0 auto; + } + + a { + text-decoration: none; + color: $link_text_color; + + &:hover { + text-decoration: underline; + } + + &[href^="mailto:"] { + white-space: nowrap; + } + } + + label, + a { + &.button { + @extend button; + + display: inline-block; + } + } + + // .icon { + // &.baseline { + // // https://blog.prototypr.io/align-svg-icons-to-text-and-say-goodbye-to-font-icons-d44b3d7b26b4#6e0c + // bottom: -0.125em; + // } + // } + + @mixin custom-background-color($background-color) { + background: $background-color; + border-color: $background-color; + + &:focus { + $focus_color: darken($background-color, 10%); + + background: $focus_color; + border-color: $focus_color; + } + + &:disabled { + $disabled_color: mix($background-color, #ddd); + + background: $disabled_color; + border-color: $disabled_color; + } + } + + @import 'lib/small_and_large_elements'; + @import 'lib/input_with_clear_button'; + + // https://stackoverflow.com/a/23211766/2630849 + #{inputs_with_types('text', 'email', 'password', 'number', 'url', 'search')}, + textarea { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + } + + #{all_horizontal_inputs()}, + textarea, + select, + .cccombo > button { + &:disabled { + color: #333; + background: #f5f5f5; + } + } + + #{all_horizontal_inputs()}, + textarea, + select, + button { + // Fix for Safari :see_no_evil: + margin: 0; + padding: $input_padding; + + font: { + size: inherit; + family: inherit; + } + + line-height: $input_line_height; + color: black; + background: white; + border: $input_border_width solid $input_border_color; + border-radius: $input_border_radius; + } + + textarea { + border-radius: $common_border_radius; + } + + select { + $svg_size: 24; + + appearance: none; + + background: { + image: url( + "data:image/svg+xml;utf8," + ); + repeat: no-repeat; + position-x: 100%; + position-y: center; + } + + padding-right: #{$svg_size}px; + + white-space: normal; + } + + @mixin primary { + @include custom-background-color($accent_background_color); + + color: white; + } + + @mixin warning { + @include custom-background-color($warning_background_color); + + color: white; + } + + @mixin danger { + @include custom-background-color($danger_background_color); + + color: white; + } + + @mixin success { + @include custom-background-color($success_background_color); + + color: white; + } + + @mixin secondary { + @include custom-background-color($footer_background_color); + + color: white; + } + + // @mixin tertiary { + // @include custom-background-color($tertiary_background_color); + // } + + @mixin light { + @include custom-background-color(white); + + color: $accent_text_color; + border-color: $accent_background_color; + + &:focus { + border-color: $accent_background_color; + } + } + + button { + @include custom-background-color(white); + + border-color: $input_border_color; + border-radius: $button_border_radius; + + &.small { + font-size: 0.875em; + padding: 0.2em 0.7em; + } + + &.large { + font: { + size: 115%; + weight: bold; + } + + text-transform: uppercase; + padding: 0.5em 0.8em; + } + + &.primary { + @include primary; + } + + &.warning { + @include warning; + } + + &.danger { + @include danger; + } + + &.success { + @include success; + } + + &.secondary { + @include secondary; + } + + // &.tertiary { + // @include tertiary; + // } + + &.light { + @include light; + } + } + + .cccombo > button { + border-radius: $input_border_radius; + } + + #{all_horizontal_inputs()} { + @include padding_for_inputs_with_clear_button { + + button:focus { + outline-color: darken($highlight_background_color, 15%); + } + } + } + + @import 'components/header'; + @import 'components/footer'; + @import 'components/flash'; + @import 'components/forms'; + @import 'components/page'; + + @include sticky_footer; +} diff --git a/template/benchmark/.rubocop.yml b/template/benchmark/.rubocop.yml index 97f8f6b..0be8f4b 100644 --- a/template/benchmark/.rubocop.yml +++ b/template/benchmark/.rubocop.yml @@ -1,17 +1,13 @@ +inherit_from: ../.rubocop.yml + +Layout/IndentationStyle: + EnforcedStyle: spaces + IndentationWidth: ~ +Layout/IndentationWidth: + Width: 2 Layout/EmptyLines: Enabled: false Layout/EmptyLineBetweenDefs: Enabled: false Metrics/MethodLength: Enabled: false - -Lint/RaiseException: - Enabled: true -Lint/StructNewOverride: - Enabled: true -Style/HashEachMethods: - Enabled: true -Style/HashTransformKeys: - Enabled: true -Style/HashTransformValues: - Enabled: true diff --git a/template/benchmark/main.example.rb b/template/benchmark/main.example.rb index cba9f4b..fba27c5 100644 --- a/template/benchmark/main.example.rb +++ b/template/benchmark/main.example.rb @@ -21,9 +21,9 @@ require 'memery' require 'gorilla_patch' -require 'sequel' +# require 'sequel' # require 'shrine' -require 'r18n-core' +# require 'r18n-core' # require 'money' # require_relative 'config/config' diff --git a/template/config.ru.erb b/template/config.ru.erb index f332676..0ac4bd8 100644 --- a/template/config.ru.erb +++ b/template/config.ru.erb @@ -1,38 +1,10 @@ # frozen_string_literal: true -require_relative 'constants' - -environment = ENV['RACK_ENV'].to_sym - -## Require gems -require 'bundler/setup' -Bundler.require( - :system, :server, :database, - :translations, :forms, :views, :assets, :mails, :others, - environment -) - -require 'erubi/capture_end' -# require 'voight_kampff/rack_request' - -## Require libs - -### For $RS constant: -### http://ruby-doc.org/stdlib/libdoc/English/rdoc/English.html -require 'English' - -require 'logger' - -### For PP.pp (differs from `pp` by arguments (width)) -require 'pp' - -# require 'money/bank/google_currency' - ## Require application require_relative 'application' ## Require dirs -<%= @short_module_name %>::Application.require_dirs <%= @short_module_name %>::APP_DIRS, ignore: [%r{lib/\w+/spec/}] +<%= @short_module_name %>::Application.require_dirs <%= @short_module_name %>::APP_DIRS, ignore: [%r{config/puma.rb}, %r{lib/\w+/spec/}] ## Require routes require_relative 'routes' @@ -50,7 +22,8 @@ use Rack::CommonLogger, <%= @short_module_name %>::Application.logger ## Aliases for rack-console if ENV['RACK_CONSOLE'] - # <%= @short_module_name %>::Acc = <%= @short_module_name %>::Account + <%= @short_module_name %>::App = <%= @short_module_name %>::Application + ## Some models, for example end ## Remove invalid UTF-8 characters from requests @@ -60,7 +33,7 @@ use Rack::UTF8Sanitizer # use Rack::RemoveTrailingSlashes ## Parse body as pointed out in Content-type -use Rack::PostBodyContentTypeParser +use Rack::JSONBodyParser ## CSRF ## Rescued and reported by `lowlevel_error_handler` in Puma config diff --git a/template/config/base.rb.erb b/template/config/base.rb.erb new file mode 100644 index 0000000..4972030 --- /dev/null +++ b/template/config/base.rb.erb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +require 'yaml' + +module <%= @module_name %> + ::<%= @short_module_name %> = ::<%= @module_name %> + + APP_DIRS = [ + 'lib', + 'config', + 'models', + # 'policies', + 'helpers', + 'mailers', + # 'actions', + 'forms', + # 'view_objects', + 'controllers' + ].freeze + + module Config + ## Class for config like a Hash with helper methods. + ## It was a part of the Flame, but some places, like Puma config, + ## need for this without Bundler (dependecies): + ## https://github.com/puma/puma/issues/2319 + class Base < Hash + ## Create an instance of application config + def initialize + populate_dirs + + self[:stdout_file] = "#{self[:logs_dir]}/out" + self[:stderr_file] = "#{self[:logs_dir]}/err" + + require_relative 'processors/server' + Processors::Server.new self + + %i[session site].each do |config_name| + load_yaml config_name, required: true + end + end + + ## Method for loading YAML-files from config directory + ## @param name [Symbol] + ## file base name (extension is `.yml` or '.yaml') + ## @example Load SMTP file without extension, by Symbol + ## config.load_yaml(:smtp) + def load_yaml(name, required: false) + file_name = "#{name}.y{a,}ml" + + file_path = find_config_file file_name, required: required + + return unless file_path + + self[name] = YAML.load_file(file_path) + end + + private + + def populate_dirs + self[:root_dir] = File.realpath "#{__dir__}/.." + + %i[config logs public tmp views].each do |dir_name| + self[:"#{dir_name}_dir"] = "#{self[:root_dir]}/#{dir_name}" + end + + self[:pids_dir] = "#{self[:tmp_dir]}/pids" + end + + def find_config_file(file_name, required:) + file_path = nil + + loop do + file_path = Dir[File.join(self[:config_dir], file_name)].first + break if file_path + + config_relative_dir = self[:config_dir].sub(self[:root_dir], '') + puts "Config file '#{file_name}' not found in '#{config_relative_dir}'" + + next if ask_to_check_config_files + + required ? abort : break + end + + file_path + end + + def ask_to_check_config_files + highline.choose do |menu| + menu.layout = :one_line + + menu.prompt = 'Do you want to check config files? ' + + menu.choice(:yes) do + system 'toys config check' + true + end + + menu.choice(:no) { false } + end + end + + def highline + @highline ||= begin + require 'highline' + HighLine.new + end + end + end + end +end diff --git a/template/config/config.rb.erb b/template/config/config.rb.erb deleted file mode 100644 index 4d6b56d..0000000 --- a/template/config/config.rb.erb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -require_relative '../application' - -## Threads (for mails) -Thread.abort_on_exception = true - -using GorillaPatch::Inflections - -<%= @short_module_name %>::Application.config.instance_exec do - %i[server session site].each do |config_name| - load_yaml config_name - end - - [ - 'Logger' - # 'R18n' - # 'Mail' - # 'Sequel' - # 'Money' - # 'Shrine' - ].each do |processor_name| - require_relative "processors/#{processor_name.underscore}" - <%= @short_module_name %>::ConfigProcessors.const_get(processor_name).new self - end -end diff --git a/template/config/database.example.yaml b/template/config/database.example.yaml deleted file mode 100644 index 6fa33a4..0000000 --- a/template/config/database.example.yaml +++ /dev/null @@ -1,5 +0,0 @@ -:adapter: 'postgres' -:host: 'localhost' -:database: 'database' -:user: 'user' -# :password: 'password' diff --git a/template/config/database.example.yaml.erb b/template/config/database.example.yaml.erb new file mode 100644 index 0000000..08ccefb --- /dev/null +++ b/template/config/database.example.yaml.erb @@ -0,0 +1,23 @@ +default: &default + :adapter: 'postgres' + # :host: 'localhost' + :database: '<%= @app_name %>' + :user: '<%= @app_name %>' + # :password: 'password' + :superuser: 'postgres' + + # :servers: + # :replica: + # :host: 'localhost' + # :database: 'replica' + # :user: 'replicauser' + # :password: 'replicapassword' + +production: + <<: *default + +demo: + <<: *default + +development: + <<: *default diff --git a/template/config/full.rb.erb b/template/config/full.rb.erb new file mode 100644 index 0000000..d8cace6 --- /dev/null +++ b/template/config/full.rb.erb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +## Config split into `base` and `full` because of `prune_bundler`: +## https://github.com/puma/puma/issues/2319 +module <%= @module_name %> + class << self + using GorillaPatch::Inflections + + def complete_config(config) + %w[Sentry R18n Mail Sequel Shrine].each do |processor_name| + require_relative "processors/#{processor_name.underscore}" + <%= @short_module_name %>::Config::Processors.const_get(processor_name).new config + end + end + end +end diff --git a/template/config/mail.example.yaml.erb b/template/config/mail.example.yaml.erb new file mode 100644 index 0000000..4524f61 --- /dev/null +++ b/template/config/mail.example.yaml.erb @@ -0,0 +1,23 @@ +:from: + :name: '<%= @module_name %>.com' + :email: 'info@<%= @domain_name %>.com' + +:site_url: + :scheme: http + :host: localhost + :port: 3000 + +## Remove `_` in the beginning of key for enabling +## If nothing enabled letter_opener would be used + +:_smtp: + :address: 'smtp.gmail.com' + :port: 587 + :user_name: 'info@<%= @domain_name %>.com' + :password: 'your_password' + :authentication: 'plain' + :enable_starttls_auto: true + :content_type: 'text/html; charset=UTF-8' + +:_send_grid: + :api_key: 'your_api_key' diff --git a/template/config/processors/logger.rb.erb b/template/config/processors/logger.rb.erb deleted file mode 100644 index ee89d99..0000000 --- a/template/config/processors/logger.rb.erb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -## Logger configuration and methods -module <%= @module_name %> - module ConfigProcessors - ## Logger configuration - class Logger - def initialize(config) - @config = config - - @config[:logs_dir] = proc do - File.join( - self[:root_dir], self[:server][self[:environment]][:logs_dir] - ) - end - end - end - end -end diff --git a/template/config/processors/mail.rb.erb b/template/config/processors/mail.rb.erb new file mode 100644 index 0000000..9ad8ba4 --- /dev/null +++ b/template/config/processors/mail.rb.erb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +require 'mail' + +require_relative '../../mailers/_base' + +module <%= @module_name %> + module Config + module Processors + ## Configuration for mails + class Mail + def initialize(config) + @config = config + + ## Threads (for mails) + Thread.abort_on_exception = true + + @mail_config = @config.load_yaml :mail + return unless @mail_config + + @config[:mails_dir] = proc { File.join self[:views_dir], 'mails' } + + validate_site_url + + configure_appropriate_provider + end + + private + + def configure_appropriate_provider + if @mail_config[:send_grid] + configure_send_grid + elsif @mail_config[:smtp] + configure_smtp + elsif @config[:environment] != 'test' + configure_letter_opener + end + end + + def configure_send_grid + require_relative '../../mailers/mail/send_grid' + + Mailers::Mail.activated = Mailers::Mail::SendGrid + + Mailers::Mail::SendGrid.client = SendGrid::API.new( + api_key: @mail_config[:send_grid][:api_key], + host: 'https://api.sendgrid.com' + ).client + end + + def configure_smtp + activate_default_mail + + smtp_config = @mail_config[:smtp] + + ::Mail.defaults do + delivery_method :smtp, smtp_config if smtp_config + end + end + + def configure_letter_opener + require 'letter_opener' + + activate_default_mail + + letter_opener_dir = File.join @config[:tmp_dir], 'letter_opener' + + ::Mail.defaults do + delivery_method LetterOpener::DeliveryMethod, location: letter_opener_dir + end + end + + def activate_default_mail + require_relative '../../mailers/mail/default' + + Mailers::Mail.activated = Mailers::Mail::Default + end + + def validate_site_url + site_url = @mail_config[:site_url] + + unless Rack::Request::DEFAULT_PORTS.key?(site_url[:scheme]) + raise 'Incorrect scheme of site URL in mail config' + end + + return if site_url[:host] + + raise 'Host of site URL in mail config is required' + end + end + end + end +end diff --git a/template/config/processors/r18n.rb.erb b/template/config/processors/r18n.rb.erb new file mode 100644 index 0000000..b055f9d --- /dev/null +++ b/template/config/processors/r18n.rb.erb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'flame/r18n' + +module <%= @module_name %> + module Config + module Processors + ## Configuration for R18n + class R18n + def initialize(config) + config[:locales_dir] = File.join config[:root_dir], 'locales' + + ::R18n.default_places = config[:locales_dir] + + ::R18n::I18n.default = 'en-US' + + ::R18n::Filters.on(:named_variables) + + ::R18n::Filters.add(::R18n::Untranslated, :raise_untranslated) do |content| + request_context = Flame::RavenContext.new(:translations, key: content) + Raven.capture_message(*request_context.exception_with_context) + content + end + end + end + end + end +end diff --git a/template/config/processors/sentry.rb.erb b/template/config/processors/sentry.rb.erb new file mode 100644 index 0000000..4948fe9 --- /dev/null +++ b/template/config/processors/sentry.rb.erb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require 'raven' + +module <%= @module_name %> + module Config + module Processors + ## Configuration for Sentry + class Sentry + STATIC_CONFIG = { + environments: %w[production demo].freeze, + ## The default timeouts are too short on slow networks. + timeout: 10, + open_timeout: 10, + app_dirs_pattern: + <%= @short_module_name %>::APP_DIRS + .map { |app_dir| Regexp.escape(app_dir) } + .join('|') + .yield_self { |regexp_template| /(#{regexp_template})/ } + .freeze + }.freeze + + def initialize(config) + @config = config + + @sentry_config = @config.load_yaml :sentry + return unless @sentry_config + + @sentry_config['front-end'][:url] = sentry_url project: 'front-end' + + configure_raven + end + + private + + def sentry_url(project:) + uri = URI::HTTPS.build( + userinfo: @sentry_config[project][:api_key], host: @sentry_config[:host] + ) + uri.merge!(@sentry_config[project][:project_id].to_s) + uri.to_s + end + + def configure_raven + Raven.configure do |raven_config| + STATIC_CONFIG.each { |field, value| raven_config.public_send("#{field}=", value) } + + raven_config.current_environment = @config[:environment] + raven_config.dsn = sentry_url project: 'back-end' + raven_config.processors -= [ + Raven::Processor::Cookies, + Raven::Processor::PostData + ] + end + end + end + end + end +end diff --git a/template/config/processors/sequel.rb.bak.erb b/template/config/processors/sequel.rb.bak.erb deleted file mode 100644 index 1d050bb..0000000 --- a/template/config/processors/sequel.rb.bak.erb +++ /dev/null @@ -1,63 +0,0 @@ -# frozen_string_literal: true - -require 'yaml' - -require 'sequel' - -## Sequel configuration and methods -module <%= @module_name %> - module ConfigProcessors - ## Database initialize for Sequel - class Sequel - def initialize(config) - @config = config - - @config.load_yaml :database - - @config[:database] = - @config[:database][ENV.fetch('RACK_ENV', 'development')] - env_db_name = ENV['DB_NAME'] - @config[:database][:database] = env_db_name if env_db_name - - configure_sequel - rescue Flame::Errors::ConfigFileNotFoundError - nil - end - - private - - EXTENSIONS = %i[ - error_sql - pg_enum - pg_json - pg_timestamptz - server_block - ].freeze - - PLUGINS = %i[ - timestamps - json_serializer - dataset_associations - association_multi_add_remove - values_methods - ].freeze - - def configure_sequel - ::Sequel::Model.raise_on_save_failure = false - - EXTENSIONS.each do |extension_name| - <%= @short_module_name %>::Application.db_connection.extension extension_name - end - - PLUGINS.each { |plugin_name| ::Sequel::Model.plugin plugin_name } - - if @config[:environment] == 'development' - <%= @short_module_name %>::Application.db_connection.loggers << <%= @short_module_name %>::Application.logger - end - - ## Freeze DB (not for `toys console`) - <%= @short_module_name %>::Application.db_connection.freeze unless ENV['RACK_CONSOLE'] - end - end - end -end diff --git a/template/config/processors/sequel.rb.erb b/template/config/processors/sequel.rb.erb new file mode 100644 index 0000000..2a77180 --- /dev/null +++ b/template/config/processors/sequel.rb.erb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'sequel' + +## Sequel configuration and methods +module <%= @module_name %> + module Config + module Processors + ## Database initialize for Sequel + class Sequel + EXTENSIONS = %i[ + error_sql + pg_enum + pg_json + pg_timestamptz + server_block + ].freeze + + PLUGINS = %i[ + timestamps + json_serializer + dataset_associations + association_multi_add_remove + ].freeze + + def initialize(config) + return unless config.load_yaml :database + + config[:database] = config[:database][config[:environment]] + env_db_name = ENV['DB_NAME'] + config[:database][:database] = env_db_name if env_db_name + + ::Sequel::Model.raise_on_save_failure = false + + PLUGINS.each { |plugin_name| ::Sequel::Model.plugin plugin_name } + end + end + end + end +end diff --git a/template/config/processors/server.rb.erb b/template/config/processors/server.rb.erb new file mode 100644 index 0000000..df2ed70 --- /dev/null +++ b/template/config/processors/server.rb.erb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module <%= @module_name %> + module Config + module Processors + ## Server (Puma) config + class Server + def initialize(config) + server_config = config.load_yaml :server, required: true + + environment = config[:environment] = + ENV['RACK_ENV'] ||= server_config[:environment] || 'development' + + server_config = config[:server] = server_config[environment] + + server_config[:puma_pid_file] = "#{config[:pids_dir]}/#{server_config[:puma_pid_file]}" + end + end + end + end +end diff --git a/template/config/processors/shrine.rb.erb b/template/config/processors/shrine.rb.erb new file mode 100644 index 0000000..543f2f1 --- /dev/null +++ b/template/config/processors/shrine.rb.erb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'shrine' +require 'shrine/storage/file_system' + +module <%= @module_name %> + module Config + module Processors + ## Shrine configuration + class Shrine + PLUGINS = %i[ + sequel + cached_attachment_data + rack_file + determine_mime_type + ].freeze + + def initialize(config) + @config = config + + configure_storages + + PLUGINS.each { |plugin_name| ::Shrine.plugin plugin_name } + end + + private + + def configure_storages + ::Shrine.storages = { + ## temporary + cache: ::Shrine::Storage::FileSystem.new( + @config[:public_dir], prefix: 'uploads/cache' + ), + ## permanent + store: ::Shrine::Storage::FileSystem.new( + @config[:public_dir], prefix: 'uploads/store' + ) + } + end + end + end + end +end diff --git a/template/config/puma.rb b/template/config/puma.rb deleted file mode 100755 index 1e38526..0000000 --- a/template/config/puma.rb +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env puma -# frozen_string_literal: true - -require 'etc' -require 'yaml' - -config = YAML.load_file(File.join(__dir__, 'server.yaml')) - -environment = ENV['RACK_ENV'] || config[:environment] -env_config = config[environment] - -root_dir = File.join(__dir__, '..') -directory root_dir - -prune_bundler - -rackup 'config.ru' - -require 'fileutils' - -raise 'Unknown directory for pid files!' unless env_config[:pids_dir] - -pids_dir = File.join root_dir, env_config[:pids_dir] -FileUtils.mkdir_p pids_dir - -pidfile File.join pids_dir, env_config[:pid_file] -state_path File.join pids_dir, 'puma.state' - -raise 'Unknown directory for log files!' unless env_config[:logs_dir] - -logs_dir = File.join root_dir, env_config[:logs_dir] -FileUtils.mkdir_p logs_dir - -if env_config[:daemonize] - stdout_redirect( - File.join(logs_dir, 'stdout'), - File.join(logs_dir, 'stderr'), - true # append to file - ) -end - -environment environment - -# preload_app! if config['environment'] != 'production' - -cores = Etc.nprocessors -workers_count = env_config[:workers_count] || (cores < 2 ? 1 : 2) - -workers workers_count -worker_timeout env_config[:daemonize] ? 15 : 1_000_000 -threads 0, env_config[:threads_count] || 4 -daemonize env_config[:daemonize] - -lowlevel_error_handler do |_exception, _env| - # request_context = Raven::RequestContext.new( - # :puma, env: env, exception: exception - # ) - # Raven.capture_exception( - # *request_context.exception_with_context - # ) - ## Rack response - [ - 500, - {}, - [ - <<~BODY - An error has occurred, and engineers have been informed. Please reload the page. If you continue to have problems, contact us. - BODY - ] - ] -end - -# bind 'unix://' + File.join(%w[tmp sockets puma.sock]) -env_config[:binds].each do |type, value| - value = "#{value[:host]}:#{value[:port]}" if type == :tcp - FileUtils.mkdir_p File.join(root_dir, File.dirname(value)) if type == :unix - bind "#{type}://#{value}" -end -# activate_control_app 'tcp://0.0.0.0:3000' diff --git a/template/config/puma.rb.erb b/template/config/puma.rb.erb new file mode 100644 index 0000000..0039af7 --- /dev/null +++ b/template/config/puma.rb.erb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'fileutils' +require 'etc' + +require_relative 'base' + +config = <%= @module_name %>::Config::Base.new + +server_config = config[:server] + +environment config[:environment] + +directory config[:root_dir] + +prune_bundler + +rackup 'config.ru' + +pids_dir = config[:pids_dir] + +FileUtils.mkdir_p pids_dir + +pidfile File.join pids_dir, 'puma.pid' +state_path File.join pids_dir, 'puma.state' + +FileUtils.mkdir_p config[:logs_dir] + +if server_config[:daemonize] + stdout_redirect( + config[:stdout_file], + config[:stderr_file], + true # append to file + ) +end + +cores = Etc.nprocessors +workers_count = server_config[:workers_count] || (cores < 2 ? 1 : 2) + +workers workers_count +worker_timeout server_config[:daemonize] ? 15 : 1_000_000 +threads 0, server_config[:threads_count] || 4 +daemonize server_config[:daemonize] + +lowlevel_error_handler do |_exception, _env| + request_context = Flame::RavenContext.new(:puma, env: env, exception: exception) + Raven.capture_exception(*request_context.exception_with_context) + ## Rack response + [ + 500, + {}, + [ + <<~BODY + An error has occurred, and engineers have been informed. Please reload the page. If you continue to have problems, contact us. + BODY + ] + ] +end + +server_config[:binds].each do |type, value| + value = "#{value[:host]}:#{value[:port]}" if type == :tcp + FileUtils.mkdir_p File.join(config[:root_dir], File.dirname(value)) if type == :unix + bind "#{type}://#{value}" +end +# activate_control_app 'tcp://0.0.0.0:3000' diff --git a/template/config/sentry.example.yaml.erb b/template/config/sentry.example.yaml.erb new file mode 100644 index 0000000..e2d9a0f --- /dev/null +++ b/template/config/sentry.example.yaml.erb @@ -0,0 +1,7 @@ +:host: sentry.<%= @domain_name %>.com +back-end: + :api_key: abcdef + :project_id: 1 +front-end: + :api_key: ghijkl + :project_id: 2 diff --git a/template/config/server.example.yaml b/template/config/server.example.yaml index 215caa2..bc893f6 100644 --- a/template/config/server.example.yaml +++ b/template/config/server.example.yaml @@ -2,15 +2,13 @@ # :environment: production :default: &default + :puma_pid_file: 'puma.pid' :binds: :tcp: :host: '0.0.0.0' :port: 3000 # :unix: 'tmp/sockets/puma.sock' :daemonize: false - :pids_dir: tmp/pids - :logs_dir: logs - :pid_file: puma.pid # :workers_count: 1 # :threads_count: 4 diff --git a/template/constants.rb.erb b/template/constants.rb.erb deleted file mode 100644 index 8d0a0d2..0000000 --- a/template/constants.rb.erb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -module <%= @module_name %> - ::<%= @short_module_name %> = ::<%= @module_name %> - - APP_DIRS = %w[ - lib - config - models - # policies - helpers - # mailers - # actions - forms - # view_objects - controllers - ].freeze -end diff --git a/template/controllers/_controller.rb.erb b/template/controllers/_controller.rb.erb index 2eefde2..f60a227 100644 --- a/template/controllers/_controller.rb.erb +++ b/template/controllers/_controller.rb.erb @@ -3,12 +3,39 @@ module <%= @module_name %> ## Base controller for any others controllers class Controller < Flame::Controller - # include Flame::R18n::Initialization + include Flame::R18n::Initialization + R18n::Filters.on(:named_variables) + + protected + + def not_found + unless request.bot? + request_context = Flame::RavenContext.new(:not_found, controller: self) + Raven.capture_message(*request_context.exception_with_context) + end + super + end + + def server_error(exception) + # if config[:environment] == 'production' + # affected_account = nil # authenticated.account + # Mailers::Error::Internal.new(exception, request, params, affected_account).send! + # end + + request_context = Flame::RavenContext.new(:server, controller: self, exception: exception) + Raven.capture_exception(*request_context.exception_with_context) + + super + end private def logger <%= @short_module_name %>::Application.logger end + + def csrf_tag + Rack::Csrf.tag(request.env) + end end end diff --git a/template/controllers/site/_controller.rb.erb b/template/controllers/site/_controller.rb.erb index 2636321..85bab4d 100644 --- a/template/controllers/site/_controller.rb.erb +++ b/template/controllers/site/_controller.rb.erb @@ -13,6 +13,17 @@ module <%= @module_name %> response.headers[Rack::CONTENT_TYPE] = 'text/html; charset=utf-8' super end + + def server_error(exception) + @exception = exception + super + end + + def default_body + render "errors/#{status}" + rescue Flame::Errors::TemplateNotFoundError + "

#{super}

" + end end end end diff --git a/template/exe/setup.sh b/template/exe/setup.sh index cfedcf3..ef62a8a 100755 --- a/template/exe/setup.sh +++ b/template/exe/setup.sh @@ -4,12 +4,13 @@ CURRENT_DIR=`dirname "$0"` . $CURRENT_DIR/_common.sh -exe git checkout $1 -exe git pull origin $1 +# exe git checkout $1 +# exe git pull origin $1 exe $CURRENT_DIR/setup/ruby.sh exe toys config check -exe toys db migrate +# exe toys db create +# exe toys db migrate exe $CURRENT_DIR/setup/node.sh diff --git a/template/exe/setup/node.sh b/template/exe/setup/node.sh index 2334544..cce3cc9 100755 --- a/template/exe/setup/node.sh +++ b/template/exe/setup/node.sh @@ -2,22 +2,21 @@ . `dirname "$0"`/../_common.sh -if [ ! -f ".node-version" ] -then - echo "File .node-version not found." - exit 0 -fi - -if [ "$(cat .node-version)" != "$(node -v | tr -d 'v')" ] +if [ ! -f ".node-version" ] || [ "$(cat .node-version)" != "$(node -v | tr -d 'v')" ] then exe git -C ~/.nodenv/plugins/node-build pull - exe nodenv install -s -fi - -## Please, install Yarn manually: https://classic.yarnpkg.com/en/docs/install -if ! yarn check -then exe yarn install + if [ ! -f ".node-version" ] + then + echo "File '.node-version' not found. Installing last stable version..." + latest_version=$(nodenv install -l | grep '^[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+$' | tail -1) + exe nodenv install -s $latest_version + exe nodenv local $latest_version + else + exe nodenv install -s + fi fi -exe yarn build +exe npm install + +exe npm run build diff --git a/template/exe/setup/ruby.sh b/template/exe/setup/ruby.sh index 0690572..421f0ed 100755 --- a/template/exe/setup/ruby.sh +++ b/template/exe/setup/ruby.sh @@ -2,13 +2,22 @@ . `dirname "$0"`/../_common.sh -if [ "$(cat .ruby-version)" != "$(ruby -e "puts RUBY_VERSION")" ] +if [ ! -f ".ruby-version" ] || [ "$(cat .ruby-version)" != "$(ruby -e "puts RUBY_VERSION")" ] then exe git -C ~/.rbenv/plugins/ruby-build pull - exe rbenv install -s + + if [ ! -f ".ruby-version" ] + then + echo "File '.ruby-version' not found. Installing last stable version..." + latest_version=$(rbenv install -l | grep '^[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+$' | tail -1) + exe rbenv install -s $latest_version + exe rbenv local $latest_version + else + exe rbenv install -s + fi fi -if [ \ +if [ ! -f "Gemfile.lock" ] || [ \ "$(tail -n 1 Gemfile.lock | tr -d [:blank:])" != \ "$(bundler -v | cut -d ' ' -f 3)" \ ] diff --git a/template/exe/update.sh b/template/exe/update.sh index c289ed2..993f24c 100755 --- a/template/exe/update.sh +++ b/template/exe/update.sh @@ -4,8 +4,8 @@ CURRENT_DIR=`dirname "$0"` . $CURRENT_DIR/_common.sh -exe $CURRENT_DIR/../server stop +exe toys server stop exe $CURRENT_DIR/setup.sh "$@" -exe $CURRENT_DIR/../server start +exe toys server start diff --git a/template/filewatchers.yaml b/template/filewatchers.yaml index 0e6547a..2176609 100644 --- a/template/filewatchers.yaml +++ b/template/filewatchers.yaml @@ -2,11 +2,11 @@ :exclude: '**/{spec/**/*,config/**/*.example*}' :command: bundle exec pumactl restart -F config/puma.rb -# - :pattern: 'assets/styles/**/*' -# :command: yarn build:styles -# -# - :pattern: '{assets/scripts/**/*,{.babelrc,webpack.config.js}}' -# :command: yarn build:scripts -# -# - :pattern: 'package.json' -# :command: yarn install && yarn build:scripts +- :pattern: '{assets/styles/**/*,postcss.config.js}' + :command: npm run build:styles + +- :pattern: '{assets/scripts/**/*,babel.config.json,rollup.config.js}' + :command: npm run build:scripts + +- :pattern: 'package.json' + :command: npm install && npm run build diff --git a/template/forms/.keep b/template/forms/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/template/forms/_base.rb.erb b/template/forms/_base.rb.erb new file mode 100644 index 0000000..3146c52 --- /dev/null +++ b/template/forms/_base.rb.erb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'formalism/sequel_transactions' + +require 'formalism/r18n_errors' +require 'formalism/r18n_errors/validation_helpers' + +## Define base form and model forms via gem +module <%= @module_name %> + module Forms + ## Base class for forms + class Base < Formalism::Form + extend Forwardable + def_delegators 'self.class', :instance_name + + include Memery + include Formalism::SequelTransactions + include Formalism::R18nErrors + include Formalism::R18nErrors::ValidationHelpers + + class << self + include Memery + + using GorillaPatch::Inflections + + memoize def instance_name + name.split('::')[2].underscore.to_sym + end + end + + def initialize(params = {}) + @errors_key = instance_name + @mailers = [] + + super + end + + def before_retry + clear_memery_cache! + + @mailers.clear + + super + end + + def send_mails! + nested_forms.each_value(&__method__) + + @mailers.each(&:send!) + end + + private + + def db_connection + <%= @short_module_name %>::Application.db_connection + end + + def after_db_transaction_commit + send_mails! + end + end + end + + Formalism::ModelForms.define_for_project self +end diff --git a/template/lib/.keep b/template/lib/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/template/lib/flame/raven_context.rb b/template/lib/flame/raven_context.rb new file mode 100644 index 0000000..99a48b9 --- /dev/null +++ b/template/lib/flame/raven_context.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require 'flame/raven_context' + +module Flame + ## Redefine `user` object + class RavenContext + private + + def user + # @controller&.send(:authenticated)&.account + end + end +end diff --git a/template/mailers/_base.rb.erb b/template/mailers/_base.rb.erb new file mode 100644 index 0000000..ed7a29a --- /dev/null +++ b/template/mailers/_base.rb.erb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +require 'memery' +require 'r18n-core/helpers' + +require_relative 'mail/_base' + +module <%= @module_name %> + ## Module for mailers + module Mailers + ## Base class for sending mails + class Base + INTERVAL = 0.5 + + include ::R18n::Helpers + include Memery + + using GorillaPatch::Inflections + + def initialize(**args) + @from = <%= @short_module_name %>::Application.config[:mail][:from] + @args = args + @mails = [] + + original_locale = R18n.get + add_mails + R18n.thread_set original_locale + end + + def send! + File.write lock_file, '' + Thread.new do + send_mails + ensure + File.delete lock_file + end + end + + private + + memoize def path_parts + self.class.name.underscore.split('/').drop(2) + end + + def add_mail(type, account, **view_args) + return if ENV['RACK_ENV'] == 'test' + + @controller = <%= @short_module_name %>::MailController.new + @controller.recipient = account + @controller.path_parts = [*path_parts, type.to_s] + body = @controller.render_mail @args.merge(view_args) + @mails << initialize_mail(account, body) + end + + def initialize_mail(account, body) + Mailers::Mail.activated.new( + from: @from, + to: account.email, + subject: @controller.mail_subject, + body: body + ) + end + + def send_mails + @mails.each.with_index(1) do |mail, index| + <%= @short_module_name %>::Application.logger.info "#{mail.log_message} [#{index}/#{count}]..." + mail.send! + sleep INTERVAL + end + end + + memoize def lock_file + File.join(<%= @short_module_name %>::Application.config[:tmp_dir], "mailing_#{object_id}") + end + + memoize def count + @mails.count + end + end + end +end diff --git a/template/mailers/mail/_base.rb.erb b/template/mailers/mail/_base.rb.erb new file mode 100644 index 0000000..727f44e --- /dev/null +++ b/template/mailers/mail/_base.rb.erb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module <%= @module_name %> + module Mailers + ## Module for mail objects + module Mail + class << self + attr_accessor :activated + end + + ## Base class for generating emails + class Base + def initialize(from:, to:, subject:, body:); end + + def send! + Retries.run( + max_tries: 5, base_sleep_seconds: 1, max_sleep_seconds: 30 + ) do + sending_behavior + end + rescue StandardError => e + <%= @short_module_name %>::Application.logger.error e + Raven.capture_exception( + e, logger: :email, extra: { failed_recipient: to } + ) + end + + def log_message + "Sending email '#{subject}' to #{to}" + end + + private + + def subject + @mail.subject + end + + def to + @mail.to + end + + def sending_behavior + raise 'Sending behavior should be defined' + end + end + end + end +end diff --git a/template/mailers/mail/default.rb.erb b/template/mailers/mail/default.rb.erb new file mode 100644 index 0000000..e02cab7 --- /dev/null +++ b/template/mailers/mail/default.rb.erb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module <%= @module_name %> + module Mailers + module Mail + ## Class for generating emails by `mail` gem + class Default < Mail::Base + def initialize(from:, to:, subject:, body:) + super + @mail = ::Mail.new do + from "#{from[:name]} <#{from[:email]}>" + to to + subject subject + html_part do + content_type 'text/html; charset=UTF-8' + body body + end + end + end + + private + + def sending_behavior + @mail.deliver! + end + end + end + end +end diff --git a/template/package.json b/template/package.json index 8e24b5b..de24c95 100644 --- a/template/package.json +++ b/template/package.json @@ -1,40 +1,41 @@ { "private": true, "dependencies": { - "@babel/plugin-transform-object-assign": "^7.8.3", - "@babel/preset-env": "^7.8.7", - "@rollup/plugin-commonjs": "^11.0.2", - "@rollup/plugin-json": "^4.0.2", - "@rollup/plugin-node-resolve": "^7.1.1", - "autoprefixer": "^9.7.4", - "core-js": "^3.6.4", - "postcss-cli": "^7.1.0", - "postcss-flexbugs-fixes": "^4.2.0", - "promise-polyfill": "^8.1.3", - "rollup": "^1.20.0", - "rollup-plugin-babel": "^4.4.0", - "sass": "^1.26.2", - "simplify-js": "^1.2.4", - "whatwg-fetch": "^3.0.0" + "@babel/core": "*", + "@babel/plugin-transform-object-assign": "*", + "@babel/preset-env": "*", + "@rollup/plugin-babel": "*", + "@rollup/plugin-commonjs": "*", + "@rollup/plugin-json": "*", + "@rollup/plugin-node-resolve": "*", + "autoprefixer": "*", + "core-js": "*", + "postcss-cli": "*", + "postcss-flexbugs-fixes": "*", + "promise-polyfill": "*", + "rollup": "*", + "sass": "*", + "simplify-js": "*", + "whatwg-fetch": "*" }, "devDependencies": { - "stylelint-config-standard": "^20.0.0", - "stylelint-no-unsupported-browser-features": "^4.0.0" - }, - "optionalDependencies": { - "@babel/core": "^7.8.7", - "eslint": "^6.8.0", - "stylelint": "^13.2.1" + "eslint": "*", + "remark-cli": "*", + "remark-preset-lint-recommended": "*", + "stylelint": "*", + "stylelint-config-standard": "*", + "stylelint-no-unsupported-browser-features": "*" }, "scripts": { "build:styles": "sass assets/styles/main.scss public/styles/main.css -s compressed && postcss public/styles/main.css -o public/styles/main.css", - "build:scripts": "rollup assets/scripts/app.js -o public/scripts/app/compiled/app.js -c", - "build": "yarn build:styles && yarn build:scripts", + "build:scripts": "rollup assets/scripts/application.js -o public/scripts/application/compiled/application.js -c", + "build": "npm run build:styles && npm run build:scripts", "clean:styles": "rm -f public/styles/main.css public/styles/main.css.map", - "clean:scripts": "rm -rvf public/scripts/app/compiled", - "clean": "yarn clean:styles && yarn clean:scripts", + "clean:scripts": "rm -rvf public/scripts/application/compiled", + "clean": "npm run clean:styles && npm run clean:scripts", + "lint:docs": "remark .", "lint:styles": "stylelint assets/styles/", - "lint:scripts": "eslint assets/scripts/ public/scripts/app/ ./*.js", - "lint": "yarn lint:styles; styles_lint_result=$?; yarn lint:scripts && exit $styles_lint_result" + "lint:scripts": "eslint assets/scripts/ ./*.js", + "lint": "npm run lint:docs; docs_lint_result=$?; npm run lint:styles; styles_lint_result=$?; npm run lint:scripts && $styles_lint_result && $docs_lint_result" } } diff --git a/template/rollup.config.js.erb b/template/rollup.config.js.erb index 0aa1b25..7ab4d53 100644 --- a/template/rollup.config.js.erb +++ b/template/rollup.config.js.erb @@ -1,7 +1,7 @@ import json from '@rollup/plugin-json'; import resolve from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; -import babel from 'rollup-plugin-babel'; +import babel from '@rollup/plugin-babel'; export default { output: { @@ -16,6 +16,8 @@ export default { browser: true }), commonjs(), - babel() + babel({ + babelHelpers: 'bundled' + }) ] }; diff --git a/template/server b/template/server deleted file mode 100755 index 63c193b..0000000 --- a/template/server +++ /dev/null @@ -1,213 +0,0 @@ -#!/usr/bin/env ruby - -# frozen_string_literal: true - -require 'shellwords' -require 'English' -require 'yaml' -require 'fileutils' - -SERVER_CONFIG = YAML.load_file File.join(__dir__, 'config', 'server.yaml') - -ENVIRONMENT = ENV['RACK_ENV'] || SERVER_CONFIG[:environment] - -puts "ENVIRONMENT : #{ENVIRONMENT}" - -PUMA_PID_FILE = File.join( - __dir__, *SERVER_CONFIG[ENVIRONMENT].values_at(:pids_dir, :pid_file) -) - -## Class for pids files -class PidsFile - def initialize(name, *pids) - @file = File.join PIDS_DIR, "#{name}.pids" - @pids = pids.any? ? pids : read - end - - def dump(pids = @pids) - FileUtils.mkdir_p PIDS_DIR - puts "PidsFile dump : #{pids} => #{@file}" - File.write @file, pids.join($RS) - end - - def read - return unless File.exist?(@file) - - @pids = File.read(@file).split($RS).map(&:to_i) - puts "PidsFile read : #{@pids}" - @pids - end - - def kill_each(pids = @pids) - Array(pids).each do |pid| - puts "PidsFile kill : #{pid}" - Process.kill('TERM', -Process.getpgid(pid)) - rescue Errno::ESRCH - puts "Process #{pid} doesn't exist" - end - self - end - - def delete - return unless File.exist?(@file) - - File.delete @file - end -end - -PidsFile::PIDS_DIR = SERVER_CONFIG[ENVIRONMENT][:pids_dir] - -def show_usage - puts <<~USAGE - Usage: ./server COMMAND - - COMMAND is one of: - start - Start server - stop - Stop server - kill - Kill server (and filewatcher) - restart - Restart server - monitor - Show logs - devel, dev - Restart and monitor server - status, ps - Show processes of server - USAGE -end - -def bash(command, print: true) - puts command if print - system bash_command(command) || abort -end - -def bash_command(command) - escaped_command = Shellwords.escape(command) - "bash -c #{escaped_command}" -end - -def bash_spawn(command, print: true) - puts "spawn #{command}" if print - spawn bash_command(command), pgroup: true -end - -def server(command) - abort unless bash "#{__dir__}/exe/setup/ruby.sh" - if %i[start restart].include?(command) && - !bash("#{__dir__}/exe/setup/node.sh") - abort - end - if %i[stop restart].include?(command) - filewatcher_pids_file = PidsFile.new(:filewatcher) - filewatcher_pids_file.kill_each.delete - end - web_server(command) -end - -def puma_command(command) - bash "bundle exec pumactl #{command}" -end - -def web_server(command) - if ENVIRONMENT == 'development' && command == :restart - return development_restart - end - - waiting_mailing_lock if %i[stop restart phased-restart].include?(command) - puma_command command -end - -def development_restart - filewatcher_pids = - development_filewatchers.map { |command| bash_spawn command } - - PidsFile.new(:filewatcher, filewatcher_pids).dump - - puma_command File.exist?(PUMA_PID_FILE) ? 'restart' : 'start' -rescue SystemExit, Interrupt => e - PidsFile.new(:filewatcher).kill_each.delete - - raise e -end - -def filewatcher_command(pattern, execute, exclude: nil) - <<-CMD.split.join(' ') - bundle exec " - filewatcher - '#{pattern}' - #{"--exclude '#{exclude}'" unless exclude.nil?} - '#{execute}' - " - CMD -end - -def development_filewatchers - YAML.load_file(File.join(__dir__, 'filewatchers.yaml')).map do |args| - filewatcher_command args[:pattern], args[:command], exclude: args[:exclude] - end -end - -def waiting_mailing_lock - while Dir[File.join(__dir__, 'tmp', 'mailing_*')].any? - puts "\e[31m\e[1mMails sending in progress!\e[22m\e[0m\nWaiting..." - sleep 1 - end -end - -def monitor_server - bash "tail -f #{File.join(__dir__, %w[logs {stdout,stderr}])}" -end - -def dependencies_check - bash('bundle check || bundle install') # && bash('yarn install') -end - -def assets_build - puts 'Assets not enabled.' - true - # bash 'yarn build', print: false -end - -def ps_with_grep(pattern) - bash "ps aux | grep #{pattern} --color", print: false -end - -def server_ps - puts - puts 'Filewatcher:' - puts - ps_with_grep '[f]ilewatcher' - puts - puts 'Puma:' - puts - ps_with_grep '[p]uma[\ :]' -end - -## Runtime -case ARGV[0] -when 'start' - server :start -when 'stop' - server :stop - PidsFile.new(:filewatcher).kill_each.delete -when 'restart' - server :restart -when 'phased-restart' - server :'phased-restart' -when 'kill' - server :stop - bash 'pkill -f filewatcher' - bash 'pkill -f puma' -when 'monitor' - monitor_server -when 'devel', 'dev' - bash 'toys config check' - server :restart - if SERVER_CONFIG[ENVIRONMENT][:daemonize] - puts 'Waiting for logs...' - sleep 1.5 - monitor_server - end -when 'ps', 'status' - server_ps -else - puts "Unknown command #{ARGV[0]}" - puts - show_usage -end diff --git a/template/views/site/errors/400.html.erb.erb b/template/views/site/errors/400.html.erb.erb new file mode 100644 index 0000000..9330d12 --- /dev/null +++ b/template/views/site/errors/400.html.erb.erb @@ -0,0 +1,12 @@ +
diff --git a/template/views/site/errors/404.html.erb.erb b/template/views/site/errors/404.html.erb.erb new file mode 100644 index 0000000..037041a --- /dev/null +++ b/template/views/site/errors/404.html.erb.erb @@ -0,0 +1,7 @@ +
+

<%= "<\%= t.error.page.itself.not_found %\>" %>

+
+ " %>"> + <%= "<\%= t.button.home %\>" %> + +
diff --git a/template/views/site/errors/500.html.erb.erb b/template/views/site/errors/500.html.erb.erb new file mode 100644 index 0000000..e51cb11 --- /dev/null +++ b/template/views/site/errors/500.html.erb.erb @@ -0,0 +1,28 @@ +
" %>"> +

<%= "<\%= t.error.unexpected_error.title %\>" %>

+

<%= "<\%= t.error.unexpected_error.subtitle %\>" %>

+ +

+ <%= "<\%= t.error.unexpected_error.text %\>" %> +

+ + <%= "<\% if config[:environment] == 'development' %\>" %> +

<%= "<\%==" %> + "#{@exception.class} - #{@exception.message}" + <%= "%\>" %>

<%= "<\%" %> + if @exception.respond_to? :sql + <%= "%\>" %>

<%= "<\%=" %> + @exception.sql + <%= "%\>" %>

<%= "<\% end %\>" %><%= "<\%=" %> + highlighted_backtrace_for @exception + <%= "%\>" %>
+ <%= "<\% end %\>" %> + +
+ + " %>"> + <%= "<\%= t.button.back %\>" %> + + +

+
diff --git a/template/views/site/layout.html.erb.erb b/template/views/site/layout.html.erb.erb index 6d41992..36e29c7 100644 --- a/template/views/site/layout.html.erb.erb +++ b/template/views/site/layout.html.erb.erb @@ -2,9 +2,80 @@ - <%= "\<%= #{@short_module_name}::Application.config[:site][:site_name] %\>" %> + + + <%= "<\%= config[:site][:site_name] %\>" %> + + <%= "<\%" %> + %i[ + main + ].each do |name| + <%= "%\>" %> + " %>" /> + <%= "<\% end %\>" %> + + <%= "<\% if Raven.configuration.environments.include?(config[:environment]) && !request.bot? %\>" %> + + + + <%= "<\% end %\>" %> + + <%= "<\%" %> + { + libs: %w[], + # dom4.max + # modernizr-custom + # svgxuse.min + # cccombo + # ], + 'application/compiled': %w[application] + } + .each do |dir, file_names| + <%= "%\>" %> + <%= "<\% file_names.each do |name| %\>" %> + + <%= "<\% end %\>" %> + <%= "<\% end %\>" %> - <%= "\<%= yield %\>" %> + <%= "<\%= yield %\>" %> From bdeb352f6e559c37425eac2983376ee33f68413c Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Sun, 2 Aug 2020 19:45:21 +0300 Subject: [PATCH 04/62] Remove Ruby 2.5 from CI Gem requires Ruby >= 2.6. --- .cirrus.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.cirrus.yml b/.cirrus.yml index 67312fb..708d05e 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -43,7 +43,6 @@ rspec_task: - rubocop container: matrix: - image: ruby:2.5 image: ruby:2.6 image: ruby:2.7 <<: *bundle_cache From 87d36c4763815fda46c913c7f647816182f39d9b Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Fri, 21 Aug 2020 15:31:53 +0300 Subject: [PATCH 05/62] Update a template again --- .rubocop.yml | 2 + .toys.rb | 20 +--- flame-cli.gemspec | 4 +- spec/flame/cli/new/app_spec.rb | 69 ++++++----- template/.eslintrc.yaml | 4 +- template/.remarkrc.yaml | 2 + .../{.stylelintrc.yml => .stylelintrc.yaml} | 2 +- template/.toys/.toys.rb.erb | 29 ++--- template/Gemfile | 7 +- template/application.rb.erb | 13 +-- template/assets/scripts/.eslintrc.yaml | 4 - template/babel.config.json | 8 -- template/browserslist | 2 + template/config/base.rb.erb | 110 ------------------ template/config/full.rb.erb | 16 --- template/config/main.rb.erb | 68 +++++++++++ template/config/processors/sentry.rb.erb | 2 +- template/config/processors/server.rb.erb | 2 + template/config/puma.rb.erb | 17 ++- template/config/server.example.yaml | 4 +- template/constants.rb.erb | 18 +++ template/exe/setup/node.sh | 8 +- template/filewatchers.yaml | 12 +- template/lib/flame/raven_context.rb | 3 +- template/mailers/mail/_base.rb.erb | 2 +- template/rollup.config.js.erb | 4 - 26 files changed, 184 insertions(+), 248 deletions(-) create mode 100644 template/.remarkrc.yaml rename template/{.stylelintrc.yml => .stylelintrc.yaml} (99%) delete mode 100644 template/babel.config.json create mode 100644 template/browserslist delete mode 100644 template/config/base.rb.erb delete mode 100644 template/config/full.rb.erb create mode 100644 template/config/main.rb.erb create mode 100644 template/constants.rb.erb diff --git a/.rubocop.yml b/.rubocop.yml index 159dc1f..f4867c1 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -42,6 +42,8 @@ Metrics/BlockLength: - 'spec/**/*' - '*.gemspec' +RSpec/MultipleMemoizedHelpers: + Enabled: false ## Because of we're testing CLI through native calls RSpec/DescribeClass: Enabled: false diff --git a/.toys.rb b/.toys.rb index da6a106..2167dee 100644 --- a/.toys.rb +++ b/.toys.rb @@ -2,26 +2,14 @@ include :bundler, static: true -subtool_apply do - include :exec, exit_on_nonzero_status: true, log_level: Logger::UNKNOWN unless include? :exec -end - require 'gem_toys' expand GemToys::Template alias_tool :g, :gem -tool :rspec do - def run - exec 'rspec' - end -end +expand :rspec -alias_tool :spec, :rspec -alias_tool :test, :rspec +alias_tool :rspec, :spec +alias_tool :test, :spec -tool :rubocop do - def run - exec 'rubocop' - end -end +expand :rubocop diff --git a/flame-cli.gemspec b/flame-cli.gemspec index e2898e5..c7cc2fc 100644 --- a/flame-cli.gemspec +++ b/flame-cli.gemspec @@ -30,13 +30,13 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'bundler', '~> 2.0' spec.add_development_dependency 'gem_toys', '~> 0.3.0' - spec.add_development_dependency 'toys', '~> 0.10.4' + spec.add_development_dependency 'toys', '~> 0.11.0' spec.add_development_dependency 'codecov', '~> 0.2.0' spec.add_development_dependency 'rspec', '~> 3.9' spec.add_development_dependency 'simplecov', '~> 0.18.0' - spec.add_development_dependency 'rubocop', '~> 0.88.0' + spec.add_development_dependency 'rubocop', '~> 0.90.0' spec.add_development_dependency 'rubocop-performance', '~> 1.0' spec.add_development_dependency 'rubocop-rspec', '~> 1.0' diff --git a/spec/flame/cli/new/app_spec.rb b/spec/flame/cli/new/app_spec.rb index 63708f2..9b31e29 100644 --- a/spec/flame/cli/new/app_spec.rb +++ b/spec/flame/cli/new/app_spec.rb @@ -14,9 +14,10 @@ let(:template_dir) { "#{root_dir}/template" } let(:template_dir_pathname) { Pathname.new(template_dir) } let(:template_ext) { '.erb' } + let(:temp_app_dir) { "#{root_dir}/#{app_name}" } after do - FileUtils.rm_r "#{root_dir}/#{app_name}" + FileUtils.rm_r temp_app_dir end describe 'output' do @@ -30,10 +31,9 @@ '- README.md', '- application.rb', '- config.ru', - '- config/base.rb', '- config/database.example.yaml', - '- config/full.rb', '- config/mail.example.yaml', + '- config/main.rb', '- config/processors/mail.rb', '- config/processors/r18n.rb', '- config/processors/sentry.rb', @@ -43,6 +43,7 @@ '- config/puma.rb', '- config/sentry.example.yaml', '- config/site.example.yaml', + '- constants.rb', '- controllers/_controller.rb', '- controllers/site/_controller.rb', '- controllers/site/index_controller.rb', @@ -52,6 +53,9 @@ '- mailers/mail/default.rb', '- rollup.config.js', '- routes.rb', + '- views/site/errors/400.html.erb', + '- views/site/errors/404.html.erb', + '- views/site/errors/500.html.erb', '- views/site/index.html.erb', '- views/site/layout.html.erb', # 'Grant permissions to files...', @@ -96,7 +100,7 @@ end describe 'cleans directories' do - subject { Dir.glob("#{app_name}/**/.keep", File::FNM_DOTMATCH) } + subject { Dir.glob("#{temp_app_dir}/**/.keep", File::FNM_DOTMATCH) } before { execute_command } @@ -114,8 +118,7 @@ let(:expected_words) do [ 'FB::Application', - 'expand FlameGenerateToys::Template, namespace: FooBar', - 'FB::Config::Base.new' + 'expand FlameGenerateToys::Template, namespace: FooBar' ] end @@ -125,10 +128,7 @@ describe 'application.rb' do let(:expected_words) do [ - 'config = FooBar::Config::Base.new', - 'FooBar.complete_config config', - 'module FooBar', - 'class Application < Flame::Application' + 'module FooBar' ] end @@ -150,23 +150,11 @@ it { is_expected.to match_words(*expected_words) } end - describe 'config/base.rb' do - let(:expected_words) do - [ - 'module FooBar', - '::FB = ::FooBar', - 'APP_DIRS =' - ] - end - - it { is_expected.to match_words(*expected_words) } - end - - describe 'config/full.rb' do + describe 'config/main.rb' do let(:expected_words) do [ - 'module FooBar', - 'FB::Config::Processors.const_get(processor_name).new config' + 'config = FB::Application.config', + 'FB::Config::Processors.const_get(processor_name).new self' ] end @@ -176,7 +164,7 @@ describe 'config/puma.rb' do let(:expected_words) do [ - 'config = FooBar::Config::Base.new' + 'config = FB::Application.config' ] end @@ -277,6 +265,17 @@ it { is_expected.to match_words(*expected_words) } end + describe 'constants.rb' do + let(:expected_words) do + [ + 'module FooBar', + '::FB = ::FooBar' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + describe 'controllers/_controller.rb' do let(:expected_words) do [ @@ -506,20 +505,18 @@ end end - around do |example| - ## HACK: https://github.com/dazuma/toys/issues/57 - original_toys_file_name = "#{__dir__}/../../../../.toys.rb" - File.rename original_toys_file_name, "#{original_toys_file_name}.bak" - - example.run - - File.rename "#{original_toys_file_name}.bak", original_toys_file_name - end - before do Bundler.with_unbundled_env do execute_command + ## HACK: https://github.com/dazuma/toys/issues/57 + toys_command = 'truncate_load_path!' + temp_app_toys_file_path = "#{temp_app_dir}/.toys/.toys.rb" + File.write( + temp_app_toys_file_path, + File.read(temp_app_toys_file_path).sub("# #{toys_command}", toys_command) + ) + Dir.chdir app_name Dir['config/*.example.yaml'].each do |config_example_file_name| diff --git a/template/.eslintrc.yaml b/template/.eslintrc.yaml index 9cc83bb..0f75f9d 100644 --- a/template/.eslintrc.yaml +++ b/template/.eslintrc.yaml @@ -23,7 +23,7 @@ rules: - avoidEscape: true semi: - error - - always + - never no-multi-spaces: - error keyword-spacing: @@ -61,6 +61,8 @@ rules: - allow: - error - warn + no-var: + - error arrow-body-style: - warn arrow-parens: diff --git a/template/.remarkrc.yaml b/template/.remarkrc.yaml new file mode 100644 index 0000000..755a2b1 --- /dev/null +++ b/template/.remarkrc.yaml @@ -0,0 +1,2 @@ +plugins: + - remark-preset-lint-recommended diff --git a/template/.stylelintrc.yml b/template/.stylelintrc.yaml similarity index 99% rename from template/.stylelintrc.yml rename to template/.stylelintrc.yaml index 97ebd81..b433c4e 100644 --- a/template/.stylelintrc.yml +++ b/template/.stylelintrc.yaml @@ -9,7 +9,7 @@ rules: - tab max-line-length: - - 80 + - 100 - ignorePattern: /// https?:/// at-rule-no-unknown: diff --git a/template/.toys/.toys.rb.erb b/template/.toys/.toys.rb.erb index 524e03e..f4dff33 100644 --- a/template/.toys/.toys.rb.erb +++ b/template/.toys/.toys.rb.erb @@ -1,20 +1,25 @@ # frozen_string_literal: true -include :bundler, static: true +## Uncomment if you want, read discussion here: https://github.com/dazuma/toys/issues/57 +# truncate_load_path! -config_dir = "#{__dir__}/../config" +include :bundler, static: true -require "#{config_dir}/base" +root_dir = "#{__dir__}/.." +config_dir = "#{root_dir}/config" +require "#{root_dir}/constants" require 'benchmark_toys' expand BenchmarkToys::Template alias_tool :b, :benchmark application_proc = proc do - require_relative '../application' + require "#{config_dir}/main" <%= @short_module_name %>::Application end +config_proc = proc { application_proc.call.config } + require 'config_toys' expand ConfigToys::Template, config_dir: config_dir @@ -27,10 +32,7 @@ expand SequelMigrationsToys::Template, db_connection_proc: db_connection_proc require 'psql_toys' expand PSQLToys::Template, - db_config_proc: (proc do - ## For full config, not base - application_proc.call.config[:database] - end), + db_config_proc: proc { config_proc.call[:database] }, db_connection_proc: db_connection_proc, db_extensions: %w[citext pgcrypto].freeze @@ -46,10 +48,7 @@ require 'flame_routes_toys' expand FlameRoutesToys::Template, application_proc: application_proc require 'flame_server_toys' -expand FlameServerToys::Template, - config_proc: (proc do - <%= @short_module_name %>::Config::Base.new - end) +expand FlameServerToys::Template, config_proc: config_proc require 'locales_toys' expand LocalesToys::Template @@ -65,8 +64,4 @@ expand RackConsoleToys::Template require 'static_files_toys' expand StaticFilesToys::Template -tool :rubocop do - def run - exec 'rubocop' - end -end +expand :rubocop diff --git a/template/Gemfile b/template/Gemfile index 8a01804..898324d 100644 --- a/template/Gemfile +++ b/template/Gemfile @@ -12,8 +12,7 @@ group :system do end group :server do - # gem 'flame', github: 'AlexWayfer/flame' - gem 'flame', path: '~/Projects/ruby/flame' + gem 'flame', github: 'AlexWayfer/flame' gem 'flame-flash', github: 'AlexWayfer/flame-flash' gem 'puma' gem 'rack-contrib' @@ -27,7 +26,6 @@ group :server do end group :development do - gem 'filewatcher' gem 'letter_opener' end @@ -100,7 +98,8 @@ group :toys do gem 'flame_deploy_toys' gem 'flame_generate_toys' gem 'flame_routes_toys' - gem 'flame_server_toys' + ## TODO: Switch to stable version after `filewatcher-cli` release + gem 'flame_server_toys', github: 'AlexWayfer/flame_server_toys' gem 'locales_toys' gem 'psql_toys' gem 'rack_console_toys' diff --git a/template/application.rb.erb b/template/application.rb.erb index e5afc3c..e3f456f 100644 --- a/template/application.rb.erb +++ b/template/application.rb.erb @@ -1,14 +1,9 @@ # frozen_string_literal: true -require_relative 'config/base' -config = <%= @module_name %>::Config::Base.new - ## Require gems require 'bundler/setup' Bundler.require( - :system, :server, :database, - :translations, :forms, :views, :assets, :mails, :others, - config[:environment] + :system, :server, :database, :translations, :forms, :views, :assets, :mails, :others ) require 'erubi/capture_end' @@ -27,9 +22,7 @@ require 'pp' # require 'money/bank/google_currency' -## Load full application config, with dependencies from Bundler -require_relative 'config/full' -<%= @module_name %>.complete_config config +require_relative 'constants' module <%= @module_name %> ## Class for application @@ -74,5 +67,3 @@ module <%= @module_name %> end end end - -<%= @short_module_name %>::Application.config = config diff --git a/template/assets/scripts/.eslintrc.yaml b/template/assets/scripts/.eslintrc.yaml index 4b47c3e..cf7ae01 100644 --- a/template/assets/scripts/.eslintrc.yaml +++ b/template/assets/scripts/.eslintrc.yaml @@ -7,7 +7,3 @@ parserOptions: ## Libs: # google: true # Cccombo: true - -rules: - no-var: - - error diff --git a/template/babel.config.json b/template/babel.config.json deleted file mode 100644 index d2020cd..0000000 --- a/template/babel.config.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "presets": [ - ["@babel/preset-env", { "modules": false }] - ], - "plugins": [ - "@babel/plugin-transform-object-assign" - ] -} diff --git a/template/browserslist b/template/browserslist new file mode 100644 index 0000000..e639fb0 --- /dev/null +++ b/template/browserslist @@ -0,0 +1,2 @@ +> 1% +not IE 11 diff --git a/template/config/base.rb.erb b/template/config/base.rb.erb deleted file mode 100644 index 4972030..0000000 --- a/template/config/base.rb.erb +++ /dev/null @@ -1,110 +0,0 @@ -# frozen_string_literal: true - -require 'yaml' - -module <%= @module_name %> - ::<%= @short_module_name %> = ::<%= @module_name %> - - APP_DIRS = [ - 'lib', - 'config', - 'models', - # 'policies', - 'helpers', - 'mailers', - # 'actions', - 'forms', - # 'view_objects', - 'controllers' - ].freeze - - module Config - ## Class for config like a Hash with helper methods. - ## It was a part of the Flame, but some places, like Puma config, - ## need for this without Bundler (dependecies): - ## https://github.com/puma/puma/issues/2319 - class Base < Hash - ## Create an instance of application config - def initialize - populate_dirs - - self[:stdout_file] = "#{self[:logs_dir]}/out" - self[:stderr_file] = "#{self[:logs_dir]}/err" - - require_relative 'processors/server' - Processors::Server.new self - - %i[session site].each do |config_name| - load_yaml config_name, required: true - end - end - - ## Method for loading YAML-files from config directory - ## @param name [Symbol] - ## file base name (extension is `.yml` or '.yaml') - ## @example Load SMTP file without extension, by Symbol - ## config.load_yaml(:smtp) - def load_yaml(name, required: false) - file_name = "#{name}.y{a,}ml" - - file_path = find_config_file file_name, required: required - - return unless file_path - - self[name] = YAML.load_file(file_path) - end - - private - - def populate_dirs - self[:root_dir] = File.realpath "#{__dir__}/.." - - %i[config logs public tmp views].each do |dir_name| - self[:"#{dir_name}_dir"] = "#{self[:root_dir]}/#{dir_name}" - end - - self[:pids_dir] = "#{self[:tmp_dir]}/pids" - end - - def find_config_file(file_name, required:) - file_path = nil - - loop do - file_path = Dir[File.join(self[:config_dir], file_name)].first - break if file_path - - config_relative_dir = self[:config_dir].sub(self[:root_dir], '') - puts "Config file '#{file_name}' not found in '#{config_relative_dir}'" - - next if ask_to_check_config_files - - required ? abort : break - end - - file_path - end - - def ask_to_check_config_files - highline.choose do |menu| - menu.layout = :one_line - - menu.prompt = 'Do you want to check config files? ' - - menu.choice(:yes) do - system 'toys config check' - true - end - - menu.choice(:no) { false } - end - end - - def highline - @highline ||= begin - require 'highline' - HighLine.new - end - end - end - end -end diff --git a/template/config/full.rb.erb b/template/config/full.rb.erb deleted file mode 100644 index d8cace6..0000000 --- a/template/config/full.rb.erb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -## Config split into `base` and `full` because of `prune_bundler`: -## https://github.com/puma/puma/issues/2319 -module <%= @module_name %> - class << self - using GorillaPatch::Inflections - - def complete_config(config) - %w[Sentry R18n Mail Sequel Shrine].each do |processor_name| - require_relative "processors/#{processor_name.underscore}" - <%= @short_module_name %>::Config::Processors.const_get(processor_name).new config - end - end - end -end diff --git a/template/config/main.rb.erb b/template/config/main.rb.erb new file mode 100644 index 0000000..b6aaa26 --- /dev/null +++ b/template/config/main.rb.erb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require_relative '../application' + +config = <%= @short_module_name %>::Application.config + +class << config + private + + def find_config_file(filename, required:) + file_path = nil + + loop do + file_path = super + break + rescue Flame::Errors::ConfigFileNotFoundError => e + puts e.message + + next if ask_to_check_config_files + + required ? abort : break + end + + file_path + end + + def ask_to_check_config_files + highline.choose do |menu| + menu.layout = :one_line + + menu.prompt = 'Do you want to check config files? ' + + menu.choice(:yes) do + system 'toys config check' + true + end + + menu.choice(:no) { false } + end + end + + def highline + @highline ||= begin + require 'highline' + HighLine.new + end + end +end + +using GorillaPatch::Inflections + +config.instance_exec do + self[:root_dir] = File.realpath "#{__dir__}/.." + + self[:pids_dir] = "#{self[:tmp_dir]}/pids" + + self[:stdout_file] = "#{self[:log_dir]}/out" + self[:stderr_file] = "#{self[:log_dir]}/err" + + %i[session site].each do |config_name| + load_yaml config_name, required: true + end + + %w[Server Sentry R18n Mail Sequel Shrine].each do |processor_name| + require_relative "processors/#{processor_name.underscore}" + <%= @short_module_name %>::Config::Processors.const_get(processor_name).new self + end +end diff --git a/template/config/processors/sentry.rb.erb b/template/config/processors/sentry.rb.erb index 4948fe9..b140f4b 100644 --- a/template/config/processors/sentry.rb.erb +++ b/template/config/processors/sentry.rb.erb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'raven' +require_relative '../../lib/flame/raven_context' module <%= @module_name %> module Config diff --git a/template/config/processors/server.rb.erb b/template/config/processors/server.rb.erb index df2ed70..e7b42ba 100644 --- a/template/config/processors/server.rb.erb +++ b/template/config/processors/server.rb.erb @@ -11,6 +11,8 @@ module <%= @module_name %> environment = config[:environment] = ENV['RACK_ENV'] ||= server_config[:environment] || 'development' + Bundler.require environment + server_config = config[:server] = server_config[environment] server_config[:puma_pid_file] = "#{config[:pids_dir]}/#{server_config[:puma_pid_file]}" diff --git a/template/config/puma.rb.erb b/template/config/puma.rb.erb index 0039af7..2d6bd9b 100644 --- a/template/config/puma.rb.erb +++ b/template/config/puma.rb.erb @@ -3,9 +3,9 @@ require 'fileutils' require 'etc' -require_relative 'base' +require_relative 'main' -config = <%= @module_name %>::Config::Base.new +config = <%= @short_module_name %>::Application.config server_config = config[:server] @@ -13,6 +13,7 @@ environment config[:environment] directory config[:root_dir] +## Be careful: https://github.com/puma/puma/issues/2319 prune_bundler rackup 'config.ru' @@ -24,23 +25,27 @@ FileUtils.mkdir_p pids_dir pidfile File.join pids_dir, 'puma.pid' state_path File.join pids_dir, 'puma.state' -FileUtils.mkdir_p config[:logs_dir] +FileUtils.mkdir_p config[:log_dir] -if server_config[:daemonize] +case (output = server_config[:output]) +when :stdout + ## nothing to do, this is default +when :file stdout_redirect( config[:stdout_file], config[:stderr_file], true # append to file ) +else + raise "Unsupported #{output} value of `:output` config" end cores = Etc.nprocessors workers_count = server_config[:workers_count] || (cores < 2 ? 1 : 2) workers workers_count -worker_timeout server_config[:daemonize] ? 15 : 1_000_000 +worker_timeout config[:environment] == 'development' ? 1_000_000 : 15 threads 0, server_config[:threads_count] || 4 -daemonize server_config[:daemonize] lowlevel_error_handler do |_exception, _env| request_context = Flame::RavenContext.new(:puma, env: env, exception: exception) diff --git a/template/config/server.example.yaml b/template/config/server.example.yaml index bc893f6..92492db 100644 --- a/template/config/server.example.yaml +++ b/template/config/server.example.yaml @@ -8,7 +8,7 @@ :host: '0.0.0.0' :port: 3000 # :unix: 'tmp/sockets/puma.sock' - :daemonize: false + :output: :stdout # can be `:file` # :workers_count: 1 # :threads_count: 4 @@ -19,7 +19,7 @@ development: &development test: &test <<: *default - :daemonize: false + :output: :stdout production: &production <<: *default diff --git a/template/constants.rb.erb b/template/constants.rb.erb new file mode 100644 index 0000000..f5fffce --- /dev/null +++ b/template/constants.rb.erb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module <%= @module_name %> + ::<%= @short_module_name %> = ::<%= @module_name %> + + APP_DIRS = [ + 'lib', + 'config', + 'models', + # 'policies', + 'helpers', + 'mailers', + # 'actions', + 'forms', + # 'view_objects', + 'controllers' + ].freeze +end diff --git a/template/exe/setup/node.sh b/template/exe/setup/node.sh index cce3cc9..d0398ea 100755 --- a/template/exe/setup/node.sh +++ b/template/exe/setup/node.sh @@ -17,6 +17,10 @@ then fi fi -exe npm install +exe npm install -g pnpm -exe npm run build +exe nodenv rehash + +exe pnpm install + +exe pnpm run build diff --git a/template/filewatchers.yaml b/template/filewatchers.yaml index 2176609..2bd3531 100644 --- a/template/filewatchers.yaml +++ b/template/filewatchers.yaml @@ -1,12 +1,14 @@ - :pattern: '**/{Gemfile,*.{rb,ru,y{a,}ml}}' :exclude: '**/{spec/**/*,config/**/*.example*}' - :command: bundle exec pumactl restart -F config/puma.rb + ## Don't use `bundle exec`, it's already required by `toys` + ## https://github.com/dazuma/toys/issues/65 + :command: pumactl restart - :pattern: '{assets/styles/**/*,postcss.config.js}' - :command: npm run build:styles + :command: pnpm run build:styles -- :pattern: '{assets/scripts/**/*,babel.config.json,rollup.config.js}' - :command: npm run build:scripts +- :pattern: '{assets/scripts/**/*,rollup.config.js}' + :command: pnpm run build:scripts - :pattern: 'package.json' - :command: npm install && npm run build + :command: pnpm install && pnpm run build diff --git a/template/lib/flame/raven_context.rb b/template/lib/flame/raven_context.rb index 99a48b9..3fa2c34 100644 --- a/template/lib/flame/raven_context.rb +++ b/template/lib/flame/raven_context.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true -require 'flame/raven_context' +## Required via `Bundler.require` in `application.rb` +# require 'flame/raven_context' module Flame ## Redefine `user` object diff --git a/template/mailers/mail/_base.rb.erb b/template/mailers/mail/_base.rb.erb index 727f44e..79fb066 100644 --- a/template/mailers/mail/_base.rb.erb +++ b/template/mailers/mail/_base.rb.erb @@ -10,7 +10,7 @@ module <%= @module_name %> ## Base class for generating emails class Base - def initialize(from:, to:, subject:, body:); end + # def initialize(from:, to:, subject:, body:); end def send! Retries.run( diff --git a/template/rollup.config.js.erb b/template/rollup.config.js.erb index 7ab4d53..7a36240 100644 --- a/template/rollup.config.js.erb +++ b/template/rollup.config.js.erb @@ -1,7 +1,6 @@ import json from '@rollup/plugin-json'; import resolve from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; -import babel from '@rollup/plugin-babel'; export default { output: { @@ -16,8 +15,5 @@ export default { browser: true }), commonjs(), - babel({ - babelHelpers: 'bundled' - }) ] }; From 148167e6d8a9940e3c9356adf500d2788f57d212 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Tue, 22 Sep 2020 00:09:27 +0300 Subject: [PATCH 06/62] Add tests via `rack-test`, update code a bit --- template/.rubocop.yml | 4 ++++ template/Gemfile | 17 ++++++++------- template/config.ru.erb | 3 --- template/config/processors/sentry.rb.erb | 2 +- template/lib/flame/raven_context.rb | 15 -------------- template/lib/flame/raven_context/patch.rb | 17 +++++++++++++++ template/routes.rb.erb | 3 +++ template/spec/integration/index_spec.rb | 21 +++++++++++++++++++ template/spec/integration/spec_helper.rb | 25 +++++++++++++++++++++++ template/spec/spec_helper.rb | 11 ++++++++++ 10 files changed, 92 insertions(+), 26 deletions(-) delete mode 100644 template/lib/flame/raven_context.rb create mode 100644 template/lib/flame/raven_context/patch.rb create mode 100644 template/spec/integration/index_spec.rb create mode 100644 template/spec/integration/spec_helper.rb create mode 100644 template/spec/spec_helper.rb diff --git a/template/.rubocop.yml b/template/.rubocop.yml index 6e41d99..de33790 100644 --- a/template/.rubocop.yml +++ b/template/.rubocop.yml @@ -38,3 +38,7 @@ Metrics/BlockLength: Exclude: - Rakefile - db/migrations/* + +RSpec/DescribeClass: + Exclude: + - spec/integration/**/* diff --git a/template/Gemfile b/template/Gemfile index 898324d..261e53f 100644 --- a/template/Gemfile +++ b/template/Gemfile @@ -65,13 +65,16 @@ group :mails do gem 'mail' end -# group :test do -# gem 'database_cleaner' -# gem 'fabrication' -# # gem 'page-object' -# gem 'rack-test' -# gem 'rspec' -# end +group :test do + # gem 'database_cleaner' + # gem 'fabrication' + # gem 'page-object' + gem 'rack-test' + # gem 'retriable' + gem 'rspec' + gem 'warning' + # gem 'watir' +end group :others do # gem 'faker' diff --git a/template/config.ru.erb b/template/config.ru.erb index 0ac4bd8..647382e 100644 --- a/template/config.ru.erb +++ b/template/config.ru.erb @@ -3,9 +3,6 @@ ## Require application require_relative 'application' -## Require dirs -<%= @short_module_name %>::Application.require_dirs <%= @short_module_name %>::APP_DIRS, ignore: [%r{config/puma.rb}, %r{lib/\w+/spec/}] - ## Require routes require_relative 'routes' diff --git a/template/config/processors/sentry.rb.erb b/template/config/processors/sentry.rb.erb index b140f4b..02e169c 100644 --- a/template/config/processors/sentry.rb.erb +++ b/template/config/processors/sentry.rb.erb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative '../../lib/flame/raven_context' +require_relative '../../lib/flame/raven_context/patch' module <%= @module_name %> module Config diff --git a/template/lib/flame/raven_context.rb b/template/lib/flame/raven_context.rb deleted file mode 100644 index 3fa2c34..0000000 --- a/template/lib/flame/raven_context.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -## Required via `Bundler.require` in `application.rb` -# require 'flame/raven_context' - -module Flame - ## Redefine `user` object - class RavenContext - private - - def user - # @controller&.send(:authenticated)&.account - end - end -end diff --git a/template/lib/flame/raven_context/patch.rb b/template/lib/flame/raven_context/patch.rb new file mode 100644 index 0000000..06d32b3 --- /dev/null +++ b/template/lib/flame/raven_context/patch.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Flame + ## Define and include patch + class RavenContext + ## Patch for `Flame::RavenContext` with `#user` definition + module Patch + private + + def user + # @controller&.send(:authenticated)&.account + end + end + + include Patch + end +end diff --git a/template/routes.rb.erb b/template/routes.rb.erb index 35f4e32..efac179 100644 --- a/template/routes.rb.erb +++ b/template/routes.rb.erb @@ -1,5 +1,8 @@ # frozen_string_literal: true +## Require dirs +<%= @short_module_name %>::Application.require_dirs <%= @short_module_name %>::APP_DIRS, ignore: [%r{config/puma.rb}, %r{lib/\w+/spec/}] + <%= @short_module_name %>::Application.class_exec do ## Site mount :site, '/' diff --git a/template/spec/integration/index_spec.rb b/template/spec/integration/index_spec.rb new file mode 100644 index 0000000..ff43503 --- /dev/null +++ b/template/spec/integration/index_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +describe '/' do + include_context 'with Rack Test' + + before do + get '/' + end + + describe 'response' do + subject { last_response } + + it { is_expected.to be_ok } + + describe 'body' do + subject { super().body } + + it { is_expected.to include 'Hello, world!' } + end + end +end diff --git a/template/spec/integration/spec_helper.rb b/template/spec/integration/spec_helper.rb new file mode 100644 index 0000000..5f12971 --- /dev/null +++ b/template/spec/integration/spec_helper.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'rack/test' + +require 'warning' + +Warning.ignore(:missing_ivar, Gem.loaded_specs['sequel'].full_gem_path) + +## https://github.com/getsentry/sentry-ruby/issues/1036 +Warning.ignore( + /loading in progress, circular require considered harmful/, + Gem.loaded_specs['sentry-raven'].full_gem_path +) + +shared_context 'with Rack Test' do + include Rack::Test::Methods + + let(:app) do + root_dir = '../..' + require_relative "#{root_dir}/application" + require_relative "#{root_dir}/routes" + + ST::Application + end +end diff --git a/template/spec/spec_helper.rb b/template/spec/spec_helper.rb new file mode 100644 index 0000000..ec7e316 --- /dev/null +++ b/template/spec/spec_helper.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +ENV['RACK_ENV'] ||= 'test' + +require 'pry-byebug' + +Dir["#{__dir__}/**/spec_helper.rb"].sort.each do |spec_helper| + next if spec_helper == __FILE__ + + require spec_helper +end From fa4bea2d237154b5b92178c6c4a5f5ebd5a348ed Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Wed, 23 Sep 2020 23:48:27 +0300 Subject: [PATCH 07/62] Drop extra `TargetRubyVersion` for RuboCop https://github.com/rubocop-hq/rubocop/pull/3228 --- template/.rubocop.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/template/.rubocop.yml b/template/.rubocop.yml index de33790..71a044b 100644 --- a/template/.rubocop.yml +++ b/template/.rubocop.yml @@ -31,7 +31,6 @@ Layout/MultilineHashBraceLayout: EnforcedStyle: new_line AllCops: - TargetRubyVersion: 2.6 NewCops: enable Metrics/BlockLength: From 2487acfc770d3d6303ecb7111cafca936c46fab6 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Thu, 24 Sep 2020 00:09:12 +0300 Subject: [PATCH 08/62] Delete unused `rubocop` toys tool Just use `bundle exec rubocop`. --- template/.toys/.toys.rb.erb | 2 -- 1 file changed, 2 deletions(-) diff --git a/template/.toys/.toys.rb.erb b/template/.toys/.toys.rb.erb index f4dff33..597467e 100644 --- a/template/.toys/.toys.rb.erb +++ b/template/.toys/.toys.rb.erb @@ -63,5 +63,3 @@ expand RackConsoleToys::Template require 'static_files_toys' expand StaticFilesToys::Template - -expand :rubocop From 696340cbffa23e3286eff67be9453541527d8d2c Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Thu, 24 Sep 2020 00:09:31 +0300 Subject: [PATCH 09/62] Use `pnpm run` instead of `npm run` --- template/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/template/package.json b/template/package.json index de24c95..e090f20 100644 --- a/template/package.json +++ b/template/package.json @@ -29,13 +29,13 @@ "scripts": { "build:styles": "sass assets/styles/main.scss public/styles/main.css -s compressed && postcss public/styles/main.css -o public/styles/main.css", "build:scripts": "rollup assets/scripts/application.js -o public/scripts/application/compiled/application.js -c", - "build": "npm run build:styles && npm run build:scripts", + "build": "pnpm run build:styles && pnpm run build:scripts", "clean:styles": "rm -f public/styles/main.css public/styles/main.css.map", "clean:scripts": "rm -rvf public/scripts/application/compiled", - "clean": "npm run clean:styles && npm run clean:scripts", + "clean": "pnpm run clean:styles && pnpm run clean:scripts", "lint:docs": "remark .", "lint:styles": "stylelint assets/styles/", "lint:scripts": "eslint assets/scripts/ ./*.js", - "lint": "npm run lint:docs; docs_lint_result=$?; npm run lint:styles; styles_lint_result=$?; npm run lint:scripts && $styles_lint_result && $docs_lint_result" + "lint": "pnpm run lint:docs; docs_lint_result=$?; pnpm run lint:styles; styles_lint_result=$?; pnpm run lint:scripts && $styles_lint_result && $docs_lint_result" } } From 1b7b6f056cedffb37a98c409c5f3c415ecb3b4e7 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Thu, 24 Sep 2020 00:11:04 +0300 Subject: [PATCH 10/62] Update setup instruction in the README --- template/README.md.erb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/template/README.md.erb b/template/README.md.erb index ff8ef13..2e5306b 100644 --- a/template/README.md.erb +++ b/template/README.md.erb @@ -19,8 +19,9 @@ 4. Install [`nodenv`](https://github.com/nodenv/nodenv). 5. Clone this repository and checkout to directory. 6. Set the [`EDITOR` environment variable][1] (`nano`, `vim`, `mcedit`, etc.). -7. Run `exe/setup.sh` to install Ruby (with gems), Node (with modules), - fill configs, create database (if needed) and run database migrations. +7. Run `exe/setup.sh` to install Ruby (with gems) and fill configs. +8. Run `toys db create` to create database. +9. Run `toys db migrate` to run database migrations. [1]: https://en.wikibooks.org/wiki/Guide_to_Unix/Environment_Variables#EDITOR From bebc58bb5a1d0539bccaa7ada87a4ca0e1a761ae Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Thu, 24 Sep 2020 00:44:51 +0300 Subject: [PATCH 11/62] Don't use extra `Bundler.with_unbundled_env` in specs --- spec/flame/cli/new/app_spec.rb | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/spec/flame/cli/new/app_spec.rb b/spec/flame/cli/new/app_spec.rb index 9b31e29..f42479d 100644 --- a/spec/flame/cli/new/app_spec.rb +++ b/spec/flame/cli/new/app_spec.rb @@ -5,7 +5,7 @@ describe 'Flame::CLI::New::App' do subject(:execute_command) do - Bundler.with_unbundled_env { `#{FLAME_CLI} new app #{app_name}` } + `#{FLAME_CLI} new app #{app_name}` end let(:app_name) { 'foo_bar' } @@ -455,19 +455,15 @@ describe 'generates RuboCop-satisfying app' do subject do - Bundler.with_unbundled_env do - system 'bundle exec rubocop' - end + system 'bundle exec rubocop' end before do - Bundler.with_unbundled_env do - execute_command + execute_command - Dir.chdir app_name + Dir.chdir app_name - system 'bundle install' - end + system 'exe/setup/ruby.sh' end after do From 574469ac7afdc7b22bd3510b5d9a6bc4340a416d Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Thu, 24 Sep 2020 00:45:26 +0300 Subject: [PATCH 12/62] Update template again --- spec/flame/cli/new/app_spec.rb | 19 +---- template/.eslintignore | 2 +- template/.gitignore | 42 ++++++---- template/.toys/.toys.rb.erb | 2 +- template/Gemfile | 3 +- template/application.rb.erb | 15 ++++ template/assets/scripts/application.js | 78 ------------------- template/assets/scripts/main.js | 76 ++++++++++++++++++ template/assets/styles/_colors.scss | 2 +- template/assets/styles/_sizes.scss | 6 +- template/config.ru.erb | 4 +- template/config/main.rb.erb | 2 - template/config/puma.rb.erb | 4 +- .../controllers/site/index_controller.rb.erb | 2 +- template/filewatchers.yaml | 5 +- template/package.json | 4 +- template/postcss.config.js | 2 +- template/rollup.config.js.erb | 8 +- template/routes.rb.erb | 9 --- template/spec/integration/spec_helper.rb | 11 ++- template/views/site/layout.html.erb.erb | 26 ++++++- 21 files changed, 174 insertions(+), 148 deletions(-) delete mode 100644 template/assets/scripts/application.js create mode 100644 template/assets/scripts/main.js delete mode 100644 template/routes.rb.erb diff --git a/spec/flame/cli/new/app_spec.rb b/spec/flame/cli/new/app_spec.rb index f42479d..157cb02 100644 --- a/spec/flame/cli/new/app_spec.rb +++ b/spec/flame/cli/new/app_spec.rb @@ -52,7 +52,6 @@ '- mailers/mail/_base.rb', '- mailers/mail/default.rb', '- rollup.config.js', - '- routes.rb', '- views/site/errors/400.html.erb', '- views/site/errors/404.html.erb', '- views/site/errors/500.html.erb', @@ -138,7 +137,7 @@ describe 'config.ru' do let(:expected_words) do [ - 'FB::Application.require_dirs FB::APP_DIRS', + 'FB::Application.setup', 'if FB::Application.config[:session]', 'use Rack::Session::Cookie, FB::Application.config[:session][:cookie]', 'use Rack::CommonLogger, FB::Application.logger', @@ -301,8 +300,7 @@ describe 'controllers/site/index_controller.rb' do let(:expected_words) do [ - 'module FooBar', - 'class IndexController < FB::Site::Controller' + 'module FooBar' ] end @@ -333,16 +331,6 @@ it { is_expected.to match_words(*expected_words) } end - describe 'routes.rb' do - let(:expected_words) do - [ - 'FB::Application.class_exec do' - ] - end - - it { is_expected.to match_words(*expected_words) } - end - describe 'forms/_base.rb' do let(:expected_words) do [ @@ -445,7 +433,8 @@ [ '<%= config[:site][:site_name] %>', '<% if Raven.configuration.environments.include?(config[:environment]) &&', - "environment: '<%= config[:environment] %>'," + "environment: '<%= config[:environment] %>',", + '' ] end diff --git a/template/.eslintignore b/template/.eslintignore index 45a1997..8c666c7 100644 --- a/template/.eslintignore +++ b/template/.eslintignore @@ -1 +1 @@ -public/scripts/application/compiled/ +/public/scripts/compiled/ diff --git a/template/.gitignore b/template/.gitignore index 23cb3eb..4b36673 100644 --- a/template/.gitignore +++ b/template/.gitignore @@ -1,29 +1,37 @@ # configuration -config/**/* -!config/**/*.example* -!config/**/*.rb -!config/**/*.erb -!config/nginx -!config/nginx/snippets -!config/nginx/snippets/*.example* -!config/nginx/snippets/ssl-params.conf -!config/nginx/sites -!config/nginx/sites/*.example* -!config/processors +/config/**/* +!/config/**/*.example* +!/config/**/*.rb +!/config/**/*.erb +!/config/nginx +!/config/nginx/snippets +!/config/nginx/snippets/*.example* +!/config/nginx/snippets/ssl-params.conf +!/config/nginx/sites +!/config/nginx/sites/*.example* +!/config/processors # temp .sass-cache/ -logs/ -tmp/ +/log/ +/tmp/ *.bak *~ # uploaded files -#public/files +#/public/files # dumps -*.sql +/db/dumps/**/* # seo files -#views/site/sitemap.html -#public/robots.txt +#/views/site/sitemap.html +#/public/robots.txt + +# node modules +/node_modules/ + +# compiled CSS +/public/styles/main.css +/public/styles/main.css.map +/public/scripts/compiled/ diff --git a/template/.toys/.toys.rb.erb b/template/.toys/.toys.rb.erb index 597467e..a91114c 100644 --- a/template/.toys/.toys.rb.erb +++ b/template/.toys/.toys.rb.erb @@ -14,7 +14,7 @@ expand BenchmarkToys::Template alias_tool :b, :benchmark application_proc = proc do - require "#{config_dir}/main" + require "#{root_dir}/application" <%= @short_module_name %>::Application end diff --git a/template/Gemfile b/template/Gemfile index 261e53f..d355cc9 100644 --- a/template/Gemfile +++ b/template/Gemfile @@ -101,8 +101,7 @@ group :toys do gem 'flame_deploy_toys' gem 'flame_generate_toys' gem 'flame_routes_toys' - ## TODO: Switch to stable version after `filewatcher-cli` release - gem 'flame_server_toys', github: 'AlexWayfer/flame_server_toys' + gem 'flame_server_toys' gem 'locales_toys' gem 'psql_toys' gem 'rack_console_toys' diff --git a/template/application.rb.erb b/template/application.rb.erb index e3f456f..933d0ce 100644 --- a/template/application.rb.erb +++ b/template/application.rb.erb @@ -59,11 +59,26 @@ module <%= @module_name %> } end + def setup + ## Initialize Sequel connection before models requiring + db_connection + + ## Require dirs + require_dirs APP_DIRS, ignore: [%r{config/puma.rb}, %r{lib/\w+/spec/}] + + class_exec do + ## Site + mount :site, '/' + end + end + private memoize def development_or_toys (config[:environment] == 'development') || (File.basename($PROGRAM_NAME) == 'toys') end end + + require "#{config[:config_dir]}/main" end end diff --git a/template/assets/scripts/application.js b/template/assets/scripts/application.js deleted file mode 100644 index dfe2e91..0000000 --- a/template/assets/scripts/application.js +++ /dev/null @@ -1,78 +0,0 @@ -import 'core-js/stable/string/includes'; - -// https://github.com/zloirock/core-js/issues/618#issuecomment-522618151 -// https://github.com/zloirock/core-js/issues/619#issuecomment-522624271 -import 'core-js/stable/symbol/iterator'; - -import 'core-js/stable/array/from'; -import 'core-js/stable/object/entries'; -import 'core-js/stable/dom-collections/for-each'; - -export class App { - document_ready() { - // Prevent double form submission - document.querySelectorAll('form').forEach( - form => { - form.addEventListener('submit', event => { - if (form.submitting == true) { - event.preventDefault(); - event.stopImmediatePropagation(); - } else { - form.submitting = true; - } - }); - } - ); - - // Prevent `.` typing in number inputs with step = 1 - document.querySelectorAll('input[type="number"][step="1"]').forEach( - input => input.addEventListener('keypress', event => { - if (event.key == '.') { - event.preventDefault(); - return false; - } - }) - ); - - // `input[type="number"]` should allow only numbers - // https://stackoverflow.com/a/469362/2630849 - const number_input_filter = value => /^-?\d*[.,]?\d*$/.test(value); - document.querySelectorAll('input[type="number"]').forEach(number_input => { - [ - 'input', 'keydown', 'keyup', 'mousedown', 'mouseup', 'select', - 'contextmenu', 'drop' - ].forEach(event_type => { - number_input.addEventListener(event_type, () => { - if (number_input_filter(number_input.value)) { - number_input.oldValue = number_input.value; - number_input.oldSelectionStart = number_input.selectionStart; - number_input.oldSelectionEnd = number_input.selectionEnd; - } else if ( - Object.prototype.hasOwnProperty.call(number_input, 'oldValue') - ) { - number_input.value = number_input.oldValue; - number_input.setSelectionRange( - number_input.oldSelectionStart, number_input.oldSelectionEnd - ); - } - }); - }); - }); - - // `button` is not focused while clicked in macOS and iOS - // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus - document.querySelectorAll('button[type="submit"][name]').forEach(button => { - const form = button.form; - const input = form.querySelector(`input[name="${button.name}"][value="${button.value}"]`); - if (input) return; - - button.addEventListener('click', () => { - const input = document.createElement('input'); - input.type = 'hidden'; - input.name = button.name; - input.value = button.value; - form.appendChild(input); - }); - }); - } -} diff --git a/template/assets/scripts/main.js b/template/assets/scripts/main.js new file mode 100644 index 0000000..e6b5c69 --- /dev/null +++ b/template/assets/scripts/main.js @@ -0,0 +1,76 @@ +import 'core-js/stable/string/includes' + +// https://github.com/zloirock/core-js/issues/618#issuecomment-522618151 +// https://github.com/zloirock/core-js/issues/619#issuecomment-522624271 +import 'core-js/stable/symbol/iterator' + +import 'core-js/stable/array/from' +import 'core-js/stable/object/entries' +import 'core-js/stable/dom-collections/for-each' + +document.addEventListener('DOMContentLoaded', () => { + // Prevent double form submission + document.querySelectorAll('form').forEach( + form => { + form.addEventListener('submit', event => { + if (form.submitting == true) { + event.preventDefault() + event.stopImmediatePropagation() + } else { + form.submitting = true + } + }) + } + ) + + // Prevent `.` typing in number inputs with step = 1 + document.querySelectorAll('input[type="number"][step="1"]').forEach( + input => input.addEventListener('keypress', event => { + if (event.key == '.') { + event.preventDefault() + return false + } + }) + ) + + // `input[type="number"]` should allow only numbers + // https://stackoverflow.com/a/469362/2630849 + const number_input_filter = value => /^-?\d*[.,]?\d*$/.test(value) + document.querySelectorAll('input[type="number"]').forEach(number_input => { + [ + 'input', 'keydown', 'keyup', 'mousedown', 'mouseup', 'select', + 'contextmenu', 'drop' + ].forEach(event_type => { + number_input.addEventListener(event_type, () => { + if (number_input_filter(number_input.value)) { + number_input.oldValue = number_input.value + number_input.oldSelectionStart = number_input.selectionStart + number_input.oldSelectionEnd = number_input.selectionEnd + } else if ( + Object.prototype.hasOwnProperty.call(number_input, 'oldValue') + ) { + number_input.value = number_input.oldValue + number_input.setSelectionRange( + number_input.oldSelectionStart, number_input.oldSelectionEnd + ) + } + }) + }) + }) + + // `button` is not focused while clicked in macOS and iOS + // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus + document.querySelectorAll('button[type="submit"][name]').forEach(button => { + const form = button.form + const input = form.querySelector(`input[name="${button.name}"][value="${button.value}"]`) + if (input) return + + button.addEventListener('click', () => { + const input = document.createElement('input') + input.type = 'hidden' + input.name = button.name + input.value = button.value + form.appendChild(input) + }) + }) +}) diff --git a/template/assets/styles/_colors.scss b/template/assets/styles/_colors.scss index ea6b05d..2f61e9d 100644 --- a/template/assets/styles/_colors.scss +++ b/template/assets/styles/_colors.scss @@ -1,4 +1,4 @@ -$accent_background_color: #f24900; +$accent_background_color: white; $accent_text_color: darken($accent_background_color, 15%); $footer_background_color: #2a2a2a; diff --git a/template/assets/styles/_sizes.scss b/template/assets/styles/_sizes.scss index 8d8a28d..92095a4 100644 --- a/template/assets/styles/_sizes.scss +++ b/template/assets/styles/_sizes.scss @@ -4,12 +4,12 @@ $container_max_width: 1200px; $common_border_width: 1px; -$common_border_radius: 0.5em; +$common_border_radius: 0.45em; $input_border_radius: $common_border_radius; $input_border_width: $common_border_width; -$input_vertical_padding: 0.375em; -$input_padding: $input_vertical_padding 0.75em; +$input_vertical_padding: 0.25em; +$input_padding: $input_vertical_padding 0.6em; $input_line_height: 1.4em; $button_border_radius: $input_border_radius; diff --git a/template/config.ru.erb b/template/config.ru.erb index 647382e..37cb056 100644 --- a/template/config.ru.erb +++ b/template/config.ru.erb @@ -3,8 +3,8 @@ ## Require application require_relative 'application' -## Require routes -require_relative 'routes' +## Require project directories, define routes, etc. +<%= @short_module_name %>::Application.setup ## Use session middleware if <%= @short_module_name %>::Application.config[:session] diff --git a/template/config/main.rb.erb b/template/config/main.rb.erb index b6aaa26..6a593e0 100644 --- a/template/config/main.rb.erb +++ b/template/config/main.rb.erb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative '../application' - config = <%= @short_module_name %>::Application.config class << config diff --git a/template/config/puma.rb.erb b/template/config/puma.rb.erb index 2d6bd9b..505a167 100644 --- a/template/config/puma.rb.erb +++ b/template/config/puma.rb.erb @@ -3,7 +3,7 @@ require 'fileutils' require 'etc' -require_relative 'main' +require_relative '../application' config = <%= @short_module_name %>::Application.config @@ -47,7 +47,7 @@ workers workers_count worker_timeout config[:environment] == 'development' ? 1_000_000 : 15 threads 0, server_config[:threads_count] || 4 -lowlevel_error_handler do |_exception, _env| +lowlevel_error_handler do |exception, env| request_context = Flame::RavenContext.new(:puma, env: env, exception: exception) Raven.capture_exception(*request_context.exception_with_context) ## Rack response diff --git a/template/controllers/site/index_controller.rb.erb b/template/controllers/site/index_controller.rb.erb index c1d724a..20ed0ea 100644 --- a/template/controllers/site/index_controller.rb.erb +++ b/template/controllers/site/index_controller.rb.erb @@ -3,7 +3,7 @@ module <%= @module_name %> module Site ## Index controller for example - class IndexController < <%= @short_module_name %>::Site::Controller + class IndexController < Site::Controller def index view end diff --git a/template/filewatchers.yaml b/template/filewatchers.yaml index 2bd3531..a1164d0 100644 --- a/template/filewatchers.yaml +++ b/template/filewatchers.yaml @@ -1,4 +1,7 @@ -- :pattern: '**/{Gemfile,*.{rb,ru,y{a,}ml}}' +- :pattern: 'Gemfile{,.lock}' + :command: bundle install && pumactl restart + +- :pattern: '**/{*.{rb,ru,y{a,}ml}}' :exclude: '**/{spec/**/*,config/**/*.example*}' ## Don't use `bundle exec`, it's already required by `toys` ## https://github.com/dazuma/toys/issues/65 diff --git a/template/package.json b/template/package.json index e090f20..471c3e8 100644 --- a/template/package.json +++ b/template/package.json @@ -28,10 +28,10 @@ }, "scripts": { "build:styles": "sass assets/styles/main.scss public/styles/main.css -s compressed && postcss public/styles/main.css -o public/styles/main.css", - "build:scripts": "rollup assets/scripts/application.js -o public/scripts/application/compiled/application.js -c", + "build:scripts": "rollup assets/scripts/main.js -o public/scripts/compiled/main.js -c", "build": "pnpm run build:styles && pnpm run build:scripts", "clean:styles": "rm -f public/styles/main.css public/styles/main.css.map", - "clean:scripts": "rm -rvf public/scripts/application/compiled", + "clean:scripts": "rm -rvf public/scripts/compiled", "clean": "pnpm run clean:styles && pnpm run clean:scripts", "lint:docs": "remark .", "lint:styles": "stylelint assets/styles/", diff --git a/template/postcss.config.js b/template/postcss.config.js index b3fd627..9e8dc7a 100644 --- a/template/postcss.config.js +++ b/template/postcss.config.js @@ -3,4 +3,4 @@ module.exports = { require('postcss-flexbugs-fixes'), require('autoprefixer') ] -}; +} diff --git a/template/rollup.config.js.erb b/template/rollup.config.js.erb index 7a36240..5a860b1 100644 --- a/template/rollup.config.js.erb +++ b/template/rollup.config.js.erb @@ -1,6 +1,6 @@ -import json from '@rollup/plugin-json'; -import resolve from '@rollup/plugin-node-resolve'; -import commonjs from '@rollup/plugin-commonjs'; +import json from '@rollup/plugin-json' +import resolve from '@rollup/plugin-node-resolve' +import commonjs from '@rollup/plugin-commonjs' export default { output: { @@ -16,4 +16,4 @@ export default { }), commonjs(), ] -}; +} diff --git a/template/routes.rb.erb b/template/routes.rb.erb deleted file mode 100644 index efac179..0000000 --- a/template/routes.rb.erb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -## Require dirs -<%= @short_module_name %>::Application.require_dirs <%= @short_module_name %>::APP_DIRS, ignore: [%r{config/puma.rb}, %r{lib/\w+/spec/}] - -<%= @short_module_name %>::Application.class_exec do - ## Site - mount :site, '/' -end diff --git a/template/spec/integration/spec_helper.rb b/template/spec/integration/spec_helper.rb index 5f12971..a7584bd 100644 --- a/template/spec/integration/spec_helper.rb +++ b/template/spec/integration/spec_helper.rb @@ -16,10 +16,13 @@ include Rack::Test::Methods let(:app) do - root_dir = '../..' - require_relative "#{root_dir}/application" - require_relative "#{root_dir}/routes" + require_relative '../../application' - ST::Application + app = ST::Application + + ## Require project directories, define routes, etc. + app.setup + + app end end diff --git a/template/views/site/layout.html.erb.erb b/template/views/site/layout.html.erb.erb index 36e29c7..188b8e3 100644 --- a/template/views/site/layout.html.erb.erb +++ b/template/views/site/layout.html.erb.erb @@ -64,7 +64,7 @@ # svgxuse.min # cccombo # ], - 'application/compiled': %w[application] + 'compiled': %w[main] } .each do |dir, file_names| <%= "%\>" %> @@ -76,6 +76,28 @@ <%= "<\% end %\>" %> - <%= "<\%= yield %\>" %> +
+ " %>"> +

<%= "<\%= config[:site][:site_name] %\>" %>

+
+
+ +
+ + + <%= "<\%= yield %\>" %> +
From b7da5101dd2e7832ad1472926cc544d717ee1db6 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Wed, 11 Nov 2020 14:00:33 +0300 Subject: [PATCH 13/62] Add `--domain` option --- lib/flame/cli/new/app.rb | 4 +++- template/config/mail.example.yaml.erb | 6 +++--- template/config/sentry.example.yaml.erb | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/flame/cli/new/app.rb b/lib/flame/cli/new/app.rb index 20b3e63..194feb4 100644 --- a/lib/flame/cli/new/app.rb +++ b/lib/flame/cli/new/app.rb @@ -9,6 +9,8 @@ class App < Clamp::Command parameter 'APP_NAME', 'application name' + option ['-d', '--domain'], 'NAME', 'domain name for configuration' + def execute initialize_instance_variables @@ -31,7 +33,7 @@ def initialize_instance_variables @short_module_name = @module_name.split(/([[:upper:]][[:lower:]]*)/).map! { |s| s[0] }.join - @domain_name = @module_name.downcase + @domain_name = domain || "#{@module_name.downcase}.com" end def make_dir(&block) diff --git a/template/config/mail.example.yaml.erb b/template/config/mail.example.yaml.erb index 4524f61..1914160 100644 --- a/template/config/mail.example.yaml.erb +++ b/template/config/mail.example.yaml.erb @@ -1,6 +1,6 @@ :from: - :name: '<%= @module_name %>.com' - :email: 'info@<%= @domain_name %>.com' + :name: '<%= @module_name %>.<%= @domain_name.split('.', 2).last %>' + :email: 'info@<%= @domain_name %>' :site_url: :scheme: http @@ -13,7 +13,7 @@ :_smtp: :address: 'smtp.gmail.com' :port: 587 - :user_name: 'info@<%= @domain_name %>.com' + :user_name: 'info@<%= @domain_name %>' :password: 'your_password' :authentication: 'plain' :enable_starttls_auto: true diff --git a/template/config/sentry.example.yaml.erb b/template/config/sentry.example.yaml.erb index e2d9a0f..e67f6ea 100644 --- a/template/config/sentry.example.yaml.erb +++ b/template/config/sentry.example.yaml.erb @@ -1,4 +1,4 @@ -:host: sentry.<%= @domain_name %>.com +:host: sentry.<%= @domain_name %> back-end: :api_key: abcdef :project_id: 1 From 1cbe688654e4f2428a8b7cdf389bbca142ddcc26 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Wed, 11 Nov 2020 14:03:07 +0300 Subject: [PATCH 14/62] Fix path to index from 404 view --- spec/flame/cli/new/app_spec.rb | 2 +- template/views/site/errors/404.html.erb.erb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/flame/cli/new/app_spec.rb b/spec/flame/cli/new/app_spec.rb index 157cb02..571af36 100644 --- a/spec/flame/cli/new/app_spec.rb +++ b/spec/flame/cli/new/app_spec.rb @@ -398,7 +398,7 @@ let(:expected_words) do [ '

<%= t.error.page.itself.not_found %>

', - '', + '', '<%= t.button.home %>' ] end diff --git a/template/views/site/errors/404.html.erb.erb b/template/views/site/errors/404.html.erb.erb index 037041a..7f79bbe 100644 --- a/template/views/site/errors/404.html.erb.erb +++ b/template/views/site/errors/404.html.erb.erb @@ -1,7 +1,7 @@ From 65030065b7462110d5c41c07bb86d7bfd24bbba7 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Wed, 11 Nov 2020 14:03:58 +0300 Subject: [PATCH 15/62] Simplify `constants.rb` --- spec/flame/cli/new/app_spec.rb | 2 +- template/constants.rb.erb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/flame/cli/new/app_spec.rb b/spec/flame/cli/new/app_spec.rb index 571af36..588177d 100644 --- a/spec/flame/cli/new/app_spec.rb +++ b/spec/flame/cli/new/app_spec.rb @@ -268,7 +268,7 @@ let(:expected_words) do [ 'module FooBar', - '::FB = ::FooBar' + '::FB = self' ] end diff --git a/template/constants.rb.erb b/template/constants.rb.erb index f5fffce..92f1d85 100644 --- a/template/constants.rb.erb +++ b/template/constants.rb.erb @@ -1,7 +1,7 @@ # frozen_string_literal: true module <%= @module_name %> - ::<%= @short_module_name %> = ::<%= @module_name %> + ::<%= @short_module_name %> = self APP_DIRS = [ 'lib', From acfa3f804983aa67063e899a9b8f21faa7c2b37c Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Wed, 11 Nov 2020 14:04:44 +0300 Subject: [PATCH 16/62] Add small helper JS script for custom confirmations on forms submitting --- template/assets/scripts/main.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/template/assets/scripts/main.js b/template/assets/scripts/main.js index e6b5c69..971dff1 100644 --- a/template/assets/scripts/main.js +++ b/template/assets/scripts/main.js @@ -73,4 +73,15 @@ document.addEventListener('DOMContentLoaded', () => { form.appendChild(input) }) }) + + // Custom confirmations on forms submitting + document.querySelectorAll('form[data-confirm]').forEach(form => { + form.addEventListener('submit', event => { + const result = confirm(form.dataset.confirm) + if (!result) { + form.submitting = false + event.preventDefault() + } + }) + }) }) From 17d43d453776d5faed3dd9c5e609dd686794f03c Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Wed, 11 Nov 2020 14:05:40 +0300 Subject: [PATCH 17/62] Update styles, especially for forms --- template/assets/styles/_sizes.scss | 4 +- template/assets/styles/components/_forms.scss | 64 ++++++++++++++++++- .../assets/styles/lib/_inputs_with_types.scss | 2 +- template/assets/styles/main.scss | 14 ++++ 4 files changed, 78 insertions(+), 6 deletions(-) diff --git a/template/assets/styles/_sizes.scss b/template/assets/styles/_sizes.scss index 92095a4..cb2627a 100644 --- a/template/assets/styles/_sizes.scss +++ b/template/assets/styles/_sizes.scss @@ -8,8 +8,8 @@ $common_border_radius: 0.45em; $input_border_radius: $common_border_radius; $input_border_width: $common_border_width; -$input_vertical_padding: 0.25em; -$input_padding: $input_vertical_padding 0.6em; +$input_vertical_padding: 0.2em; +$input_padding: $input_vertical_padding 0.55em; $input_line_height: 1.4em; $button_border_radius: $input_border_radius; diff --git a/template/assets/styles/components/_forms.scss b/template/assets/styles/components/_forms.scss index 531a3d4..e72ceb4 100644 --- a/template/assets/styles/components/_forms.scss +++ b/template/assets/styles/components/_forms.scss @@ -2,9 +2,37 @@ $form_vertical_label_margin: 0.8em; $form_vertical_input_margin: 0.4em; form { - label { - font-weight: bold; + text-align: center; + + fieldset { + text-align: initial; + margin: 1em 0; + padding: 0.4em 1em 0.9em; + border-radius: $common_border_radius; + border-style: solid; + border-width: $input_border_width; + border-color: $input_border_color; + + > fieldset { + &:first-of-type { + margin-top: 0.4em; + } + + &:last-of-type { + margin-bottom: 0.4em; + } + } + > legend { + font-weight: bold; + } + } + + > fieldset:first-of-type { + margin-top: 0; + } + + label { > p { font-weight: normal; } @@ -48,7 +76,8 @@ form { ul, table, p, - article { + article, + fieldset { text-align: left; } @@ -80,4 +109,33 @@ form { } } } + + &.table { + label { + display: table-row; + + > span { + display: table-cell; + padding: 0.4em 0; + vertical-align: middle; + + &:first-of-type { + text-align: right; + padding-right: 0.4em; + } + } + + &:first-of-type { + > span { + padding-top: 0.2em; + } + } + + &:last-of-type { + > span { + padding-bottom: 0.2em; + } + } + } + } } diff --git a/template/assets/styles/lib/_inputs_with_types.scss b/template/assets/styles/lib/_inputs_with_types.scss index ea820f5..8ea3f89 100644 --- a/template/assets/styles/lib/_inputs_with_types.scss +++ b/template/assets/styles/lib/_inputs_with_types.scss @@ -11,6 +11,6 @@ @function all_horizontal_inputs() { @return inputs_with_types( 'text', 'email', 'password', 'number', 'url', 'search', 'date', 'time', - 'file' + 'file', 'color', 'range' ); } diff --git a/template/assets/styles/main.scss b/template/assets/styles/main.scss index 8e434f6..6ce8238 100644 --- a/template/assets/styles/main.scss +++ b/template/assets/styles/main.scss @@ -148,6 +148,20 @@ body { white-space: normal; } + input { + &[type="range"] { + padding-left: 0; + padding-right: 0; + border: none; + } + + &[type="color"] { + box-sizing: content-box; + padding: 0 0.2em; + height: $input_line_height + $input_vertical_padding * 2; + } + } + @mixin primary { @include custom-background-color($accent_background_color); From 6167b3b5570703f4e154bf56c09e49e204f00e50 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Wed, 11 Nov 2020 14:06:49 +0300 Subject: [PATCH 18/62] Redirect Puma output into file for production environment --- template/config/server.example.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/template/config/server.example.yaml b/template/config/server.example.yaml index 92492db..0def8d5 100644 --- a/template/config/server.example.yaml +++ b/template/config/server.example.yaml @@ -23,6 +23,7 @@ test: &test production: &production <<: *default + :output: :file :binds: # :tcp: # :host: '0.0.0.0' From 3d55dc3eca9e1f00d95cf3f9135ab1660d8f01ec Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Wed, 11 Nov 2020 14:07:26 +0300 Subject: [PATCH 19/62] Add forgotten `highlighted_backtrace_for(exception)` for 500 error page --- template/controllers/site/_controller.rb.erb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/template/controllers/site/_controller.rb.erb b/template/controllers/site/_controller.rb.erb index 85bab4d..30d96b1 100644 --- a/template/controllers/site/_controller.rb.erb +++ b/template/controllers/site/_controller.rb.erb @@ -24,6 +24,16 @@ module <%= @module_name %> rescue Flame::Errors::TemplateNotFoundError "

#{super}

" end + + private + + def highlighted_backtrace_for(exception) + root_dir = config[:root_dir] + lines = exception.backtrace.map do |line| + line[root_dir] ? "#{root_dir}#{line.split(root_dir).last}" : line + end + lines.join($RS) + end end end end From dda5f69a9bd1b9e470fe58056eb18205a4ad3ae0 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Wed, 11 Nov 2020 14:07:48 +0300 Subject: [PATCH 20/62] Fix code documentation a bit --- template/controllers/site/index_controller.rb.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/template/controllers/site/index_controller.rb.erb b/template/controllers/site/index_controller.rb.erb index 20ed0ea..dc015e0 100644 --- a/template/controllers/site/index_controller.rb.erb +++ b/template/controllers/site/index_controller.rb.erb @@ -2,7 +2,7 @@ module <%= @module_name %> module Site - ## Index controller for example + ## Index controller class IndexController < Site::Controller def index view From cffe6461d34cec41f605bba8e0c0df56dbe8b3bb Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Wed, 11 Nov 2020 14:08:58 +0300 Subject: [PATCH 21/62] Move `git pull` commands from `setup.sh` to `update.sh` --- template/exe/setup.sh | 3 --- template/exe/update.sh | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/template/exe/setup.sh b/template/exe/setup.sh index ef62a8a..0a9a2f3 100755 --- a/template/exe/setup.sh +++ b/template/exe/setup.sh @@ -4,9 +4,6 @@ CURRENT_DIR=`dirname "$0"` . $CURRENT_DIR/_common.sh -# exe git checkout $1 -# exe git pull origin $1 - exe $CURRENT_DIR/setup/ruby.sh exe toys config check diff --git a/template/exe/update.sh b/template/exe/update.sh index 993f24c..e8ef966 100755 --- a/template/exe/update.sh +++ b/template/exe/update.sh @@ -6,6 +6,9 @@ CURRENT_DIR=`dirname "$0"` exe toys server stop +exe git checkout $1 +exe git pull origin $1 + exe $CURRENT_DIR/setup.sh "$@" exe toys server start From 77f241237093c97b586186ee73fa91f190ecb7ae Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Wed, 11 Nov 2020 14:09:20 +0300 Subject: [PATCH 22/62] Add forgotten locales for errors pages --- template/locales/en.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/template/locales/en.yaml b/template/locales/en.yaml index e69de29..3d4e763 100644 --- a/template/locales/en.yaml +++ b/template/locales/en.yaml @@ -0,0 +1,13 @@ +button: + back: Back + home: Home + +error: + page: + itself: + not_found: Page not found. + + unexpected_error: + title: Unexpected error occurred. + subtitle: Try to go back and refresh the page. + text: Information for fixing has been already sent to our developers. From d698b05f34d23a4a96f00e6dd328f9be77b9502f Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Wed, 11 Nov 2020 14:09:53 +0300 Subject: [PATCH 23/62] Improve arrays of assets in `layout.html.erb` --- template/views/site/layout.html.erb.erb | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/template/views/site/layout.html.erb.erb b/template/views/site/layout.html.erb.erb index 188b8e3..9a6964c 100644 --- a/template/views/site/layout.html.erb.erb +++ b/template/views/site/layout.html.erb.erb @@ -7,7 +7,7 @@ <%= "<\%= config[:site][:site_name] %\>" %> <%= "<\%" %> - %i[ + %w[ main ].each do |name| <%= "%\>" %> @@ -58,12 +58,11 @@ <%= "<\%" %> { - libs: %w[], - # dom4.max - # modernizr-custom - # svgxuse.min - # cccombo - # ], + lib: %w[], + # dom4.max + # modernizr-custom + # svgxuse.min + # cccombo 'compiled': %w[main] } .each do |dir, file_names| From a49c200f76cb6f210d3c89c9987b363ed905fcf1 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Wed, 11 Nov 2020 14:12:58 +0300 Subject: [PATCH 24/62] Replace relative path for Puma's UNIX socket with `/run` Necessary for RHEL distributives, like Fedora. Source: https://twitter.com/AlexWayfer/status/1311951794201923585 --- spec/flame/cli/new/app_spec.rb | 10 ++++++++++ template/config/server.example.yaml | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/spec/flame/cli/new/app_spec.rb b/spec/flame/cli/new/app_spec.rb index 588177d..8570d92 100644 --- a/spec/flame/cli/new/app_spec.rb +++ b/spec/flame/cli/new/app_spec.rb @@ -203,6 +203,16 @@ it { is_expected.to match_words(*expected_words) } end + describe 'config/server.example.yaml' do + let(:expected_words) do + [ + ":unix: '/run/foo_bar/puma.sock'" + ] + end + + it { is_expected.to match_words(*expected_words) } + end + describe 'config/processors/mail.rb' do let(:expected_words) do [ diff --git a/template/config/server.example.yaml b/template/config/server.example.yaml index 0def8d5..5e06ff3 100644 --- a/template/config/server.example.yaml +++ b/template/config/server.example.yaml @@ -28,4 +28,4 @@ production: &production # :tcp: # :host: '0.0.0.0' # :port: 3000 - :unix: 'tmp/sockets/puma.sock' + :unix: '/run/<%= @app_name %>/puma.sock' From dbc9f587a590f740c46f6097982e57d53391f39c Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Wed, 11 Nov 2020 17:56:04 +0300 Subject: [PATCH 25/62] Fix general assets linting, add specs for this --- spec/flame/cli/new/app_spec.rb | 20 ++++++++++++++++++++ template/package.json | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/spec/flame/cli/new/app_spec.rb b/spec/flame/cli/new/app_spec.rb index 8570d92..2d2a050 100644 --- a/spec/flame/cli/new/app_spec.rb +++ b/spec/flame/cli/new/app_spec.rb @@ -472,6 +472,26 @@ it { is_expected.to be true } end + describe 'generates assets linting satisfying app' do + subject do + system 'pnpm lint' + end + + before do + execute_command + + Dir.chdir app_name + + system 'exe/setup/node.sh' + end + + after do + Dir.chdir '..' + end + + it { is_expected.to be true } + end + describe 'generates working app' do subject do Bundler.with_unbundled_env do diff --git a/template/package.json b/template/package.json index 471c3e8..5e63208 100644 --- a/template/package.json +++ b/template/package.json @@ -36,6 +36,6 @@ "lint:docs": "remark .", "lint:styles": "stylelint assets/styles/", "lint:scripts": "eslint assets/scripts/ ./*.js", - "lint": "pnpm run lint:docs; docs_lint_result=$?; pnpm run lint:styles; styles_lint_result=$?; pnpm run lint:scripts && $styles_lint_result && $docs_lint_result" + "lint": "pnpm run lint:docs; docs_lint_result=$?; pnpm run lint:styles; styles_lint_result=$?; pnpm run lint:scripts && [ $styles_lint_result -eq 0 ] && [ $docs_lint_result -eq 0 ]" } } From a32ea520e0d8d226334e838504d43a8a17f31cc6 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Wed, 11 Nov 2020 17:56:41 +0300 Subject: [PATCH 26/62] Fix `@app_name` render in `configserver.example.yaml` --- template/config/{server.example.yaml => server.example.yaml.erb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename template/config/{server.example.yaml => server.example.yaml.erb} (100%) diff --git a/template/config/server.example.yaml b/template/config/server.example.yaml.erb similarity index 100% rename from template/config/server.example.yaml rename to template/config/server.example.yaml.erb From c29146989c6f67761ce8afb116315dc0e7d5274a Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Tue, 17 Nov 2020 00:38:51 +0300 Subject: [PATCH 27/62] Merge inherited `Include` and `Exclude` in RuboCop config --- .rubocop.yml | 5 +++++ template/.rubocop.yml | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/.rubocop.yml b/.rubocop.yml index f4867c1..804d91e 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -2,6 +2,11 @@ require: - rubocop-performance - rubocop-rspec +inherit_mode: + merge: + - Include + - Exclude + Layout/IndentationStyle: EnforcedStyle: tabs IndentationWidth: 2 diff --git a/template/.rubocop.yml b/template/.rubocop.yml index 71a044b..9441ca9 100644 --- a/template/.rubocop.yml +++ b/template/.rubocop.yml @@ -2,6 +2,11 @@ require: - rubocop-performance - rubocop-rspec +inherit_mode: + merge: + - Include + - Exclude + Layout/IndentationStyle: EnforcedStyle: tabs IndentationWidth: 2 From 6446c8d60421e2651695d0e6013f7ae95fc7017c Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Tue, 17 Nov 2020 01:20:20 +0300 Subject: [PATCH 28/62] Replace `<%= "<\%` with `<%%` --- spec/flame/cli/new/app_spec.rb | 8 +++- template/views/site/errors/400.html.erb.erb | 10 ++--- template/views/site/errors/404.html.erb.erb | 6 +-- template/views/site/errors/500.html.erb.erb | 26 +++++------ template/views/site/layout.html.erb.erb | 50 ++++++++++----------- 5 files changed, 53 insertions(+), 47 deletions(-) diff --git a/spec/flame/cli/new/app_spec.rb b/spec/flame/cli/new/app_spec.rb index 2d2a050..dd8114e 100644 --- a/spec/flame/cli/new/app_spec.rb +++ b/spec/flame/cli/new/app_spec.rb @@ -440,12 +440,18 @@ describe 'views/site/layout.html.erb' do let(:expected_words) do + ## https://github.com/rubocop-hq/rubocop/issues/8416 + # rubocop:disable Lint/InterpolationCheck [ '<%= config[:site][:site_name] %>', + '" />', '<% if Raven.configuration.environments.include?(config[:environment]) &&', "environment: '<%= config[:environment] %>',", - '' + 'src="<%= url_to "/scripts/#{dir}/#{name}.js", version: true %>"', + '', + '

<%= config[:site][:site_name] %>

' ] + # rubocop:enable Lint/InterpolationCheck end it { is_expected.to match_words(*expected_words) } diff --git a/template/views/site/errors/400.html.erb.erb b/template/views/site/errors/400.html.erb.erb index 9330d12..71227ac 100644 --- a/template/views/site/errors/400.html.erb.erb +++ b/template/views/site/errors/400.html.erb.erb @@ -1,12 +1,12 @@
diff --git a/template/views/site/errors/404.html.erb.erb b/template/views/site/errors/404.html.erb.erb index 7f79bbe..dcd1721 100644 --- a/template/views/site/errors/404.html.erb.erb +++ b/template/views/site/errors/404.html.erb.erb @@ -1,7 +1,7 @@
-

<%= "<\%= t.error.page.itself.not_found %\>" %>

+

<%%= t.error.page.itself.not_found %>


- " %>"> - <%= "<\%= t.button.home %\>" %> + + <%%= t.button.home %>
diff --git a/template/views/site/errors/500.html.erb.erb b/template/views/site/errors/500.html.erb.erb index e51cb11..179e51f 100644 --- a/template/views/site/errors/500.html.erb.erb +++ b/template/views/site/errors/500.html.erb.erb @@ -1,27 +1,27 @@ -
" %>"> -

<%= "<\%= t.error.unexpected_error.title %\>" %>

-

<%= "<\%= t.error.unexpected_error.subtitle %\>" %>

+
+

<%%= t.error.unexpected_error.title %>

+

<%%= t.error.unexpected_error.subtitle %>

- <%= "<\%= t.error.unexpected_error.text %\>" %> + <%%= t.error.unexpected_error.text %>

- <%= "<\% if config[:environment] == 'development' %\>" %> -

<%= "<\%==" %> + <%% if config[:environment] == 'development' %> +

<%%== "#{@exception.class} - #{@exception.message}" - <%= "%\>" %>

<%= "<\%" %> + %>

<%% if @exception.respond_to? :sql - <%= "%\>" %>

<%= "<\%=" %> + %>

<%%= @exception.sql - <%= "%\>" %>

<%= "<\% end %\>" %><%= "<\%=" %> + %><%% end %><%%= highlighted_backtrace_for @exception - <%= "%\>" %>
- <%= "<\% end %\>" %> + %> + <%% end %>
- " %>"> - <%= "<\%= t.button.back %\>" %> + + <%%= t.button.back %>

diff --git a/template/views/site/layout.html.erb.erb b/template/views/site/layout.html.erb.erb index 9a6964c..ddef473 100644 --- a/template/views/site/layout.html.erb.erb +++ b/template/views/site/layout.html.erb.erb @@ -4,17 +4,17 @@ - <%= "<\%= config[:site][:site_name] %\>" %> + <%%= config[:site][:site_name] %> - <%= "<\%" %> + <%% %w[ main ].each do |name| - <%= "%\>" %> - " %>" /> - <%= "<\% end %\>" %> + %> + " /> + <%% end %> - <%= "<\% if Raven.configuration.environments.include?(config[:environment]) && !request.bot? %\>" %> + <%% if Raven.configuration.environments.include?(config[:environment]) && !request.bot? %> - <%= "<\% end %\>" %> + <%% end %> - <%= "<\%" %> + <%% { lib: %w[], # dom4.max @@ -66,37 +66,37 @@ 'compiled': %w[main] } .each do |dir, file_names| - <%= "%\>" %> - <%= "<\% file_names.each do |name| %\>" %> + %> + <%% file_names.each do |name| %> - <%= "<\% end %\>" %> - <%= "<\% end %\>" %> + <%% end %> + <%% end %>
- " %>"> -

<%= "<\%= config[:site][:site_name] %\>" %>

+
+

<%%= config[:site][:site_name] %>

- <%= "<\%= yield %\>" %> + <%%= yield %>
From a4038b7e0ec3116867e4d2dfada3e248f9180ac0 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Tue, 17 Nov 2020 01:22:17 +0300 Subject: [PATCH 29/62] Add `nginx` config example files --- spec/flame/cli/new/app_spec.rb | 2 +- template/config/nginx/site.example.conf.erb | 77 +++++++++++++++++++ .../config/nginx/snippets/ssl-params.conf | 22 ++++++ .../nginx/snippets/ssl.example.conf.erb | 2 + 4 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 template/config/nginx/site.example.conf.erb create mode 100644 template/config/nginx/snippets/ssl-params.conf create mode 100644 template/config/nginx/snippets/ssl.example.conf.erb diff --git a/spec/flame/cli/new/app_spec.rb b/spec/flame/cli/new/app_spec.rb index dd8114e..028b202 100644 --- a/spec/flame/cli/new/app_spec.rb +++ b/spec/flame/cli/new/app_spec.rb @@ -540,7 +540,7 @@ Dir.chdir app_name - Dir['config/*.example.yaml'].each do |config_example_file_name| + Dir['config/**/*.example.{yaml,conf}'].each do |config_example_file_name| FileUtils.cp config_example_file_name, config_example_file_name.sub('.example', '') end diff --git a/template/config/nginx/site.example.conf.erb b/template/config/nginx/site.example.conf.erb new file mode 100644 index 0000000..1d6bb07 --- /dev/null +++ b/template/config/nginx/site.example.conf.erb @@ -0,0 +1,77 @@ +upstream <%= @app_name %>.ruby { + server unix:/run/<%= @app_name %>/puma.sock; + #server 127.0.0.1:8080; +} + +server { + listen 80; + listen [::]:80 ipv6only=on; + server_name <%= @domain_name %> *.<%= @domain_name %>; + return 301 https://<%= @domain_name %>$request_uri; + + root /home/<%= @app_name %>/<%= @app_name %>/public; + location ~ /.well-known { + allow all; + } +} + +server { + listen 443 ssl http2; + server_name www.<%= @domain_name %>; + + include snippets/ssl-<%= @app_name %>.conf; + include snippets/ssl-params.conf; + + return 301 https://<%= @domain_name %>$request_uri; +} + +server { + listen 443 ssl http2; + listen [::]:443 ipv6only=on ssl http2; + server_name <%= @domain_name %>; + + access_log /var/log/nginx/<%= @app_name %>_access.log; + error_log /var/log/nginx/<%= @app_name %>_error.log; + root /home/<%= @app_name %>/<%= @app_name %>/public; + + # SSL + include snippets/ssl-<%= @app_name %>.conf; + include snippets/ssl-params.conf; + + # GZIP + gzip on; + gzip_disable "msie6"; + gzip_vary on; + gzip_proxied any; + gzip_types text/plain text/css application/json application/x-javascript application/javascript text/xml application/xml application/xml+rss text/javascript; + + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_redirect off; + + large_client_header_buffers 4 16k; + client_max_body_size 15m; + + ## Static assets with 304 responses + location ~* (^/files|\.(css|js|gif|jpe?g|png|svg)$) { + add_header Cache-Control public; + expires 1y; + #add_header Pragma public; + #add_header Cache-Control "public, must-revalidate, proxy-revalidate"; + } + + location / { + try_files $uri $uri/index.html $uri.html @ruby; + } + + location @ruby { + proxy_pass http://<%= @app_name %>.ruby; + } + + ## Reject all unwanted queries + location ~* (\/(\.(git|svn|hg|bzr|idea|vscode|config)\/|wp-(content|admin|config|includes|json)|magento|chamilo|cgi-bin|phpMyAdmin|phpcms|(FCK|u|kind)editor|bitrix|kube-system|phpunit|RDWeb)|(filezilla\.xml|ftpsync\.settings|\.ftpconfig|sftp-config\.json|\.remote-sync\.json|wallet\.dat|\.gitconfig)|\.(php|as[ph]x?|axd|jsp[xa]?|xsl|esp|exp|dll|ini|cgi|sql|env)(|.bak|~)(\/|$)) { + deny all; + } +} diff --git a/template/config/nginx/snippets/ssl-params.conf b/template/config/nginx/snippets/ssl-params.conf new file mode 100644 index 0000000..c924f87 --- /dev/null +++ b/template/config/nginx/snippets/ssl-params.conf @@ -0,0 +1,22 @@ +# from https://cipherli.st/ +# and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html + +ssl_protocols TLSv1 TLSv1.1 TLSv1.2; +ssl_prefer_server_ciphers on; +ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; +ssl_ecdh_curve secp384r1; +ssl_session_cache shared:SSL:10m; +ssl_session_tickets off; +ssl_stapling on; +ssl_stapling_verify on; +resolver 8.8.8.8 8.8.4.4 valid=300s; +resolver_timeout 5s; +# Disable preloading HSTS for now. You can use the commented out header line that includes +# the "preload" directive if you understand the implications. +#add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; +add_header Strict-Transport-Security "max-age=63072000; includeSubdomains"; +# Was DENY, SAMEORIGIN for strange iframes (by extensions?) +add_header X-Frame-Options SAMEORIGIN; +add_header X-Content-Type-Options nosniff; + +# ssl_dhparam /etc/ssl/certs/dhparam.pem; diff --git a/template/config/nginx/snippets/ssl.example.conf.erb b/template/config/nginx/snippets/ssl.example.conf.erb new file mode 100644 index 0000000..dce5584 --- /dev/null +++ b/template/config/nginx/snippets/ssl.example.conf.erb @@ -0,0 +1,2 @@ +ssl_certificate /etc/letsencrypt/live/<%= @domain_name %>/fullchain.pem; +ssl_certificate_key /etc/letsencrypt/live/<%= @domain_name %>/privkey.pem; From 4cb3388483cc2aba650c719527983e7da77fd3d1 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Tue, 17 Nov 2020 01:33:16 +0300 Subject: [PATCH 30/62] Rename `.cirrus.yml` to `.yaml` --- .cirrus.yml => .cirrus.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename .cirrus.yml => .cirrus.yaml (81%) diff --git a/.cirrus.yml b/.cirrus.yaml similarity index 81% rename from .cirrus.yml rename to .cirrus.yaml index 708d05e..149c096 100644 --- a/.cirrus.yml +++ b/.cirrus.yaml @@ -23,7 +23,7 @@ remark_task: lint_script: npm run remark only_if: ($CIRRUS_BRANCH == 'master') || changesInclude( - '.cirrus.yml', '.gitignore', 'package.json', '.remarkrc.yaml', '**.md' + '.cirrus.yaml', '.gitignore', 'package.json', '.remarkrc.yaml', '**.md' ) rubocop_task: @@ -33,7 +33,7 @@ rubocop_task: lint_script: toys rubocop only_if: ($CIRRUS_BRANCH == 'master') || changesInclude( - '.cirrus.yml', '.gitignore', 'Gemfile', 'Rakefile', '.rubocop.yml', '*.gemspec', + '.cirrus.yaml', '.gitignore', 'Gemfile', 'Rakefile', '.rubocop.yml', '*.gemspec', '**.rb', '**.ru' ) @@ -51,6 +51,6 @@ rspec_task: test_script: toys rspec only_if: ($CIRRUS_BRANCH == 'master') || changesInclude( - '.cirrus.yml', '.gitignore', 'Gemfile', 'Rakefile', '.rspec', '*.gemspec', 'lib/**', + '.cirrus.yaml', '.gitignore', 'Gemfile', 'Rakefile', '.rspec', '*.gemspec', 'lib/**', 'spec/**' ) From 40eaf96ee7a318bbd1c029badd767ecfabd8f786 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Tue, 17 Nov 2020 01:34:12 +0300 Subject: [PATCH 31/62] Add `bundle audit` dependency and CI task --- .cirrus.yaml | 10 ++++++++++ flame-cli.gemspec | 2 ++ template/Gemfile | 1 + 3 files changed, 13 insertions(+) diff --git a/.cirrus.yaml b/.cirrus.yaml index 149c096..1155a79 100644 --- a/.cirrus.yaml +++ b/.cirrus.yaml @@ -26,6 +26,16 @@ remark_task: '.cirrus.yaml', '.gitignore', 'package.json', '.remarkrc.yaml', '**.md' ) +bundle-audit_task: + container: + image: ruby + <<: *bundle_cache + bundle-audit_script: bundle audit check --update + only_if: ($CIRRUS_BRANCH == 'master') || + changesInclude( + '.cirrus.yaml', '.gitignore', 'Gemfile', '*.gemspec' + ) + rubocop_task: container: image: ruby:latest diff --git a/flame-cli.gemspec b/flame-cli.gemspec index c7cc2fc..ad7bbb0 100644 --- a/flame-cli.gemspec +++ b/flame-cli.gemspec @@ -32,6 +32,8 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'gem_toys', '~> 0.3.0' spec.add_development_dependency 'toys', '~> 0.11.0' + spec.add_development_dependency 'bundler-audit', '~> 0.7.0' + spec.add_development_dependency 'codecov', '~> 0.2.0' spec.add_development_dependency 'rspec', '~> 3.9' spec.add_development_dependency 'simplecov', '~> 0.18.0' diff --git a/template/Gemfile b/template/Gemfile index d355cc9..e9b4b53 100644 --- a/template/Gemfile +++ b/template/Gemfile @@ -4,6 +4,7 @@ source 'https://rubygems.org' group :system do gem 'bundler' + gem 'bundler-audit' gem 'gorilla_patch' # gem 'httpx' gem 'alt_memery' From cb3422866786b59c1f5d2a86d805dc53d3df44b6 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Tue, 17 Nov 2020 19:59:26 +0300 Subject: [PATCH 32/62] Fix `setup/ruby` script in tests, avoid current Bundler --- spec/flame/cli/new/app_spec.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/flame/cli/new/app_spec.rb b/spec/flame/cli/new/app_spec.rb index 028b202..df45e19 100644 --- a/spec/flame/cli/new/app_spec.rb +++ b/spec/flame/cli/new/app_spec.rb @@ -468,7 +468,9 @@ Dir.chdir app_name - system 'exe/setup/ruby.sh' + Bundler.with_unbundled_env do + system 'exe/setup/ruby.sh' + end end after do From 188f44741b31a12a3903e5f5dcdce0a1e6e3bd61 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Tue, 17 Nov 2020 19:59:55 +0300 Subject: [PATCH 33/62] Add `bundle audit` spec for generated application --- spec/flame/cli/new/app_spec.rb | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/spec/flame/cli/new/app_spec.rb b/spec/flame/cli/new/app_spec.rb index df45e19..817935d 100644 --- a/spec/flame/cli/new/app_spec.rb +++ b/spec/flame/cli/new/app_spec.rb @@ -500,6 +500,28 @@ it { is_expected.to be true } end + describe 'generates Bundler Audit satisfying app' do + subject do + system 'bundle audit check --update' + end + + before do + execute_command + + Dir.chdir app_name + + Bundler.with_unbundled_env do + system 'exe/setup/ruby.sh' + end + end + + after do + Dir.chdir '..' + end + + it { is_expected.to be true } + end + describe 'generates working app' do subject do Bundler.with_unbundled_env do From f4687047d828c0094638de7daebd243ac55a7b79 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Tue, 17 Nov 2020 20:00:17 +0300 Subject: [PATCH 34/62] Fix duplicate `bundler-audit` gem in template --- template/Gemfile | 1 - 1 file changed, 1 deletion(-) diff --git a/template/Gemfile b/template/Gemfile index e9b4b53..d355cc9 100644 --- a/template/Gemfile +++ b/template/Gemfile @@ -4,7 +4,6 @@ source 'https://rubygems.org' group :system do gem 'bundler' - gem 'bundler-audit' gem 'gorilla_patch' # gem 'httpx' gem 'alt_memery' From b951625f3680084275ef1cec95f098d4ab19850d Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Wed, 18 Nov 2020 02:37:37 +0300 Subject: [PATCH 35/62] Add `
` and `
` into `site/layout` --- lib/flame/cli/new/app.rb | 2 + template/assets/styles/_colors.scss | 4 +- .../assets/styles/components/_footer.scss | 1 + template/config/site.example.yaml.erb | 4 +- template/views/site/layout.html.erb.erb | 67 ++++++++++++++----- 5 files changed, 58 insertions(+), 20 deletions(-) diff --git a/lib/flame/cli/new/app.rb b/lib/flame/cli/new/app.rb index 194feb4..2e532d4 100644 --- a/lib/flame/cli/new/app.rb +++ b/lib/flame/cli/new/app.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'date' + module Flame class CLI < Clamp::Command class New < Clamp::Command diff --git a/template/assets/styles/_colors.scss b/template/assets/styles/_colors.scss index 2f61e9d..d8d6d8c 100644 --- a/template/assets/styles/_colors.scss +++ b/template/assets/styles/_colors.scss @@ -1,7 +1,7 @@ -$accent_background_color: white; +$accent_background_color: #ddd; $accent_text_color: darken($accent_background_color, 15%); -$footer_background_color: #2a2a2a; +$footer_background_color: transparent; $footer_text_color: white; // $tertiary_background_color: #ffffff; diff --git a/template/assets/styles/components/_footer.scss b/template/assets/styles/components/_footer.scss index 546c12f..b203cd2 100644 --- a/template/assets/styles/components/_footer.scss +++ b/template/assets/styles/components/_footer.scss @@ -1,3 +1,4 @@ > footer { + text-align: center; background-color: $footer_background_color; } diff --git a/template/config/site.example.yaml.erb b/template/config/site.example.yaml.erb index 67bef65..bc185f9 100644 --- a/template/config/site.example.yaml.erb +++ b/template/config/site.example.yaml.erb @@ -1,3 +1,3 @@ :site_name: '<%= @module_name %>' -:trademark_name: '<%= @module_name %>®' -:organization_name: '<%= @module_name.upcase %>' +# :trademark_name: '<%= @module_name %>®' +# :organization_name: '<%= @module_name.upcase %>' diff --git a/template/views/site/layout.html.erb.erb b/template/views/site/layout.html.erb.erb index ddef473..e5ca28c 100644 --- a/template/views/site/layout.html.erb.erb +++ b/template/views/site/layout.html.erb.erb @@ -76,27 +76,62 @@
- -

<%%= config[:site][:site_name] %>

-
+
- + %> + --> + + <%%= yield %> - <%%= yield %> +
+ + From 710a926eed5fbf2a83c3fb6a4b113c3af9d4ba9f Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Wed, 18 Nov 2020 02:59:16 +0300 Subject: [PATCH 36/62] Add specs for `--domain` option --- .rubocop.yml | 2 + spec/flame/cli/new/app_spec.rb | 551 +++++++++++++++++---------------- 2 files changed, 292 insertions(+), 261 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 804d91e..c0f4925 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -47,6 +47,8 @@ Metrics/BlockLength: - 'spec/**/*' - '*.gemspec' +RSpec/NestedGroups: + Enabled: false RSpec/MultipleMemoizedHelpers: Enabled: false ## Because of we're testing CLI through native calls diff --git a/spec/flame/cli/new/app_spec.rb b/spec/flame/cli/new/app_spec.rb index 817935d..fb35fb2 100644 --- a/spec/flame/cli/new/app_spec.rb +++ b/spec/flame/cli/new/app_spec.rb @@ -5,10 +5,11 @@ describe 'Flame::CLI::New::App' do subject(:execute_command) do - `#{FLAME_CLI} new app #{app_name}` + `#{FLAME_CLI} new app #{options} #{app_name}` end let(:app_name) { 'foo_bar' } + let(:options) { nil } let(:root_dir) { "#{__dir__}/../../../.." } let(:template_dir) { "#{root_dir}/template" } @@ -113,348 +114,376 @@ before { execute_command } - describe '.toys/.toys.rb' do - let(:expected_words) do - [ - 'FB::Application', - 'expand FlameGenerateToys::Template, namespace: FooBar' - ] + describe 'default behavior' do + describe '.toys/.toys.rb' do + let(:expected_words) do + [ + 'FB::Application', + 'expand FlameGenerateToys::Template, namespace: FooBar' + ] + end + + it { is_expected.to match_words(*expected_words) } end - it { is_expected.to match_words(*expected_words) } - end + describe 'application.rb' do + let(:expected_words) do + [ + 'module FooBar' + ] + end - describe 'application.rb' do - let(:expected_words) do - [ - 'module FooBar' - ] + it { is_expected.to match_words(*expected_words) } end - it { is_expected.to match_words(*expected_words) } - end + describe 'config.ru' do + let(:expected_words) do + [ + 'FB::Application.setup', + 'if FB::Application.config[:session]', + 'use Rack::Session::Cookie, FB::Application.config[:session][:cookie]', + 'use Rack::CommonLogger, FB::Application.logger', + 'FB::App = FB::Application', + 'run FB::Application' + ] + end - describe 'config.ru' do - let(:expected_words) do - [ - 'FB::Application.setup', - 'if FB::Application.config[:session]', - 'use Rack::Session::Cookie, FB::Application.config[:session][:cookie]', - 'use Rack::CommonLogger, FB::Application.logger', - 'FB::App = FB::Application', - 'run FB::Application' - ] + it { is_expected.to match_words(*expected_words) } end - it { is_expected.to match_words(*expected_words) } - end + describe 'config/main.rb' do + let(:expected_words) do + [ + 'config = FB::Application.config', + 'FB::Config::Processors.const_get(processor_name).new self' + ] + end - describe 'config/main.rb' do - let(:expected_words) do - [ - 'config = FB::Application.config', - 'FB::Config::Processors.const_get(processor_name).new self' - ] + it { is_expected.to match_words(*expected_words) } end - it { is_expected.to match_words(*expected_words) } - end + describe 'config/puma.rb' do + let(:expected_words) do + [ + 'config = FB::Application.config' + ] + end - describe 'config/puma.rb' do - let(:expected_words) do - [ - 'config = FB::Application.config' - ] + it { is_expected.to match_words(*expected_words) } end - it { is_expected.to match_words(*expected_words) } - end + describe 'config/database.example.yaml' do + let(:expected_words) do + [ + ":database: 'foo_bar'", + ":user: 'foo_bar'" + ] + end - describe 'config/database.example.yaml' do - let(:expected_words) do - [ - ":database: 'foo_bar'", - ":user: 'foo_bar'" - ] + it { is_expected.to match_words(*expected_words) } end - it { is_expected.to match_words(*expected_words) } - end + describe 'config/mail.example.yaml' do + let(:expected_words) do + [ + ":name: 'FooBar.com'", + ":email: 'info@foobar.com'", + ":user_name: 'info@foobar.com'" + ] + end - describe 'config/mail.example.yaml' do - let(:expected_words) do - [ - ":name: 'FooBar.com'", - ":email: 'info@foobar.com'", - ":user_name: 'info@foobar.com'" - ] + it { is_expected.to match_words(*expected_words) } end - it { is_expected.to match_words(*expected_words) } - end + describe 'config/sentry.example.yaml' do + let(:expected_words) do + [ + ':host: sentry.foobar.com' + ] + end - describe 'config/sentry.example.yaml' do - let(:expected_words) do - [ - ':host: sentry.foobar.com' - ] + it { is_expected.to match_words(*expected_words) } end - it { is_expected.to match_words(*expected_words) } - end + describe 'config/server.example.yaml' do + let(:expected_words) do + [ + ":unix: '/run/foo_bar/puma.sock'" + ] + end - describe 'config/server.example.yaml' do - let(:expected_words) do - [ - ":unix: '/run/foo_bar/puma.sock'" - ] + it { is_expected.to match_words(*expected_words) } end - it { is_expected.to match_words(*expected_words) } - end + describe 'config/processors/mail.rb' do + let(:expected_words) do + [ + 'module FooBar' + ] + end - describe 'config/processors/mail.rb' do - let(:expected_words) do - [ - 'module FooBar' - ] + it { is_expected.to match_words(*expected_words) } end - it { is_expected.to match_words(*expected_words) } - end + describe 'config/processors/r18n.rb' do + let(:expected_words) do + [ + 'module FooBar' + ] + end - describe 'config/processors/r18n.rb' do - let(:expected_words) do - [ - 'module FooBar' - ] + it { is_expected.to match_words(*expected_words) } end - it { is_expected.to match_words(*expected_words) } - end + describe 'config/processors/sentry.rb' do + let(:expected_words) do + [ + 'module FooBar', + 'FB::APP_DIRS' + ] + end - describe 'config/processors/sentry.rb' do - let(:expected_words) do - [ - 'module FooBar', - 'FB::APP_DIRS' - ] + it { is_expected.to match_words(*expected_words) } end - it { is_expected.to match_words(*expected_words) } - end + describe 'config/processors/server.rb' do + let(:expected_words) do + [ + 'module FooBar' + ] + end - describe 'config/processors/server.rb' do - let(:expected_words) do - [ - 'module FooBar' - ] + it { is_expected.to match_words(*expected_words) } end - it { is_expected.to match_words(*expected_words) } - end + describe 'config/processors/sequel.rb' do + let(:expected_words) do + [ + 'module FooBar' + ] + end - describe 'config/processors/sequel.rb' do - let(:expected_words) do - [ - 'module FooBar' - ] + it { is_expected.to match_words(*expected_words) } end - it { is_expected.to match_words(*expected_words) } - end + describe 'config/processors/shrine.rb' do + let(:expected_words) do + [ + 'module FooBar' + ] + end - describe 'config/processors/shrine.rb' do - let(:expected_words) do - [ - 'module FooBar' - ] + it { is_expected.to match_words(*expected_words) } end - it { is_expected.to match_words(*expected_words) } - end + describe 'constants.rb' do + let(:expected_words) do + [ + 'module FooBar', + '::FB = self' + ] + end - describe 'constants.rb' do - let(:expected_words) do - [ - 'module FooBar', - '::FB = self' - ] + it { is_expected.to match_words(*expected_words) } end - it { is_expected.to match_words(*expected_words) } - end + describe 'controllers/_controller.rb' do + let(:expected_words) do + [ + 'module FooBar', + 'FB::Application.logger' + ] + end - describe 'controllers/_controller.rb' do - let(:expected_words) do - [ - 'module FooBar', - 'FB::Application.logger' - ] + it { is_expected.to match_words(*expected_words) } end - it { is_expected.to match_words(*expected_words) } - end + describe 'controllers/site/_controller.rb' do + let(:expected_words) do + [ + 'module FooBar', + 'class Controller < FB::Controller' + ] + end - describe 'controllers/site/_controller.rb' do - let(:expected_words) do - [ - 'module FooBar', - 'class Controller < FB::Controller' - ] + it { is_expected.to match_words(*expected_words) } end - it { is_expected.to match_words(*expected_words) } - end + describe 'controllers/site/index_controller.rb' do + let(:expected_words) do + [ + 'module FooBar' + ] + end - describe 'controllers/site/index_controller.rb' do - let(:expected_words) do - [ - 'module FooBar' - ] + it { is_expected.to match_words(*expected_words) } end - it { is_expected.to match_words(*expected_words) } - end + describe 'README.md' do + let(:expected_words) do + [ + '# FooBar', + '`createuser -U postgres foo_bar`', + 'Run `exe/setup.sh`', + 'Add UNIX-user for project: `adduser foo_bar`', + 'Make symbolic link of project directory to `/var/www/foo_bar`' + ] + end - describe 'README.md' do - let(:expected_words) do - [ - '# FooBar', - '`createuser -U postgres foo_bar`', - 'Run `exe/setup.sh`', - 'Add UNIX-user for project: `adduser foo_bar`', - 'Make symbolic link of project directory to `/var/www/foo_bar`' - ] + it { is_expected.to match_words(*expected_words) } end - it { is_expected.to match_words(*expected_words) } - end + describe 'rollup.config.js' do + let(:expected_words) do + [ + "name: 'FB'" + ] + end - describe 'rollup.config.js' do - let(:expected_words) do - [ - "name: 'FB'" - ] + it { is_expected.to match_words(*expected_words) } end - it { is_expected.to match_words(*expected_words) } - end + describe 'forms/_base.rb' do + let(:expected_words) do + [ + 'module FooBar', + 'FB::Application.db_connection' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'mailers/_base.rb' do + let(:expected_words) do + [ + 'module FooBar', + '@from = FB::Application.config[:mail][:from]', + '@controller = FB::MailController.new', + ## https://github.com/rubocop-hq/rubocop/issues/8416 + # rubocop:disable Lint/InterpolationCheck + 'FB::Application.logger.info "#{mail.log_message} [#{index}/#{count}]..."', + 'File.join(FB::Application.config[:tmp_dir], "mailing_#{object_id}")' + # rubocop:enable Lint/InterpolationCheck + ] + end - describe 'forms/_base.rb' do - let(:expected_words) do - [ - 'module FooBar', - 'FB::Application.db_connection' - ] + it { is_expected.to match_words(*expected_words) } end - it { is_expected.to match_words(*expected_words) } - end + describe 'mailers/mail/_base.rb' do + let(:expected_words) do + [ + 'module FooBar', + 'FB::Application.logger.error e' + ] + end - describe 'mailers/_base.rb' do - let(:expected_words) do - [ - 'module FooBar', - '@from = FB::Application.config[:mail][:from]', - '@controller = FB::MailController.new', - ## https://github.com/rubocop-hq/rubocop/issues/8416 - # rubocop:disable Lint/InterpolationCheck - 'FB::Application.logger.info "#{mail.log_message} [#{index}/#{count}]..."', - 'File.join(FB::Application.config[:tmp_dir], "mailing_#{object_id}")' - # rubocop:enable Lint/InterpolationCheck - ] + it { is_expected.to match_words(*expected_words) } end - it { is_expected.to match_words(*expected_words) } - end + describe 'mailers/mail/default.rb' do + let(:expected_words) do + [ + 'module FooBar' + ] + end - describe 'mailers/mail/_base.rb' do - let(:expected_words) do - [ - 'module FooBar', - 'FB::Application.logger.error e' - ] + it { is_expected.to match_words(*expected_words) } end - it { is_expected.to match_words(*expected_words) } - end + describe 'views/site/errors/400.html.erb' do + let(:expected_words) do + [ + '

<%= t.error.bad_request.title %>

', + '

<%= t.error.bad_request.subtitle %>

', + '<%= t.error.bad_request.text %>', + '', + '<%= t.button.back %>' + ] + end - describe 'mailers/mail/default.rb' do - let(:expected_words) do - [ - 'module FooBar' - ] + it { is_expected.to match_words(*expected_words) } end - it { is_expected.to match_words(*expected_words) } - end + describe 'views/site/errors/404.html.erb' do + let(:expected_words) do + [ + '

<%= t.error.page.itself.not_found %>

', + '
', + '<%= t.button.home %>' + ] + end - describe 'views/site/errors/400.html.erb' do - let(:expected_words) do - [ - '

<%= t.error.bad_request.title %>

', - '

<%= t.error.bad_request.subtitle %>

', - '<%= t.error.bad_request.text %>', - '
', - '<%= t.button.back %>' - ] + it { is_expected.to match_words(*expected_words) } + end + + describe 'views/site/errors/500.html.erb' do + let(:expected_words) do + [ + '
', + '

<%= t.error.unexpected_error.title %>

', + '

<%= t.error.unexpected_error.subtitle %>

', + '<%= t.error.unexpected_error.text %>', + "<% if config[:environment] == 'development' %>", + '

<%==', + '%>

<%', + '%>

<%=', + '%>

<% end %><%=', + '%>
', + '<% end %>', + '
', + '<%= t.button.back %>' + ] + end + + it { is_expected.to match_words(*expected_words) } end - it { is_expected.to match_words(*expected_words) } - end + describe 'views/site/layout.html.erb' do + let(:expected_words) do + ## https://github.com/rubocop-hq/rubocop/issues/8416 + # rubocop:disable Lint/InterpolationCheck + [ + '<%= config[:site][:site_name] %>', + '" />', + '<% if Raven.configuration.environments.include?(config[:environment]) &&', + "environment: '<%= config[:environment] %>',", + 'src="<%= url_to "/scripts/#{dir}/#{name}.js", version: true %>"', + '', + '

<%= config[:site][:site_name] %>

' + ] + # rubocop:enable Lint/InterpolationCheck + end - describe 'views/site/errors/404.html.erb' do - let(:expected_words) do - [ - '

<%= t.error.page.itself.not_found %>

', - '
', - '<%= t.button.home %>' - ] + it { is_expected.to match_words(*expected_words) } end - - it { is_expected.to match_words(*expected_words) } end - describe 'views/site/errors/500.html.erb' do - let(:expected_words) do - [ - '
', - '

<%= t.error.unexpected_error.title %>

', - '

<%= t.error.unexpected_error.subtitle %>

', - '<%= t.error.unexpected_error.text %>', - "<% if config[:environment] == 'development' %>", - '

<%==', - '%>

<%', - '%>

<%=', - '%>

<% end %><%=', - '%>
', - '<% end %>', - '
', - '<%= t.button.back %>' - ] - end - - it { is_expected.to match_words(*expected_words) } - end + describe 'with `--domain` option' do + let(:options) { '--domain=foobar.net' } - describe 'views/site/layout.html.erb' do - let(:expected_words) do - ## https://github.com/rubocop-hq/rubocop/issues/8416 - # rubocop:disable Lint/InterpolationCheck - [ - '<%= config[:site][:site_name] %>', - '" />', - '<% if Raven.configuration.environments.include?(config[:environment]) &&', - "environment: '<%= config[:environment] %>',", - 'src="<%= url_to "/scripts/#{dir}/#{name}.js", version: true %>"', - '', - '

<%= config[:site][:site_name] %>

' - ] - # rubocop:enable Lint/InterpolationCheck - end - - it { is_expected.to match_words(*expected_words) } + describe 'config/mail.example.yaml' do + let(:expected_words) do + [ + ":name: 'FooBar.net'", + ":email: 'info@foobar.net'", + ":user_name: 'info@foobar.net'" + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'config/sentry.example.yaml' do + let(:expected_words) do + [ + ':host: sentry.foobar.net' + ] + end + + it { is_expected.to match_words(*expected_words) } + end end end From 388a9418128c5faab89a78c1d979bbd40164955d Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Wed, 18 Nov 2020 03:10:44 +0300 Subject: [PATCH 37/62] Add `--project-name` option for `new app` CLI --- lib/flame/cli/new/app.rb | 3 +- spec/flame/cli/new/app_spec.rb | 302 +++++++++++++++++++++++++++++++++ 2 files changed, 304 insertions(+), 1 deletion(-) diff --git a/lib/flame/cli/new/app.rb b/lib/flame/cli/new/app.rb index 2e532d4..a19ef23 100644 --- a/lib/flame/cli/new/app.rb +++ b/lib/flame/cli/new/app.rb @@ -12,6 +12,7 @@ class App < Clamp::Command parameter 'APP_NAME', 'application name' option ['-d', '--domain'], 'NAME', 'domain name for configuration' + option ['-p', '--project-name'], 'NAME', 'project name for code and configuration' def execute initialize_instance_variables @@ -30,7 +31,7 @@ def execute def initialize_instance_variables @app_name = app_name - @module_name = @app_name.camelize + @module_name = project_name || @app_name.camelize @short_module_name = @module_name.split(/([[:upper:]][[:lower:]]*)/).map! { |s| s[0] }.join diff --git a/spec/flame/cli/new/app_spec.rb b/spec/flame/cli/new/app_spec.rb index fb35fb2..63ab370 100644 --- a/spec/flame/cli/new/app_spec.rb +++ b/spec/flame/cli/new/app_spec.rb @@ -485,6 +485,308 @@ it { is_expected.to match_words(*expected_words) } end end + + describe 'with `--project-name` option' do + let(:app_name) { 'foobar' } + let(:options) { '--project-name=FooBar' } + + describe '.toys/.toys.rb' do + let(:expected_words) do + [ + 'FB::Application', + 'expand FlameGenerateToys::Template, namespace: FooBar' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'application.rb' do + let(:expected_words) do + [ + 'module FooBar' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'config.ru' do + let(:expected_words) do + [ + 'FB::Application.setup', + 'if FB::Application.config[:session]', + 'use Rack::Session::Cookie, FB::Application.config[:session][:cookie]', + 'use Rack::CommonLogger, FB::Application.logger', + 'FB::App = FB::Application', + 'run FB::Application' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'config/main.rb' do + let(:expected_words) do + [ + 'config = FB::Application.config', + 'FB::Config::Processors.const_get(processor_name).new self' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'config/puma.rb' do + let(:expected_words) do + [ + 'config = FB::Application.config' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'config/database.example.yaml' do + let(:expected_words) do + [ + ":database: 'foobar'", + ":user: 'foobar'" + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'config/mail.example.yaml' do + let(:expected_words) do + [ + ":name: 'FooBar.com'", + ":email: 'info@foobar.com'", + ":user_name: 'info@foobar.com'" + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'config/sentry.example.yaml' do + let(:expected_words) do + [ + ':host: sentry.foobar.com' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'config/server.example.yaml' do + let(:expected_words) do + [ + ":unix: '/run/foobar/puma.sock'" + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'config/processors/mail.rb' do + let(:expected_words) do + [ + 'module FooBar' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'config/processors/r18n.rb' do + let(:expected_words) do + [ + 'module FooBar' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'config/processors/sentry.rb' do + let(:expected_words) do + [ + 'module FooBar', + 'FB::APP_DIRS' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'config/processors/server.rb' do + let(:expected_words) do + [ + 'module FooBar' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'config/processors/sequel.rb' do + let(:expected_words) do + [ + 'module FooBar' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'config/processors/shrine.rb' do + let(:expected_words) do + [ + 'module FooBar' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'constants.rb' do + let(:expected_words) do + [ + 'module FooBar', + '::FB = self' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'controllers/_controller.rb' do + let(:expected_words) do + [ + 'module FooBar', + 'FB::Application.logger' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'controllers/site/_controller.rb' do + let(:expected_words) do + [ + 'module FooBar', + 'class Controller < FB::Controller' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'controllers/site/index_controller.rb' do + let(:expected_words) do + [ + 'module FooBar' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'README.md' do + let(:expected_words) do + [ + '# FooBar', + '`createuser -U postgres foobar`', + 'Run `exe/setup.sh`', + 'Add UNIX-user for project: `adduser foobar`', + 'Make symbolic link of project directory to `/var/www/foobar`' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'rollup.config.js' do + let(:expected_words) do + [ + "name: 'FB'" + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'forms/_base.rb' do + let(:expected_words) do + [ + 'module FooBar', + 'FB::Application.db_connection' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'mailers/_base.rb' do + let(:expected_words) do + [ + 'module FooBar', + '@from = FB::Application.config[:mail][:from]', + '@controller = FB::MailController.new', + ## https://github.com/rubocop-hq/rubocop/issues/8416 + # rubocop:disable Lint/InterpolationCheck + 'FB::Application.logger.info "#{mail.log_message} [#{index}/#{count}]..."', + 'File.join(FB::Application.config[:tmp_dir], "mailing_#{object_id}")' + # rubocop:enable Lint/InterpolationCheck + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'mailers/mail/_base.rb' do + let(:expected_words) do + [ + 'module FooBar', + 'FB::Application.logger.error e' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'mailers/mail/default.rb' do + let(:expected_words) do + [ + 'module FooBar' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'views/site/errors/404.html.erb' do + let(:expected_words) do + [ + '
' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + + describe 'views/site/layout.html.erb' do + let(:expected_words) do + [ + '' + ] + end + + it { is_expected.to match_words(*expected_words) } + end + end end describe 'generates RuboCop-satisfying app' do From 9013fe4118e6f4fe8d6c8c3be1531897f91dfd34 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Thu, 19 Nov 2020 00:52:11 +0300 Subject: [PATCH 38/62] Simplify `new app generation` specs I wanted to speed up them by `before(:all)`, but there is the `RSpec/BeforeAfterAll` cop. At least, the code became simpler. --- spec/flame/cli/new/app_spec.rb | 188 +++++++++++++++------------------ 1 file changed, 86 insertions(+), 102 deletions(-) diff --git a/spec/flame/cli/new/app_spec.rb b/spec/flame/cli/new/app_spec.rb index 63ab370..20b0efd 100644 --- a/spec/flame/cli/new/app_spec.rb +++ b/spec/flame/cli/new/app_spec.rb @@ -789,18 +789,12 @@ end end - describe 'generates RuboCop-satisfying app' do - subject do - system 'bundle exec rubocop' - end - + describe 'generation' do before do - execute_command - - Dir.chdir app_name - Bundler.with_unbundled_env do - system 'exe/setup/ruby.sh' + execute_command + + Dir.chdir app_name end end @@ -808,127 +802,117 @@ Dir.chdir '..' end - it { is_expected.to be true } - end - - describe 'generates assets linting satisfying app' do - subject do - system 'pnpm lint' - end - - before do - execute_command + describe 'RuboCop-satisfying app' do + subject do + system 'bundle exec rubocop' + end - Dir.chdir app_name + before do + Bundler.with_unbundled_env do + system 'exe/setup/ruby.sh' + end + end - system 'exe/setup/node.sh' + it { is_expected.to be true } end - after do - Dir.chdir '..' - end + describe 'assets linting satisfying app' do + subject do + system 'pnpm lint' + end - it { is_expected.to be true } - end + before do + Bundler.with_unbundled_env do + system 'exe/setup/node.sh' + end + end - describe 'generates Bundler Audit satisfying app' do - subject do - system 'bundle audit check --update' + it { is_expected.to be true } end - before do - execute_command - - Dir.chdir app_name + describe 'Bundler Audit satisfying app' do + subject do + system 'bundle audit check --update' + end - Bundler.with_unbundled_env do - system 'exe/setup/ruby.sh' + before do + Bundler.with_unbundled_env do + system 'exe/setup/ruby.sh' + end end - end - after do - Dir.chdir '..' + it { is_expected.to be true } end - it { is_expected.to be true } - end + describe 'working app' do + subject do + Bundler.with_unbundled_env do + pid = spawn 'toys server start' - describe 'generates working app' do - subject do - Bundler.with_unbundled_env do - pid = spawn 'toys server start' + Process.detach pid - Process.detach pid + sleep 0.1 - sleep 0.1 + number_of_attempts = 0 - number_of_attempts = 0 + begin + number_of_attempts += 1 + ## https://github.com/gruntjs/grunt-contrib-connect/issues/25#issuecomment-16293494 + response = Net::HTTP.get URI("http://127.0.0.1:#{port}/") + rescue Errno::ECONNREFUSED => e + sleep 1 + retry if number_of_attempts < 20 + raise e + end - begin - number_of_attempts += 1 - ## https://github.com/gruntjs/grunt-contrib-connect/issues/25#issuecomment-16293494 - response = Net::HTTP.get URI("http://127.0.0.1:#{port}/") - rescue Errno::ECONNREFUSED => e - sleep 1 - retry if number_of_attempts < 20 - raise e + response + ensure + Bundler.with_unbundled_env { `toys server stop` } + Process.wait pid end - - response - ensure - Bundler.with_unbundled_env { `toys server stop` } - Process.wait pid end - end - before do - Bundler.with_unbundled_env do - execute_command + before do + Bundler.with_unbundled_env do + ## HACK: https://github.com/dazuma/toys/issues/57 + toys_command = 'truncate_load_path!' + temp_app_toys_file_path = "#{temp_app_dir}/.toys/.toys.rb" + File.write( + temp_app_toys_file_path, + File.read(temp_app_toys_file_path).sub("# #{toys_command}", toys_command) + ) - ## HACK: https://github.com/dazuma/toys/issues/57 - toys_command = 'truncate_load_path!' - temp_app_toys_file_path = "#{temp_app_dir}/.toys/.toys.rb" - File.write( - temp_app_toys_file_path, - File.read(temp_app_toys_file_path).sub("# #{toys_command}", toys_command) - ) + Dir['config/**/*.example.{yaml,conf}'].each do |config_example_file_name| + FileUtils.cp config_example_file_name, config_example_file_name.sub('.example', '') + end - Dir.chdir app_name + ## HACK for testing while some server is running + File.write( + 'config/server.yaml', + File.read('config/server.yaml').sub('port: 3000', "port: #{port}") + ) - Dir['config/**/*.example.{yaml,conf}'].each do |config_example_file_name| - FileUtils.cp config_example_file_name, config_example_file_name.sub('.example', '') + system 'exe/setup.sh' end - - ## HACK for testing while some server is running - File.write( - 'config/server.yaml', - File.read('config/server.yaml').sub('port: 3000', "port: #{port}") - ) - - system 'exe/setup.sh' end - end - after do - Dir.chdir '..' - end + let(:port) do + ## https://stackoverflow.com/a/5985984/2630849 + socket = Socket.new(:INET, :STREAM, 0) + socket.bind Addrinfo.tcp('127.0.0.1', 0) + result = socket.local_address.ip_port + socket.close + result + end - let(:port) do - ## https://stackoverflow.com/a/5985984/2630849 - socket = Socket.new(:INET, :STREAM, 0) - socket.bind Addrinfo.tcp('127.0.0.1', 0) - result = socket.local_address.ip_port - socket.close - result - end + let(:expected_response_lines) do + [ + 'FooBar', + '

Hello, world!

' + ] + end - let(:expected_response_lines) do - [ - 'FooBar', - '

Hello, world!

' - ] + it { is_expected.to include(*expected_response_lines) } end - - it { is_expected.to include(*expected_response_lines) } end end From 11800da4db060bf8a9d23d2f8641cdc3db0d595b Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Thu, 19 Nov 2020 01:07:44 +0300 Subject: [PATCH 39/62] Lock template to the latest Ruby version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Failed CI: https://cirrus-ci.com/task/5271098151403520?command=test#L2 I don't want to setup `rbenv` and `nodenv`, so… I've chose an easier way. --- .cirrus.yaml | 4 +--- template/.ruby-version | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) create mode 100644 template/.ruby-version diff --git a/.cirrus.yaml b/.cirrus.yaml index 1155a79..420a452 100644 --- a/.cirrus.yaml +++ b/.cirrus.yaml @@ -52,9 +52,7 @@ rspec_task: - remark - rubocop container: - matrix: - image: ruby:2.6 - image: ruby:2.7 + image: ruby:2.7 <<: *bundle_cache environment: CODECOV_TOKEN: ENCRYPTED[5bfb9cd3d53a72c8423ce1b84b18cf7d30fbdcf3a56a25f9cd79dbdff1c3552542578272db8088ec27d7de7f2c363d72] diff --git a/template/.ruby-version b/template/.ruby-version new file mode 100644 index 0000000..37c2961 --- /dev/null +++ b/template/.ruby-version @@ -0,0 +1 @@ +2.7.2 From 6f3e9bfeb7ea2f98afc619e6ea4418b955b5ef74 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Thu, 19 Nov 2020 01:10:18 +0300 Subject: [PATCH 40/62] Improve required Ruby version lock, avoid 3.0 --- flame-cli.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flame-cli.gemspec b/flame-cli.gemspec index ad7bbb0..d8b7145 100644 --- a/flame-cli.gemspec +++ b/flame-cli.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |spec| 'wiki_uri' => 'https://github.com/AlexWayfer/flame-cli/wiki' } - spec.required_ruby_version = '>= 2.6' + spec.required_ruby_version = '~> 2.6' spec.add_runtime_dependency 'clamp', '~> 1.3' spec.add_runtime_dependency 'gorilla_patch', '~> 4.0' From c767527835ce90126c761da61e81aafdc10e18cc Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Thu, 19 Nov 2020 01:10:47 +0300 Subject: [PATCH 41/62] Update required Ruby version to 2.7 as template requires --- .rubocop.yml | 2 +- flame-cli.gemspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index c0f4925..d652f07 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -39,7 +39,7 @@ Style/ParenthesesAroundCondition: AllowInMultilineConditions: true AllCops: - TargetRubyVersion: 2.6 + TargetRubyVersion: 2.7 NewCops: enable Metrics/BlockLength: diff --git a/flame-cli.gemspec b/flame-cli.gemspec index d8b7145..9bba0f7 100644 --- a/flame-cli.gemspec +++ b/flame-cli.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |spec| 'wiki_uri' => 'https://github.com/AlexWayfer/flame-cli/wiki' } - spec.required_ruby_version = '~> 2.6' + spec.required_ruby_version = '~> 2.7' spec.add_runtime_dependency 'clamp', '~> 1.3' spec.add_runtime_dependency 'gorilla_patch', '~> 4.0' From 0ec6566b8bb36e13f3a6b4af9b6cf70c72132dad Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Thu, 19 Nov 2020 01:13:18 +0300 Subject: [PATCH 42/62] Drop `rspec` and `rubocop` toys, just use `bundle exec` --- .cirrus.yaml | 4 ++-- .toys.rb | 7 ------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.cirrus.yaml b/.cirrus.yaml index 420a452..e876144 100644 --- a/.cirrus.yaml +++ b/.cirrus.yaml @@ -40,7 +40,7 @@ rubocop_task: container: image: ruby:latest <<: *bundle_cache - lint_script: toys rubocop + lint_script: bundle exec rubocop only_if: ($CIRRUS_BRANCH == 'master') || changesInclude( '.cirrus.yaml', '.gitignore', 'Gemfile', 'Rakefile', '.rubocop.yml', '*.gemspec', @@ -56,7 +56,7 @@ rspec_task: <<: *bundle_cache environment: CODECOV_TOKEN: ENCRYPTED[5bfb9cd3d53a72c8423ce1b84b18cf7d30fbdcf3a56a25f9cd79dbdff1c3552542578272db8088ec27d7de7f2c363d72] - test_script: toys rspec + test_script: bundle exec rspec only_if: ($CIRRUS_BRANCH == 'master') || changesInclude( '.cirrus.yaml', '.gitignore', 'Gemfile', 'Rakefile', '.rspec', '*.gemspec', 'lib/**', diff --git a/.toys.rb b/.toys.rb index 2167dee..7c5c925 100644 --- a/.toys.rb +++ b/.toys.rb @@ -6,10 +6,3 @@ expand GemToys::Template alias_tool :g, :gem - -expand :rspec - -alias_tool :rspec, :spec -alias_tool :test, :spec - -expand :rubocop From d5f9bf3528a38ca7692e5e16cd3df5f9d349e4cd Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Thu, 19 Nov 2020 01:23:08 +0300 Subject: [PATCH 43/62] Use default `ruby` image for `rubocop` CI task --- .cirrus.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cirrus.yaml b/.cirrus.yaml index e876144..07f27bd 100644 --- a/.cirrus.yaml +++ b/.cirrus.yaml @@ -38,7 +38,7 @@ bundle-audit_task: rubocop_task: container: - image: ruby:latest + image: ruby <<: *bundle_cache lint_script: bundle exec rubocop only_if: ($CIRRUS_BRANCH == 'master') || From de80a0e1fd192848a9aee48652fa4ed916335609 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Thu, 19 Nov 2020 01:23:46 +0300 Subject: [PATCH 44/62] Move glob files patterns in `rspec` CI task to a separate line --- .cirrus.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.cirrus.yaml b/.cirrus.yaml index 07f27bd..a8ed4f1 100644 --- a/.cirrus.yaml +++ b/.cirrus.yaml @@ -59,6 +59,6 @@ rspec_task: test_script: bundle exec rspec only_if: ($CIRRUS_BRANCH == 'master') || changesInclude( - '.cirrus.yaml', '.gitignore', 'Gemfile', 'Rakefile', '.rspec', '*.gemspec', 'lib/**', - 'spec/**' + '.cirrus.yaml', '.gitignore', 'Gemfile', 'Rakefile', '.rspec', + '*.gemspec', 'lib/**', 'spec/**' ) From d7b80337fbb4dc13cc8f1ea3dd10da1a25a8dea9 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Thu, 19 Nov 2020 01:24:06 +0300 Subject: [PATCH 45/62] Setup `rbenv` and `nodenv` for `rspec` CI task --- .cirrus.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.cirrus.yaml b/.cirrus.yaml index a8ed4f1..b7af956 100644 --- a/.cirrus.yaml +++ b/.cirrus.yaml @@ -52,7 +52,11 @@ rspec_task: - remark - rubocop container: - image: ruby:2.7 + image: alpine + rbenv_setup_script: + - curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-installer | bash + nodenv_setup_script: + - curl -fsSL https://raw.githubusercontent.com/nodenv/nodenv-installer/master/bin/nodenv-installer | bash <<: *bundle_cache environment: CODECOV_TOKEN: ENCRYPTED[5bfb9cd3d53a72c8423ce1b84b18cf7d30fbdcf3a56a25f9cd79dbdff1c3552542578272db8088ec27d7de7f2c363d72] From d21dd8918752348d1dbac979b534041817eb12da Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Thu, 19 Nov 2020 01:25:12 +0300 Subject: [PATCH 46/62] Return Ruby 2.6 support for the gem itself --- .rubocop.yml | 2 +- flame-cli.gemspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index d652f07..c0f4925 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -39,7 +39,7 @@ Style/ParenthesesAroundCondition: AllowInMultilineConditions: true AllCops: - TargetRubyVersion: 2.7 + TargetRubyVersion: 2.6 NewCops: enable Metrics/BlockLength: diff --git a/flame-cli.gemspec b/flame-cli.gemspec index 9bba0f7..d8b7145 100644 --- a/flame-cli.gemspec +++ b/flame-cli.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |spec| 'wiki_uri' => 'https://github.com/AlexWayfer/flame-cli/wiki' } - spec.required_ruby_version = '~> 2.7' + spec.required_ruby_version = '~> 2.6' spec.add_runtime_dependency 'clamp', '~> 1.3' spec.add_runtime_dependency 'gorilla_patch', '~> 4.0' From 5d082c73be93d9f7cf69601d3d38ef8523687d45 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Thu, 19 Nov 2020 01:25:36 +0300 Subject: [PATCH 47/62] Delete `.ruby-version` file inside template, now there is a `rbenv` setup --- template/.ruby-version | 1 - 1 file changed, 1 deletion(-) delete mode 100644 template/.ruby-version diff --git a/template/.ruby-version b/template/.ruby-version deleted file mode 100644 index 37c2961..0000000 --- a/template/.ruby-version +++ /dev/null @@ -1 +0,0 @@ -2.7.2 From 983f5ebc388aa854915a241ea5dc9f44c6eb0775 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Thu, 19 Nov 2020 01:38:54 +0300 Subject: [PATCH 48/62] Change Linux distribution from `alpine` to `fedora` for `rspec` CI task Try to avoid these fails: https://cirrus-ci.com/task/5553213984210944 and make environment closer to real-life. --- .cirrus.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cirrus.yaml b/.cirrus.yaml index b7af956..2c5f9b7 100644 --- a/.cirrus.yaml +++ b/.cirrus.yaml @@ -52,7 +52,7 @@ rspec_task: - remark - rubocop container: - image: alpine + image: fedora rbenv_setup_script: - curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-installer | bash nodenv_setup_script: From 838f4f38a2ea117053c61fd980a1765cb5188aa2 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Thu, 19 Nov 2020 01:41:52 +0300 Subject: [PATCH 49/62] Install `git` and `which` required for `rbenv` and `nodenv` --- .cirrus.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.cirrus.yaml b/.cirrus.yaml index 2c5f9b7..455814d 100644 --- a/.cirrus.yaml +++ b/.cirrus.yaml @@ -53,9 +53,11 @@ rspec_task: - rubocop container: image: fedora - rbenv_setup_script: + os_setup_script: + ## https://cirrus-ci.com/task/6357513148825600 + ## https://cirrus-ci.com/task/5062843240284160?command=os_setup#L538 + - dnf update -y && dnf install curl git which -y - curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-installer | bash - nodenv_setup_script: - curl -fsSL https://raw.githubusercontent.com/nodenv/nodenv-installer/master/bin/nodenv-installer | bash <<: *bundle_cache environment: From 854884b605d0013b110b265e3d7cd1fe67901f49 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Thu, 19 Nov 2020 01:58:01 +0300 Subject: [PATCH 50/62] Improve OS setup script for CI --- .cirrus.yaml | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/.cirrus.yaml b/.cirrus.yaml index 455814d..f36bd48 100644 --- a/.cirrus.yaml +++ b/.cirrus.yaml @@ -56,9 +56,29 @@ rspec_task: os_setup_script: ## https://cirrus-ci.com/task/6357513148825600 ## https://cirrus-ci.com/task/5062843240284160?command=os_setup#L538 - - dnf update -y && dnf install curl git which -y - - curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-installer | bash - - curl -fsSL https://raw.githubusercontent.com/nodenv/nodenv-installer/master/bin/nodenv-installer | bash + ## https://cirrus-ci.com/task/6270527041961984?command=os_setup#L535 + - dnf update -y && dnf install git which make gcc -y + + ## rbenv + - git clone https://github.com/rbenv/rbenv.git ~/.rbenv + ## https://cirrus-ci.com/task/6270527041961984?command=os_setup#L543 + - echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc + ## https://cirrus-ci.com/task/5656294977699840?command=os_setup#L633 + - echo 'eval "$(rbenv init -)"' >> ~/.bashrc + + ## nodenv + - git clone https://github.com/nodenv/nodenv.git ~/.nodenv + - echo 'export PATH="$HOME/.nodenv/bin:$PATH"' >> ~/.bashrc + - echo 'eval "$(nodenv init -)"' >> ~/.bashrc + + - source ~/.bashrc + + ## https://github.com/rbenv/ruby-build#installation + - mkdir -p "$(rbenv root)"/plugins + - git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-build + ## https://github.com/nodenv/node-build#installation + - mkdir -p "$(nodenv root)"/plugins + - git clone https://github.com/nodenv/node-build.git "$(nodenv root)"/plugins/node-build <<: *bundle_cache environment: CODECOV_TOKEN: ENCRYPTED[5bfb9cd3d53a72c8423ce1b84b18cf7d30fbdcf3a56a25f9cd79dbdff1c3552542578272db8088ec27d7de7f2c363d72] From cd43f1179a6919a24794ff4353670dbdae0bbc75 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Thu, 19 Nov 2020 03:25:04 +0300 Subject: [PATCH 51/62] Change Docker image from `fedora` to `ruby` for `rspec` CI task --- .cirrus.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.cirrus.yaml b/.cirrus.yaml index f36bd48..7f600e2 100644 --- a/.cirrus.yaml +++ b/.cirrus.yaml @@ -52,12 +52,12 @@ rspec_task: - remark - rubocop container: - image: fedora + ## https://cirrus-ci.com/task/5233441622982656?command=bundle#L3 + image: ruby os_setup_script: ## https://cirrus-ci.com/task/6357513148825600 - ## https://cirrus-ci.com/task/5062843240284160?command=os_setup#L538 ## https://cirrus-ci.com/task/6270527041961984?command=os_setup#L535 - - dnf update -y && dnf install git which make gcc -y + - apt update && apt upgrade -y && apt install git make gcc -y ## rbenv - git clone https://github.com/rbenv/rbenv.git ~/.rbenv From c9eaf59c2fdd2a7f8230853d0e7226f25cf7b869 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Thu, 19 Nov 2020 03:28:57 +0300 Subject: [PATCH 52/62] Fix `rbenv` and `nodenv` in `rspec` CI task --- .cirrus.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.cirrus.yaml b/.cirrus.yaml index 7f600e2..b8f1af7 100644 --- a/.cirrus.yaml +++ b/.cirrus.yaml @@ -82,7 +82,10 @@ rspec_task: <<: *bundle_cache environment: CODECOV_TOKEN: ENCRYPTED[5bfb9cd3d53a72c8423ce1b84b18cf7d30fbdcf3a56a25f9cd79dbdff1c3552542578272db8088ec27d7de7f2c363d72] - test_script: bundle exec rspec + test_script: + - cat ~/.bashrc + - source ~/.bashrc + - bundle exec rspec only_if: ($CIRRUS_BRANCH == 'master') || changesInclude( '.cirrus.yaml', '.gitignore', 'Gemfile', 'Rakefile', '.rspec', From d29f8b8bb4b7dd6667dfad9fd6f9a5aa7fdd32a4 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Thu, 19 Nov 2020 03:45:54 +0300 Subject: [PATCH 53/62] Don't establish database connection without models Useful for CI: https://cirrus-ci.com/task/4975995981660160 --- flame-cli.gemspec | 1 + spec/flame/cli/new/app_spec.rb | 6 +++++- template/application.rb.erb | 3 ++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/flame-cli.gemspec b/flame-cli.gemspec index d8b7145..3c13610 100644 --- a/flame-cli.gemspec +++ b/flame-cli.gemspec @@ -38,6 +38,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rspec', '~> 3.9' spec.add_development_dependency 'simplecov', '~> 0.18.0' + spec.add_development_dependency 'example_file', '~> 0.2.0' spec.add_development_dependency 'rubocop', '~> 0.90.0' spec.add_development_dependency 'rubocop-performance', '~> 1.0' spec.add_development_dependency 'rubocop-rspec', '~> 1.0' diff --git a/spec/flame/cli/new/app_spec.rb b/spec/flame/cli/new/app_spec.rb index 20b0efd..4138d75 100644 --- a/spec/flame/cli/new/app_spec.rb +++ b/spec/flame/cli/new/app_spec.rb @@ -3,6 +3,8 @@ require 'pathname' require 'net/http' +require 'example_file' + describe 'Flame::CLI::New::App' do subject(:execute_command) do `#{FLAME_CLI} new app #{options} #{app_name}` @@ -883,7 +885,9 @@ ) Dir['config/**/*.example.{yaml,conf}'].each do |config_example_file_name| - FileUtils.cp config_example_file_name, config_example_file_name.sub('.example', '') + FileUtils.cp( + config_example_file_name, config_example_file_name.sub(ExampleFile::SUFFIX, '') + ) end ## HACK for testing while some server is running diff --git a/template/application.rb.erb b/template/application.rb.erb index 933d0ce..92528cd 100644 --- a/template/application.rb.erb +++ b/template/application.rb.erb @@ -61,7 +61,8 @@ module <%= @module_name %> def setup ## Initialize Sequel connection before models requiring - db_connection + ## https://cirrus-ci.com/task/4975995981660160 + db_connection if Dir["#{__dir__}/models/**/*"].any? ## Require dirs require_dirs APP_DIRS, ignore: [%r{config/puma.rb}, %r{lib/\w+/spec/}] From ab39451644ff92f14b6f461d46f1893208dd0858 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Thu, 19 Nov 2020 18:33:09 +0300 Subject: [PATCH 54/62] Comment `import 'core-js/'` code, it's not always necessary --- template/assets/scripts/main.js | 20 +++++++++++--------- template/package.json | 1 - 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/template/assets/scripts/main.js b/template/assets/scripts/main.js index 971dff1..e7e5ff5 100644 --- a/template/assets/scripts/main.js +++ b/template/assets/scripts/main.js @@ -1,12 +1,14 @@ -import 'core-js/stable/string/includes' - -// https://github.com/zloirock/core-js/issues/618#issuecomment-522618151 -// https://github.com/zloirock/core-js/issues/619#issuecomment-522624271 -import 'core-js/stable/symbol/iterator' - -import 'core-js/stable/array/from' -import 'core-js/stable/object/entries' -import 'core-js/stable/dom-collections/for-each' +//// Uncomment for IE11 and other old browsers compatibility, +//// also don't forget to `core-js` dependency into `package.json` +// import 'core-js/stable/string/includes' +// +// //// https://github.com/zloirock/core-js/issues/618#issuecomment-522618151 +// //// https://github.com/zloirock/core-js/issues/619#issuecomment-522624271 +// import 'core-js/stable/symbol/iterator' +// +// import 'core-js/stable/array/from' +// import 'core-js/stable/object/entries' +// import 'core-js/stable/dom-collections/for-each' document.addEventListener('DOMContentLoaded', () => { // Prevent double form submission diff --git a/template/package.json b/template/package.json index 5e63208..3684272 100644 --- a/template/package.json +++ b/template/package.json @@ -9,7 +9,6 @@ "@rollup/plugin-json": "*", "@rollup/plugin-node-resolve": "*", "autoprefixer": "*", - "core-js": "*", "postcss-cli": "*", "postcss-flexbugs-fixes": "*", "promise-polyfill": "*", From caf37db90a7c07f03afa2967763437ff2761e07f Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Thu, 19 Nov 2020 18:43:21 +0300 Subject: [PATCH 55/62] Cache `rbenv` and `nodenv` in CI --- .cirrus.yaml | 52 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/.cirrus.yaml b/.cirrus.yaml index b8f1af7..1ec3330 100644 --- a/.cirrus.yaml +++ b/.cirrus.yaml @@ -48,44 +48,62 @@ rubocop_task: ) rspec_task: + depends_on: - remark - rubocop + container: ## https://cirrus-ci.com/task/5233441622982656?command=bundle#L3 image: ruby + os_setup_script: ## https://cirrus-ci.com/task/6357513148825600 ## https://cirrus-ci.com/task/6270527041961984?command=os_setup#L535 - apt update && apt upgrade -y && apt install git make gcc -y - ## rbenv - - git clone https://github.com/rbenv/rbenv.git ~/.rbenv - ## https://cirrus-ci.com/task/6270527041961984?command=os_setup#L543 - - echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc - ## https://cirrus-ci.com/task/5656294977699840?command=os_setup#L633 - - echo 'eval "$(rbenv init -)"' >> ~/.bashrc + rbenv_cache: + folder: $HOME/.rbenv + populate_script: + - git clone https://github.com/rbenv/rbenv.git ~/.rbenv - ## nodenv - - git clone https://github.com/nodenv/nodenv.git ~/.nodenv - - echo 'export PATH="$HOME/.nodenv/bin:$PATH"' >> ~/.bashrc - - echo 'eval "$(nodenv init -)"' >> ~/.bashrc + ## https://cirrus-ci.com/task/6270527041961984?command=os_setup#L543 + - echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc + ## https://cirrus-ci.com/task/5656294977699840?command=os_setup#L633 + - echo 'eval "$(rbenv init -)"' >> ~/.bashrc - - source ~/.bashrc + - source ~/.bashrc + + ## https://github.com/rbenv/ruby-build#installation + - mkdir -p "$(rbenv root)"/plugins + - git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-build + + nodenv_cache: + folder: $HOME/.nodenv + populate_script: + - git clone https://github.com/nodenv/nodenv.git ~/.nodenv + + ## https://cirrus-ci.com/task/6270527041961984?command=os_setup#L543 + - echo 'export PATH="$HOME/.nodenv/bin:$PATH"' >> ~/.bashrc + ## https://cirrus-ci.com/task/5656294977699840?command=os_setup#L633 + - echo 'eval "$(nodenv init -)"' >> ~/.bashrc + + - source ~/.bashrc + + ## https://github.com/nodenv/node-build#installation + - mkdir -p "$(nodenv root)"/plugins + - git clone https://github.com/nodenv/node-build.git "$(nodenv root)"/plugins/node-build - ## https://github.com/rbenv/ruby-build#installation - - mkdir -p "$(rbenv root)"/plugins - - git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-build - ## https://github.com/nodenv/node-build#installation - - mkdir -p "$(nodenv root)"/plugins - - git clone https://github.com/nodenv/node-build.git "$(nodenv root)"/plugins/node-build <<: *bundle_cache + environment: CODECOV_TOKEN: ENCRYPTED[5bfb9cd3d53a72c8423ce1b84b18cf7d30fbdcf3a56a25f9cd79dbdff1c3552542578272db8088ec27d7de7f2c363d72] + test_script: - cat ~/.bashrc - source ~/.bashrc - bundle exec rspec + only_if: ($CIRRUS_BRANCH == 'master') || changesInclude( '.cirrus.yaml', '.gitignore', 'Gemfile', 'Rakefile', '.rspec', From b583509722943f511da55b81194c7777610e855b Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Thu, 19 Nov 2020 19:02:00 +0300 Subject: [PATCH 56/62] Make `rbenv` and `nodenv` cache uploading always --- .cirrus.yaml | 63 ++++++++++++++++++++++++++-------------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/.cirrus.yaml b/.cirrus.yaml index 1ec3330..53ae3de 100644 --- a/.cirrus.yaml +++ b/.cirrus.yaml @@ -62,37 +62,38 @@ rspec_task: ## https://cirrus-ci.com/task/6270527041961984?command=os_setup#L535 - apt update && apt upgrade -y && apt install git make gcc -y - rbenv_cache: - folder: $HOME/.rbenv - populate_script: - - git clone https://github.com/rbenv/rbenv.git ~/.rbenv - - ## https://cirrus-ci.com/task/6270527041961984?command=os_setup#L543 - - echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc - ## https://cirrus-ci.com/task/5656294977699840?command=os_setup#L633 - - echo 'eval "$(rbenv init -)"' >> ~/.bashrc - - - source ~/.bashrc - - ## https://github.com/rbenv/ruby-build#installation - - mkdir -p "$(rbenv root)"/plugins - - git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-build - - nodenv_cache: - folder: $HOME/.nodenv - populate_script: - - git clone https://github.com/nodenv/nodenv.git ~/.nodenv - - ## https://cirrus-ci.com/task/6270527041961984?command=os_setup#L543 - - echo 'export PATH="$HOME/.nodenv/bin:$PATH"' >> ~/.bashrc - ## https://cirrus-ci.com/task/5656294977699840?command=os_setup#L633 - - echo 'eval "$(nodenv init -)"' >> ~/.bashrc - - - source ~/.bashrc - - ## https://github.com/nodenv/node-build#installation - - mkdir -p "$(nodenv root)"/plugins - - git clone https://github.com/nodenv/node-build.git "$(nodenv root)"/plugins/node-build + always: + rbenv_cache: + folder: $HOME/.rbenv + populate_script: + - git clone https://github.com/rbenv/rbenv.git ~/.rbenv + + ## https://cirrus-ci.com/task/6270527041961984?command=os_setup#L543 + - echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc + ## https://cirrus-ci.com/task/5656294977699840?command=os_setup#L633 + - echo 'eval "$(rbenv init -)"' >> ~/.bashrc + + - source ~/.bashrc + + ## https://github.com/rbenv/ruby-build#installation + - mkdir -p "$(rbenv root)"/plugins + - git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-build + + nodenv_cache: + folder: $HOME/.nodenv + populate_script: + - git clone https://github.com/nodenv/nodenv.git ~/.nodenv + + ## https://cirrus-ci.com/task/6270527041961984?command=os_setup#L543 + - echo 'export PATH="$HOME/.nodenv/bin:$PATH"' >> ~/.bashrc + ## https://cirrus-ci.com/task/5656294977699840?command=os_setup#L633 + - echo 'eval "$(nodenv init -)"' >> ~/.bashrc + + - source ~/.bashrc + + ## https://github.com/nodenv/node-build#installation + - mkdir -p "$(nodenv root)"/plugins + - git clone https://github.com/nodenv/node-build.git "$(nodenv root)"/plugins/node-build <<: *bundle_cache From 2394b8a92d06a525c5d0d50c77eb8e41fa5241da Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Thu, 19 Nov 2020 19:02:41 +0300 Subject: [PATCH 57/62] Increase waiting for application start in specs https://cirrus-ci.com/task/6398694570328064?command=test#L937 --- spec/flame/cli/new/app_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/flame/cli/new/app_spec.rb b/spec/flame/cli/new/app_spec.rb index 4138d75..bee02f6 100644 --- a/spec/flame/cli/new/app_spec.rb +++ b/spec/flame/cli/new/app_spec.rb @@ -863,7 +863,7 @@ response = Net::HTTP.get URI("http://127.0.0.1:#{port}/") rescue Errno::ECONNREFUSED => e sleep 1 - retry if number_of_attempts < 20 + retry if number_of_attempts < 30 raise e end From 84c549ffda29096fa41068caa8c91a11b4952fd1 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Wed, 2 Dec 2020 02:45:18 +0300 Subject: [PATCH 58/62] Rescue an exception in tests when server already stopped --- spec/flame/cli/new/app_spec.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spec/flame/cli/new/app_spec.rb b/spec/flame/cli/new/app_spec.rb index bee02f6..5ae5e7e 100644 --- a/spec/flame/cli/new/app_spec.rb +++ b/spec/flame/cli/new/app_spec.rb @@ -870,7 +870,11 @@ response ensure Bundler.with_unbundled_env { `toys server stop` } - Process.wait pid + begin + Process.wait pid + rescue Errno::ECHILD + ## process already stopped + end end end From 563ddac79c53d7ad1d723a215e0098d01003c51f Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Wed, 2 Dec 2020 02:48:07 +0300 Subject: [PATCH 59/62] Remove `cat ~/.bashrc` in CI for debugging Everything (almost) looks OK, anyway `source ~/.bashrc` displays its content. --- .cirrus.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.cirrus.yaml b/.cirrus.yaml index 53ae3de..73fabde 100644 --- a/.cirrus.yaml +++ b/.cirrus.yaml @@ -101,7 +101,6 @@ rspec_task: CODECOV_TOKEN: ENCRYPTED[5bfb9cd3d53a72c8423ce1b84b18cf7d30fbdcf3a56a25f9cd79dbdff1c3552542578272db8088ec27d7de7f2c363d72] test_script: - - cat ~/.bashrc - source ~/.bashrc - bundle exec rspec From ec7d5dd7894e2132596d09ef635a9b6a40410e00 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Wed, 2 Dec 2020 02:53:49 +0300 Subject: [PATCH 60/62] Fix `~/.bashrc` for Cirrus CI It fails inside `populate_script` on cache hits: https://cirrus-ci.com/task/6153771409473536?command=test#L42 --- .cirrus.yaml | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/.cirrus.yaml b/.cirrus.yaml index 73fabde..49ebeee 100644 --- a/.cirrus.yaml +++ b/.cirrus.yaml @@ -62,17 +62,25 @@ rspec_task: ## https://cirrus-ci.com/task/6270527041961984?command=os_setup#L535 - apt update && apt upgrade -y && apt install git make gcc -y + ## Modify `.bashrc` here because it doesn't run on cache hit: + ## https://cirrus-ci.com/task/6153771409473536?command=test#L42 + + ## https://cirrus-ci.com/task/6270527041961984?command=os_setup#L543 + - echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc + ## https://cirrus-ci.com/task/5656294977699840?command=os_setup#L633 + - echo 'eval "$(rbenv init -)"' >> ~/.bashrc + + ## https://cirrus-ci.com/task/6270527041961984?command=os_setup#L543 + - echo 'export PATH="$HOME/.nodenv/bin:$PATH"' >> ~/.bashrc + ## https://cirrus-ci.com/task/5656294977699840?command=os_setup#L633 + - echo 'eval "$(nodenv init -)"' >> ~/.bashrc + always: rbenv_cache: folder: $HOME/.rbenv populate_script: - git clone https://github.com/rbenv/rbenv.git ~/.rbenv - ## https://cirrus-ci.com/task/6270527041961984?command=os_setup#L543 - - echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc - ## https://cirrus-ci.com/task/5656294977699840?command=os_setup#L633 - - echo 'eval "$(rbenv init -)"' >> ~/.bashrc - - source ~/.bashrc ## https://github.com/rbenv/ruby-build#installation @@ -84,11 +92,6 @@ rspec_task: populate_script: - git clone https://github.com/nodenv/nodenv.git ~/.nodenv - ## https://cirrus-ci.com/task/6270527041961984?command=os_setup#L543 - - echo 'export PATH="$HOME/.nodenv/bin:$PATH"' >> ~/.bashrc - ## https://cirrus-ci.com/task/5656294977699840?command=os_setup#L633 - - echo 'eval "$(nodenv init -)"' >> ~/.bashrc - - source ~/.bashrc ## https://github.com/nodenv/node-build#installation From 104aecf0db5df51ab551b803126f826d96bb6066 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Wed, 2 Dec 2020 02:59:12 +0300 Subject: [PATCH 61/62] Update dependencies --- flame-cli.gemspec | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flame-cli.gemspec b/flame-cli.gemspec index 3c13610..c283568 100644 --- a/flame-cli.gemspec +++ b/flame-cli.gemspec @@ -29,19 +29,19 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'pry-byebug', '~> 3.9' spec.add_development_dependency 'bundler', '~> 2.0' - spec.add_development_dependency 'gem_toys', '~> 0.3.0' + spec.add_development_dependency 'gem_toys', '~> 0.5.0' spec.add_development_dependency 'toys', '~> 0.11.0' spec.add_development_dependency 'bundler-audit', '~> 0.7.0' spec.add_development_dependency 'codecov', '~> 0.2.0' spec.add_development_dependency 'rspec', '~> 3.9' - spec.add_development_dependency 'simplecov', '~> 0.18.0' + spec.add_development_dependency 'simplecov', '~> 0.20.0' spec.add_development_dependency 'example_file', '~> 0.2.0' - spec.add_development_dependency 'rubocop', '~> 0.90.0' + spec.add_development_dependency 'rubocop', '~> 1.0' spec.add_development_dependency 'rubocop-performance', '~> 1.0' - spec.add_development_dependency 'rubocop-rspec', '~> 1.0' + spec.add_development_dependency 'rubocop-rspec', '~> 2.0' spec.files = Dir.glob('{lib,template}/**/*', File::FNM_DOTMATCH) spec.bindir = 'exe' From a9591fc82f1f8fade46ceea13bee5becb8d212d5 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Wed, 2 Dec 2020 02:59:20 +0300 Subject: [PATCH 62/62] Add `systemd` service example --- template/config/systemd.example.service.erb | 23 +++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 template/config/systemd.example.service.erb diff --git a/template/config/systemd.example.service.erb b/template/config/systemd.example.service.erb new file mode 100644 index 0000000..c21312f --- /dev/null +++ b/template/config/systemd.example.service.erb @@ -0,0 +1,23 @@ +[Unit] +Description=<%= @module_name %> Server +After=network.target + +# Uncomment for socket activation (see below) +# Requires=puma.socket + +[Service] +# Foreground process (do not use --daemon in ExecStart or config.rb) +Type=simple + +# The path to the your application code root directory. +WorkingDirectory=%h/<%= @app_name %> + +# Helpful for debugging socket activation, etc. +# Environment=PUMA_DEBUG=1 + +ExecStart=%h/<%= @app_name %>/exe/systemd.sh + +Restart=always + +[Install] +WantedBy=multi-user.target