shopify-cli 2.7.1 → 2.8.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 (111) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +2 -2
  3. data/.github/workflows/shopify.yml +1 -1
  4. data/.gitignore +1 -0
  5. data/.ruby-version +1 -1
  6. data/CHANGELOG.md +43 -0
  7. data/Codespace.dockerfile +2 -2
  8. data/Gemfile.lock +4 -4
  9. data/Tests.dockerfile +2 -2
  10. data/dev.yml +3 -3
  11. data/ext/javy/hashes/javy-arm-macos-v0.1.0.gz.sha256 +1 -0
  12. data/ext/javy/hashes/javy-x86_64-linux-v0.1.0.gz.sha256 +1 -0
  13. data/ext/javy/hashes/javy-x86_64-macos-v0.1.0.gz.sha256 +1 -0
  14. data/ext/javy/hashes/javy-x86_64-windows-v0.1.0.gz.sha256 +1 -0
  15. data/ext/javy/javy.rb +30 -12
  16. data/lib/graphql/get_extension_registrations.graphql +27 -0
  17. data/lib/project_types/extension/cli.rb +27 -2
  18. data/lib/project_types/extension/commands/build.rb +10 -15
  19. data/lib/project_types/extension/commands/create.rb +3 -6
  20. data/lib/project_types/extension/commands/push.rb +36 -8
  21. data/lib/project_types/extension/extension_project.rb +1 -1
  22. data/lib/project_types/extension/features/argo_serve.rb +6 -5
  23. data/lib/project_types/extension/forms/questions/ask_registration.rb +6 -2
  24. data/lib/project_types/extension/loaders/project.rb +29 -0
  25. data/lib/project_types/extension/loaders/specification_handler.rb +22 -0
  26. data/lib/project_types/extension/messages/messages.rb +4 -2
  27. data/lib/project_types/extension/models/app.rb +1 -1
  28. data/lib/project_types/extension/models/development_server.rb +2 -2
  29. data/lib/project_types/extension/models/specification_handlers/default.rb +4 -0
  30. data/lib/project_types/extension/tasks/convert_server_config.rb +3 -1
  31. data/lib/project_types/extension/tasks/execute_commands/base.rb +13 -0
  32. data/lib/project_types/extension/tasks/execute_commands/build.rb +29 -0
  33. data/lib/project_types/extension/tasks/execute_commands/create.rb +33 -0
  34. data/lib/project_types/extension/tasks/execute_commands/serve.rb +35 -0
  35. data/lib/project_types/extension/tasks/merge_server_config.rb +33 -22
  36. data/lib/project_types/rails/commands/create.rb +2 -4
  37. data/lib/project_types/script/cli.rb +8 -1
  38. data/lib/project_types/script/commands/connect.rb +19 -0
  39. data/lib/project_types/script/commands/create.rb +1 -3
  40. data/lib/project_types/script/commands/javy.rb +0 -2
  41. data/lib/project_types/script/commands/push.rb +2 -1
  42. data/lib/project_types/script/config/extension_points.yml +10 -28
  43. data/lib/project_types/script/forms/ask_app.rb +32 -0
  44. data/lib/project_types/script/forms/ask_org.rb +30 -0
  45. data/lib/project_types/script/forms/ask_script_uuid.rb +22 -0
  46. data/lib/project_types/script/forms/run_against_shopify_org.rb +14 -0
  47. data/lib/project_types/script/graphql/app_script_set.graphql +2 -2
  48. data/lib/project_types/script/layers/application/build_script.rb +0 -1
  49. data/lib/project_types/script/layers/application/connect_app.rb +79 -0
  50. data/lib/project_types/script/layers/application/create_script.rb +17 -17
  51. data/lib/project_types/script/layers/application/push_script.rb +1 -1
  52. data/lib/project_types/script/layers/domain/errors.rb +1 -4
  53. data/lib/project_types/script/layers/domain/push_package.rb +3 -3
  54. data/lib/project_types/script/layers/domain/{script_json.rb → script_config.rb} +2 -2
  55. data/lib/project_types/script/layers/domain/script_project.rb +5 -1
  56. data/lib/project_types/script/layers/infrastructure/errors.rb +28 -6
  57. data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_task_runner.rb +0 -4
  58. data/lib/project_types/script/layers/infrastructure/languages/typescript_task_runner.rb +0 -4
  59. data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +2 -2
  60. data/lib/project_types/script/layers/infrastructure/script_project_repository.rb +125 -27
  61. data/lib/project_types/script/layers/infrastructure/script_service.rb +11 -11
  62. data/lib/project_types/script/messages/messages.rb +20 -5
  63. data/lib/project_types/script/ui/error_handler.rb +30 -20
  64. data/lib/project_types/theme/commands/pull.rb +3 -0
  65. data/lib/project_types/theme/commands/push.rb +7 -1
  66. data/lib/project_types/theme/commands/serve.rb +1 -1
  67. data/lib/project_types/theme/messages/messages.rb +10 -0
  68. data/lib/project_types/theme/ui/sync_progress_bar.rb +2 -2
  69. data/lib/shopify_cli/command/project_command.rb +20 -7
  70. data/lib/shopify_cli/command.rb +6 -0
  71. data/lib/shopify_cli/commands/app/create/node.rb +1 -3
  72. data/lib/shopify_cli/commands/app/create/rails.rb +1 -3
  73. data/lib/shopify_cli/commands/login.rb +1 -1
  74. data/lib/shopify_cli/commands/switch.rb +1 -1
  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 +8 -1
  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/rails_service.rb +1 -1
  89. data/lib/shopify_cli/services/app/serve/node_service.rb +1 -1
  90. data/lib/shopify_cli/services/app/serve/rails_service.rb +1 -1
  91. data/lib/shopify_cli/tasks/ensure_authenticated.rb +9 -3
  92. data/lib/shopify_cli/theme/dev_server/cdn_fonts.rb +73 -0
  93. data/lib/shopify_cli/theme/dev_server/hot-reload.js +38 -9
  94. data/lib/shopify_cli/theme/dev_server/proxy/template_param_builder.rb +84 -0
  95. data/lib/shopify_cli/theme/dev_server/proxy.rb +9 -15
  96. data/lib/shopify_cli/theme/dev_server.rb +6 -4
  97. data/lib/shopify_cli/theme/syncer/error_reporter.rb +45 -0
  98. data/lib/shopify_cli/theme/syncer/operation.rb +56 -0
  99. data/lib/shopify_cli/theme/syncer/standard_reporter.rb +32 -0
  100. data/lib/shopify_cli/theme/syncer.rb +40 -39
  101. data/lib/shopify_cli/theme/theme.rb +31 -19
  102. data/lib/shopify_cli/thread_pool/job.rb +27 -0
  103. data/lib/shopify_cli/thread_pool.rb +37 -0
  104. data/lib/shopify_cli/tunnel.rb +9 -10
  105. data/lib/shopify_cli/version.rb +1 -1
  106. data/shopify-cli.gemspec +1 -1
  107. data/vendor/deps/cli-kit/lib/cli/kit/error_handler.rb +3 -1
  108. metadata +31 -8
  109. data/lib/graphql/all_orgs_with_extensions.graphql +0 -37
  110. data/lib/project_types/extension/tasks/run_extension_command.rb +0 -82
  111. data/lib/project_types/script/tasks/ensure_env.rb +0 -106
@@ -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}}
@@ -99,6 +101,13 @@ module Theme
99
101
  viewing_theme: "Viewing theme…",
100
102
  syncing_theme: "Syncing theme #%s on %s",
101
103
  open_fail: "Couldn't open the theme",
104
+ operation: {
105
+ status: {
106
+ error: "ERROR",
107
+ synced: "Synced",
108
+ fixed: "Fixed",
109
+ },
110
+ },
102
111
  error: {
103
112
  address_binding_error: "Couldn't bind to localhost."\
104
113
  " To serve your theme, set a different address with {{command:%s theme serve --host=<address>}}",
@@ -181,6 +190,7 @@ module Theme
181
190
 
182
191
  Options:
183
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.
184
194
  {{command:-n, --nodelete}} Runs the pull command without deleting local files.
185
195
 
186
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 }
@@ -7,7 +7,7 @@ module ShopifyCLI
7
7
  PERMANENT_DOMAIN_SUFFIX = /\.myshopify\.(com|io)$/
8
8
 
9
9
  options do |parser, flags|
10
- parser.on("--store=STORE") { |url| flags[:shop] = url }
10
+ parser.on("-s", "--store=STORE") { |url| flags[:shop] = url }
11
11
  # backwards compatibility allow 'shop' for now
12
12
  parser.on("--shop=SHOP") { |url| flags[:shop] = url }
13
13
  parser.on("--password=PASSWORD") { |password| flags[:password] = password }
@@ -4,7 +4,7 @@ module ShopifyCLI
4
4
  module Commands
5
5
  class Switch < ShopifyCLI::Command
6
6
  options do |parser, flags|
7
- parser.on("--store=STORE") { |url| flags[:shop] = url }
7
+ parser.on("-s", "--store=STORE") { |url| flags[:shop] = url }
8
8
  # backwards compatibility allow 'shop' for now
9
9
  parser.on("--shop=SHOP") { |url| flags[:shop] = url }
10
10
  end
@@ -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",
@@ -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
@@ -7,6 +7,7 @@ module ShopifyCLI
7
7
  #
8
8
  class PartnersAPI < API
9
9
  autoload :Organizations, "shopify_cli/partners_api/organizations"
10
+ autoload :AppExtensions, "shopify_cli/partners_api/app_extensions"
10
11
 
11
12
  class << self
12
13
  ##
@@ -61,18 +62,11 @@ module ShopifyCLI
61
62
  def api_client(ctx)
62
63
  new(
63
64
  ctx: ctx,
64
- token: access_token(ctx),
65
+ token: IdentityAuth.fetch_or_auth_partners_token(ctx: ctx),
65
66
  url: "https://#{Environment.partners_domain}/api/cli/graphql",
66
67
  )
67
68
  end
68
69
 
69
- def access_token(ctx)
70
- ShopifyCLI::DB.get(:partners_exchange_token) do
71
- IdentityAuth.new(ctx: ctx).authenticate
72
- ShopifyCLI::DB.get(:partners_exchange_token)
73
- end
74
- end
75
-
76
70
  def auth_failure_info(ctx, error)
77
71
  if error.response
78
72
  headers = %w(www-authenticate x-request-id)
@@ -107,13 +107,6 @@ module ShopifyCLI
107
107
  @dir = nil
108
108
  end
109
109
 
110
- private
111
-
112
- def directory(dir)
113
- @dir ||= Hash.new { |h, k| h[k] = __directory(k) }
114
- @dir[dir]
115
- end
116
-
117
110
  def at(dir)
118
111
  proj_dir = directory(dir)
119
112
  unless proj_dir
@@ -123,6 +116,13 @@ module ShopifyCLI
123
116
  @at[proj_dir]
124
117
  end
125
118
 
119
+ private
120
+
121
+ def directory(dir)
122
+ @dir ||= Hash.new { |h, k| h[k] = __directory(k) }
123
+ @dir[dir]
124
+ end
125
+
126
126
  def __directory(curr)
127
127
  loop do
128
128
  return nil if curr == "/" || /^[A-Z]:\/$/.match?(curr)
@@ -134,6 +134,7 @@ module ShopifyCLI
134
134
  end
135
135
 
136
136
  property :directory # :nodoc:
137
+ property :env # :nodoc:
137
138
 
138
139
  ##
139
140
  # will read, parse and return the envfile for the project
@@ -14,11 +14,15 @@ module ShopifyCLI
14
14
  }
15
15
 
16
16
  class << self
17
- def read(_directory = Dir.pwd)
18
- input = parse_external_env
17
+ def read(_directory = Dir.pwd, overrides: {})
18
+ input = parse_external_env(overrides: overrides)
19
19
  new(input)
20
20
  end
21
21
 
22
+ def from_hash(hash)
23
+ new(env_input(hash))
24
+ end
25
+
22
26
  def parse(directory)
23
27
  File.read(File.join(directory, FILENAME))
24
28
  .gsub("\r\n", "\n").split("\n").each_with_object({}) do |line, output|
@@ -37,10 +41,14 @@ module ShopifyCLI
37
41
  end
38
42
  end
39
43
 
40
- def parse_external_env(directory = Dir.pwd)
44
+ def parse_external_env(directory = Dir.pwd, overrides: {})
45
+ env_input(parse(directory), overrides: overrides)
46
+ end
47
+
48
+ def env_input(parsed_source, overrides: {})
41
49
  env_details = {}
42
50
  extra = {}
43
- parse(directory).each do |key, value|
51
+ parsed_source.merge(overrides).each do |key, value|
44
52
  if KEY_MAP[key]
45
53
  env_details[KEY_MAP[key]] = value
46
54
  else
@@ -53,7 +61,7 @@ module ShopifyCLI
53
61
  end
54
62
 
55
63
  property :api_key, required: true
56
- property :secret, required: true
64
+ property :secret
57
65
  property :shop
58
66
  property :scopes
59
67
  property :host
@@ -159,10 +159,10 @@ module ShopifyCLI
159
159
 
160
160
  CLI::UI::Frame.open(context.message("core.app.create.rails.generating_app", name)) do
161
161
  new_command = %w(rails new)
162
+ new_command << name
162
163
  new_command += DEFAULT_RAILS_FLAGS
163
164
  new_command << "--database=#{db}"
164
165
  new_command += rails_opts.split unless rails_opts.nil?
165
- new_command << name
166
166
 
167
167
  syscall(new_command)
168
168
  end
@@ -14,7 +14,7 @@ module ShopifyCLI
14
14
 
15
15
  def call
16
16
  project = ShopifyCLI::Project.current
17
- url = host || ShopifyCLI::Tunnel.start(context)
17
+ url = host || ShopifyCLI::Tunnel.start(context, port: port)
18
18
  raise ShopifyCLI::Abort,
19
19
  context.message("core.app.serve.error.host_must_be_https") if url.match(/^https/i).nil?
20
20
  project.env.update(context, :host, url)
@@ -14,7 +14,7 @@ module ShopifyCLI
14
14
 
15
15
  def call
16
16
  project = ShopifyCLI::Project.current
17
- url = host || ShopifyCLI::Tunnel.start(context)
17
+ url = host || ShopifyCLI::Tunnel.start(context, port: port)
18
18
  raise ShopifyCLI::Abort,
19
19
  context.message("core.app.serve.error.host_must_be_https") if url.match(/^https/i).nil?
20
20
  project.env.update(context, :host, url)
@@ -4,9 +4,15 @@ module ShopifyCLI
4
4
  module Tasks
5
5
  class EnsureAuthenticated < ShopifyCLI::Task
6
6
  def call(ctx)
7
- ctx.abort(
8
- ctx.message("core.identity_auth.login_prompt", ShopifyCLI::TOOL_NAME)
9
- ) unless ShopifyCLI::IdentityAuth::IDENTITY_ACCESS_TOKENS.all? { |key| ShopifyCLI::DB.exists?(key) }
7
+ return if ShopifyCLI::Environment.acceptance_test?
8
+ unless ShopifyCLI::IdentityAuth.authenticated?
9
+ raise ShopifyCLI::Abort,
10
+ ctx.message("core.identity_auth.login_prompt", ShopifyCLI::TOOL_NAME)
11
+ end
12
+ if ShopifyCLI::IdentityAuth.environment_auth_token?
13
+ ctx.puts(ctx.message("core.identity_auth.token_authentication",
14
+ ShopifyCLI::Constants::EnvironmentVariables::AUTH_TOKEN))
15
+ end
10
16
  end
11
17
  end
12
18
  end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyCLI
4
+ module Theme
5
+ module DevServer
6
+ class CdnFonts
7
+ FONTS_PATH = "/fonts"
8
+ FONTS_CDN = "https://fonts.shopifycdn.com"
9
+ FONTS_REGEX = %r{#{FONTS_CDN}}
10
+
11
+ def initialize(app, theme:)
12
+ @app = app
13
+ @theme = theme
14
+ end
15
+
16
+ def call(env)
17
+ path = env["PATH_INFO"]
18
+
19
+ # Serve from fonts CDN
20
+ return serve_font(env) if path.start_with?(FONTS_PATH)
21
+
22
+ # Proxy the request, and replace the URLs in the response
23
+ status, headers, body = @app.call(env)
24
+ body = replace_font_urls(body)
25
+ [status, headers, body]
26
+ end
27
+
28
+ private
29
+
30
+ def serve_font(env)
31
+ parameters = %w(PATH_INFO QUERY_STRING REQUEST_METHOD rack.input)
32
+ path, query, method, body_stream = *env.slice(*parameters).values
33
+
34
+ uri = fonts_cdn_uri(path, query)
35
+
36
+ response = Net::HTTP.start(uri.host, 443, use_ssl: true) do |http|
37
+ req_class = Net::HTTP.const_get(method.capitalize)
38
+ req = req_class.new(uri)
39
+ req.initialize_http_header(fonts_cdn_headers)
40
+ req.body_stream = body_stream
41
+ http.request(req)
42
+ end
43
+
44
+ [
45
+ response.code.to_s,
46
+ {
47
+ "Content-Type" => response.content_type,
48
+ "Content-Length" => response.content_length.to_s,
49
+ },
50
+ [response.body],
51
+ ]
52
+ end
53
+
54
+ def fonts_cdn_headers
55
+ {
56
+ "Referer" => "https://#{@theme.shop}",
57
+ "Transfer-Encoding" => "chunked",
58
+ }
59
+ end
60
+
61
+ def fonts_cdn_uri(path, query)
62
+ uri = URI.join("#{FONTS_CDN}/", path.gsub(%r{^#{FONTS_PATH}\/}, ""))
63
+ uri.query = query.split("&").last
64
+ uri
65
+ end
66
+
67
+ def replace_font_urls(body)
68
+ [body.join.gsub(FONTS_REGEX, FONTS_PATH)]
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end