shopify-cli 2.7.0 → 2.7.4

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 (114) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +2 -2
  3. data/.github/workflows/shopify.yml +1 -1
  4. data/.gitignore +2 -0
  5. data/.ruby-version +1 -1
  6. data/CHANGELOG.md +46 -0
  7. data/Codespace.dockerfile +2 -2
  8. data/Gemfile.lock +4 -4
  9. data/Rakefile +27 -0
  10. data/Tests.dockerfile +2 -2
  11. data/dev.yml +3 -3
  12. data/ext/javy/hashes/javy-arm-macos-v0.1.0.gz.sha256 +1 -0
  13. data/ext/javy/hashes/javy-x86_64-linux-v0.1.0.gz.sha256 +1 -0
  14. data/ext/javy/hashes/javy-x86_64-macos-v0.1.0.gz.sha256 +1 -0
  15. data/ext/javy/hashes/javy-x86_64-windows-v0.1.0.gz.sha256 +1 -0
  16. data/ext/javy/javy.rb +204 -0
  17. data/ext/javy/version +1 -0
  18. data/lib/graphql/get_extension_registrations.graphql +27 -0
  19. data/lib/project_types/extension/cli.rb +27 -2
  20. data/lib/project_types/extension/commands/build.rb +10 -14
  21. data/lib/project_types/extension/commands/create.rb +3 -6
  22. data/lib/project_types/extension/commands/push.rb +36 -8
  23. data/lib/project_types/extension/extension_project.rb +1 -1
  24. data/lib/project_types/extension/features/argo_serve.rb +6 -5
  25. data/lib/project_types/extension/forms/questions/ask_registration.rb +6 -2
  26. data/lib/project_types/extension/loaders/project.rb +29 -0
  27. data/lib/project_types/extension/loaders/specification_handler.rb +22 -0
  28. data/lib/project_types/extension/messages/messages.rb +4 -2
  29. data/lib/project_types/extension/models/app.rb +1 -1
  30. data/lib/project_types/extension/models/development_server.rb +2 -2
  31. data/lib/project_types/extension/models/specification_handlers/default.rb +4 -0
  32. data/lib/project_types/extension/tasks/convert_server_config.rb +3 -1
  33. data/lib/project_types/extension/tasks/execute_commands/base.rb +13 -0
  34. data/lib/project_types/extension/tasks/execute_commands/build.rb +29 -0
  35. data/lib/project_types/extension/tasks/execute_commands/create.rb +33 -0
  36. data/lib/project_types/extension/tasks/execute_commands/serve.rb +35 -0
  37. data/lib/project_types/extension/tasks/merge_server_config.rb +33 -22
  38. data/lib/project_types/rails/commands/create.rb +2 -4
  39. data/lib/project_types/script/cli.rb +9 -1
  40. data/lib/project_types/script/commands/connect.rb +19 -0
  41. data/lib/project_types/script/commands/create.rb +1 -3
  42. data/lib/project_types/script/commands/javy.rb +29 -0
  43. data/lib/project_types/script/commands/push.rb +2 -1
  44. data/lib/project_types/script/config/extension_points.yml +12 -30
  45. data/lib/project_types/script/forms/ask_app.rb +32 -0
  46. data/lib/project_types/script/forms/ask_org.rb +30 -0
  47. data/lib/project_types/script/forms/ask_script_uuid.rb +22 -0
  48. data/lib/project_types/script/forms/run_against_shopify_org.rb +14 -0
  49. data/lib/project_types/script/graphql/app_script_set.graphql +2 -2
  50. data/lib/project_types/script/layers/application/build_script.rb +0 -1
  51. data/lib/project_types/script/layers/application/connect_app.rb +79 -0
  52. data/lib/project_types/script/layers/application/create_script.rb +17 -17
  53. data/lib/project_types/script/layers/application/push_script.rb +1 -1
  54. data/lib/project_types/script/layers/domain/errors.rb +1 -4
  55. data/lib/project_types/script/layers/domain/push_package.rb +3 -3
  56. data/lib/project_types/script/layers/domain/{script_json.rb → script_config.rb} +2 -2
  57. data/lib/project_types/script/layers/domain/script_project.rb +5 -1
  58. data/lib/project_types/script/layers/infrastructure/errors.rb +36 -7
  59. data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_task_runner.rb +0 -4
  60. data/lib/project_types/script/layers/infrastructure/languages/typescript_task_runner.rb +0 -4
  61. data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +2 -2
  62. data/lib/project_types/script/layers/infrastructure/script_project_repository.rb +125 -27
  63. data/lib/project_types/script/layers/infrastructure/script_service.rb +11 -11
  64. data/lib/project_types/script/messages/messages.rb +32 -4
  65. data/lib/project_types/script/ui/error_handler.rb +31 -21
  66. data/lib/project_types/theme/commands/pull.rb +3 -0
  67. data/lib/project_types/theme/commands/push.rb +7 -1
  68. data/lib/project_types/theme/commands/serve.rb +1 -1
  69. data/lib/project_types/theme/messages/messages.rb +35 -1
  70. data/lib/project_types/theme/ui/sync_progress_bar.rb +2 -2
  71. data/lib/shopify_cli/command/project_command.rb +20 -7
  72. data/lib/shopify_cli/command.rb +6 -0
  73. data/lib/shopify_cli/commands/app/create/node.rb +1 -3
  74. data/lib/shopify_cli/commands/app/create/rails.rb +1 -3
  75. data/lib/shopify_cli/constants.rb +7 -0
  76. data/lib/shopify_cli/context.rb +11 -1
  77. data/lib/shopify_cli/environment.rb +4 -0
  78. data/lib/shopify_cli/form.rb +2 -0
  79. data/lib/shopify_cli/git.rb +2 -0
  80. data/lib/shopify_cli/identity_auth.rb +18 -0
  81. data/lib/shopify_cli/messages/messages.rb +9 -2
  82. data/lib/shopify_cli/partners_api/app_extensions/job.rb +36 -0
  83. data/lib/shopify_cli/partners_api/app_extensions.rb +46 -0
  84. data/lib/shopify_cli/partners_api/organizations.rb +2 -5
  85. data/lib/shopify_cli/partners_api.rb +2 -8
  86. data/lib/shopify_cli/project.rb +8 -7
  87. data/lib/shopify_cli/resources/env_file.rb +13 -5
  88. data/lib/shopify_cli/services/app/create/node_service.rb +2 -0
  89. data/lib/shopify_cli/services/app/create/php_service.rb +1 -1
  90. data/lib/shopify_cli/services/app/create/rails_service.rb +3 -1
  91. data/lib/shopify_cli/services/app/serve/node_service.rb +1 -1
  92. data/lib/shopify_cli/services/app/serve/rails_service.rb +1 -1
  93. data/lib/shopify_cli/tasks/ensure_authenticated.rb +9 -3
  94. data/lib/shopify_cli/theme/dev_server/cdn_fonts.rb +73 -0
  95. data/lib/shopify_cli/theme/dev_server/hot-reload.js +38 -9
  96. data/lib/shopify_cli/theme/dev_server/proxy/template_param_builder.rb +84 -0
  97. data/lib/shopify_cli/theme/dev_server/proxy.rb +9 -15
  98. data/lib/shopify_cli/theme/dev_server.rb +32 -19
  99. data/lib/shopify_cli/theme/syncer/error_reporter.rb +45 -0
  100. data/lib/shopify_cli/theme/syncer/operation.rb +56 -0
  101. data/lib/shopify_cli/theme/syncer/standard_reporter.rb +32 -0
  102. data/lib/shopify_cli/theme/syncer.rb +40 -39
  103. data/lib/shopify_cli/theme/theme.rb +31 -19
  104. data/lib/shopify_cli/thread_pool/job.rb +27 -0
  105. data/lib/shopify_cli/thread_pool.rb +37 -0
  106. data/lib/shopify_cli/tunnel.rb +26 -22
  107. data/lib/shopify_cli/version.rb +1 -1
  108. data/shopify-cli.gemspec +1 -1
  109. data/vendor/deps/cli-kit/lib/cli/kit/error_handler.rb +3 -1
  110. data/vendor/deps/cli-kit/lib/cli/kit/system.rb +1 -1
  111. metadata +34 -8
  112. data/lib/graphql/all_orgs_with_extensions.graphql +0 -37
  113. data/lib/project_types/extension/tasks/run_extension_command.rb +0 -82
  114. data/lib/project_types/script/tasks/ensure_env.rb +0 -106
@@ -47,14 +47,20 @@ module Script
47
47
  invalid_language_cause: "Invalid language %s.",
48
48
  invalid_language_help: "Allowed values: %s.",
49
49
 
50
+ missing_script_config_yml_field_cause: "The script.config.yml file is missing the required %s field.",
51
+ missing_script_config_yml_field_help: "Add the field and try again.",
52
+
50
53
  missing_script_json_field_cause: "The script.json file is missing the required %s field.",
51
54
  missing_script_json_field_help: "Add the field and try again.",
52
55
 
53
56
  invalid_script_json_definition_cause: "The script.json file contains invalid JSON.",
54
57
  invalid_script_json_definition_help: "Fix the errors and try again.",
55
58
 
56
- no_script_json_file_cause: "The script.json file is missing.",
57
- no_script_json_file_help: "Create this file and try again.",
59
+ invalid_script_config_yml_definition_cause: "The script.config.yml file contains invalid YAML.",
60
+ invalid_script_config_yml_definition_help: "Fix the errors and try again.",
61
+
62
+ no_script_config_yml_file_cause: "The script.config.yml file is missing.",
63
+ no_script_config_yml_file_help: "Create this file and try again.",
58
64
 
59
65
  configuration_syntax_error_cause: "The script.json is not formatted properly.",
60
66
  configuration_syntax_error_help: "Fix the errors and try again.",
@@ -115,8 +121,6 @@ module Script
115
121
  script_repush_cause: "A version of this script already exists on the app.",
116
122
  script_repush_help: "Use {{cyan:--force}} to replace the existing script.",
117
123
 
118
- invalid_build_script: "The root package.json contains an invalid build command that " \
119
- "is needed to compile your script to WebAssembly.",
120
124
  build_script_not_found: "The root package.json is missing the build command that " \
121
125
  "is needed to compile your script to WebAssembly.",
122
126
  # rubocop:disable Layout/LineLength
@@ -140,6 +144,7 @@ module Script
140
144
 
141
145
  language_library_for_api_not_found_cause: "Script can’t be pushed because the %{language} library for API %{api} is missing.",
142
146
  language_library_for_api_not_found_help: "Make sure extension_point.yml contains the correct API library.",
147
+ no_scripts_found_in_app: "The selected apps have no scripts. Please, create them first on the partners' dashboard.",
143
148
  },
144
149
 
145
150
  create: {
@@ -175,6 +180,29 @@ module Script
175
180
 
176
181
  script_pushed: "{{v}} Script pushed to app (API key: %{api_key}).",
177
182
  },
183
+ connect: {
184
+ connected: "Connected! Your project is now connected to {{green:%s}}",
185
+ missing_script: "No script has been selected.",
186
+ help: <<~HELP,
187
+ {{command:%s script connect}}: Connects an existing script to an app.
188
+ Usage: {{command:%s script connect}}
189
+ HELP
190
+ error: {
191
+ operation_failed: "Couldn't connect script to app.",
192
+ },
193
+ },
194
+ javy: {
195
+ help: <<~HELP,
196
+ Compile the JavaScript code into WebAssembly.
197
+ Usage: {{command:%s script javy}}
198
+ Options:
199
+ {{command:--in}} The name of the JavaScript file that will be compiled.
200
+ {{command:--out}} The name of the file that the WebAssembly should be written to.
201
+ HELP
202
+ errors: {
203
+ invalid_arguments: "Javy was run with invalid arguments. Run {{command: %s script javy --help}} for more information.",
204
+ },
205
+ },
178
206
 
179
207
  project_deps: {
180
208
  none_required: "{{v}} None required",
@@ -74,7 +74,7 @@ module Script
74
74
  }
75
75
  when Layers::Infrastructure::Errors::DeprecatedEPError
76
76
  {
77
- cause_of_error: ShopifyCLI::Context.message("script.error.deprecated_ep", e.ep),
77
+ cause_of_error: ShopifyCLI::Context.message("script.error.deprecated_ep", e.extension_point),
78
78
  help_suggestion: ShopifyCLI::Context.message("script.error.deprecated_ep_cause"),
79
79
  }
80
80
  when Layers::Domain::Errors::InvalidExtensionPointError
@@ -103,32 +103,47 @@ module Script
103
103
  cause_of_error: ShopifyCLI::Context.message("script.error.metadata_not_found_cause"),
104
104
  help_suggestion: ShopifyCLI::Context.message("script.error.metadata_not_found_help"),
105
105
  }
106
- when Layers::Domain::Errors::MissingScriptJsonFieldError
106
+ when Layers::Infrastructure::Errors::BuildError
107
107
  {
108
- cause_of_error: ShopifyCLI::Context.message("script.error.missing_script_json_field_cause", e.field),
109
- help_suggestion: ShopifyCLI::Context.message("script.error.missing_script_json_field_help"),
108
+ cause_of_error: ShopifyCLI::Context.message("script.error.build_error_cause"),
109
+ help_suggestion: ShopifyCLI::Context.message("script.error.build_error_help"),
110
110
  }
111
- when Layers::Domain::Errors::InvalidScriptJsonDefinitionError
111
+ when Layers::Infrastructure::Errors::InvalidScriptConfigYmlDefinitionError
112
+ {
113
+ cause_of_error: ShopifyCLI::Context.message("script.error.invalid_script_config_yml_definition_cause"),
114
+ help_suggestion: ShopifyCLI::Context.message("script.error.invalid_script_config_yml_definition_help"),
115
+ }
116
+ when Layers::Infrastructure::Errors::InvalidScriptJsonDefinitionError
112
117
  {
113
118
  cause_of_error: ShopifyCLI::Context.message("script.error.invalid_script_json_definition_cause"),
114
119
  help_suggestion: ShopifyCLI::Context.message("script.error.invalid_script_json_definition_help"),
115
120
  }
116
- when Layers::Domain::Errors::NoScriptJsonFile
121
+ when Layers::Infrastructure::Errors::MissingScriptConfigYmlFieldError
117
122
  {
118
- cause_of_error: ShopifyCLI::Context.message("script.error.no_script_json_file_cause"),
119
- help_suggestion: ShopifyCLI::Context.message("script.error.no_script_json_file_help"),
123
+ cause_of_error: ShopifyCLI::Context.message("script.error.missing_script_config_yml_field_cause", e.field),
124
+ help_suggestion: ShopifyCLI::Context.message("script.error.missing_script_config_yml_field_help"),
120
125
  }
121
- when Layers::Infrastructure::Errors::BuildError
126
+ when Layers::Infrastructure::Errors::MissingScriptConfigYmlFieldError
122
127
  {
123
- cause_of_error: ShopifyCLI::Context.message("script.error.build_error_cause"),
124
- help_suggestion: ShopifyCLI::Context.message("script.error.build_error_help"),
128
+ cause_of_error: ShopifyCLI::Context.message("script.error.missing_script_config_yml_field_cause", e.field),
129
+ help_suggestion: ShopifyCLI::Context.message("script.error.missing_script_config_yml_field_help"),
125
130
  }
126
- when Layers::Infrastructure::Errors::ScriptJsonSyntaxError
131
+ when Layers::Infrastructure::Errors::MissingScriptJsonFieldError
132
+ {
133
+ cause_of_error: ShopifyCLI::Context.message("script.error.missing_script_json_field_cause", e.field),
134
+ help_suggestion: ShopifyCLI::Context.message("script.error.missing_script_json_field_help"),
135
+ }
136
+ when Layers::Infrastructure::Errors::NoScriptConfigYmlFileError
137
+ {
138
+ cause_of_error: ShopifyCLI::Context.message("script.error.no_script_config_yml_file_cause"),
139
+ help_suggestion: ShopifyCLI::Context.message("script.error.no_script_config_yml_file_help"),
140
+ }
141
+ when Layers::Infrastructure::Errors::ScriptConfigSyntaxError
127
142
  {
128
143
  cause_of_error: ShopifyCLI::Context.message("script.error.configuration_syntax_error_cause"),
129
144
  help_suggestion: ShopifyCLI::Context.message("script.error.configuration_syntax_error_help"),
130
145
  }
131
- when Layers::Infrastructure::Errors::ScriptJsonMissingKeysError
146
+ when Layers::Infrastructure::Errors::ScriptConfigMissingKeysError
132
147
  {
133
148
  cause_of_error: ShopifyCLI::Context.message(
134
149
  "script.error.configuration_missing_keys_error_cause",
@@ -136,7 +151,7 @@ module Script
136
151
  ),
137
152
  help_suggestion: ShopifyCLI::Context.message("script.error.configuration_missing_keys_error_help"),
138
153
  }
139
- when Layers::Infrastructure::Errors::ScriptJsonInvalidValueError
154
+ when Layers::Infrastructure::Errors::ScriptConfigInvalidValueError
140
155
  {
141
156
  cause_of_error: ShopifyCLI::Context.message(
142
157
  "script.error.configuration_invalid_value_error_cause",
@@ -144,7 +159,7 @@ module Script
144
159
  ),
145
160
  help_suggestion: ShopifyCLI::Context.message("script.error.configuration_invalid_value_error_help"),
146
161
  }
147
- when Layers::Infrastructure::Errors::ScriptJsonFieldsMissingKeysError
162
+ when Layers::Infrastructure::Errors::ScriptConfigFieldsMissingKeysError
148
163
  {
149
164
  cause_of_error: ShopifyCLI::Context.message(
150
165
  "script.error.configuration_schema_field_missing_keys_error_cause",
@@ -154,7 +169,7 @@ module Script
154
169
  "script.error.configuration_definition_schema_field_missing_keys_error_help"
155
170
  ),
156
171
  }
157
- when Layers::Infrastructure::Errors::ScriptJsonFieldsInvalidValueError
172
+ when Layers::Infrastructure::Errors::ScriptConfigFieldsInvalidValueError
158
173
  {
159
174
  cause_of_error: ShopifyCLI::Context.message(
160
175
  "script.error.configuration_schema_field_invalid_value_error_cause",
@@ -201,11 +216,6 @@ module Script
201
216
  cause_of_error: ShopifyCLI::Context.message("script.error.build_script_not_found"),
202
217
  help_suggestion: ShopifyCLI::Context.message("script.error.build_script_suggestion"),
203
218
  }
204
- when Layers::Infrastructure::Errors::InvalidBuildScriptError
205
- {
206
- cause_of_error: ShopifyCLI::Context.message("script.error.invalid_build_script"),
207
- help_suggestion: ShopifyCLI::Context.message("script.error.build_script_suggestion"),
208
- }
209
219
  when Layers::Infrastructure::Errors::WebAssemblyBinaryNotFoundError
210
220
  {
211
221
  cause_of_error: ShopifyCLI::Context.message("script.error.web_assembly_binary_not_found"),
@@ -9,6 +9,7 @@ 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("-l", "--live") { flags[:live] = true }
12
13
  parser.on("-x", "--ignore=PATTERN") do |pattern|
13
14
  flags[:ignores] ||= []
14
15
  flags[:ignores] << pattern
@@ -21,6 +22,8 @@ module Theme
21
22
 
22
23
  theme = if (theme_id = options.flags[:theme_id])
23
24
  ShopifyCLI::Theme::Theme.new(@ctx, root: root, id: theme_id)
25
+ elsif options.flags[:live]
26
+ ShopifyCLI::Theme::Theme.live(@ctx, root: root)
24
27
  else
25
28
  form = Forms::Select.ask(
26
29
  @ctx,
@@ -10,6 +10,7 @@ module Theme
10
10
  options do |parser, flags|
11
11
  parser.on("-n", "--nodelete") { flags[:nodelete] = true }
12
12
  parser.on("-i", "--themeid=ID") { |theme_id| flags[:theme_id] = theme_id }
13
+ parser.on("-l", "--live") { flags[:live] = true }
13
14
  parser.on("-d", "--development") { flags[:development] = true }
14
15
  parser.on("-u", "--unpublished") { flags[:unpublished] = true }
15
16
  parser.on("-j", "--json") { flags[:json] = true }
@@ -27,6 +28,8 @@ module Theme
27
28
 
28
29
  theme = if (theme_id = options.flags[:theme_id])
29
30
  ShopifyCLI::Theme::Theme.new(@ctx, root: root, id: theme_id)
31
+ elsif options.flags[:live]
32
+ ShopifyCLI::Theme::Theme.live(@ctx, root: root)
30
33
  elsif options.flags[:development]
31
34
  theme = ShopifyCLI::Theme::DevelopmentTheme.new(@ctx, root: root)
32
35
  theme.ensure_exists!
@@ -48,7 +51,9 @@ module Theme
48
51
  end
49
52
 
50
53
  if theme.live? && !options.flags[:allow_live]
51
- return unless CLI::UI::Prompt.confirm(@ctx.message("theme.push.live"))
54
+ question = @ctx.message("theme.push.live")
55
+ question += @ctx.message("theme.push.theme", theme.name, theme.id) if options.flags[:live]
56
+ return unless CLI::UI::Prompt.confirm(question)
52
57
  end
53
58
 
54
59
  ignore_filter = ShopifyCLI::Theme::IgnoreFilter.from_path(root)
@@ -72,6 +77,7 @@ module Theme
72
77
  @ctx.done(@ctx.message("theme.push.done", theme.preview_url, theme.editor_url))
73
78
  end
74
79
  end
80
+ raise ShopifyCLI::AbortSilent if syncer.has_any_error?
75
81
  rescue ShopifyCLI::API::APIRequestNotFoundError
76
82
  @ctx.abort(@ctx.message("theme.push.theme_not_found", theme.id))
77
83
  ensure
@@ -15,7 +15,7 @@ module Theme
15
15
  def call(*)
16
16
  flags = options.flags.dup
17
17
  host = flags[:host] || DEFAULT_HTTP_HOST
18
- ShopifyCLI::Theme::DevServer.start(@ctx, ".", http_bind: host, **flags) do |syncer|
18
+ ShopifyCLI::Theme::DevServer.start(@ctx, ".", host: host, **flags) do |syncer|
19
19
  UI::SyncProgressBar.new(syncer).progress(:upload_theme!, delay_low_priority_files: true)
20
20
  end
21
21
  rescue ShopifyCLI::Theme::DevServer::AddressBindingError
@@ -58,6 +58,7 @@ module Theme
58
58
 
59
59
  Options:
60
60
  {{command:-i, --themeid=THEMEID}} Theme ID. Must be an existing theme on your store.
61
+ {{command:-l, --live}} Push to your remote live theme, and update your live store.
61
62
  {{command:-d, --development}} Push to your remote development theme, and create it if needed.
62
63
  {{command:-u, --unpublished}} Create a new unpublished theme and push to it.
63
64
  {{command:-n, --nodelete}} Runs the push command without deleting remote files from Shopify.
@@ -73,6 +74,7 @@ module Theme
73
74
  push: "Pushing theme files to Shopify",
74
75
  select: "Select theme to push to",
75
76
  live: "Are you sure you want to push to your live theme?",
77
+ theme: "\n Theme: {{blue:%s #%s}} {{green:[live]}}",
76
78
  theme_not_found: "Theme #%s doesn't exist",
77
79
  done: <<~DONE,
78
80
  {{green:Your theme was pushed successfully}}
@@ -96,12 +98,43 @@ module Theme
96
98
  {{command:--poll}} Force polling to detect file changes
97
99
  {{command:--host=HOST}} Set which network interface the web server listens on. The default value is 127.0.0.1.
98
100
  HELP
99
- serve: "Viewing theme…",
101
+ viewing_theme: "Viewing theme…",
102
+ syncing_theme: "Syncing theme #%s on %s",
100
103
  open_fail: "Couldn't open the theme",
104
+ operation: {
105
+ status: {
106
+ error: "ERROR",
107
+ synced: "Synced",
108
+ fixed: "Fixed",
109
+ },
110
+ },
101
111
  error: {
102
112
  address_binding_error: "Couldn't bind to localhost."\
103
113
  " To serve your theme, set a different address with {{command:%s theme serve --host=<address>}}",
104
114
  },
115
+ serving: <<~SERVING,
116
+
117
+ Serving %s
118
+
119
+ SERVING
120
+ customize_or_preview: <<~CUSTOMIZE_OR_PREVIEW,
121
+
122
+ Customize this theme in the Online Store Editor:
123
+ {{green:%s}}
124
+
125
+ Share this theme preview:
126
+ {{green:%s}}
127
+
128
+ (Use Ctrl-C to stop)
129
+ CUSTOMIZE_OR_PREVIEW
130
+ ensure_user: <<~ENSURE_USER,
131
+ You are not authorized to edit themes on %s.
132
+ Make sure you are a user of that store, and allowed to edit themes.
133
+ ENSURE_USER
134
+ already_in_use_error: "Error",
135
+ address_already_in_use: "The address \"%s\" is already in use.",
136
+ try_this: "Try this",
137
+ try_port_option: "Use the --port=PORT option to serve the theme in a different port.",
105
138
  },
106
139
  check: {
107
140
  help: <<~HELP,
@@ -157,6 +190,7 @@ module Theme
157
190
 
158
191
  Options:
159
192
  {{command:-i, --themeid=THEMEID}} The Theme ID. Must be an existing theme on your store.
193
+ {{command:-l, --live}} Pull theme files from your remote live theme.
160
194
  {{command:-n, --nodelete}} Runs the pull command without deleting local files.
161
195
 
162
196
  Run without options to select theme from a list.
@@ -6,14 +6,14 @@ module Theme
6
6
  end
7
7
 
8
8
  def progress(method, **args)
9
- @syncer.delay_errors!
9
+ @syncer.lock_io!
10
10
  CLI::UI::Progress.progress do |bar|
11
11
  @syncer.public_send(method, **args) do |left, total|
12
12
  bar.tick(set_percent: 1 - left.to_f / total)
13
13
  end
14
14
  bar.tick(set_percent: 1)
15
15
  end
16
- @syncer.report_errors!
16
+ @syncer.unlock_io!
17
17
  end
18
18
  end
19
19
  end
@@ -5,13 +5,26 @@ module ShopifyCLI
5
5
  @ctx.puts(self.class.help)
6
6
  end
7
7
 
8
- def self.help
9
- project_type = name.split("::")[0].downcase
10
- ShopifyCLI::Context.message(
11
- "#{project_type}.help",
12
- ShopifyCLI::TOOL_NAME,
13
- subcommand_registry.command_names.join(" | ")
14
- )
8
+ class << self
9
+ def help
10
+ project_type = name.split("::")[0].downcase
11
+ ShopifyCLI::Context.message(
12
+ "#{project_type}.help",
13
+ ShopifyCLI::TOOL_NAME,
14
+ available_subcommands
15
+ )
16
+ end
17
+
18
+ private
19
+
20
+ def available_subcommands
21
+ subcommand_registry
22
+ .resolved_commands
23
+ .reject { |_name, command| command.hidden? }
24
+ .keys
25
+ .sort
26
+ .join(" | ")
27
+ end
15
28
  end
16
29
  end
17
30
  end
@@ -29,6 +29,12 @@ module ShopifyCLI
29
29
  run_prerequisites
30
30
  cmd.call(args, command_name)
31
31
  end
32
+ rescue OptionParser::InvalidOption => error
33
+ arg = error.args.first
34
+ raise ShopifyCLI::Abort, @ctx.message("core.errors.option_parser.invalid_option", arg)
35
+ rescue OptionParser::MissingArgument => error
36
+ arg = error.args.first
37
+ raise ShopifyCLI::Abort, @ctx.message("core.errors.option_parser.missing_argument", arg)
32
38
  end
33
39
 
34
40
  def options(&block)
@@ -3,9 +3,7 @@ module ShopifyCLI
3
3
  class App
4
4
  class Create
5
5
  class Node < ShopifyCLI::Command::AppSubCommand
6
- unless ShopifyCLI::Environment.acceptance_test?
7
- prerequisite_task :ensure_authenticated
8
- end
6
+ prerequisite_task :ensure_authenticated
9
7
 
10
8
  options do |parser, flags|
11
9
  parser.on("--name=NAME") { |t| flags[:name] = t }
@@ -3,9 +3,7 @@ module ShopifyCLI
3
3
  class App
4
4
  class Create
5
5
  class Rails < ShopifyCLI::Command::AppSubCommand
6
- unless ShopifyCLI::Environment.acceptance_test?
7
- prerequisite_task :ensure_authenticated
8
- end
6
+ prerequisite_task :ensure_authenticated
9
7
 
10
8
  options do |parser, flags|
11
9
  parser.on("--name=NAME") { |t| flags[:name] = t }
@@ -46,6 +46,9 @@ module ShopifyCLI
46
46
  ACCEPTANCE_TEST = "SHOPIFY_CLI_ACCEPTANCE_TEST"
47
47
  DEVELOPMENT = "SHOPIFY_CLI_DEVELOPMENT"
48
48
 
49
+ # Authentication
50
+ AUTH_TOKEN = "SHOPIFY_CLI_AUTH_TOKEN"
51
+
49
52
  # Monorail
50
53
  MONORAIL_REAL_EVENTS = "MONORAIL_REAL_EVENTS"
51
54
  end
@@ -58,5 +61,9 @@ module ShopifyCLI
58
61
  module Links
59
62
  NEW_ISSUE = "https://github.com/Shopify/shopify-cli/issues/new"
60
63
  end
64
+
65
+ module Extension
66
+ DEFAULT_PORT = 39351
67
+ end
61
68
  end
62
69
  end
@@ -61,6 +61,7 @@ module ShopifyCLI
61
61
  # will return which operating system that the cli is running on [:mac, :linux]
62
62
  def os
63
63
  host = uname
64
+ return :mac_m1 if /arm64-apple-darwin/i.match(host)
64
65
  return :mac if /darwin/i.match(host)
65
66
  return :windows if /mswin|mingw|cygwin/i.match(host)
66
67
  return :linux if /linux|bsd/i.match(host)
@@ -89,7 +90,7 @@ module ShopifyCLI
89
90
 
90
91
  # will return true if being launched from a tty
91
92
  def tty?
92
- $stdin.tty? && !testing?
93
+ !testing? && $stdin.tty?
93
94
  end
94
95
 
95
96
  # will return true if the cli is being run from an installation, and not a
@@ -357,6 +358,15 @@ module ShopifyCLI
357
358
  Kernel.puts(CLI::UI.fmt(*args))
358
359
  end
359
360
 
361
+ # a wrapper around $stderr.puts to allow for easy formatting
362
+ #
363
+ # #### Parameters
364
+ # * `text` - a string message to output
365
+ #
366
+ def error(text)
367
+ $stderr.puts(CLI::UI.fmt(text))
368
+ end
369
+
360
370
  # a wrapper around Kernel.warn to allow for easy formatting
361
371
  #
362
372
  # #### Parameters
@@ -86,6 +86,10 @@ module ShopifyCLI
86
86
  )
87
87
  end
88
88
 
89
+ def self.auth_token(env_variables: ENV)
90
+ env_variables[Constants::EnvironmentVariables::AUTH_TOKEN]
91
+ end
92
+
89
93
  def self.env_variable_truthy?(variable_name, env_variables: ENV)
90
94
  TRUTHY_ENV_VARIABLE_VALUES.include?(env_variables[variable_name.to_s])
91
95
  end
@@ -15,6 +15,8 @@ module ShopifyCLI
15
15
  rescue ShopifyCLI::Abort => err
16
16
  ctx.puts(err.message)
17
17
  nil
18
+ rescue ShopifyCLI::AbortSilent
19
+ nil
18
20
  end
19
21
  end
20
22
 
@@ -8,6 +8,8 @@ module ShopifyCLI
8
8
  def available?(ctx)
9
9
  _output, status = ctx.capture2e("git", "status")
10
10
  status.success?
11
+ rescue Errno::ENOENT # git is not installed
12
+ false
11
13
  end
12
14
 
13
15
  ##
@@ -68,6 +68,24 @@ module ShopifyCLI
68
68
  request_exchange_tokens
69
69
  end
70
70
 
71
+ def self.fetch_or_auth_partners_token(ctx:)
72
+ env_var_auth_token = Environment.auth_token
73
+ return env_var_auth_token if env_var_auth_token
74
+
75
+ ShopifyCLI::DB.get(:partners_exchange_token) do
76
+ IdentityAuth.new(ctx: ctx).authenticate
77
+ ShopifyCLI::DB.get(:partners_exchange_token)
78
+ end
79
+ end
80
+
81
+ def self.environment_auth_token?
82
+ !!Environment.auth_token
83
+ end
84
+
85
+ def self.authenticated?
86
+ environment_auth_token? || IDENTITY_ACCESS_TOKENS.all? { |key| ShopifyCLI::DB.exists?(key) }
87
+ end
88
+
71
89
  def reauthenticate
72
90
  return if refresh_exchange_tokens || refresh_access_tokens
73
91
  ctx.abort(ctx.message("core.identity_auth.error.reauthenticate", ShopifyCLI::TOOL_NAME))
@@ -14,6 +14,12 @@ module ShopifyCLI
14
14
  },
15
15
  },
16
16
  core: {
17
+ errors: {
18
+ option_parser: {
19
+ invalid_option: "The option {{command:%s}} is not supported.",
20
+ missing_argument: "The required argument {{command:%s}} is missing.",
21
+ },
22
+ },
17
23
  app: {
18
24
  help: <<~HELP,
19
25
  Suite of commands for developing apps. See {{command:%1$s app <command> --help}} for usage of each command.
@@ -457,6 +463,7 @@ module ShopifyCLI
457
463
  not_authenticated: "Failed to authenticate",
458
464
  },
459
465
  login_prompt: "Please ensure you've logged in with {{command:%s login}} and try again",
466
+ token_authentication: "%s environment variable. We'll authenticate using its value as a token.",
460
467
  },
461
468
 
462
469
  options: {
@@ -718,7 +725,7 @@ module ShopifyCLI
718
725
  signup_suggestion: <<~MESSAGE,
719
726
  {{*}} To avoid tunnels that timeout, it is recommended to signup for a free ngrok
720
727
  account at {{underline:https://ngrok.com/signup}}. After you signup, install your
721
- personalized authorization token using {{command:%s [ node | rails ] tunnel auth <token>}}.
728
+ personalized authorization token using {{command:%s app tunnel auth <token>}}.
722
729
  MESSAGE
723
730
  start: "{{v}} ngrok tunnel running at {{underline:%s}}",
724
731
  start_with_account: "{{v}} ngrok tunnel running at {{underline:%s}}, with account %s",
@@ -764,7 +771,7 @@ module ShopifyCLI
764
771
  Anonymized reports will be sent to Shopify.
765
772
  MESSAGE
766
773
  turned_off_message: <<~MESSAGE,
767
- Turn on automatic reporting later wtih {{command:%s reporting on}}.
774
+ Turn on automatic reporting later with {{command:%s reporting on}}.
768
775
  MESSAGE
769
776
  },
770
777
  whoami: {
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shopify_cli/thread_pool/job"
4
+
5
+ module ShopifyCLI
6
+ class PartnersAPI
7
+ class AppExtensions
8
+ class Job < ShopifyCLI::ThreadPool::Job
9
+ attr_reader :result
10
+
11
+ def initialize(ctx, app, type)
12
+ super()
13
+ @ctx = ctx
14
+ @app = app
15
+ @api_key = @app["apiKey"]
16
+ @type = type
17
+ end
18
+
19
+ def perform!
20
+ resp = PartnersAPI.query(@ctx, "get_extension_registrations", **params)
21
+ @result = resp&.dig("data", "app") || {}
22
+ end
23
+
24
+ def patch_app_with_extensions!
25
+ @app.merge!(result)
26
+ end
27
+
28
+ private
29
+
30
+ def params
31
+ { api_key: @api_key, type: @type }
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shopify_cli/thread_pool"
4
+
5
+ require_relative "app_extensions/job"
6
+
7
+ module ShopifyCLI
8
+ class PartnersAPI
9
+ class AppExtensions
10
+ class << self
11
+ def fetch_apps_extensions(ctx, orgs, type)
12
+ jobs = apps(orgs).map { |app| AppExtensions::Job.new(ctx, app, type) }
13
+
14
+ consume_jobs!(jobs)
15
+ patch_apps_with_extensions!(jobs)
16
+
17
+ orgs
18
+ end
19
+
20
+ private
21
+
22
+ def apps(orgs)
23
+ orgs.flat_map { |org| org["apps"] }
24
+ end
25
+
26
+ def consume_jobs!(jobs)
27
+ thread_pool = ShopifyCLI::ThreadPool.new
28
+ jobs.each do |job|
29
+ thread_pool.schedule(job)
30
+ end
31
+ thread_pool.shutdown
32
+
33
+ raise_if_any_error(jobs)
34
+ end
35
+
36
+ def patch_apps_with_extensions!(jobs)
37
+ jobs.each(&:patch_app_with_extensions!)
38
+ end
39
+
40
+ def raise_if_any_error(jobs)
41
+ jobs.find(&:error?).tap { |job| raise job.error if job }
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -28,11 +28,8 @@ module ShopifyCLI
28
28
  end
29
29
 
30
30
  def fetch_with_extensions(ctx, type)
31
- resp = PartnersAPI.query(ctx, "all_orgs_with_extensions", type: type)
32
- (resp&.dig("data", "organizations", "nodes") || []).map do |org|
33
- org["apps"] = (org.dig("apps", "nodes") || [])
34
- org
35
- end
31
+ orgs = fetch_with_app(ctx)
32
+ AppExtensions.fetch_apps_extensions(ctx, orgs, type)
36
33
  end
37
34
  end
38
35
  end