potassium 6.0.0 → 6.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +41 -45
- data/lib/potassium/assets/.eslintrc.json +13 -4
- data/lib/potassium/assets/.github/pull_request_template.md +9 -0
- data/lib/potassium/assets/README.yml +6 -0
- data/lib/potassium/assets/app/graphql/graphql_controller.rb +55 -0
- data/lib/potassium/assets/app/graphql/mutations/login_mutation.rb +23 -0
- data/lib/potassium/assets/app/graphql/queries/base_query.rb +4 -0
- data/lib/potassium/assets/app/graphql/types/base/base_argument.rb +4 -0
- data/lib/potassium/assets/app/graphql/types/base/base_enum.rb +4 -0
- data/lib/potassium/assets/app/graphql/types/base/base_field.rb +5 -0
- data/lib/potassium/assets/app/graphql/types/base/base_input_object.rb +5 -0
- data/lib/potassium/assets/app/graphql/types/base/base_interface.rb +7 -0
- data/lib/potassium/assets/app/graphql/types/base/base_object.rb +5 -0
- data/lib/potassium/assets/app/graphql/types/base/base_scalar.rb +4 -0
- data/lib/potassium/assets/app/graphql/types/base/base_union.rb +4 -0
- data/lib/potassium/assets/app/graphql/types/mutation_type.rb +10 -0
- data/lib/potassium/assets/app/graphql/types/query_type.rb +13 -0
- data/lib/potassium/assets/app/uploaders/base_uploader.rb +1 -3
- data/lib/potassium/assets/config/graphql_playground.rb +20 -0
- data/lib/potassium/assets/config/puma.rb +1 -1
- data/lib/potassium/assets/config/shrine.rb +4 -1
- data/lib/potassium/assets/redis.yml +1 -2
- data/lib/potassium/assets/testing/rails_helper.rb +2 -0
- data/lib/potassium/cli/commands/create.rb +11 -19
- data/lib/potassium/cli_options.rb +59 -7
- data/lib/potassium/newest_version_ensurer.rb +19 -36
- data/lib/potassium/node_version_ensurer.rb +30 -0
- data/lib/potassium/recipes/api.rb +91 -27
- data/lib/potassium/recipes/background_processor.rb +34 -1
- data/lib/potassium/recipes/database.rb +4 -0
- data/lib/potassium/recipes/draper.rb +0 -9
- data/lib/potassium/recipes/file_storage.rb +1 -0
- data/lib/potassium/recipes/front_end.rb +62 -0
- data/lib/potassium/recipes/github.rb +93 -15
- data/lib/potassium/version.rb +1 -1
- data/potassium.gemspec +2 -1
- data/spec/features/api_spec.rb +25 -0
- data/spec/features/background_processor_spec.rb +12 -1
- data/spec/features/draper_spec.rb +1 -6
- data/spec/features/file_storage_spec.rb +5 -0
- data/spec/features/front_end_spec.rb +14 -0
- data/spec/features/github_spec.rb +53 -8
- data/spec/features/graphql_spec.rb +71 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/support/fake_octokit.rb +31 -0
- metadata +40 -8
- data/lib/potassium/assets/api/api_error_concern.rb +0 -32
- data/lib/potassium/assets/api/base_controller.rb +0 -7
- data/lib/potassium/assets/api/draper_responder.rb +0 -62
- data/lib/potassium/assets/api/responder.rb +0 -41
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
26
|
-
|
27
|
-
|
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
|
data/lib/potassium/version.rb
CHANGED
data/potassium.gemspec
CHANGED
@@ -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", "~>
|
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("
|
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
|
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 "
|
10
|
-
create_dummy_project(
|
11
|
-
|
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(
|
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 "
|
17
|
-
create_dummy_project(
|
18
|
-
|
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(
|
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
|