view_component-contrib 0.0.1 → 0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +453 -21
- data/app/templates/install/application_view_component.rb +2 -0
- data/app/templates/install/application_view_component_preview.rb +3 -0
- data/app/templates/install/builder.rb +24 -0
- data/app/templates/install/class_for.rb +3 -0
- data/app/templates/install/generator.rb +275 -0
- data/app/templates/install/identifier.rb +7 -0
- data/app/templates/install/index.js +2 -0
- data/app/templates/install/index.stimulus.js +20 -0
- data/app/templates/install/initializer.rb +16 -0
- data/app/templates/install/postcss-modules.js +13 -0
- data/app/templates/install/template.rb +133 -0
- data/app/views/view_component_contrib/preview.html.erb +7 -0
- data/lib/view_component-contrib.rb +1 -2
- data/lib/view_component_contrib.rb +17 -0
- data/lib/view_component_contrib/base.rb +9 -0
- data/lib/view_component_contrib/preview.rb +11 -0
- data/lib/view_component_contrib/preview/abstract.rb +22 -0
- data/lib/view_component_contrib/preview/base.rb +68 -0
- data/lib/view_component_contrib/preview/default_template.rb +42 -0
- data/lib/view_component_contrib/preview/sidecarable.rb +25 -0
- data/lib/view_component_contrib/translation_helper.rb +45 -0
- data/lib/view_component_contrib/version.rb +5 -0
- data/lib/view_component_contrib/wrapped_helper.rb +10 -0
- data/lib/view_component_contrib/wrapper_component.rb +40 -0
- metadata +101 -10
- data/lib/view_component/contrib/railtie.rb +0 -8
- data/lib/view_component/contrib/version.rb +0 -7
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class TemplateBuilder
|
4
|
+
attr_reader :root
|
5
|
+
|
6
|
+
def initialize(root)
|
7
|
+
@root = root
|
8
|
+
end
|
9
|
+
|
10
|
+
def get_binding
|
11
|
+
binding
|
12
|
+
end
|
13
|
+
|
14
|
+
def embed_code(path)
|
15
|
+
contents = File.read(File.join(root, path))
|
16
|
+
%Q(<<-CODE
|
17
|
+
#{contents}
|
18
|
+
CODE)
|
19
|
+
end
|
20
|
+
|
21
|
+
def embed(path)
|
22
|
+
File.read(File.join(root, path))
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,275 @@
|
|
1
|
+
if yes?("Would you like to create a custom generator for your setup? (Recommended)")
|
2
|
+
template_choice_to_ext = {"1" => ".erb", "2" => ".haml", "3" => ".slim"}
|
3
|
+
|
4
|
+
template = ask "Which template processor do you use? (1) ERB, (2) Haml, (3) Slim, (0) Other"
|
5
|
+
|
6
|
+
TEMPLATE_EXT = template_choice_to_ext.fetch(template, "")
|
7
|
+
TEST_SUFFIX = USE_RSPEC ? 'spec' : 'test'
|
8
|
+
|
9
|
+
file "lib/generators/view_component/view_component_generator.rb", <<~CODE
|
10
|
+
# frozen_string_literal: true
|
11
|
+
|
12
|
+
# Based on https://github.com/github/view_component/blob/master/lib/rails/generators/component/component_generator.rb
|
13
|
+
class ViewComponentGenerator < Rails::Generators::NamedBase
|
14
|
+
source_root File.expand_path("templates", __dir__)
|
15
|
+
|
16
|
+
class_option :skip_test, type: :boolean, default: false
|
17
|
+
class_option :skip_preview, type: :boolean, default: false
|
18
|
+
|
19
|
+
argument :attributes, type: :array, default: [], banner: "attribute"
|
20
|
+
|
21
|
+
def create_component_file
|
22
|
+
template "component.rb", File.join("#{ROOT_PATH}", class_path, file_name, "component.rb")
|
23
|
+
end
|
24
|
+
|
25
|
+
def create_template_file
|
26
|
+
template "component.html#{TEMPLATE_EXT}", File.join("#{ROOT_PATH}", class_path, file_name, "component.html#{TEMPLATE_EXT}")
|
27
|
+
end
|
28
|
+
|
29
|
+
def create_test_file
|
30
|
+
return if options[:skip_test]
|
31
|
+
|
32
|
+
template "component_#{TEST_SUFFIX}.rb", File.join("#{TEST_ROOT_PATH}", class_path, "\#{file_name}_#{TEST_SUFFIX}.rb")
|
33
|
+
end
|
34
|
+
|
35
|
+
def create_preview_file
|
36
|
+
return if options[:skip_preview]
|
37
|
+
|
38
|
+
template "preview.rb", File.join("#{ROOT_PATH}", class_path, file_name, "preview.rb")
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def parent_class
|
44
|
+
"ApplicationViewComponent"
|
45
|
+
end
|
46
|
+
|
47
|
+
def preview_parent_class
|
48
|
+
"ApplicationViewComponentPreview"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
CODE
|
52
|
+
|
53
|
+
if USE_WEBPACK
|
54
|
+
inject_into_file "lib/generators/view_component/view_component_generator.rb", after: "class_option :skip_preview, type: :boolean, default: false\n" do
|
55
|
+
<<-CODE
|
56
|
+
class_option :skip_js, type: :boolean, default: false
|
57
|
+
class_option :skip_css, type: :boolean, default: false
|
58
|
+
CODE
|
59
|
+
end
|
60
|
+
|
61
|
+
inject_into_file "lib/generators/view_component/view_component_generator.rb", before: "\n private" do
|
62
|
+
<<-CODE
|
63
|
+
def create_css_file
|
64
|
+
return if options[:skip_css] || options[:skip_js]
|
65
|
+
|
66
|
+
template "index.css", File.join("#{ROOT_PATH}", class_path, file_name, "index.css")
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_js_file
|
70
|
+
return if options[:skip_js]
|
71
|
+
|
72
|
+
template "index.js", File.join("#{ROOT_PATH}", class_path, file_name, "index.js")
|
73
|
+
end
|
74
|
+
CODE
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
if USE_DRY
|
79
|
+
inject_into_file "lib/generators/view_component/view_component_generator.rb", before: "\nend" do
|
80
|
+
<<-CODE
|
81
|
+
|
82
|
+
|
83
|
+
def initialize_signature
|
84
|
+
return if attributes.blank?
|
85
|
+
|
86
|
+
attributes.map { |attr| "option :\#{attr.name}" }.join("\\n ")
|
87
|
+
end
|
88
|
+
CODE
|
89
|
+
end
|
90
|
+
|
91
|
+
file "lib/generators/view_component/templates/component.rb.tt",
|
92
|
+
<<~CODE
|
93
|
+
# frozen_string_literal: true
|
94
|
+
|
95
|
+
class <%= class_name %>::Component < <%= parent_class %>
|
96
|
+
<%- if initialize_signature -%>
|
97
|
+
<%= initialize_signature %>
|
98
|
+
<%- end -%>
|
99
|
+
end
|
100
|
+
CODE
|
101
|
+
else
|
102
|
+
inject_into_file "lib/generators/view_component/view_component_generator.rb", before: "\nend" do
|
103
|
+
<<-CODE
|
104
|
+
|
105
|
+
|
106
|
+
def initialize_signature
|
107
|
+
return if attributes.blank?
|
108
|
+
|
109
|
+
attributes.map { |attr| "\#{attr.name}:" }.join(", ")
|
110
|
+
end
|
111
|
+
|
112
|
+
def initialize_body
|
113
|
+
attributes.map { |attr| "@\#{attr.name} = \#{attr.name}" }.join("\\n ")
|
114
|
+
end
|
115
|
+
CODE
|
116
|
+
end
|
117
|
+
|
118
|
+
file "lib/generators/view_component/templates/component.rb.tt",
|
119
|
+
<<~CODE
|
120
|
+
# frozen_string_literal: true
|
121
|
+
|
122
|
+
class <%= class_name %>::Component < <%= parent_class %>
|
123
|
+
<%- if initialize_signature -%>
|
124
|
+
def initialize(<%= initialize_signature %>)
|
125
|
+
<%= initialize_body %>
|
126
|
+
end
|
127
|
+
<%- end -%>
|
128
|
+
end
|
129
|
+
CODE
|
130
|
+
end
|
131
|
+
|
132
|
+
if TEMPLATE_EXT == ".slim"
|
133
|
+
file "lib/generators/view_component/templates/component.html.slim.tt", <<~CODE
|
134
|
+
div Add <%= class_name %> template here
|
135
|
+
CODE
|
136
|
+
end
|
137
|
+
|
138
|
+
if TEMPLATE_EXT == ".erb"
|
139
|
+
file "lib/generators/view_component/templates/component.html.erb.tt", <<~CODE
|
140
|
+
<div>Add <%= class_name %> template here</div>
|
141
|
+
CODE
|
142
|
+
end
|
143
|
+
|
144
|
+
if TEMPLATE_EXT == ".haml"
|
145
|
+
file "lib/generators/view_component/templates/component.html.tt", <<~CODE
|
146
|
+
%div Add <%= class_name %> template here
|
147
|
+
CODE
|
148
|
+
end
|
149
|
+
|
150
|
+
if TEMPLATE_EXT == ""
|
151
|
+
file "lib/generators/view_component/templates/component.html.tt", <<~CODE
|
152
|
+
<div>Add <%= class_name %> template here</div>
|
153
|
+
CODE
|
154
|
+
end
|
155
|
+
|
156
|
+
file "lib/generators/view_component/templates/preview.rb.tt", <<~CODE
|
157
|
+
# frozen_string_literal: true
|
158
|
+
|
159
|
+
class <%= class_name %>::Preview < <%= preview_parent_class %>
|
160
|
+
# You can specify the container class for the default template
|
161
|
+
# self.container_class = "w-1/2 border border-gray-300"
|
162
|
+
|
163
|
+
def default
|
164
|
+
end
|
165
|
+
end
|
166
|
+
CODE
|
167
|
+
|
168
|
+
if USE_WEBPACK
|
169
|
+
if USE_STIMULUS
|
170
|
+
file "lib/generators/view_component/templates/index.js.tt",
|
171
|
+
<<-CODE
|
172
|
+
import "./index.css"
|
173
|
+
|
174
|
+
// Add a Stimulus controller for this component.
|
175
|
+
// It will automatically registered and its name will be available
|
176
|
+
// via #component_name in the component class.
|
177
|
+
//
|
178
|
+
// import { Controller as BaseController } from "stimulus";
|
179
|
+
//
|
180
|
+
// export class Controller extends BaseController {
|
181
|
+
// connect() {
|
182
|
+
// }
|
183
|
+
//
|
184
|
+
// disconnect() {
|
185
|
+
// }
|
186
|
+
// }
|
187
|
+
CODE
|
188
|
+
else
|
189
|
+
file "lib/generators/view_component/templates/index.js.tt", <<~CODE
|
190
|
+
import "./index.css"
|
191
|
+
|
192
|
+
CODE
|
193
|
+
end
|
194
|
+
|
195
|
+
if USE_POSTCSS_MODULES
|
196
|
+
file "lib/generators/view_component/templates/index.css.tt", <<~CODE
|
197
|
+
/* Use component-local class names and add them to HTML via #class_for(name) helper */
|
198
|
+
|
199
|
+
CODE
|
200
|
+
else
|
201
|
+
file "lib/generators/view_component/templates/index.css.tt", ""
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
if USE_RSPEC
|
206
|
+
file "lib/generators/view_component/templates/component_spec.rb.tt", <<~CODE
|
207
|
+
# frozen_string_literal: true
|
208
|
+
|
209
|
+
require "rails_helper"
|
210
|
+
|
211
|
+
describe <%= class_name %>::Component do
|
212
|
+
let(:options) { {} }
|
213
|
+
let(:component) { <%= class_name %>::Component.new(**options) }
|
214
|
+
|
215
|
+
subject { rendered_component }
|
216
|
+
|
217
|
+
it "renders" do
|
218
|
+
render_inline(component)
|
219
|
+
|
220
|
+
is_expected.to have_css "div"
|
221
|
+
end
|
222
|
+
end
|
223
|
+
CODE
|
224
|
+
else
|
225
|
+
file "lib/generators/view_component/templates/component_test.rb.tt", <<~CODE
|
226
|
+
# frozen_string_literal: true
|
227
|
+
|
228
|
+
require "test_helper"
|
229
|
+
|
230
|
+
class <%= class_name %>::ComponentTest < ActiveSupport::TestCase
|
231
|
+
include ViewComponent::TestHelpers
|
232
|
+
|
233
|
+
def test_renders
|
234
|
+
component = build_component
|
235
|
+
|
236
|
+
render_inline(component)
|
237
|
+
|
238
|
+
assert_selector "div"
|
239
|
+
end
|
240
|
+
|
241
|
+
private
|
242
|
+
|
243
|
+
def build_component(**options)
|
244
|
+
<%= class_name %>::Component.new(**options)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
CODE
|
248
|
+
end
|
249
|
+
|
250
|
+
file "lib/generators/view_component/USAGE", <<~CODE
|
251
|
+
Description:
|
252
|
+
============
|
253
|
+
Creates a new view component, test and preview files.
|
254
|
+
Pass the component name, either CamelCased or under_scored, and an optional list of attributes as arguments.
|
255
|
+
|
256
|
+
Example:
|
257
|
+
========
|
258
|
+
bin/rails generate view_component Profile name age
|
259
|
+
|
260
|
+
creates a Profile component and test:
|
261
|
+
Component: #{ROOT_PATH}/profile/component.rb
|
262
|
+
Template: #{ROOT_PATH}/profile/component.html#{TEMPLATE_EXT}
|
263
|
+
Test: #{TEST_ROOT_PATH}/profile_component_#{TEST_SUFFIX}.rb
|
264
|
+
Preview: #{ROOT_PATH}/profile/component_preview.rb
|
265
|
+
CODE
|
266
|
+
|
267
|
+
if USE_WEBPACK
|
268
|
+
inject_into_file "lib/generators/view_component/USAGE" do
|
269
|
+
<<-CODE
|
270
|
+
JS: #{ROOT_PATH}/profile/component.js
|
271
|
+
CSS: #{ROOT_PATH}/profile/component.css
|
272
|
+
CODE
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
// IMPORTANT: Update this import to reflect the location of your Stimulus application
|
2
|
+
// See https://github.com/palkan/view_component-contrib#using-with-stimulusjs
|
3
|
+
import { application } from "../init/stimulus";
|
4
|
+
|
5
|
+
const context = require.context(".", true, /index.js$/)
|
6
|
+
context.keys().forEach((path) => {
|
7
|
+
const mod = context(path);
|
8
|
+
|
9
|
+
// Check whether a module has the Controller export defined
|
10
|
+
if (!mod.Controller) return;
|
11
|
+
|
12
|
+
// Convert path into a controller identifier:
|
13
|
+
// example/index.js -> example
|
14
|
+
// nav/user_info/index.js -> nav--user-info
|
15
|
+
const identifier = path.replace(/^\\.\\//, '')
|
16
|
+
.replace(/\\/index\\.js$/, '')
|
17
|
+
.replace(/\\//, '--');
|
18
|
+
|
19
|
+
application.register(identifier, mod.Controller);
|
20
|
+
});
|
@@ -0,0 +1,16 @@
|
|
1
|
+
ActiveSupport.on_load(:view_component) do
|
2
|
+
# Extend your preview controller to support authentication and other
|
3
|
+
# application-specific stuff
|
4
|
+
#
|
5
|
+
# Rails.application.config.to_prepare do
|
6
|
+
# ViewComponentsController.class_eval do
|
7
|
+
# include Authenticated
|
8
|
+
# end
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# Make it possible to store previews in sidecar folders
|
12
|
+
# See https://github.com/palkan/view_component-contrib#organizing-components-or-sidecar-pattern-extended
|
13
|
+
ViewComponent::Preview.extend ViewComponentContrib::Preview::Sidecarable
|
14
|
+
# Enable `self.abstract_class = true` to exclude previews from the list
|
15
|
+
ViewComponent::Preview.extend ViewComponentContrib::Preview::Abstract
|
16
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
generateScopedName: (name, filename, _css) => {
|
2
|
+
const matches = filename.match(/#{ROOT_PATH.gsub('/', '\/')}\\/?(.*)\\/index.css$/);
|
3
|
+
// Do not transform CSS files from outside of the components folder
|
4
|
+
if (!matches) return name;
|
5
|
+
|
6
|
+
// identifier here is the same identifier we used for Stimulus controller (see above)
|
7
|
+
const identifier = matches[1].replace("/", "--");
|
8
|
+
|
9
|
+
// We also add the `c-` prefix to all components classes
|
10
|
+
return `c-${identifier}-${name}`;
|
11
|
+
},
|
12
|
+
// Do not generate *.css.json files (we don't use them)
|
13
|
+
getJSON: () => {}
|
@@ -0,0 +1,133 @@
|
|
1
|
+
say "👋 Welcome to interactive ViewComponent installer and configurator. " \
|
2
|
+
"Make sure you've read the view_component-contrib guide: https://github.com/palkan/view_component-contrib"
|
3
|
+
|
4
|
+
run "bundle add view_component view_component-contrib --skip-install"
|
5
|
+
|
6
|
+
inject_into_file "config/application.rb", "require \"view_component/engine\"\n", before: "\nBundler.require(*Rails.groups)"
|
7
|
+
|
8
|
+
say_status :info, "✅ ViewComponent gems added"
|
9
|
+
|
10
|
+
DEFAULT_ROOT = "app/frontend/components"
|
11
|
+
|
12
|
+
root = ask("Where do you want to store your view components? (default: #{DEFAULT_ROOT})")
|
13
|
+
ROOT_PATH = root.present? && root.downcase != "n" ? root : DEFAULT_ROOT
|
14
|
+
|
15
|
+
root_paths = ROOT_PATH.split("/").map { |path| "\"#{path}\"" }.join(", ")
|
16
|
+
|
17
|
+
application "config.view_component.preview_paths << Rails.root.join(#{root_paths})"
|
18
|
+
application "config.autoload_paths << Rails.root.join(#{root_paths})"
|
19
|
+
|
20
|
+
say_status :info, "✅ ViewComponent paths configured"
|
21
|
+
|
22
|
+
file "#{ROOT_PATH}/application_view_component.rb",
|
23
|
+
<%= embed_code("./application_view_component.rb") %>
|
24
|
+
|
25
|
+
file "#{ROOT_PATH}/application_view_component_preview.rb",
|
26
|
+
<%= embed_code("./application_view_component_preview.rb") %>
|
27
|
+
|
28
|
+
say_status :info, "✅ ApplicationViewComponent and ApplicationViewComponentPreview classes added"
|
29
|
+
|
30
|
+
USE_RSPEC = File.directory?("spec")
|
31
|
+
TEST_ROOT_PATH = USE_RSPEC ? File.join("spec", ROOT_PATH.sub("app/", "")) : File.join("test", ROOT_PATH.sub("app/", ""))
|
32
|
+
|
33
|
+
USE_DRY = yes? "Would you like to use dry-initializer in your component classes?"
|
34
|
+
|
35
|
+
if USE_DRY
|
36
|
+
run "bundle add dry-initializer --skip-install"
|
37
|
+
|
38
|
+
inject_into_file "#{ROOT_PATH}/application_view_component.rb", "\n extend Dry::Initializer", after: "class ApplicationViewComponent < ViewComponentContrib::Base"
|
39
|
+
|
40
|
+
say_status :info, "✅ Extended ApplicationViewComponent with Dry::Initializer"
|
41
|
+
end
|
42
|
+
|
43
|
+
initializer "view_component.rb",
|
44
|
+
<%= embed_code("./initializer.rb") %>
|
45
|
+
|
46
|
+
say_status :info, "✅ Added ViewComponent initializer with required patches"
|
47
|
+
|
48
|
+
if USE_RSPEC
|
49
|
+
inject_into_file "spec/rails_helper.rb", after: "require \"rspec/rails\"\n" do
|
50
|
+
"require \"capybara/rspec\"\nrequire \"view_component/test_helpers\"\n"
|
51
|
+
end
|
52
|
+
|
53
|
+
inject_into_file "spec/rails_helper.rb", after: "RSpec.configure do |config|\n" do
|
54
|
+
<<-CODE
|
55
|
+
config.include ViewComponent::TestHelpers, type: :view_component
|
56
|
+
config.include Capybara::RSpecMatchers, type: :view_component
|
57
|
+
|
58
|
+
config.define_derived_metadata(file_path: %r{/#{TEST_ROOT_PATH}}) do |metadata|
|
59
|
+
metadata[:type] = :view_component
|
60
|
+
end
|
61
|
+
|
62
|
+
CODE
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
say_status :info, "✅ RSpec configured"
|
67
|
+
|
68
|
+
USE_WEBPACK = File.directory?("config/webpack")
|
69
|
+
|
70
|
+
if USE_WEBPACK
|
71
|
+
USE_STIMULUS = yes? "Do you use StimulusJS?"
|
72
|
+
|
73
|
+
if USE_STIMULUS
|
74
|
+
file "#{ROOT_PATH}/index.js",
|
75
|
+
<%= embed_code("./index.stimulus.js") %>
|
76
|
+
|
77
|
+
inject_into_file "#{ROOT_PATH}/application_view_component.rb", before: "\nend" do
|
78
|
+
<%= embed_code("./identifier.rb") %>
|
79
|
+
end
|
80
|
+
else
|
81
|
+
file "#{ROOT_PATH}/index.js",
|
82
|
+
<%= embed_code("./index.js") %>
|
83
|
+
end
|
84
|
+
|
85
|
+
say_status :info, "✅ Added index.js to load components JS/CSS"
|
86
|
+
say "⚠️ Don't forget to import component JS/CSS (#{ROOT_PATH}/index.js) from your application.js entrypoint"
|
87
|
+
|
88
|
+
USE_POSTCSS_MODULES = yes? "Would you like to use postcss-modules to isolate component styles?"
|
89
|
+
|
90
|
+
if USE_POSTCSS_MODULES
|
91
|
+
run "yarn add postcss-modules"
|
92
|
+
|
93
|
+
if File.read("postcss.config.js").match(/plugins:\s*\[/)
|
94
|
+
inject_into_file "postcss.config.js", after: "plugins: [" do
|
95
|
+
<<-CODE
|
96
|
+
|
97
|
+
require('postcss-modules')({
|
98
|
+
<%= embed("./postcss-modules.js") %>
|
99
|
+
}),
|
100
|
+
CODE
|
101
|
+
end
|
102
|
+
else
|
103
|
+
inject_into_file "postcss.config.js", after: "plugins: {" do
|
104
|
+
<<-CODE
|
105
|
+
|
106
|
+
'postcss-modules': {
|
107
|
+
<%= embed("./postcss-modules.js") %>
|
108
|
+
},
|
109
|
+
CODE
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
if !USE_STIMULUS
|
114
|
+
inject_into_file "#{ROOT_PATH}/application_view_component.rb", before: "\nend" do
|
115
|
+
<%= embed_code("./identifier.rb") %>
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
inject_into_file "#{ROOT_PATH}/application_view_component.rb", before: "\nend" do
|
120
|
+
<%= embed_code("./class_for.rb") %>
|
121
|
+
end
|
122
|
+
|
123
|
+
say_status :info, "✅ postcss-modules configured"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
<%= embed("./generator.rb") %>
|
128
|
+
|
129
|
+
say "Installing gems..."
|
130
|
+
|
131
|
+
Bundler.with_unbundled_env { run "bundle install" }
|
132
|
+
|
133
|
+
say_status :info, "✅ You're ready to rock!"
|