shopify-cli 0.9.3 → 1.0.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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -4
  3. data/docs/getting-started/install/index.md +37 -1
  4. data/docs/getting-started/migrate/index.md +34 -1
  5. data/lib/project_types/extension/cli.rb +1 -1
  6. data/lib/project_types/extension/commands/build.rb +1 -1
  7. data/lib/project_types/node/forms/create.rb +3 -54
  8. data/lib/project_types/node/messages/messages.rb +3 -14
  9. data/lib/project_types/rails/cli.rb +0 -1
  10. data/lib/project_types/rails/forms/create.rb +3 -52
  11. data/lib/project_types/rails/messages/messages.rb +2 -13
  12. data/lib/project_types/script/cli.rb +2 -3
  13. data/lib/project_types/script/commands/create.rb +4 -4
  14. data/lib/project_types/script/commands/disable.rb +4 -14
  15. data/lib/project_types/script/commands/enable.rb +35 -11
  16. data/lib/project_types/script/commands/push.rb +9 -9
  17. data/lib/project_types/script/config/extension_points.yml +9 -3
  18. data/lib/project_types/script/forms/script_form.rb +5 -2
  19. data/lib/project_types/script/layers/application/create_script.rb +7 -6
  20. data/lib/project_types/script/layers/application/disable_script.rb +9 -7
  21. data/lib/project_types/script/layers/application/enable_script.rb +11 -9
  22. data/lib/project_types/script/layers/application/push_script.rb +6 -4
  23. data/lib/project_types/script/layers/infrastructure/assemblyscript_project_creator.rb +2 -2
  24. data/lib/project_types/script/layers/infrastructure/assemblyscript_task_runner.rb +2 -2
  25. data/lib/project_types/script/layers/infrastructure/errors.rb +1 -0
  26. data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +1 -1
  27. data/lib/project_types/script/layers/infrastructure/script_repository.rb +1 -1
  28. data/lib/project_types/script/layers/infrastructure/script_service.rb +2 -0
  29. data/lib/project_types/script/messages/messages.rb +16 -19
  30. data/lib/project_types/script/script_project.rb +8 -4
  31. data/lib/project_types/script/templates/ts/as-pect.config.js +6 -0
  32. data/lib/project_types/script/ui/error_handler.rb +4 -0
  33. data/lib/project_types/script/ui/printing_spinner.rb +75 -0
  34. data/lib/shopify-cli/admin_api.rb +1 -2
  35. data/lib/shopify-cli/admin_api/populate_resource_command.rb +10 -1
  36. data/lib/shopify-cli/admin_api/schema.rb +20 -8
  37. data/lib/shopify-cli/command.rb +2 -5
  38. data/lib/shopify-cli/commands.rb +1 -0
  39. data/lib/shopify-cli/commands/config.rb +44 -0
  40. data/lib/shopify-cli/commands/connect.rb +18 -11
  41. data/lib/shopify-cli/commands/create.rb +1 -1
  42. data/lib/shopify-cli/commands/help.rb +1 -1
  43. data/lib/shopify-cli/commands/system.rb +1 -1
  44. data/lib/shopify-cli/context.rb +10 -1
  45. data/lib/shopify-cli/core.rb +0 -1
  46. data/lib/shopify-cli/core/entry_point.rb +6 -0
  47. data/lib/shopify-cli/core/finalize.rb +13 -0
  48. data/lib/shopify-cli/feature.rb +97 -0
  49. data/lib/shopify-cli/messages/messages.rb +45 -2
  50. data/lib/shopify-cli/partners_api/organizations.rb +7 -7
  51. data/lib/shopify-cli/project_type.rb +2 -5
  52. data/lib/shopify-cli/tasks.rb +1 -0
  53. data/lib/shopify-cli/tasks/ensure_env.rb +0 -1
  54. data/lib/shopify-cli/tasks/select_org_and_shop.rb +77 -0
  55. data/lib/shopify-cli/tasks/update_dashboard_urls.rb +4 -3
  56. data/lib/shopify-cli/tunnel.rb +33 -9
  57. data/lib/shopify-cli/version.rb +1 -1
  58. data/lib/shopify_cli.rb +1 -0
  59. metadata +7 -4
  60. data/lib/project_types/script/forms/enable.rb +0 -24
  61. data/lib/project_types/script/forms/push.rb +0 -19
@@ -17,5 +17,11 @@ module.exports = {
17
17
  reportMax: false,
18
18
  reportMin: false,
19
19
  },
20
+ wasi: {
21
+ args: [],
22
+ env: process.env,
23
+ preopens: {},
24
+ returnOnExit: false,
25
+ },
20
26
  outputBinary: false,
21
27
  };
@@ -92,6 +92,10 @@ module Script
92
92
  {
93
93
  cause_of_error: ShopifyCli::Context.message('script.error.app_not_installed_cause'),
94
94
  }
95
+ when Layers::Infrastructure::Errors::AppScriptNotPushedError
96
+ {
97
+ cause_of_error: ShopifyCli::Context.message('script.error.app_script_not_pushed_help'),
98
+ }
95
99
  when Layers::Infrastructure::Errors::AppScriptUndefinedError
96
100
  {
97
101
  help_suggestion: ShopifyCli::Context.message('script.error.app_script_undefined_help'),
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+ require 'shopify_cli'
3
+
4
+ module Script
5
+ module UI
6
+ module PrintingSpinner
7
+ ##
8
+ # Creates a single spinner that runs the provided block.
9
+ # The block can take in a ctx argument that formats printed output to support
10
+ # printing from within the spin block.
11
+ #
12
+ # ==== Attributes
13
+ #
14
+ # * +ctx+ - The current context.
15
+ # * +title+ - Title of the spinner to use
16
+ #
17
+ # ==== Options
18
+ #
19
+ # * +:auto_debrief+ - Automatically debrief exceptions? Default to true
20
+ #
21
+ # ==== Block
22
+ #
23
+ # * +ctx+ - Instance of the PrintingSpinnerContext built from the ctx attribute.
24
+ # - +ctx.puts(...)+ formats output to enable support for printing within spinners
25
+ # * +spinner+ - Instance of the spinner. Can call +update_title+ to update the user of changes
26
+ #
27
+ def self.spin(ctx, title, auto_debrief: false)
28
+ StrictSpinner.spin(title, auto_debrief: auto_debrief) do |spinner, *args|
29
+ Thread.current[:cliui_output_hook] = nil
30
+ yield(PrintingSpinnerContext.from(ctx, spinner), spinner, *args)
31
+ end
32
+ end
33
+
34
+ ##
35
+ # Printing within spinners requires the manipulation of ANSI escape
36
+ # sequences in order to make sure the CLI::UI::Spinner does not overwrite
37
+ # previously printed content.
38
+ class PrintingSpinnerContext < ShopifyCli::Context
39
+ include SmartProperties
40
+ property :spinner, required: true
41
+
42
+ def self.from(ctx, spinner)
43
+ new_ctx = new(spinner: spinner)
44
+ ctx.instance_variables.each do |var|
45
+ new_ctx.instance_variable_set(var, ctx.instance_variable_get(var))
46
+ end
47
+ new_ctx
48
+ end
49
+
50
+ def puts(*input)
51
+ super(encoded_lines(*input) + "\n" + spinner_text)
52
+ end
53
+
54
+ private
55
+
56
+ def encoded_lines(*lines)
57
+ lines
58
+ .join("\n")
59
+ .split("\n")
60
+ .map { |line| encode_ansi(line) unless line.nil? }
61
+ .join(CLI::UI::ANSI.next_line + "\n")
62
+ end
63
+
64
+ def encode_ansi(line)
65
+ CLI::UI::ANSI.previous_line + line + CLI::UI::ANSI.clear_to_end_of_line
66
+ end
67
+
68
+ def spinner_text
69
+ spinner.render(0, true)
70
+ end
71
+ end
72
+ private_constant(:PrintingSpinnerContext)
73
+ end
74
+ end
75
+ end
@@ -37,8 +37,7 @@ module ShopifyCli
37
37
  #
38
38
  # ShopifyCli::AdminAPI.query(@ctx, 'all_organizations')
39
39
  #
40
- def query(ctx, query_name, api_version: nil, shop: nil, **variables)
41
- shop ||= Project.current.env.shop
40
+ def query(ctx, query_name, shop:, api_version: nil, **variables)
42
41
  authenticated_req(ctx, shop) do
43
42
  api_client(ctx, api_version, shop).query(query_name, variables: variables)
44
43
  end
@@ -41,6 +41,8 @@ module ShopifyCli
41
41
  return @ctx.puts(output)
42
42
  end
43
43
 
44
+ @shop ||= Project.current.env.shop || get_shop(@ctx)
45
+
44
46
  if @silent
45
47
  spin_group = CLI::UI::SpinGroup.new
46
48
  spin_group.add(@ctx.message('core.populate.populating', @count, camel_case_resource_type)) do |spinner|
@@ -114,7 +116,7 @@ module ShopifyCli
114
116
 
115
117
  def run_mutation(data)
116
118
  kwargs = { input: data }
117
- kwargs[:shop] = @shop if @shop
119
+ kwargs[:shop] = @shop
118
120
  resp = AdminAPI.query(
119
121
  @ctx, "create_#{snake_case_resource_type}", kwargs
120
122
  )
@@ -145,6 +147,13 @@ module ShopifyCli
145
147
 
146
148
  private
147
149
 
150
+ def get_shop(ctx)
151
+ res = ShopifyCli::Tasks::SelectOrgAndShop.call(ctx)
152
+ domain = res[:shop_domain]
153
+ Project.current.env.update(ctx, :shop, domain)
154
+ domain
155
+ end
156
+
148
157
  def camel_case_resource_type
149
158
  @camel_case_resource_type ||= self.class.to_s.split('::').last
150
159
  end
@@ -3,15 +3,27 @@ require 'shopify_cli'
3
3
  module ShopifyCli
4
4
  class AdminAPI
5
5
  class Schema < Hash
6
- def self.get(ctx)
7
- unless ShopifyCli::DB.exists?(:shopify_admin_schema)
8
- schema = AdminAPI.query(ctx, 'admin_introspection')
9
- ShopifyCli::DB.set(shopify_admin_schema: JSON.dump(schema))
6
+ class << self
7
+ def get(ctx)
8
+ unless ShopifyCli::DB.exists?(:shopify_admin_schema)
9
+ shop = Project.current.env.shop || get_shop(ctx)
10
+ schema = AdminAPI.query(ctx, 'admin_introspection', shop: shop)
11
+ ShopifyCli::DB.set(shopify_admin_schema: JSON.dump(schema))
12
+ end
13
+ # This is ruby magic for making a new hash with another hash.
14
+ # It wraps the JSON in our Schema Class to have the helper methods
15
+ # available
16
+ self[JSON.parse(ShopifyCli::DB.get(:shopify_admin_schema))]
17
+ end
18
+
19
+ private
20
+
21
+ def get_shop(ctx)
22
+ res = ShopifyCli::Tasks::SelectOrgAndShop.call(ctx)
23
+ domain = res[:shop_domain]
24
+ Project.current.env.update(ctx, :shop, domain)
25
+ domain
10
26
  end
11
- # This is ruby magic for making a new hash with another hash.
12
- # It wraps the JSON in our Schema Class to have the helper methods
13
- # available
14
- self[JSON.parse(ShopifyCli::DB.get(:shopify_admin_schema))]
15
27
  end
16
28
 
17
29
  def type(name)
@@ -3,12 +3,13 @@ require 'shopify_cli'
3
3
 
4
4
  module ShopifyCli
5
5
  class Command < CLI::Kit::BaseCommand
6
+ extend Feature::Set
7
+
6
8
  attr_writer :ctx
7
9
  attr_accessor :options
8
10
 
9
11
  class << self
10
12
  attr_writer :ctx
11
- attr_reader :hidden
12
13
 
13
14
  def call(args, command_name)
14
15
  subcommand, resolved_name = subcommand_registry.lookup_command(args.first)
@@ -23,10 +24,6 @@ module ShopifyCli
23
24
  end
24
25
  end
25
26
 
26
- def hidden_command
27
- @hidden = true
28
- end
29
-
30
27
  def options(&block)
31
28
  @_options = block
32
29
  end
@@ -18,6 +18,7 @@ module ShopifyCli
18
18
  @core_commands.include?(cmd)
19
19
  end
20
20
 
21
+ register :Config, 'config', 'shopify-cli/commands/config', true
21
22
  register :Connect, 'connect', 'shopify-cli/commands/connect', true
22
23
  register :Create, 'create', 'shopify-cli/commands/create', true
23
24
  register :Help, 'help', 'shopify-cli/commands/help', true
@@ -0,0 +1,44 @@
1
+ require 'shopify_cli'
2
+
3
+ module ShopifyCli
4
+ module Commands
5
+ class Config < ShopifyCli::Command
6
+ hidden_feature(feature_set: :debug)
7
+
8
+ subcommand :Feature, 'feature'
9
+
10
+ def call(*)
11
+ @ctx.puts(self.class.help)
12
+ end
13
+
14
+ def self.help
15
+ ShopifyCli::Context.message('core.config.help', ShopifyCli::TOOL_NAME)
16
+ end
17
+
18
+ class Feature < ShopifyCli::SubCommand
19
+ options do |parser, flags|
20
+ parser.on('--enable') { flags[:action] = 'enable' }
21
+ parser.on('--disable') { flags[:action] = 'disable' }
22
+ parser.on('--status') { flags[:action] = 'status' }
23
+ end
24
+
25
+ def call(args, _name)
26
+ feature_name = args.shift
27
+ return @ctx.puts(@ctx.message('core.config.help', ShopifyCli::TOOL_NAME)) if feature_name.nil?
28
+ is_enabled = ShopifyCli::Feature.enabled?(feature_name)
29
+ if options.flags[:action] == 'disable' && is_enabled
30
+ ShopifyCli::Feature.disable(feature_name)
31
+ @ctx.puts(@ctx.message('core.config.feature.disabled', is_enabled))
32
+ elsif options.flags[:action] == 'enable' && !is_enabled
33
+ ShopifyCli::Feature.enable(feature_name)
34
+ @ctx.puts(@ctx.message('core.config.feature.enabled', feature_name))
35
+ elsif is_enabled
36
+ @ctx.puts(@ctx.message('core.config.feature.is_enabled', feature_name))
37
+ else
38
+ @ctx.puts(@ctx.message('core.config.feature.is_disabled', feature_name))
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -6,7 +6,7 @@ module ShopifyCli
6
6
  def call(*)
7
7
  project_type = ask_project_type unless Project.has_current?
8
8
 
9
- if Project.has_current? && Project.current
9
+ if Project.has_current? && Project.current && Project.current.env
10
10
  @ctx.puts @ctx.message('core.connect.already_connected_warning')
11
11
  prod_warning = @ctx.message('core.connect.production_warning')
12
12
  @ctx.puts prod_warning if [:rails, :node].include?(Project.current_project_type)
@@ -20,13 +20,13 @@ module ShopifyCli
20
20
 
21
21
  org = fetch_org
22
22
  id = org['id']
23
- app = get_app(org['apps'])
23
+ app = get_app(id, org['apps'])
24
24
  shop = get_shop(org['stores'], id)
25
25
 
26
26
  write_env(app, shop, env_data[:scopes], env_data[:extra])
27
27
  write_cli_yml(project_type, id) unless Project.has_current?
28
28
 
29
- @ctx.puts(@ctx.message('core.connect.connected', app.first['title']))
29
+ @ctx.puts(@ctx.message('core.connect.connected', app['title']))
30
30
  end
31
31
 
32
32
  def ask_project_type
@@ -45,7 +45,7 @@ module ShopifyCli
45
45
  CLI::UI::Prompt.ask(@ctx.message('core.connect.organization_select')) do |handler|
46
46
  orgs.each do |org|
47
47
  handler.option(
48
- ctx.message('core.partners_api.org_name_and_id', org['businessName'], org['id'])
48
+ @ctx.message('core.partners_api.org_name_and_id', org['businessName'], org['id'])
49
49
  ) { org["id"] }
50
50
  end
51
51
  end
@@ -54,15 +54,22 @@ module ShopifyCli
54
54
  org
55
55
  end
56
56
 
57
- def get_app(apps)
58
- app_id = if apps.count == 1
59
- apps.first["id"]
57
+ def get_app(org_id, apps)
58
+ if apps.count == 1
59
+ apps.first
60
+ elsif apps.count == 0
61
+ @ctx.puts(@ctx.message('core.connect.no_apps'))
62
+ title = CLI::UI::Prompt.ask(@ctx.message('core.connect.app_name'))
63
+ type = CLI::UI::Prompt.ask(@ctx.message('core.connect.app_type.select')) do |handler|
64
+ handler.option(@ctx.message('core.connect.app_type.select_public')) { 'public' }
65
+ handler.option(@ctx.message('core.connect.app_type.select_custom')) { 'custom' }
66
+ end
67
+ ShopifyCli::Tasks::CreateApiClient.call(@ctx, org_id: org_id, title: title, type: type)
60
68
  else
61
69
  CLI::UI::Prompt.ask(@ctx.message('core.connect.app_select')) do |handler|
62
- apps.each { |app| handler.option(app["title"]) { app["id"] } }
70
+ apps.each { |app| handler.option(app["title"]) { app } }
63
71
  end
64
72
  end
65
- apps.select { |app| app["id"] == app_id }
66
73
  end
67
74
 
68
75
  def get_shop(shops, id)
@@ -83,8 +90,8 @@ module ShopifyCli
83
90
  extra = {} if extra.nil?
84
91
 
85
92
  Resources::EnvFile.new(
86
- api_key: app.first["apiKey"],
87
- secret: app.first["apiSecretKeys"].first["secret"],
93
+ api_key: app["apiKey"],
94
+ secret: app["apiSecretKeys"].first["secret"],
88
95
  shop: shop,
89
96
  scopes: scopes,
90
97
  extra: extra,
@@ -28,7 +28,7 @@ module ShopifyCli
28
28
  def self.all_visible_type
29
29
  ProjectType
30
30
  .load_all
31
- .select { |type| !type.hidden }
31
+ .select { |type| !type.hidden? }
32
32
  end
33
33
 
34
34
  def self.help
@@ -47,7 +47,7 @@ module ShopifyCli
47
47
 
48
48
  def core_commands
49
49
  resolved_commands
50
- .select { |_name, c| !c.hidden }
50
+ .select { |_name, c| !c.hidden? }
51
51
  .select { |name, _c| Commands.core_command?(name) }
52
52
  end
53
53
 
@@ -4,7 +4,7 @@ require 'rbconfig'
4
4
  module ShopifyCli
5
5
  module Commands
6
6
  class System < ShopifyCli::Command
7
- hidden_command
7
+ hidden_feature(feature_set: :debug)
8
8
 
9
9
  def call(args, _name)
10
10
  show_all_details = false
@@ -146,10 +146,19 @@ module ShopifyCli
146
146
  # #### Parameters
147
147
  # * `path` - the file path to a directory, relative to the context root to remove from the FS
148
148
  #
149
- def exist?(path)
149
+ def dir_exist?(path)
150
150
  Dir.exist?(ctx_path(path))
151
151
  end
152
152
 
153
+ # checks if a file exists, the filepath is relative to the command root unless absolute
154
+ #
155
+ # #### Parameters
156
+ # * `path` - the file path to a file, relative to the context root to remove from the FS
157
+ #
158
+ def file_exist?(path)
159
+ File.exist?(ctx_path(path))
160
+ end
161
+
153
162
  # will recursively copy a directory from the FS, the filepath is relative to the command
154
163
  # root unless absolute
155
164
  #
@@ -4,6 +4,5 @@ module ShopifyCli
4
4
  autoload :Executor, 'shopify-cli/core/executor'
5
5
  autoload :HelpResolver, 'shopify-cli/core/help_resolver'
6
6
  autoload :Monorail, 'shopify-cli/core/monorail'
7
- autoload :Update, 'shopify-cli/core/update'
8
7
  end
9
8
  end
@@ -11,6 +11,12 @@ module ShopifyCli
11
11
  IO.open(9) { is_shell_shim = true }
12
12
  rescue Errno::EBADF
13
13
  # This is expected if the descriptor doesn't exist
14
+ rescue ArgumentError => e
15
+ # This can happen on RVM, because it can use fd 9 itself and block access to it. That only happens if the fd
16
+ # did not exist beforehand, so that means there was no fd 9 before Ruby started.
17
+ unless e.message == 'The given fd is not accessible because RubyVM reserves it'
18
+ raise e
19
+ end
14
20
  end
15
21
 
16
22
  if !ctx.testing? && is_shell_shim
@@ -0,0 +1,13 @@
1
+ module ShopifyCli
2
+ module Core
3
+ # This class is just a dummy to make sure that we don't trigger warnings on the first time the updated code runs.
4
+ # The old code would try to call the Finalizer after it is done updating, which would then trigger an autoload of
5
+ # this class and fail.
6
+ module Finalize
7
+ class << self
8
+ def deliver!
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,97 @@
1
+ module ShopifyCli
2
+ ##
3
+ # ShopifyCli::Feature contains the logic to hide and show features across the CLI
4
+ # These features can be either commands or project types currently.
5
+ #
6
+ # Feature flags will persist between runs so if the flag is enabled or disabled,
7
+ # it will still be in that same state on the next cli invocation.
8
+ class Feature
9
+ SECTION = 'features'
10
+
11
+ ##
12
+ # ShopifyCli::Feature::Set is included on commands and projects to allow you to hide
13
+ # and enable projects and commands based on feature flags.
14
+ module Set
15
+ ##
16
+ # will hide a feature, either a project_type or a command
17
+ #
18
+ # #### Parameters
19
+ #
20
+ # * `feature_set` - either a single, or array of symbols that represent feature sets
21
+ #
22
+ # #### Example
23
+ #
24
+ # module ShopifyCli
25
+ # module Commands
26
+ # class Config < ShopifyCli::Command
27
+ # hidden_feature(feature_set: :basic)
28
+ # ....
29
+ #
30
+ def hidden_feature(feature_set: [])
31
+ @feature_hidden = true
32
+ @hidden_feature_set = Array(feature_set).compact
33
+ end
34
+
35
+ ##
36
+ # will return if the feature has been hidden or not
37
+ #
38
+ # #### Returns
39
+ #
40
+ # * `is_hidden` - returns true if the feature has been hidden and false otherwise
41
+ #
42
+ # #### Example
43
+ #
44
+ # ShopifyCli::Commands::Config.hidden?
45
+ #
46
+ def hidden?
47
+ enabled = (@hidden_feature_set || []).any? do |feature|
48
+ Feature.enabled?(feature)
49
+ end
50
+ @feature_hidden && !enabled
51
+ end
52
+ end
53
+
54
+ class << self
55
+ ##
56
+ # will enable a feature in the CLI.
57
+ #
58
+ # #### Parameters
59
+ #
60
+ # * `feature` - a symbol representing the flag to be enabled
61
+ def enable(feature)
62
+ set(feature, true)
63
+ end
64
+
65
+ ##
66
+ # will disable a feature in the CLI.
67
+ #
68
+ # #### Parameters
69
+ #
70
+ # * `feature` - a symbol representing the flag to be disabled
71
+ def disable(feature)
72
+ set(feature, false)
73
+ end
74
+
75
+ ##
76
+ # will check if the feature has been enabled
77
+ #
78
+ # #### Parameters
79
+ #
80
+ # * `feature` - a symbol representing a flag that the status should be requested
81
+ #
82
+ # #### Returns
83
+ #
84
+ # * `is_enabled` - will be true if the feature has been enabled.
85
+ def enabled?(feature)
86
+ return false if feature.nil?
87
+ ShopifyCli::Config.get_bool(SECTION, feature.to_s)
88
+ end
89
+
90
+ private
91
+
92
+ def set(feature, value)
93
+ ShopifyCli::Config.set(SECTION, feature.to_s, value)
94
+ end
95
+ end
96
+ end
97
+ end