shopify-cli 1.4.1 → 1.5.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/.github/CODEOWNERS +2 -2
- data/.github/CONTRIBUTING.md +9 -1
- data/.github/PULL_REQUEST_TEMPLATE.md +2 -2
- data/.github/workflows/release.yml +0 -1
- data/.github/workflows/triage.yml +22 -0
- data/.rubocop.yml +21 -7
- data/.rubocop_todo.yml +2 -15
- data/.travis.yml +0 -1
- data/CHANGELOG.md +5 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +9 -6
- data/RELEASING.md +5 -13
- data/lib/project_types/extension/cli.rb +2 -1
- data/lib/project_types/node/cli.rb +4 -1
- data/lib/project_types/node/commands/connect.rb +15 -0
- data/lib/project_types/node/commands/create.rb +6 -7
- data/lib/project_types/node/messages/messages.rb +7 -6
- data/lib/project_types/rails/cli.rb +4 -1
- data/lib/project_types/rails/commands/connect.rb +15 -0
- data/lib/project_types/rails/commands/create.rb +6 -7
- data/lib/project_types/rails/messages/messages.rb +7 -4
- data/lib/project_types/script/cli.rb +2 -1
- data/lib/project_types/script/commands/enable.rb +12 -4
- data/lib/project_types/script/config/extension_points.yml +9 -8
- data/lib/project_types/script/errors.rb +4 -0
- data/lib/project_types/script/layers/application/build_script.rb +12 -16
- data/lib/project_types/script/layers/domain/errors.rb +3 -0
- data/lib/project_types/script/layers/domain/extension_point.rb +0 -1
- data/lib/project_types/script/layers/infrastructure/assemblyscript_project_creator.rb +13 -48
- data/lib/project_types/script/layers/infrastructure/assemblyscript_task_runner.rb +28 -7
- data/lib/project_types/script/layers/infrastructure/errors.rb +16 -0
- data/lib/project_types/script/layers/infrastructure/script_repository.rb +0 -12
- data/lib/project_types/script/layers/infrastructure/script_service.rb +2 -0
- data/lib/project_types/script/messages/messages.rb +22 -2
- data/lib/project_types/script/ui/error_handler.rb +25 -0
- data/lib/shopify-cli/api.rb +3 -1
- data/lib/shopify-cli/commands/config.rb +24 -0
- data/lib/shopify-cli/commands/connect.rb +32 -15
- data/lib/shopify-cli/core/monorail.rb +2 -1
- data/lib/shopify-cli/js_deps.rb +1 -1
- data/lib/shopify-cli/messages/messages.rb +22 -4
- data/lib/shopify-cli/partners_api.rb +17 -1
- data/lib/shopify-cli/process_supervision.rb +1 -1
- data/lib/shopify-cli/project.rb +12 -8
- data/lib/shopify-cli/project_type.rb +17 -1
- data/lib/shopify-cli/shopifolk.rb +32 -13
- data/lib/shopify-cli/task.rb +8 -0
- data/lib/shopify-cli/tasks/create_api_client.rb +9 -0
- data/lib/shopify-cli/tasks/ensure_env.rb +3 -0
- data/lib/shopify-cli/tasks/select_org_and_shop.rb +3 -0
- data/lib/shopify-cli/version.rb +1 -1
- metadata +5 -3
- data/lib/project_types/script/layers/infrastructure/assemblyscript_tsconfig.rb +0 -38
@@ -44,6 +44,11 @@ module Script
|
|
44
44
|
cause_of_error: ShopifyCli::Context.message('script.error.invalid_context_cause'),
|
45
45
|
help_suggestion: ShopifyCli::Context.message('script.error.invalid_context_help'),
|
46
46
|
}
|
47
|
+
when Errors::InvalidConfigProps
|
48
|
+
{
|
49
|
+
cause_of_error: ShopifyCli::Context.message('script.error.invalid_config_props_cause'),
|
50
|
+
help_suggestion: ShopifyCli::Context.message('script.error.invalid_config_props_help'),
|
51
|
+
}
|
47
52
|
when Errors::InvalidConfigYAMLError
|
48
53
|
{
|
49
54
|
cause_of_error: ShopifyCli::Context.message('script.error.invalid_config', e.config_file),
|
@@ -111,6 +116,11 @@ module Script
|
|
111
116
|
cause_of_error: ShopifyCli::Context.message('script.error.dependency_install_cause'),
|
112
117
|
help_suggestion: ShopifyCli::Context.message('script.error.dependency_install_help'),
|
113
118
|
}
|
119
|
+
when Layers::Infrastructure::Errors::EmptyResponseError
|
120
|
+
{
|
121
|
+
cause_of_error: ShopifyCli::Context.message('script.error.failed_api_request_cause'),
|
122
|
+
help_suggestion: ShopifyCli::Context.message('script.error.failed_api_request_help'),
|
123
|
+
}
|
114
124
|
when Layers::Infrastructure::Errors::ForbiddenError
|
115
125
|
{
|
116
126
|
cause_of_error: ShopifyCli::Context.message('script.error.forbidden_error_cause'),
|
@@ -150,6 +160,21 @@ module Script
|
|
150
160
|
e.outdated_packages.collect { |package| "#{package}@latest" }.join(' ')
|
151
161
|
),
|
152
162
|
}
|
163
|
+
when Layers::Infrastructure::Errors::BuildScriptNotFoundError
|
164
|
+
{
|
165
|
+
cause_of_error: ShopifyCli::Context.message('script.error.build_script_not_found'),
|
166
|
+
help_suggestion: ShopifyCli::Context.message('script.error.build_script_suggestion'),
|
167
|
+
}
|
168
|
+
when Layers::Infrastructure::Errors::InvalidBuildScriptError
|
169
|
+
{
|
170
|
+
cause_of_error: ShopifyCli::Context.message('script.error.invalid_build_script'),
|
171
|
+
help_suggestion: ShopifyCli::Context.message('script.error.build_script_suggestion'),
|
172
|
+
}
|
173
|
+
when Layers::Infrastructure::Errors::WebAssemblyBinaryNotFoundError
|
174
|
+
{
|
175
|
+
cause_of_error: ShopifyCli::Context.message('script.error.web_assembly_binary_not_found'),
|
176
|
+
help_suggestion: ShopifyCli::Context.message('script.error.web_assembly_binary_not_found_suggestion'),
|
177
|
+
}
|
153
178
|
end
|
154
179
|
end
|
155
180
|
end
|
data/lib/shopify-cli/api.rb
CHANGED
@@ -91,7 +91,9 @@ module ShopifyCli
|
|
91
91
|
def default_headers
|
92
92
|
{
|
93
93
|
'User-Agent' => "Shopify App CLI #{ShopifyCli::VERSION} #{current_sha} | #{ctx.uname}",
|
94
|
-
}.
|
94
|
+
}.tap do |headers|
|
95
|
+
headers['X-Shopify-Cli-Employee'] = '1' if Shopifolk.acting_as_shopify_organization?
|
96
|
+
end.merge(auth_headers(token))
|
95
97
|
end
|
96
98
|
|
97
99
|
def auth_headers(token)
|
@@ -7,6 +7,7 @@ module ShopifyCli
|
|
7
7
|
|
8
8
|
subcommand :Feature, 'feature'
|
9
9
|
subcommand :Analytics, 'analytics'
|
10
|
+
subcommand :ShopifolkBeta, 'shopifolk-beta'
|
10
11
|
|
11
12
|
def call(*)
|
12
13
|
@ctx.puts(self.class.help)
|
@@ -71,6 +72,29 @@ module ShopifyCli
|
|
71
72
|
end
|
72
73
|
end
|
73
74
|
end
|
75
|
+
|
76
|
+
class ShopifolkBeta < ShopifyCli::SubCommand
|
77
|
+
options do |parser, flags|
|
78
|
+
parser.on('--enable') { flags[:action] = 'enable' }
|
79
|
+
parser.on('--disable') { flags[:action] = 'disable' }
|
80
|
+
parser.on('--status') { flags[:action] = 'status' }
|
81
|
+
end
|
82
|
+
|
83
|
+
def call(_args, _name)
|
84
|
+
is_enabled = ShopifyCli::Config.get_bool('shopifolk-beta', 'enabled')
|
85
|
+
if options.flags[:action] == 'disable' && is_enabled
|
86
|
+
ShopifyCli::Config.set('shopifolk-beta', 'enabled', false)
|
87
|
+
@ctx.puts(@ctx.message('core.config.shopifolk_beta.disabled'))
|
88
|
+
elsif options.flags[:action] == 'enable' && !is_enabled
|
89
|
+
ShopifyCli::Config.set('shopifolk-beta', 'enabled', true)
|
90
|
+
@ctx.puts(@ctx.message('core.config.shopifolk_beta.enabled'))
|
91
|
+
elsif is_enabled
|
92
|
+
@ctx.puts(@ctx.message('core.config.shopifolk_beta.is_enabled'))
|
93
|
+
else
|
94
|
+
@ctx.puts(@ctx.message('core.config.shopifolk_beta.is_disabled'))
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
74
98
|
end
|
75
99
|
end
|
76
100
|
end
|
@@ -3,23 +3,33 @@ require 'shopify_cli'
|
|
3
3
|
module ShopifyCli
|
4
4
|
module Commands
|
5
5
|
class Connect < ShopifyCli::Command
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
@ctx.puts @ctx.message('core.connect.already_connected_warning')
|
11
|
-
prod_warning = @ctx.message('core.connect.production_warning')
|
12
|
-
@ctx.puts prod_warning if [:rails, :node].include?(Project.current_project_type)
|
6
|
+
class << self
|
7
|
+
def call(args, command_name)
|
8
|
+
ProjectType.load_type(args[0]) unless args.empty?
|
9
|
+
super
|
13
10
|
end
|
14
11
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
@ctx.puts(@ctx.message('core.connect.connected', get_app(org['apps'], api_key).first["title"]))
|
12
|
+
def help
|
13
|
+
ShopifyCli::Context.message('core.connect.help', ShopifyCli::TOOL_NAME)
|
14
|
+
end
|
19
15
|
end
|
20
16
|
|
21
|
-
def
|
22
|
-
|
17
|
+
def call(args, command_name)
|
18
|
+
if Project.current&.env
|
19
|
+
@ctx.puts(@ctx.message('core.connect.already_connected_warning'))
|
20
|
+
end
|
21
|
+
|
22
|
+
project_type = ask_project_type
|
23
|
+
|
24
|
+
klass = ProjectType.load_type(project_type)&.connect_command
|
25
|
+
|
26
|
+
if klass
|
27
|
+
klass.ctx = @ctx
|
28
|
+
klass.call(args, command_name, 'connect')
|
29
|
+
else
|
30
|
+
app = default_connect(project_type)
|
31
|
+
@ctx.done(@ctx.message('core.connect.connected', app))
|
32
|
+
end
|
23
33
|
end
|
24
34
|
|
25
35
|
def ask_project_type
|
@@ -30,6 +40,13 @@ module ShopifyCli
|
|
30
40
|
end
|
31
41
|
end
|
32
42
|
|
43
|
+
def default_connect(project_type)
|
44
|
+
org = ShopifyCli::Tasks::EnsureEnv.call(@ctx, regenerate: true)
|
45
|
+
write_cli_yml(project_type, org['id']) unless Project.has_current?
|
46
|
+
api_key = Project.current(force_reload: true).env['api_key']
|
47
|
+
get_app(org['apps'], api_key).first['title']
|
48
|
+
end
|
49
|
+
|
33
50
|
def write_cli_yml(project_type, org_id)
|
34
51
|
ShopifyCli::Project.write(
|
35
52
|
@ctx,
|
@@ -39,8 +56,8 @@ module ShopifyCli
|
|
39
56
|
@ctx.done(@ctx.message('core.connect.cli_yml_saved'))
|
40
57
|
end
|
41
58
|
|
42
|
-
def
|
43
|
-
|
59
|
+
def get_app(apps, api_key)
|
60
|
+
apps.select { |app| app["apiKey"] == api_key }
|
44
61
|
end
|
45
62
|
end
|
46
63
|
end
|
@@ -7,7 +7,7 @@ module ShopifyCli
|
|
7
7
|
module Core
|
8
8
|
module Monorail
|
9
9
|
ENDPOINT_URI = URI.parse('https://monorail-edge.shopifycloud.com/v1/produce')
|
10
|
-
INVOCATIONS_SCHEMA = 'app_cli_command/
|
10
|
+
INVOCATIONS_SCHEMA = 'app_cli_command/5.0'
|
11
11
|
|
12
12
|
# Extra hash of data that will be sent in the payload
|
13
13
|
@metadata = {}
|
@@ -101,6 +101,7 @@ module ShopifyCli
|
|
101
101
|
uname: RbConfig::CONFIG["host"],
|
102
102
|
cli_version: ShopifyCli::VERSION,
|
103
103
|
ruby_version: RUBY_VERSION,
|
104
|
+
is_employee: ShopifyCli::Shopifolk.acting_as_shopify_organization?,
|
104
105
|
}.tap do |payload|
|
105
106
|
payload[:api_key] = metadata.delete(:api_key)
|
106
107
|
payload[:partner_id] = metadata.delete(:organization_id)
|
data/lib/shopify-cli/js_deps.rb
CHANGED
@@ -3,6 +3,16 @@
|
|
3
3
|
module ShopifyCli
|
4
4
|
module Messages
|
5
5
|
MESSAGES = {
|
6
|
+
apps: {
|
7
|
+
create: {
|
8
|
+
info: {
|
9
|
+
created: "{{v}} {{green:%s}} was created in the organization's Partner Dashboard {{underline:%s}}",
|
10
|
+
serve: "{{*}} Change directories to your new project folder {{green:%s}} and run {{command:%s serve}} " \
|
11
|
+
"to start a local server",
|
12
|
+
install: "{{*}} Then, visit {{underline:%s/test}} to install {{green:%s}} on your Dev Store",
|
13
|
+
},
|
14
|
+
},
|
15
|
+
},
|
6
16
|
core: {
|
7
17
|
connect: {
|
8
18
|
help: <<~HELP,
|
@@ -10,11 +20,7 @@ module ShopifyCli
|
|
10
20
|
Usage: {{command:%s connect}}
|
11
21
|
HELP
|
12
22
|
|
13
|
-
production_warning: <<~MESSAGE,
|
14
|
-
{{yellow:! Warning: if you have connected to an {{bold:app in production}}, running {{command:serve}} may update the app URL and cause an outage.
|
15
|
-
MESSAGE
|
16
23
|
already_connected_warning: "{{yellow:! This app appears to be already connected}}",
|
17
|
-
connected: "{{v}} Project now connected to {{green:%s}}",
|
18
24
|
project_type_select: "What type of project would you like to connect?",
|
19
25
|
cli_yml_saved: ".shopify-cli.yml saved to project root",
|
20
26
|
},
|
@@ -70,6 +76,16 @@ module ShopifyCli
|
|
70
76
|
is_enabled: "{{v}} analytics are currently enabled",
|
71
77
|
is_disabled: "{{v}} analytics are currently disabled",
|
72
78
|
},
|
79
|
+
shopifolk_beta: {
|
80
|
+
help: <<~HELP,
|
81
|
+
Opt in/out of shopifolk beta
|
82
|
+
Usage: {{command:%s config [ analytics ] }}
|
83
|
+
HELP
|
84
|
+
enabled: "{{v}} shopifolk-beta has been enabled",
|
85
|
+
disabled: "{{v}} shopifolk-beta has been disabled",
|
86
|
+
is_enabled: "{{v}} shopifolk-beta is currently enabled",
|
87
|
+
is_disabled: "{{v}} shopifolk-beta is currently disabled",
|
88
|
+
},
|
73
89
|
},
|
74
90
|
|
75
91
|
git: {
|
@@ -300,6 +316,8 @@ module ShopifyCli
|
|
300
316
|
organization_not_found: "Cannot find a partner organization with that ID",
|
301
317
|
partners_notice: "Please visit https://partners.shopify.com/ to create a partners account",
|
302
318
|
},
|
319
|
+
first_party: "Are you working on a 1P (1st Party) app?",
|
320
|
+
identified_as_shopify: "We've identified you as a {{green:Shopify}} employee.",
|
303
321
|
organization: "Partner organization {{green:%s (%s)}}",
|
304
322
|
organization_select: "Select partner organization",
|
305
323
|
},
|
@@ -27,7 +27,7 @@ module ShopifyCli
|
|
27
27
|
# #### Parameters
|
28
28
|
# - `ctx`: running context from your command
|
29
29
|
# - `query_name`: name of the query you want to use, loaded from the `lib/graphql` directory.
|
30
|
-
# - `**
|
30
|
+
# - `**variables`: a hash of variables to be supplied to the query or mutation
|
31
31
|
#
|
32
32
|
# #### Raises
|
33
33
|
#
|
@@ -50,6 +50,13 @@ module ShopifyCli
|
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
|
+
def partners_url_for(organization_id, api_client_id, local_debug)
|
54
|
+
if ShopifyCli::Shopifolk.acting_as_shopify_organization?
|
55
|
+
organization_id = 'internal'
|
56
|
+
end
|
57
|
+
"#{partners_endpoint(local_debug)}/#{organization_id}/apps/#{api_client_id}"
|
58
|
+
end
|
59
|
+
|
53
60
|
private
|
54
61
|
|
55
62
|
def authenticated_req(ctx)
|
@@ -105,6 +112,15 @@ module ShopifyCli
|
|
105
112
|
return 'https://partners.shopify.com' if ENV[LOCAL_DEBUG].nil?
|
106
113
|
'https://partners.myshopify.io/'
|
107
114
|
end
|
115
|
+
|
116
|
+
def partners_endpoint(local_debug)
|
117
|
+
domain = if local_debug
|
118
|
+
'partners.myshopify.io'
|
119
|
+
else
|
120
|
+
'partners.shopify.com'
|
121
|
+
end
|
122
|
+
"https://#{domain}"
|
123
|
+
end
|
108
124
|
end
|
109
125
|
|
110
126
|
def auth_headers(token)
|
data/lib/shopify-cli/project.rb
CHANGED
@@ -34,7 +34,8 @@ module ShopifyCli
|
|
34
34
|
# project = ShopifyCli::Project.current
|
35
35
|
#
|
36
36
|
def current(force_reload: false)
|
37
|
-
|
37
|
+
clear if force_reload
|
38
|
+
at(Dir.pwd)
|
38
39
|
end
|
39
40
|
|
40
41
|
##
|
@@ -87,27 +88,30 @@ module ShopifyCli
|
|
87
88
|
content = Hash[{ project_type: project_type, organization_id: organization_id.to_i }
|
88
89
|
.merge(identifiers)
|
89
90
|
.collect { |k, v| [k.to_s, v] }]
|
91
|
+
content['shopify_organization'] = true if Shopifolk.acting_as_shopify_organization?
|
90
92
|
|
91
93
|
ctx.write('.shopify-cli.yml', YAML.dump(content))
|
94
|
+
clear
|
92
95
|
end
|
93
96
|
|
94
97
|
def project_name
|
95
98
|
File.basename(current.directory)
|
96
99
|
end
|
97
100
|
|
98
|
-
|
101
|
+
def clear
|
102
|
+
@at = nil
|
103
|
+
@dir = nil
|
104
|
+
end
|
99
105
|
|
100
|
-
|
101
|
-
@dir = nil if force_reload
|
106
|
+
private
|
102
107
|
|
108
|
+
def directory(dir)
|
103
109
|
@dir ||= Hash.new { |h, k| h[k] = __directory(k) }
|
104
110
|
@dir[dir]
|
105
111
|
end
|
106
112
|
|
107
|
-
def at(dir
|
108
|
-
|
109
|
-
|
110
|
-
proj_dir = directory(dir, force_reload: force_reload)
|
113
|
+
def at(dir)
|
114
|
+
proj_dir = directory(dir)
|
111
115
|
unless proj_dir
|
112
116
|
raise(ShopifyCli::Abort, Context.message('core.project.error.not_in_project'))
|
113
117
|
end
|
@@ -45,8 +45,11 @@ module ShopifyCli
|
|
45
45
|
File.join(ShopifyCli::PROJECT_TYPES_DIR, project_type.to_s, path)
|
46
46
|
end
|
47
47
|
|
48
|
-
def
|
48
|
+
def title(name)
|
49
49
|
@project_name = name
|
50
|
+
end
|
51
|
+
|
52
|
+
def creator(command_const)
|
50
53
|
@project_creator_command_class = command_const
|
51
54
|
ShopifyCli::Commands::Create.subcommand(command_const, @project_type)
|
52
55
|
end
|
@@ -55,6 +58,19 @@ module ShopifyCli
|
|
55
58
|
const_get(@project_creator_command_class)
|
56
59
|
end
|
57
60
|
|
61
|
+
def connector(command_const)
|
62
|
+
@project_connector_command_class = command_const
|
63
|
+
ShopifyCli::Commands::Connect.subcommand(command_const, @project_type)
|
64
|
+
end
|
65
|
+
|
66
|
+
def connect_command
|
67
|
+
if @project_connector_command_class.nil?
|
68
|
+
nil
|
69
|
+
else
|
70
|
+
const_get(@project_connector_command_class)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
58
74
|
def register_command(const, cmd)
|
59
75
|
return if project_load_shallow
|
60
76
|
Context.new.abort(
|
@@ -10,19 +10,36 @@ module ShopifyCli
|
|
10
10
|
SECTION = 'core'
|
11
11
|
FEATURE_NAME = 'shopifolk'
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
ShopifyCli::Shopifolk.
|
13
|
+
class << self
|
14
|
+
attr_writer :acting_as_shopify_organization
|
15
|
+
|
16
|
+
##
|
17
|
+
# will return if the user appears to be a Shopify employee, based on several heuristics
|
18
|
+
#
|
19
|
+
# #### Returns
|
20
|
+
#
|
21
|
+
# * `is_shopifolk` - returns true if the user is a Shopify Employee
|
22
|
+
#
|
23
|
+
# #### Example
|
24
|
+
#
|
25
|
+
# ShopifyCli::Shopifolk.check
|
26
|
+
#
|
27
|
+
def check
|
28
|
+
return false unless Feature.enabled?('shopifolk-beta')
|
29
|
+
ShopifyCli::Shopifolk.new.shopifolk?
|
30
|
+
end
|
31
|
+
|
32
|
+
def act_as_shopify_organization
|
33
|
+
@acting_as_shopify_organization = true
|
34
|
+
end
|
35
|
+
|
36
|
+
def acting_as_shopify_organization?
|
37
|
+
!!@acting_as_shopify_organization || (Project.has_current? && Project.current.config['shopify_organization'])
|
38
|
+
end
|
39
|
+
|
40
|
+
def reset
|
41
|
+
@acting_as_shopify_organization = nil
|
42
|
+
end
|
26
43
|
end
|
27
44
|
|
28
45
|
##
|
@@ -34,7 +51,9 @@ module ShopifyCli
|
|
34
51
|
# a valid google cloud config file with email ending in "@shopify.com"
|
35
52
|
#
|
36
53
|
def shopifolk?
|
54
|
+
return false unless Feature.enabled?('shopifolk-beta')
|
37
55
|
return true if Feature.enabled?(FEATURE_NAME)
|
56
|
+
|
38
57
|
if shopifolk_by_gcloud? && shopifolk_by_dev?
|
39
58
|
ShopifyCli::Feature.enable(FEATURE_NAME)
|
40
59
|
true
|
data/lib/shopify-cli/task.rb
CHANGED
@@ -6,5 +6,13 @@ module ShopifyCli
|
|
6
6
|
task = new
|
7
7
|
task.call(*args, **kwargs)
|
8
8
|
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def wants_to_run_against_shopify_org?
|
13
|
+
@ctx.puts(@ctx.message('core.tasks.select_org_and_shop.identified_as_shopify'))
|
14
|
+
message = @ctx.message('core.tasks.select_org_and_shop.first_party')
|
15
|
+
CLI::UI::Prompt.confirm(message, default: false)
|
16
|
+
end
|
9
17
|
end
|
10
18
|
end
|