potassium 6.0.0 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/README.md +41 -45
  4. data/lib/potassium/assets/.eslintrc.json +13 -4
  5. data/lib/potassium/assets/.github/pull_request_template.md +9 -0
  6. data/lib/potassium/assets/README.yml +6 -0
  7. data/lib/potassium/assets/app/graphql/graphql_controller.rb +55 -0
  8. data/lib/potassium/assets/app/graphql/mutations/login_mutation.rb +23 -0
  9. data/lib/potassium/assets/app/graphql/queries/base_query.rb +4 -0
  10. data/lib/potassium/assets/app/graphql/types/base/base_argument.rb +4 -0
  11. data/lib/potassium/assets/app/graphql/types/base/base_enum.rb +4 -0
  12. data/lib/potassium/assets/app/graphql/types/base/base_field.rb +5 -0
  13. data/lib/potassium/assets/app/graphql/types/base/base_input_object.rb +5 -0
  14. data/lib/potassium/assets/app/graphql/types/base/base_interface.rb +7 -0
  15. data/lib/potassium/assets/app/graphql/types/base/base_object.rb +5 -0
  16. data/lib/potassium/assets/app/graphql/types/base/base_scalar.rb +4 -0
  17. data/lib/potassium/assets/app/graphql/types/base/base_union.rb +4 -0
  18. data/lib/potassium/assets/app/graphql/types/mutation_type.rb +10 -0
  19. data/lib/potassium/assets/app/graphql/types/query_type.rb +13 -0
  20. data/lib/potassium/assets/app/uploaders/base_uploader.rb +1 -3
  21. data/lib/potassium/assets/config/graphql_playground.rb +20 -0
  22. data/lib/potassium/assets/config/puma.rb +1 -1
  23. data/lib/potassium/assets/config/shrine.rb +4 -1
  24. data/lib/potassium/assets/redis.yml +1 -2
  25. data/lib/potassium/assets/testing/rails_helper.rb +2 -0
  26. data/lib/potassium/cli/commands/create.rb +11 -19
  27. data/lib/potassium/cli_options.rb +59 -7
  28. data/lib/potassium/newest_version_ensurer.rb +19 -36
  29. data/lib/potassium/node_version_ensurer.rb +30 -0
  30. data/lib/potassium/recipes/api.rb +91 -27
  31. data/lib/potassium/recipes/background_processor.rb +34 -1
  32. data/lib/potassium/recipes/database.rb +4 -0
  33. data/lib/potassium/recipes/draper.rb +0 -9
  34. data/lib/potassium/recipes/file_storage.rb +1 -0
  35. data/lib/potassium/recipes/front_end.rb +62 -0
  36. data/lib/potassium/recipes/github.rb +93 -15
  37. data/lib/potassium/version.rb +1 -1
  38. data/potassium.gemspec +2 -1
  39. data/spec/features/api_spec.rb +25 -0
  40. data/spec/features/background_processor_spec.rb +12 -1
  41. data/spec/features/draper_spec.rb +1 -6
  42. data/spec/features/file_storage_spec.rb +5 -0
  43. data/spec/features/front_end_spec.rb +14 -0
  44. data/spec/features/github_spec.rb +53 -8
  45. data/spec/features/graphql_spec.rb +71 -0
  46. data/spec/spec_helper.rb +1 -0
  47. data/spec/support/fake_octokit.rb +31 -0
  48. metadata +40 -8
  49. data/lib/potassium/assets/api/api_error_concern.rb +0 -32
  50. data/lib/potassium/assets/api/base_controller.rb +0 -7
  51. data/lib/potassium/assets/api/draper_responder.rb +0 -62
  52. data/lib/potassium/assets/api/responder.rb +0 -41
@@ -46,5 +46,9 @@ class Recipes::Database < Rails::AppBuilder
46
46
  else
47
47
  gather_gem db[:gem_name]
48
48
  end
49
+
50
+ after(:gem_install) do
51
+ generate 'strong_migrations:install'
52
+ end
49
53
  end
50
54
  end
@@ -7,7 +7,6 @@ class Recipes::Draper < Rails::AppBuilder
7
7
  def create
8
8
  return unless selected?(:draper)
9
9
  add_draper
10
- add_api_responder if selected?(:api_support)
11
10
  end
12
11
 
13
12
  def installed?
@@ -16,8 +15,6 @@ class Recipes::Draper < Rails::AppBuilder
16
15
 
17
16
  def install
18
17
  add_draper
19
- api_recipe = load_recipe(:api)
20
- add_api_responder if api_recipe.installed?
21
18
  end
22
19
 
23
20
  def add_draper
@@ -25,10 +22,4 @@ class Recipes::Draper < Rails::AppBuilder
25
22
  add_readme_section :internal_dependencies, :draper
26
23
  create_file 'app/decorators/.keep'
27
24
  end
28
-
29
- def add_api_responder
30
- after(:gem_install) do
31
- copy_file '../assets/api/draper_responder.rb', 'app/responders/api_responder.rb', force: true
32
- end
33
- end
34
25
  end
@@ -43,6 +43,7 @@ class Recipes::FileStorage < Rails::AppBuilder
43
43
  copy_file('../assets/config/shrine.rb', 'config/initializers/shrine.rb', force: true)
44
44
  copy_file('../assets/app/uploaders/image_uploader.rb', 'app/uploaders/image_uploader.rb')
45
45
  copy_file('../assets/app/uploaders/base_uploader.rb', 'app/uploaders/base_uploader.rb')
46
+ append_to_file('.gitignore', "/public/uploads\n")
46
47
  end
47
48
 
48
49
  def common_setup
@@ -26,6 +26,9 @@ class Recipes::FrontEnd < Rails::AppBuilder
26
26
  if value == :vue
27
27
  recipe.setup_vue_with_compiler_build
28
28
  recipe.setup_jest
29
+ if get(:api) == :graphql
30
+ recipe.setup_apollo
31
+ end
29
32
  end
30
33
  recipe.add_responsive_meta_tag
31
34
  recipe.setup_tailwind
@@ -83,6 +86,27 @@ class Recipes::FrontEnd < Rails::AppBuilder
83
86
  copy_file '../assets/app/javascript/app.spec.js', 'app/javascript/app.spec.js'
84
87
  end
85
88
 
89
+ def setup_apollo
90
+ run 'bin/yarn add vue-apollo graphql apollo-client apollo-link apollo-link-http apollo-cache-inmemory graphql-tag'
91
+
92
+ inject_into_file(
93
+ 'app/javascript/packs/application.js',
94
+ apollo_imports,
95
+ after: "import App from '../app.vue';"
96
+ )
97
+
98
+ inject_into_file(
99
+ 'app/javascript/packs/application.js',
100
+ apollo_loading,
101
+ after: "import VueApollo from 'vue-apollo';"
102
+ )
103
+ inject_into_file(
104
+ 'app/javascript/packs/application.js',
105
+ "\n apolloProvider,",
106
+ after: "components: { App },"
107
+ )
108
+ end
109
+
86
110
  private
87
111
 
88
112
  def frameworks(framework)
@@ -94,6 +118,35 @@ class Recipes::FrontEnd < Rails::AppBuilder
94
118
  frameworks[framework]
95
119
  end
96
120
 
121
+ def apollo_imports
122
+ <<~JS
123
+ \n
124
+ import { ApolloClient } from 'apollo-client';
125
+ import { createHttpLink } from 'apollo-link-http';
126
+ import { InMemoryCache } from 'apollo-cache-inmemory';
127
+ import VueApollo from 'vue-apollo';
128
+ JS
129
+ end
130
+
131
+ def apollo_loading
132
+ <<~JS
133
+ \n
134
+ const httpLink = createHttpLink({
135
+ uri: `${window.location.origin}/graphql`,
136
+ })
137
+ const cache = new InMemoryCache()
138
+ const apolloClient = new ApolloClient({
139
+ link: httpLink,
140
+ cache,
141
+ })
142
+
143
+ Vue.use(VueApollo)
144
+ const apolloProvider = new VueApollo({
145
+ defaultClient: apolloClient,
146
+ })
147
+ JS
148
+ end
149
+
97
150
  def setup_client_css
98
151
  application_css = 'app/javascript/css/application.css'
99
152
  create_file application_css, "", force: true
@@ -163,6 +216,15 @@ class Recipes::FrontEnd < Rails::AppBuilder
163
216
  },
164
217
  variants: {},
165
218
  plugins: [],
219
+ purge: {
220
+ enabled: process.env.NODE_ENV === 'production',
221
+ content: [
222
+ './app/**/*.html',
223
+ './app/**/*.vue',
224
+ './app/**/*.js',
225
+ './app/**/*.erb',
226
+ ],
227
+ }
166
228
  };
167
229
  JS
168
230
  end
@@ -1,29 +1,107 @@
1
+ require 'octokit'
2
+
1
3
  class Recipes::Github < Rails::AppBuilder
2
4
  def ask
3
- repo_name = "platanus/#{get(:dasherized_app_name)}"
4
5
  github_repo_create = answer(:github) do
5
- q = "Do you want to create the Github repository (https://github.com/#{repo_name}) " +
6
- "for this project?"
7
- Ask.confirm(q)
8
- end
9
- if github_repo_create
10
- github_repo_private = answer(:"github-private") do
11
- Ask.confirm("Should the repository be private?")
12
- end
6
+ Ask.confirm('Do you want to create a Github repository?')
13
7
  end
14
- set(:github_repo_name, repo_name)
15
8
  set(:github_repo, github_repo_create)
16
- set(:github_repo_private, github_repo_private)
9
+ setup_repo if github_repo_create
10
+ end
11
+
12
+ def setup_repo
13
+ setup_repo_private
14
+ setup_repo_org
15
+ setup_repo_name
16
+ set(:github_access_token, get_access_token)
17
17
  end
18
18
 
19
19
  def create
20
- github_repo_create(get(:github_repo_name), get(:github_repo_private)) if selected?(:github_repo)
20
+ return unless selected?(:github_repo)
21
+
22
+ create_github_repo
23
+ copy_file '../assets/.github/pull_request_template.md', '.github/pull_request_template.md'
21
24
  end
22
25
 
23
26
  private
24
27
 
25
- def github_repo_create(repo_name, private_repo = false)
26
- flag = private_repo ? "-p" : ""
27
- run "hub create #{flag} #{repo_name}"
28
+ def setup_repo_private
29
+ repo_private = answer(:github_private) do
30
+ Ask.confirm('Should the repository be private?')
31
+ end
32
+ set(:github_repo_private, repo_private)
33
+ end
34
+
35
+ def setup_repo_org
36
+ has_organization = answer(:github_has_org) do
37
+ Ask.confirm('Is this repo for a Github organization?')
38
+ end
39
+ set(:github_has_org, has_organization)
40
+ if has_organization
41
+ repo_organization = answer(:github_org) do
42
+ Ask.input('What is the organization for this repository?', default: 'platanus')
43
+ end
44
+ set(:github_org, repo_organization)
45
+ end
46
+ end
47
+
48
+ def setup_repo_name
49
+ repo_name = answer(:github_name) do
50
+ Ask.input('What is the name for this repository?', default: get(:dasherized_app_name))
51
+ end
52
+ set(:github_repo_name, repo_name)
53
+ end
54
+
55
+ def create_github_repo
56
+ options = { private: get(:github_repo_private) }
57
+ options[:organization] = get(:github_org) if get(:github_has_org)
58
+ repo_name = get(:github_repo_name)
59
+
60
+ is_retry = false
61
+ begin
62
+ github_client(is_retry).create_repository(repo_name, options)
63
+ rescue Octokit::Unauthorized
64
+ is_retry = true
65
+ retry if retry_create_repo
66
+ end
67
+ end
68
+
69
+ def retry_create_repo
70
+ puts "Bad credentials, information on Personal Access Tokens here:"
71
+ puts "https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token"
72
+ puts "Make sure to give repo access to the personal access token"
73
+ Ask.confirm("Do you want to retry?")
74
+ end
75
+
76
+ def github_client(is_retry = false)
77
+ access_token = is_retry ? set_access_token : get(:github_access_token)
78
+ octokit_client.new(access_token: access_token)
79
+ end
80
+
81
+ def octokit_client
82
+ if answer(:test)
83
+ require_relative '../../../spec/support/fake_octokit'
84
+ FakeOctokit
85
+ else
86
+ Octokit::Client
87
+ end
88
+ end
89
+
90
+ def get_access_token
91
+ return File.open(config_filename, 'r').read if File.exists?(config_filename)
92
+
93
+ set_access_token
94
+ end
95
+
96
+ def set_access_token
97
+ access_token = answer(:github_access_token) do
98
+ Ask.input('Enter a GitHub personal access token', password: true)
99
+ end
100
+ File.open(config_filename, 'w') { |f| f.write(access_token) }
101
+ access_token
102
+ end
103
+
104
+ def config_filename
105
+ @config_filename ||= File.expand_path('~/.potassium')
28
106
  end
29
107
  end
@@ -1,5 +1,5 @@
1
1
  module Potassium
2
- VERSION = "6.0.0"
2
+ VERSION = "6.1.0"
3
3
  RUBY_VERSION = "2.7.0"
4
4
  RAILS_VERSION = "~> 6.0.2"
5
5
  RUBOCOP_VERSION = "~> 0.65.0"
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_development_dependency "bundler", "~> 2.0"
22
22
  spec.add_development_dependency "pry", "~> 0.10.3"
23
- spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "rake", "~> 13.0"
24
24
  spec.add_development_dependency "rspec", "~> 3.4.0"
25
25
  spec.add_development_dependency "rspec_junit_formatter"
26
26
  spec.add_development_dependency "rubocop", Potassium::RUBOCOP_VERSION
@@ -29,6 +29,7 @@ Gem::Specification.new do |spec|
29
29
  spec.add_runtime_dependency "gli", "~> 2.12.2"
30
30
  spec.add_runtime_dependency "inquirer", "~> 0.2"
31
31
  spec.add_runtime_dependency "levenshtein", "~> 0.2"
32
+ spec.add_runtime_dependency "octokit", "~> 4.18"
32
33
  spec.add_runtime_dependency "rails", Potassium::RAILS_VERSION
33
34
  spec.add_runtime_dependency "semantic", "~> 1.4"
34
35
  end
@@ -0,0 +1,25 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe "Api" do
4
+ before :all do
5
+ drop_dummy_database
6
+ remove_project_directory
7
+ create_dummy_project("api" => :rest)
8
+ end
9
+
10
+ it "adds power_api related gems to Gemfile" do
11
+ gemfile_content = IO.read("#{project_path}/Gemfile")
12
+ expect(gemfile_content).to include("gem 'power_api'")
13
+ expect(gemfile_content).to include("gem 'rswag-specs'")
14
+ end
15
+
16
+ it "adds the power_api brief to README file" do
17
+ readme = IO.read("#{project_path}/README.md")
18
+ expect(readme).to include("Power API")
19
+ end
20
+
21
+ it "installs power_api" do
22
+ content = IO.read("#{project_path}/app/controllers/api/base_controller.rb")
23
+ expect(content).to include("Api::BaseController < PowerApi::BaseController")
24
+ end
25
+ end
@@ -1,4 +1,5 @@
1
1
  require "spec_helper"
2
+ require 'yaml'
2
3
 
3
4
  RSpec.describe "BackgroundProcessor" do
4
5
  context "working with sidekiq" do
@@ -41,6 +42,9 @@ RSpec.describe "BackgroundProcessor" do
41
42
  it "adds ENV vars" do
42
43
  content = IO.read("#{project_path}/.env.development")
43
44
  expect(content).to include("DB_POOL=25")
45
+ expect(content).to include('REDIS_HOST=127.0.0.1')
46
+ expect(content).to include('REDIS_PORT=$(make services-port SERVICE=redis PORT=6379)')
47
+ expect(content).to include('REDIS_URL=redis://${REDIS_HOST}:${REDIS_PORT}/1')
44
48
  end
45
49
 
46
50
  it "adds sidekiq.rb file" do
@@ -55,12 +59,19 @@ RSpec.describe "BackgroundProcessor" do
55
59
 
56
60
  it "adds redis.yml file" do
57
61
  content = IO.read("#{project_path}/config/redis.yml")
58
- expect(content).to include("REDIS_HOST")
62
+ expect(content).to include("REDIS_URL")
59
63
  end
60
64
 
61
65
  it "mounts sidekiq app" do
62
66
  content = IO.read("#{project_path}/config/routes.rb")
63
67
  expect(content).to include("mount Sidekiq::Web => '/queue'")
64
68
  end
69
+
70
+ it 'adds redis to docker-compose' do
71
+ compose_file = IO.read("#{project_path}/docker-compose.yml")
72
+ compose_content = YAML.safe_load(compose_file, symbolize_names: true)
73
+
74
+ expect(compose_content[:services]).to include(:redis)
75
+ end
65
76
  end
66
77
  end
@@ -4,7 +4,7 @@ RSpec.describe "Draper" do
4
4
  before :all do
5
5
  drop_dummy_database
6
6
  remove_project_directory
7
- create_dummy_project("draper" => true, "api" => true)
7
+ create_dummy_project("draper" => true)
8
8
  end
9
9
 
10
10
  it "adds the Draper gem to Gemfile" do
@@ -17,11 +17,6 @@ RSpec.describe "Draper" do
17
17
  expect(readme).to include("Draper")
18
18
  end
19
19
 
20
- it "adds api responder to work with draper" do
21
- responder_content = IO.read("#{project_path}/app/responders/api_responder.rb")
22
- expect(responder_content).to include("decorated_resource")
23
- end
24
-
25
20
  it "adds decorators directory" do
26
21
  content = IO.read("#{project_path}/app/decorators/.keep")
27
22
  expect(content).to be_empty
@@ -66,5 +66,10 @@ RSpec.describe "File Storage" do
66
66
  content = IO.read("#{project_path}/.env.development")
67
67
  expect(content).to include("S3_BUCKET=")
68
68
  end
69
+
70
+ it "adds filestorage path to gitignore" do
71
+ content = IO.read("#{project_path}/.gitignore")
72
+ expect(content).to include("/public/uploads")
73
+ end
69
74
  end
70
75
  end
@@ -54,6 +54,20 @@ RSpec.describe "Front end" do
54
54
  )
55
55
  expect(tailwind_config_file).to include('module.exports')
56
56
  end
57
+
58
+ context "with graphql" do
59
+ before(:all) do
60
+ remove_project_directory
61
+ create_dummy_project("front_end" => "vue", "api" => "graphql")
62
+ end
63
+
64
+ it "creates a vue project with apollo" do
65
+ expect(node_modules_file).to include("\"vue-apollo\"")
66
+ expect(application_js_file).to include("import { ApolloClient } from 'apollo-client';")
67
+ expect(application_js_file).to include("Vue.use(VueApollo)")
68
+ expect(application_js_file).to include("apolloProvider,")
69
+ end
70
+ end
57
71
  end
58
72
 
59
73
  context "with angular" do
@@ -1,22 +1,67 @@
1
1
  require "spec_helper"
2
2
 
3
3
  RSpec.describe "GitHub" do
4
+ let(:org_name) { "platanus" }
5
+ let(:repo_name) { PotassiumTestHelpers::APP_NAME.dasherize }
6
+ let(:access_token) { "1234" }
7
+ let(:pr_template_file) { IO.read("#{project_path}/.github/pull_request_template.md") }
8
+
4
9
  before do
5
10
  drop_dummy_database
6
11
  remove_project_directory
7
12
  end
8
13
 
9
- it "create a the github repository" do
10
- create_dummy_project("github" => true)
11
- app_name = PotassiumTestHelpers::APP_NAME.dasherize
14
+ it "creates the github repository" do
15
+ create_dummy_project(
16
+ "github" => true,
17
+ "github_private" => false,
18
+ "github_has_org" => false,
19
+ "github_name" => repo_name,
20
+ "github_access_token" => access_token
21
+ )
22
+
23
+ expect(FakeOctokit).to have_created_repo(repo_name)
24
+ expect(pr_template_file).to include('Contexto')
25
+ end
26
+
27
+ it "creates the private github repository" do
28
+ create_dummy_project(
29
+ "github" => true,
30
+ "github_private" => true,
31
+ "github_has_org" => false,
32
+ "github_name" => repo_name,
33
+ "github_access_token" => access_token
34
+ )
35
+
36
+ expect(FakeOctokit).to have_created_private_repo(repo_name)
37
+ expect(pr_template_file).to include('Contexto')
38
+ end
39
+
40
+ it "creates the github repository for the organization" do
41
+ create_dummy_project(
42
+ "github" => true,
43
+ "github_private" => false,
44
+ "github_has_org" => true,
45
+ "github_org" => org_name,
46
+ "github_name" => repo_name,
47
+ "github_access_token" => access_token
48
+ )
12
49
 
13
- expect(FakeGithub).to have_created_repo("platanus/#{app_name}")
50
+ expect(FakeOctokit).to have_created_repo_for_org(repo_name, org_name)
51
+ expect(pr_template_file).to include('Contexto')
14
52
  end
15
53
 
16
- it "create a the github private repository" do
17
- create_dummy_project("github" => true, "github-private" => true)
18
- app_name = PotassiumTestHelpers::APP_NAME.dasherize
54
+ it "creates the private github repository for the organization" do
55
+ create_dummy_project(
56
+ "github" => true,
57
+ "github_private" => true,
58
+ "github_has_org" => true,
59
+ "github_org" => org_name,
60
+ "github_name" => repo_name,
61
+ "github_access_token" => access_token
62
+ )
19
63
 
20
- expect(FakeGithub).to have_created_private_repo("platanus/#{app_name}")
64
+ expect(FakeOctokit).to have_created_private_repo_for_org(repo_name, org_name)
65
+ expect(pr_template_file).to include('Contexto')
21
66
  end
22
67
  end