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.
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