shopify-cli 2.2.0 → 2.4.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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -0
  3. data/.github/ISSUE_TEMPLATE.md +0 -4
  4. data/.github/workflows/shopify.yml +106 -0
  5. data/.gitignore +2 -0
  6. data/CHANGELOG.md +23 -0
  7. data/CONTRIBUTING.md +23 -0
  8. data/Dockerfile +19 -0
  9. data/Gemfile +1 -0
  10. data/Gemfile.lock +50 -13
  11. data/Rakefile +66 -0
  12. data/dev.yml +11 -1
  13. data/ext/shopify-extensions/extconf.rb +21 -0
  14. data/ext/shopify-extensions/shopify_extensions.rb +152 -0
  15. data/ext/shopify-extensions/version +1 -0
  16. data/lib/project_types/extension/cli.rb +14 -0
  17. data/lib/project_types/extension/commands/build.rb +30 -2
  18. data/lib/project_types/extension/commands/create.rb +25 -0
  19. data/lib/project_types/extension/commands/push.rb +0 -1
  20. data/lib/project_types/extension/features/argo.rb +1 -11
  21. data/lib/project_types/extension/forms/create.rb +4 -1
  22. data/lib/project_types/extension/forms/questions/ask_template.rb +44 -0
  23. data/lib/project_types/extension/messages/messages.rb +3 -0
  24. data/lib/project_types/extension/models/development_server.rb +35 -0
  25. data/lib/project_types/extension/models/development_server_requirements.rb +17 -0
  26. data/lib/project_types/extension/models/server_config/base.rb +31 -0
  27. data/lib/project_types/extension/models/server_config/development.rb +23 -0
  28. data/lib/project_types/extension/models/server_config/development_entries.rb +38 -0
  29. data/lib/project_types/extension/models/server_config/development_renderer.rb +30 -0
  30. data/lib/project_types/extension/models/server_config/extension.rb +35 -0
  31. data/lib/project_types/extension/models/server_config/root.rb +18 -0
  32. data/lib/project_types/extension/models/server_config/user.rb +10 -0
  33. data/lib/project_types/extension/models/specification_handlers/checkout_post_purchase.rb +10 -0
  34. data/lib/project_types/extension/models/specification_handlers/checkout_ui_extension.rb +1 -1
  35. data/lib/project_types/extension/tasks/choose_next_available_port.rb +1 -1
  36. data/lib/project_types/extension/tasks/run_extension_command.rb +58 -0
  37. data/lib/project_types/node/commands/create.rb +1 -5
  38. data/lib/project_types/rails/commands/create.rb +1 -5
  39. data/lib/project_types/script/cli.rb +2 -0
  40. data/lib/project_types/script/graphql/app_script_set.graphql +40 -0
  41. data/lib/project_types/script/graphql/app_script_update_or_create.graphql +0 -44
  42. data/lib/project_types/script/graphql/module_upload_url_generate.graphql +9 -0
  43. data/lib/project_types/script/layers/application/push_script.rb +10 -1
  44. data/lib/project_types/script/layers/domain/push_package.rb +1 -14
  45. data/lib/project_types/script/layers/infrastructure/api_clients.rb +89 -0
  46. data/lib/project_types/script/layers/infrastructure/errors.rb +2 -0
  47. data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_project_creator.rb +1 -1
  48. data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +0 -1
  49. data/lib/project_types/script/layers/infrastructure/script_service.rb +29 -94
  50. data/lib/project_types/script/layers/infrastructure/script_uploader.rb +27 -0
  51. data/lib/project_types/script/messages/messages.rb +3 -0
  52. data/lib/project_types/script/tasks/ensure_env.rb +2 -2
  53. data/lib/project_types/script/ui/error_handler.rb +6 -1
  54. data/lib/project_types/theme/commands/pull.rb +6 -0
  55. data/lib/project_types/theme/commands/push.rb +6 -0
  56. data/lib/shopify-cli/constants.rb +26 -0
  57. data/lib/shopify-cli/environment.rb +60 -0
  58. data/lib/shopify-cli/git.rb +2 -2
  59. data/lib/shopify-cli/identity_auth.rb +16 -23
  60. data/lib/shopify-cli/partners_api.rb +3 -27
  61. data/lib/shopify-cli/process_supervision.rb +14 -14
  62. data/lib/shopify-cli/theme/dev_server/header_hash.rb +4 -0
  63. data/lib/shopify-cli/theme/dev_server/hot_reload.rb +4 -5
  64. data/lib/shopify-cli/theme/dev_server/proxy.rb +13 -1
  65. data/lib/shopify-cli/theme/development_theme.rb +16 -2
  66. data/lib/shopify-cli/theme/file.rb +11 -3
  67. data/lib/shopify-cli/theme/ignore_filter.rb +7 -0
  68. data/lib/shopify-cli/theme/syncer.rb +1 -1
  69. data/lib/shopify-cli/version.rb +1 -1
  70. data/lib/shopify_cli.rb +4 -2
  71. data/shopify-cli.gemspec +3 -3
  72. metadata +31 -9
  73. data/.github/workflows/build.yml +0 -28
@@ -1,44 +0,0 @@
1
- mutation AppScriptUpdateOrCreate(
2
- $extensionPointName: ExtensionPointName!,
3
- $title: String,
4
- $description: String,
5
- $sourceCode: String,
6
- $language: String,
7
- $force: Boolean,
8
- $schemaMajorVersion: String,
9
- $schemaMinorVersion: String,
10
- $useMsgpack: Boolean,
11
- $uuid: String,
12
- $configurationUi: Boolean!,
13
- $scriptJsonVersion: String!,
14
- $configurationDefinition: String!,
15
- ) {
16
- appScriptUpdateOrCreate(
17
- extensionPointName: $extensionPointName
18
- title: $title
19
- description: $description
20
- sourceCode: $sourceCode
21
- language: $language
22
- force: $force
23
- schemaMajorVersion: $schemaMajorVersion
24
- schemaMinorVersion: $schemaMinorVersion
25
- useMsgpack: $useMsgpack,
26
- uuid: $uuid
27
- configurationUi: $configurationUi
28
- scriptJsonVersion: $scriptJsonVersion
29
- configurationDefinition: $configurationDefinition
30
- ) {
31
- userErrors {
32
- field
33
- message
34
- tag
35
- }
36
- appScript {
37
- uuid
38
- appKey
39
- configSchema
40
- extensionPointName
41
- title
42
- }
43
- }
44
- }
@@ -0,0 +1,9 @@
1
+ mutation moduleUploadUrlGenerate {
2
+ moduleUploadUrlGenerate {
3
+ url
4
+ userErrors {
5
+ field
6
+ message
7
+ }
8
+ }
9
+ }
@@ -20,7 +20,16 @@ module Script
20
20
  compiled_type: task_runner.compiled_type,
21
21
  metadata: task_runner.metadata,
22
22
  )
23
- uuid = package.push(Infrastructure::ScriptService.new(ctx: p_ctx), script_project.api_key, force)
23
+ script_service = Infrastructure::ScriptService.new(ctx: p_ctx, api_key: script_project.api_key)
24
+ module_upload_url = Infrastructure::ScriptUploader.new(script_service).upload(package.script_content)
25
+ uuid = script_service.set_app_script(
26
+ uuid: package.uuid,
27
+ extension_point_type: package.extension_point_type,
28
+ force: force,
29
+ metadata: package.metadata,
30
+ script_json: package.script_json,
31
+ module_upload_url: module_upload_url,
32
+ )
24
33
  script_project_repo.update_env(uuid: uuid)
25
34
  spinner.update_title(p_ctx.message("script.application.pushed"))
26
35
  end
@@ -17,7 +17,7 @@ module Script
17
17
  uuid:,
18
18
  extension_point_type:,
19
19
  script_content:,
20
- compiled_type:,
20
+ compiled_type: nil,
21
21
  metadata:,
22
22
  script_json:
23
23
  )
@@ -29,19 +29,6 @@ module Script
29
29
  @metadata = metadata
30
30
  @script_json = script_json
31
31
  end
32
-
33
- def push(script_service, api_key, force)
34
- script_service.push(
35
- uuid: @uuid,
36
- extension_point_type: @extension_point_type,
37
- script_content: @script_content,
38
- compiled_type: @compiled_type,
39
- api_key: api_key,
40
- force: force,
41
- metadata: @metadata,
42
- script_json: @script_json,
43
- )
44
- end
45
32
  end
46
33
  end
47
34
  end
@@ -0,0 +1,89 @@
1
+ module Script
2
+ module Layers
3
+ module Infrastructure
4
+ class ApiClients
5
+ def self.default_client(ctx, api_key)
6
+ if ENV["BYPASS_PARTNERS_PROXY"]
7
+ ScriptServiceApiClient.new(ctx, api_key)
8
+ else
9
+ PartnersProxyApiClient.new(ctx, api_key)
10
+ end
11
+ end
12
+
13
+ class ScriptServiceApiClient
14
+ LOCAL_INSTANCE_URL = "https://script-service.myshopify.io"
15
+
16
+ def initialize(ctx, api_key)
17
+ instance_url = script_service_url
18
+ @api = ShopifyCli::API.new(
19
+ ctx: ctx,
20
+ url: "#{instance_url}/graphql",
21
+ token: { "APP_KEY" => api_key }.compact.to_json,
22
+ auth_header: "X-Shopify-Authenticated-Tokens"
23
+ )
24
+ end
25
+
26
+ def query(query_name, variables: {})
27
+ @api.query(query_name, variables: variables)
28
+ end
29
+
30
+ private
31
+
32
+ def script_service_url
33
+ if ::ShopifyCli::Environment.use_spin_partners_instance?
34
+ "https://script-service.#{::ShopifyCli::Environment.spin_url}"
35
+ else
36
+ LOCAL_INSTANCE_URL
37
+ end
38
+ end
39
+ end
40
+ private_constant(:ScriptServiceApiClient)
41
+
42
+ class PartnersProxyApiClient
43
+ def initialize(ctx, api_key)
44
+ @ctx = ctx
45
+ @api_key = api_key
46
+ end
47
+
48
+ def query(query_name, variables: {})
49
+ response = ShimAPI.query(@ctx, query_name, api_key: @api_key, variables: variables.to_json)
50
+ raise_if_graphql_failed(response)
51
+ JSON.parse(response["data"]["scriptServiceProxy"])
52
+ end
53
+
54
+ def raise_if_graphql_failed(response)
55
+ raise Errors::EmptyResponseError if response.nil?
56
+
57
+ return unless response.key?("errors")
58
+ case error_code(response["errors"])
59
+ when "forbidden"
60
+ raise Errors::ForbiddenError
61
+ when "forbidden_on_shop"
62
+ raise Errors::ShopAuthenticationError
63
+ when "app_not_installed_on_shop"
64
+ raise Errors::AppNotInstalledError
65
+ else
66
+ raise Errors::GraphqlError, response["errors"]
67
+ end
68
+ end
69
+
70
+ def error_code(errors)
71
+ errors.map do |e|
72
+ code = e.dig("extensions", "code")
73
+ return code if code
74
+ end
75
+ end
76
+
77
+ class ShimAPI < ShopifyCli::PartnersAPI
78
+ def query(query_name, variables: {})
79
+ variables[:query] = load_query(query_name)
80
+ super("script_service_proxy", variables: variables)
81
+ end
82
+ end
83
+ private_constant(:ShimAPI)
84
+ end
85
+ private_constant(:PartnersProxyApiClient)
86
+ end
87
+ end
88
+ end
89
+ end
@@ -93,6 +93,8 @@ module Script
93
93
  super("WebAssembly binary not found")
94
94
  end
95
95
  end
96
+
97
+ class ScriptUploadError < ScriptProjectError; end
96
98
  end
97
99
  end
98
100
  end
@@ -40,7 +40,7 @@ module Script
40
40
  def extension_point_version
41
41
  return extension_point.sdks.assemblyscript.version if extension_point.sdks.assemblyscript.versioned?
42
42
 
43
- out = command_runner.call("npm show #{extension_point.sdks.assemblyscript.package} version --json")
43
+ out = command_runner.call("npm -s show #{extension_point.sdks.assemblyscript.package} version --json")
44
44
  "^#{JSON.parse(out)}"
45
45
  end
46
46
 
@@ -32,7 +32,6 @@ module Script
32
32
  uuid: script_project.uuid,
33
33
  extension_point_type: script_project.extension_point_type,
34
34
  script_content: script_content,
35
- compiled_type: compiled_type,
36
35
  metadata: metadata,
37
36
  script_json: script_project.script_json,
38
37
  )
@@ -9,37 +9,39 @@ module Script
9
9
  class ScriptService
10
10
  include SmartProperties
11
11
  property! :ctx, accepts: ShopifyCli::Context
12
+ property! :api_key, accepts: String
12
13
 
13
- def push(
14
+ def initialize(*args, ctx:, api_key:, **kwargs)
15
+ super(*args, ctx: ctx, api_key: api_key, **kwargs)
16
+ @client = ApiClients.default_client(ctx, api_key)
17
+ end
18
+
19
+ def set_app_script(
14
20
  uuid:,
15
21
  extension_point_type:,
16
- script_content:,
17
- compiled_type:,
18
- api_key: nil,
19
22
  force: false,
20
23
  metadata:,
21
- script_json:
24
+ script_json:,
25
+ module_upload_url:
22
26
  )
23
- query_name = "app_script_update_or_create"
27
+ query_name = "app_script_set"
24
28
  variables = {
25
29
  uuid: uuid,
26
30
  extensionPointName: extension_point_type.upcase,
27
31
  title: script_json.title,
28
32
  description: script_json.description,
29
- sourceCode: Base64.encode64(script_content),
30
- language: compiled_type,
31
33
  force: force,
32
34
  schemaMajorVersion: metadata.schema_major_version.to_s, # API expects string value
33
35
  schemaMinorVersion: metadata.schema_minor_version.to_s, # API expects string value
34
- useMsgpack: metadata.use_msgpack,
35
36
  scriptJsonVersion: script_json.version,
36
37
  configurationUi: script_json.configuration_ui,
37
38
  configurationDefinition: script_json.configuration&.to_json,
39
+ moduleUploadUrl: module_upload_url,
38
40
  }
39
- resp_hash = script_service_request(query_name: query_name, api_key: api_key, variables: variables)
40
- user_errors = resp_hash["data"]["appScriptUpdateOrCreate"]["userErrors"]
41
+ resp_hash = make_request(query_name: query_name, variables: variables)
42
+ user_errors = resp_hash["data"]["appScriptSet"]["userErrors"]
41
43
 
42
- return resp_hash["data"]["appScriptUpdateOrCreate"]["appScript"]["uuid"] if user_errors.empty?
44
+ return resp_hash["data"]["appScriptSet"]["appScript"]["uuid"] if user_errors.empty?
43
45
 
44
46
  if user_errors.any? { |e| e["tag"] == "already_exists_error" }
45
47
  raise Errors::ScriptRepushError, uuid
@@ -64,98 +66,31 @@ module Script
64
66
  end
65
67
  end
66
68
 
67
- def get_app_scripts(api_key:, extension_point_type:)
69
+ def get_app_scripts(extension_point_type:)
68
70
  query_name = "get_app_scripts"
69
71
  variables = { appKey: api_key, extensionPointName: extension_point_type.upcase }
70
- script_service_request(query_name: query_name, api_key: api_key, variables: variables)["data"]["appScripts"]
72
+ response = make_request(query_name: query_name, variables: variables)
73
+ response["data"]["appScripts"]
71
74
  end
72
75
 
73
- private
74
-
75
- class ScriptServiceAPI < ShopifyCli::API
76
- property(:api_key, accepts: String)
76
+ def generate_module_upload_url
77
+ query_name = "module_upload_url_generate"
78
+ variables = {}
79
+ response = make_request(query_name: query_name, variables: variables)
80
+ user_errors = response["data"]["moduleUploadUrlGenerate"]["userErrors"]
77
81
 
78
- LOCAL_INSTANCE_URL = "https://script-service.myshopify.io"
79
-
80
- def self.query(ctx, query_name, api_key: nil, variables: {})
81
- api_client(ctx, api_key).query(query_name, variables: variables)
82
- end
83
-
84
- def self.api_client(ctx, api_key)
85
- instance_url = spin_instance_url || LOCAL_INSTANCE_URL
86
- new(
87
- ctx: ctx,
88
- url: "#{instance_url}/graphql",
89
- token: "",
90
- api_key: api_key
91
- )
92
- end
93
-
94
- def self.spin_instance_url
95
- workspace = ENV["SPIN_WORKSPACE"]
96
- namespace = ENV["SPIN_NAMESPACE"]
97
- return if workspace.nil? || namespace.nil?
98
-
99
- "https://script-service.#{workspace}.#{namespace}.us.spin.dev"
100
- end
101
-
102
- def auth_headers(*)
103
- tokens = { "APP_KEY" => api_key }.compact.to_json
104
- { "X-Shopify-Authenticated-Tokens" => tokens }
105
- end
82
+ raise Errors::GraphqlError, user_errors if user_errors.any?
83
+ response["data"]["moduleUploadUrlGenerate"]["url"]
106
84
  end
107
- private_constant(:ScriptServiceAPI)
108
85
 
109
- class PartnersProxyAPI < ShopifyCli::PartnersAPI
110
- def query(query_name, variables: {})
111
- variables[:query] = load_query(query_name)
112
- super("script_service_proxy", variables: variables)
113
- end
114
- end
115
- private_constant(:PartnersProxyAPI)
116
-
117
- def script_service_request(query_name:, variables: nil, **options)
118
- resp = if bypass_partners_proxy
119
- ScriptServiceAPI.query(ctx, query_name, variables: variables, **options)
120
- else
121
- proxy_through_partners(query_name: query_name, variables: variables, **options)
122
- end
123
- raise_if_graphql_failed(resp)
124
- resp
125
- end
126
-
127
- def bypass_partners_proxy
128
- !ENV["BYPASS_PARTNERS_PROXY"].nil?
129
- end
130
-
131
- def proxy_through_partners(query_name:, variables: nil, **options)
132
- options[:variables] = variables.to_json if variables
133
- resp = PartnersProxyAPI.query(ctx, query_name, **options)
134
- raise_if_graphql_failed(resp)
135
- JSON.parse(resp["data"]["scriptServiceProxy"])
136
- end
86
+ private
137
87
 
138
- def raise_if_graphql_failed(response)
88
+ def make_request(query_name:, variables: {})
89
+ response = @client.query(query_name, variables: variables)
139
90
  raise Errors::EmptyResponseError if response.nil?
91
+ raise Errors::GraphqlError, response["errors"] if response.key?("errors")
140
92
 
141
- return unless response.key?("errors")
142
- case error_code(response["errors"])
143
- when "forbidden"
144
- raise Errors::ForbiddenError
145
- when "forbidden_on_shop"
146
- raise Errors::ShopAuthenticationError
147
- when "app_not_installed_on_shop"
148
- raise Errors::AppNotInstalledError
149
- else
150
- raise Errors::GraphqlError, response["errors"]
151
- end
152
- end
153
-
154
- def error_code(errors)
155
- errors.map do |e|
156
- code = e.dig("extensions", "code")
157
- return code if code
158
- end
93
+ response
159
94
  end
160
95
  end
161
96
  end
@@ -0,0 +1,27 @@
1
+ module Script
2
+ module Layers
3
+ module Infrastructure
4
+ class ScriptUploader
5
+ def initialize(script_service)
6
+ @script_service = script_service
7
+ end
8
+
9
+ def upload(script_content)
10
+ @script_service.generate_module_upload_url.tap do |url|
11
+ url = URI(url)
12
+
13
+ https = Net::HTTP.new(url.host, url.port)
14
+ https.use_ssl = true
15
+
16
+ request = Net::HTTP::Put.new(url)
17
+ request["Content-Type"] = "application/wasm"
18
+ request.body = script_content
19
+
20
+ response = https.request(request)
21
+ raise Errors::ScriptUploadError unless response.code == "200"
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -141,6 +141,9 @@ module Script
141
141
  web_assembly_binary_not_found_suggestion: "No WebAssembly binary found." \
142
142
  "Check that your build npm script outputs the generated binary to the root of the directory." \
143
143
  "Generated binary should match the script name: <script_name>.wasm",
144
+
145
+ script_upload_cause: "Fail to upload script.",
146
+ script_upload_help: "Try again.",
144
147
  },
145
148
 
146
149
  create: {
@@ -89,8 +89,8 @@ module Script
89
89
  end
90
90
 
91
91
  def ask_script_uuid(app, extension_point_type)
92
- script_service = Layers::Infrastructure::ScriptService.new(ctx: ctx)
93
- scripts = script_service.get_app_scripts(api_key: app["apiKey"], extension_point_type: extension_point_type)
92
+ script_service = Layers::Infrastructure::ScriptService.new(ctx: ctx, api_key: app["apiKey"])
93
+ scripts = script_service.get_app_scripts(extension_point_type: extension_point_type)
94
94
 
95
95
  return nil unless scripts.count > 0 &&
96
96
  CLI::UI::Prompt.confirm(ctx.message("script.application.ensure_env.ask_connect_to_existing_script"))
@@ -99,7 +99,7 @@ module Script
99
99
  cause_of_error: ShopifyCli::Context.message("script.error.invalid_extension_cause", e.type),
100
100
  help_suggestion: ShopifyCli::Context.message(
101
101
  "script.error.invalid_extension_help",
102
- Script::Layers::Application::ExtensionPoints.types.join(", ")
102
+ Script::Layers::Application::ExtensionPoints.available_types.join(", ")
103
103
  ),
104
104
  }
105
105
  when Layers::Domain::Errors::ScriptNotFoundError
@@ -237,6 +237,11 @@ module Script
237
237
  cause_of_error: ShopifyCli::Context.message("script.error.web_assembly_binary_not_found"),
238
238
  help_suggestion: ShopifyCli::Context.message("script.error.web_assembly_binary_not_found_suggestion"),
239
239
  }
240
+ when Layers::Infrastructure::Errors::ScriptUploadError
241
+ {
242
+ cause_of_error: ShopifyCli::Context.message("script.error.script_upload_cause"),
243
+ help_suggestion: ShopifyCli::Context.message("script.error.script_upload_help"),
244
+ }
240
245
  end
241
246
  end
242
247
  end
@@ -9,6 +9,10 @@ module Theme
9
9
  options do |parser, flags|
10
10
  parser.on("-n", "--nodelete") { flags[:nodelete] = true }
11
11
  parser.on("-i", "--themeid=ID") { |theme_id| flags[:theme_id] = theme_id }
12
+ parser.on("-x", "--ignore=PATTERN") do |pattern|
13
+ flags[:ignores] ||= []
14
+ flags[:ignores] << pattern
15
+ end
12
16
  end
13
17
 
14
18
  def call(args, _name)
@@ -29,6 +33,8 @@ module Theme
29
33
  end
30
34
 
31
35
  ignore_filter = ShopifyCli::Theme::IgnoreFilter.from_path(root)
36
+ ignore_filter.add_patterns(options.flags[:ignores]) if options.flags[:ignores]
37
+
32
38
  syncer = ShopifyCli::Theme::Syncer.new(@ctx, theme: theme, ignore_filter: ignore_filter)
33
39
  begin
34
40
  syncer.start_threads
@@ -15,6 +15,10 @@ module Theme
15
15
  parser.on("-j", "--json") { flags[:json] = true }
16
16
  parser.on("-a", "--allow-live") { flags[:allow_live] = true }
17
17
  parser.on("-p", "--publish") { flags[:publish] = true }
18
+ parser.on("-x", "--ignore=PATTERN") do |pattern|
19
+ flags[:ignores] ||= []
20
+ flags[:ignores] << pattern
21
+ end
18
22
  end
19
23
 
20
24
  def call(args, _name)
@@ -48,6 +52,8 @@ module Theme
48
52
  end
49
53
 
50
54
  ignore_filter = ShopifyCli::Theme::IgnoreFilter.from_path(root)
55
+ ignore_filter.add_patterns(options.flags[:ignores]) if options.flags[:ignores]
56
+
51
57
  syncer = ShopifyCli::Theme::Syncer.new(@ctx, theme: theme, ignore_filter: ignore_filter)
52
58
  begin
53
59
  syncer.start_threads
@@ -0,0 +1,26 @@
1
+ module ShopifyCli
2
+ module Constants
3
+ module EnvironmentVariables
4
+ # When true the CLI points to a local instance of
5
+ # the partners dashboard and identity.
6
+ LOCAL_PARTNERS = "SHOPIFY_APP_CLI_LOCAL_PARTNERS"
7
+
8
+ # When true the CLI points to a spin instance of spin
9
+ SPIN_PARTNERS = "SHOPIFY_APP_CLI_SPIN_PARTNERS"
10
+
11
+ SPIN_WORKSPACE = "SPIN_WORKSPACE"
12
+
13
+ SPIN_NAMESPACE = "SPIN_NAMESPACE"
14
+
15
+ SPIN_HOST = "SPIN_HOST"
16
+
17
+ # Set to true when running tests.
18
+ RUNNING_TESTS = "RUNNING_SHOPIFY_CLI_TESTS"
19
+ end
20
+
21
+ module Identity
22
+ CLIENT_ID_DEV = "e5380e02-312a-7408-5718-e07017e9cf52"
23
+ CLIENT_ID = "fbdb2649-e327-4907-8f67-908d24cfd7e3"
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,60 @@
1
+ module ShopifyCli
2
+ # The environment module provides an interface to get information from
3
+ # the environment in which the CLI runs
4
+ module Environment
5
+ TRUTHY_ENV_VARIABLE_VALUES = ["1", "true", "TRUE", "yes", "YES"]
6
+ def self.use_local_partners_instance?(env_variables: ENV)
7
+ env_variable_truthy?(
8
+ Constants::EnvironmentVariables::LOCAL_PARTNERS,
9
+ env_variables: env_variables
10
+ )
11
+ end
12
+
13
+ def self.use_spin_partners_instance?(env_variables: ENV)
14
+ env_variable_truthy?(
15
+ Constants::EnvironmentVariables::SPIN_PARTNERS,
16
+ env_variables: env_variables
17
+ )
18
+ end
19
+
20
+ def self.running_tests?(env_variables: ENV)
21
+ env_variable_truthy?(
22
+ Constants::EnvironmentVariables::RUNNING_TESTS,
23
+ env_variables: env_variables
24
+ )
25
+ end
26
+
27
+ def self.partners_domain(env_variables: ENV)
28
+ if use_local_partners_instance?(env_variables: env_variables)
29
+ "partners.myshopify.io"
30
+ elsif use_spin_partners_instance?(env_variables: env_variables)
31
+ "partners.#{spin_url(env_variables: env_variables)}"
32
+ else
33
+ "partners.shopify.com"
34
+ end
35
+ end
36
+
37
+ def self.spin_url(env_variables: ENV)
38
+ spin_workspace = spin_workspace(env_variables: env_variables)
39
+ spin_namespace = spin_namespace(env_variables: env_variables)
40
+ spin_host = spin_host(env_variables: env_variables)
41
+ "#{spin_workspace}.#{spin_namespace}.#{spin_host}"
42
+ end
43
+
44
+ def self.env_variable_truthy?(variable_name, env_variables: ENV)
45
+ TRUTHY_ENV_VARIABLE_VALUES.include?(env_variables[variable_name.to_s])
46
+ end
47
+
48
+ def self.spin_workspace(env_variables: ENV)
49
+ env_variables[Constants::EnvironmentVariables::SPIN_WORKSPACE]
50
+ end
51
+
52
+ def self.spin_namespace(env_variables: ENV)
53
+ env_variables[Constants::EnvironmentVariables::SPIN_NAMESPACE]
54
+ end
55
+
56
+ def self.spin_host(env_variables: ENV)
57
+ env_variables[Constants::EnvironmentVariables::SPIN_HOST] || "us.spin.dev"
58
+ end
59
+ end
60
+ end
@@ -108,8 +108,8 @@ module ShopifyCli
108
108
  private
109
109
 
110
110
  def exec(*args, dir: Dir.pwd, default: nil, ctx: Context.new)
111
- args = %w(git) + args
112
- out, _, stat = ctx.capture3(*args, chdir: dir)
111
+ args = %w(git) + ["--git-dir", File.join(dir, ".git")] + args
112
+ out, _, stat = ctx.capture3(*args)
113
113
  return default unless stat.success?
114
114
  out.chomp
115
115
  end
@@ -17,7 +17,6 @@ module ShopifyCli
17
17
  class Error < StandardError; end
18
18
  class Timeout < StandardError; end
19
19
  LocalRequest = Struct.new(:method, :path, :query, :protocol)
20
- LOCAL_DEBUG = "SHOPIFY_APP_CLI_LOCAL_PARTNERS"
21
20
 
22
21
  DEFAULT_PORT = 3456
23
22
  REDIRECT_HOST = "http://127.0.0.1:#{DEFAULT_PORT}"
@@ -236,12 +235,17 @@ module ShopifyCli
236
235
  end
237
236
 
238
237
  def auth_url
239
- return "https://accounts.shopify.com/oauth" if ENV[LOCAL_DEBUG].nil?
240
- "https://identity.myshopify.io/oauth"
238
+ if Environment.use_local_partners_instance?
239
+ "https://identity.myshopify.io/oauth"
240
+ elsif Environment.use_spin_partners_instance?
241
+ "https://identity.#{Environment.spin_url}/oauth"
242
+ else
243
+ "https://accounts.shopify.com/oauth"
244
+ end
241
245
  end
242
246
 
243
247
  def client_id_for_application(application_name)
244
- client_ids = if ENV[LOCAL_DEBUG]
248
+ client_ids = if Environment.use_local_partners_instance? || Environment.use_spin_partners_instance?
245
249
  DEV_APPLICATION_CLIENT_IDS
246
250
  else
247
251
  APPLICATION_CLIENT_IDS
@@ -257,26 +261,15 @@ module ShopifyCli
257
261
  end
258
262
 
259
263
  def client_id
260
- return "fbdb2649-e327-4907-8f67-908d24cfd7e3" if ENV[LOCAL_DEBUG].nil?
261
-
262
- ctx.abort(ctx.message("core.identity_auth.error.local_identity_not_running")) unless local_identity_running?
263
-
264
- # Fetch the client ID from the local Identity Dynamic Registration endpoint
265
- response = post_request("/client", {
266
- name: "shopify-cli-development",
267
- public_type: "native",
268
- })
269
-
270
- response["client_id"]
271
- end
272
-
273
- def local_identity_running?
274
- Net::HTTP.start("identity.myshopify.io", 443, use_ssl: true, open_timeout: 1, read_timeout: 10) do |http|
275
- req = Net::HTTP::Get.new(URI.join("https://identity.myshopify.io", "/services/ping"))
276
- http.request(req).is_a?(Net::HTTPSuccess)
264
+ if Environment.use_local_partners_instance? || Environment.use_spin_partners_instance?
265
+ Constants::Identity::CLIENT_ID_DEV
266
+ else
267
+ # In the future we might want to use Identity's dynamic
268
+ # registration. To migrate to a dynamic client ID we
269
+ # need to refactor some code that relies on a static
270
+ # value for the client
271
+ Constants::Identity::CLIENT_ID
277
272
  end
278
- rescue Timeout::Error, Errno::EHOSTUNREACH, Errno::EHOSTDOWN, Errno::EADDRNOTAVAIL, Errno::ECONNREFUSED
279
- false
280
273
  end
281
274
  end
282
275
  end