shopify-cli 1.4.1 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +2 -2
  3. data/.github/CONTRIBUTING.md +9 -1
  4. data/.github/PULL_REQUEST_TEMPLATE.md +2 -2
  5. data/.github/workflows/release.yml +0 -1
  6. data/.github/workflows/triage.yml +22 -0
  7. data/.rubocop.yml +21 -7
  8. data/.rubocop_todo.yml +2 -15
  9. data/.travis.yml +0 -1
  10. data/CHANGELOG.md +5 -0
  11. data/Gemfile +1 -0
  12. data/Gemfile.lock +9 -6
  13. data/RELEASING.md +5 -13
  14. data/lib/project_types/extension/cli.rb +2 -1
  15. data/lib/project_types/node/cli.rb +4 -1
  16. data/lib/project_types/node/commands/connect.rb +15 -0
  17. data/lib/project_types/node/commands/create.rb +6 -7
  18. data/lib/project_types/node/messages/messages.rb +7 -6
  19. data/lib/project_types/rails/cli.rb +4 -1
  20. data/lib/project_types/rails/commands/connect.rb +15 -0
  21. data/lib/project_types/rails/commands/create.rb +6 -7
  22. data/lib/project_types/rails/messages/messages.rb +7 -4
  23. data/lib/project_types/script/cli.rb +2 -1
  24. data/lib/project_types/script/commands/enable.rb +12 -4
  25. data/lib/project_types/script/config/extension_points.yml +9 -8
  26. data/lib/project_types/script/errors.rb +4 -0
  27. data/lib/project_types/script/layers/application/build_script.rb +12 -16
  28. data/lib/project_types/script/layers/domain/errors.rb +3 -0
  29. data/lib/project_types/script/layers/domain/extension_point.rb +0 -1
  30. data/lib/project_types/script/layers/infrastructure/assemblyscript_project_creator.rb +13 -48
  31. data/lib/project_types/script/layers/infrastructure/assemblyscript_task_runner.rb +28 -7
  32. data/lib/project_types/script/layers/infrastructure/errors.rb +16 -0
  33. data/lib/project_types/script/layers/infrastructure/script_repository.rb +0 -12
  34. data/lib/project_types/script/layers/infrastructure/script_service.rb +2 -0
  35. data/lib/project_types/script/messages/messages.rb +22 -2
  36. data/lib/project_types/script/ui/error_handler.rb +25 -0
  37. data/lib/shopify-cli/api.rb +3 -1
  38. data/lib/shopify-cli/commands/config.rb +24 -0
  39. data/lib/shopify-cli/commands/connect.rb +32 -15
  40. data/lib/shopify-cli/core/monorail.rb +2 -1
  41. data/lib/shopify-cli/js_deps.rb +1 -1
  42. data/lib/shopify-cli/messages/messages.rb +22 -4
  43. data/lib/shopify-cli/partners_api.rb +17 -1
  44. data/lib/shopify-cli/process_supervision.rb +1 -1
  45. data/lib/shopify-cli/project.rb +12 -8
  46. data/lib/shopify-cli/project_type.rb +17 -1
  47. data/lib/shopify-cli/shopifolk.rb +32 -13
  48. data/lib/shopify-cli/task.rb +8 -0
  49. data/lib/shopify-cli/tasks/create_api_client.rb +9 -0
  50. data/lib/shopify-cli/tasks/ensure_env.rb +3 -0
  51. data/lib/shopify-cli/tasks/select_org_and_shop.rb +3 -0
  52. data/lib/shopify-cli/version.rb +1 -1
  53. metadata +5 -3
  54. 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
@@ -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
- }.merge(auth_headers(token))
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
- def call(*)
7
- project_type = ask_project_type unless Project.has_current?
8
-
9
- if Project.has_current? && Project.current && Project.current.env
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
- org = ShopifyCli::Tasks::EnsureEnv.call(@ctx, regenerate: true)
16
- write_cli_yml(project_type, org['id']) unless Project.has_current?
17
- api_key = Project.current(force_reload: true).env['api_key']
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 get_app(apps, api_key)
22
- apps.select { |app| app["apiKey"] == api_key }
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 self.help
43
- ShopifyCli::Context.message('core.connect.help', ShopifyCli::TOOL_NAME)
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/4.0'
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)
@@ -59,7 +59,7 @@ module ShopifyCli
59
59
  end
60
60
 
61
61
  def npm(verbose = false)
62
- cmd = %w(npm install --no-audit --no-optional)
62
+ cmd = %w(npm install --no-audit)
63
63
  cmd << '--quiet' unless verbose
64
64
 
65
65
  run_install_command(cmd)
@@ -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
- # - `**variable`: a hash of variables to be supplied to the query ro mutation
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)
@@ -206,7 +206,7 @@ module ShopifyCli
206
206
  unless ctx.windows?
207
207
  Process.kill('TERM', id)
208
208
  50.times do
209
- sleep 0.1
209
+ sleep(0.1)
210
210
  break unless stat(id)
211
211
  end
212
212
  end
@@ -34,7 +34,8 @@ module ShopifyCli
34
34
  # project = ShopifyCli::Project.current
35
35
  #
36
36
  def current(force_reload: false)
37
- at(Dir.pwd, force_reload: force_reload)
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
- private
101
+ def clear
102
+ @at = nil
103
+ @dir = nil
104
+ end
99
105
 
100
- def directory(dir, force_reload: false)
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, force_reload: false)
108
- @at = nil if force_reload
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 creator(name, command_const)
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
- # will return if the user appears to be a Shopify employee, based on several heuristics
15
- #
16
- # #### Returns
17
- #
18
- # * `is_shopifolk` - returns true if the user is a Shopify Employee
19
- #
20
- # #### Example
21
- #
22
- # ShopifyCli::Shopifolk.check
23
- #
24
- def self.check
25
- ShopifyCli::Shopifolk.new.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
@@ -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