shopify-cli 1.0.5 → 1.1.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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/bin/load_shopify.rb +3 -1
  4. data/bin/shopify +2 -0
  5. data/ext/shopify-cli/extconf.rb +40 -20
  6. data/lib/project_types/node/commands/create.rb +4 -4
  7. data/lib/project_types/node/commands/deploy/heroku.rb +6 -1
  8. data/lib/project_types/node/commands/generate/billing.rb +6 -5
  9. data/lib/project_types/node/commands/generate/page.rb +8 -5
  10. data/lib/project_types/node/commands/generate/webhook.rb +4 -1
  11. data/lib/project_types/node/messages/messages.rb +1 -0
  12. data/lib/project_types/rails/commands/create.rb +52 -4
  13. data/lib/project_types/rails/commands/generate.rb +1 -0
  14. data/lib/project_types/rails/commands/generate/webhook.rb +3 -2
  15. data/lib/project_types/rails/commands/serve.rb +6 -2
  16. data/lib/project_types/rails/gem.rb +61 -6
  17. data/lib/project_types/rails/messages/messages.rb +27 -11
  18. data/lib/project_types/script/config/extension_points.yml +3 -3
  19. data/lib/project_types/script/forms/create.rb +1 -1
  20. data/lib/rubygems_plugin.rb +18 -10
  21. data/lib/shopify-cli/admin_api/populate_resource_command.rb +1 -1
  22. data/lib/shopify-cli/commands/connect.rb +1 -1
  23. data/lib/shopify-cli/commands/system.rb +9 -8
  24. data/lib/shopify-cli/context.rb +28 -0
  25. data/lib/shopify-cli/heroku.rb +18 -2
  26. data/lib/shopify-cli/js_deps.rb +2 -2
  27. data/lib/shopify-cli/js_system.rb +2 -2
  28. data/lib/shopify-cli/process_supervision.rb +52 -15
  29. data/lib/shopify-cli/project.rb +14 -6
  30. data/lib/shopify-cli/tunnel.rb +10 -4
  31. data/lib/shopify-cli/version.rb +1 -1
  32. data/lib/shopify_cli.rb +6 -2
  33. data/shopify-cli.gemspec +4 -1
  34. data/vendor/deps/cli-kit/REVISION +1 -1
  35. data/vendor/deps/cli-kit/lib/cli/kit.rb +1 -1
  36. data/vendor/deps/cli-kit/lib/cli/kit/autocall.rb +2 -2
  37. data/vendor/deps/cli-kit/lib/cli/kit/error_handler.rb +12 -6
  38. data/vendor/deps/cli-kit/lib/cli/kit/executor.rb +9 -11
  39. data/vendor/deps/cli-kit/lib/cli/kit/logger.rb +8 -2
  40. data/vendor/deps/cli-kit/lib/cli/kit/support/test_helper.rb +7 -7
  41. data/vendor/deps/cli-kit/lib/cli/kit/system.rb +48 -17
  42. data/vendor/deps/cli-ui/REVISION +1 -1
  43. data/vendor/deps/cli-ui/lib/cli/ui.rb +5 -4
  44. data/vendor/deps/cli-ui/lib/cli/ui/ansi.rb +9 -3
  45. data/vendor/deps/cli-ui/lib/cli/ui/color.rb +1 -0
  46. data/vendor/deps/cli-ui/lib/cli/ui/frame.rb +3 -2
  47. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style.rb +1 -0
  48. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/box.rb +13 -5
  49. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/bracket.rb +29 -2
  50. data/vendor/deps/cli-ui/lib/cli/ui/glyph.rb +21 -10
  51. data/vendor/deps/cli-ui/lib/cli/ui/os.rb +63 -0
  52. data/vendor/deps/cli-ui/lib/cli/ui/prompt.rb +11 -2
  53. data/vendor/deps/cli-ui/lib/cli/ui/prompt/interactive_options.rb +1 -0
  54. data/vendor/deps/cli-ui/lib/cli/ui/spinner.rb +3 -3
  55. data/vendor/deps/cli-ui/lib/cli/ui/spinner/spin_group.rb +6 -8
  56. data/vendor/deps/cli-ui/lib/cli/ui/widgets.rb +2 -0
  57. data/vendor/gen/lib/gen.rb +39 -0
  58. data/vendor/gen/lib/gen/commands.rb +18 -0
  59. data/vendor/gen/lib/gen/commands/help.rb +20 -0
  60. data/vendor/gen/lib/gen/commands/new.rb +21 -0
  61. data/vendor/gen/lib/gen/entry_point.rb +10 -0
  62. data/vendor/gen/lib/gen/generator.rb +165 -0
  63. data/vendor/gen/template/.gitignore +2 -0
  64. data/vendor/gen/template/Gemfile +10 -0
  65. data/vendor/gen/template/README.md +1 -0
  66. data/vendor/gen/template/bin/testunit +23 -0
  67. data/vendor/gen/template/bin/update-deps +97 -0
  68. data/vendor/gen/template/dev-gems.yml +3 -0
  69. data/vendor/gen/template/dev-vendor.yml +4 -0
  70. data/vendor/gen/template/exe/__app__-gems +17 -0
  71. data/vendor/gen/template/exe/__app__-vendor +18 -0
  72. data/vendor/gen/template/lib/__app__.rb +33 -0
  73. data/vendor/gen/template/lib/__app__/commands.rb +18 -0
  74. data/vendor/gen/template/lib/__app__/commands/example.rb +19 -0
  75. data/vendor/gen/template/lib/__app__/commands/help.rb +21 -0
  76. data/vendor/gen/template/lib/__app__/entry_point.rb +10 -0
  77. data/vendor/gen/template/test/example_test.rb +17 -0
  78. data/vendor/gen/template/test/test_helper.rb +22 -0
  79. metadata +25 -4
  80. data/Vagrantfile +0 -17
  81. data/lib/project_types/script/forms/script_form.rb +0 -69
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b05da9533ccffc11607217b455d2a23590668a445a0af58c67c168fe5a5dfe96
4
- data.tar.gz: e10408bf78ac963dd50953edbf438bc4dbf247d0001db8a1b1013b0f981189f1
3
+ metadata.gz: 9f47a49090da5b25b266e4186c08b377e91e6fe240620076f24bb0174accc854
4
+ data.tar.gz: 68be2a02c00aa4e0c67295024d0997c453eb40b9d92f4cec9ae06dc5c0b3d0fa
5
5
  SHA512:
6
- metadata.gz: 7626a67bc6ba0fe13b6223b4239927e4f4e39c74a2536e7fe0a38b4403d9c115771a913c4921d28a81175de84c1cc9e7fc9b84e046042a5957ba9f3aa1d56d7d
7
- data.tar.gz: 630008b941f6c034f51fcdb370f6dd09dba5bcb18a44b5bd1035367443bdc28d679adf69ee9e0f3a9417ddfcbef473411d99934a9a14fc777310edf67677711c
6
+ metadata.gz: f0cd5e1c1e79ec89fcba9834eb1c1ea98d53a98ad46f2fcc45e5fefebfabcd9b1655cb73c1431c503b9c1a26f2d684d6a1c330813423733601670839c7e82efc
7
+ data.tar.gz: a3f83e966247aee450f3a73615dce3d324f82ab944304be131dec81973d1984c405d060844601047ceb259163faaa79524d859bbe9a874479ea4a26c21530152
@@ -1,3 +1,7 @@
1
+ Version 1.1.0
2
+ ------
3
+ * Add native Windows 10 support, including variety of stability fixes.
4
+
1
5
  Version 1.0.5
2
6
  ------
3
7
  * Fix a bug in out opt-in metrics
@@ -9,7 +9,9 @@ ENV['USER_PWD'] ||= Dir.pwd
9
9
  # Prune non-absolute paths from PATH to prevent non-deterministic behavior
10
10
  # i.e. If user has "." or "./bin" in their PATH
11
11
  # Note that this logic is duplicated in lib/shopify.rb
12
- ENV['PATH'] = ENV['PATH'].split(':').select { |p| p.start_with?('/', '~') }.join(':')
12
+ ENV['PATH'] = ENV['PATH'].split(File::PATH_SEPARATOR).select do |p|
13
+ p.start_with?('/', '~', /[A-Z]:\\/)
14
+ end.join(File::PATH_SEPARATOR)
13
15
 
14
16
  $original_env = ENV.to_hash
15
17
 
@@ -8,6 +8,8 @@ module Kernel
8
8
  def require(name)
9
9
  original_require(name)
10
10
  rescue LoadError => e
11
+ # Special case for readline.so, which fails harmlessly on Windows
12
+ raise if (name == "readline.so") && ShopifyCli::Context.new.windows?
11
13
  # Special case for psych (yaml), which rescues this itself
12
14
  raise if name == "#{RUBY_VERSION[/\d+\.\d+/]}/psych.so"
13
15
  STDERR.puts "[Note] You cannot use gems with Shopify App CLI."
@@ -3,25 +3,45 @@ require 'fileutils'
3
3
 
4
4
  gem = File.expand_path('../../../', __FILE__)
5
5
  exe = File.join(gem, 'bin', 'shopify')
6
- script = exe + '.sh'
7
- symlink = '/usr/local/bin/shopify'
8
-
9
- script_content = <<~SCRIPT
10
- #!/usr/bin/env bash
11
- #{RbConfig.ruby} --disable=gems -I #{gem} #{exe} $@
12
- SCRIPT
13
-
14
- File.write(script, script_content)
15
- FileUtils.chmod("+x", script)
16
-
17
- makefile_content = <<~MAKEFILE
18
- .PHONY: clean install
19
-
20
- clean:
21
- \t@sudo rm -f #{symlink}
22
-
23
- install: clean
24
- \t@sudo ln -s #{script} #{symlink}
25
- MAKEFILE
6
+
7
+ if RUBY_PLATFORM.match(/mingw32/)
8
+ bat_path = File.dirname(RbConfig.ruby)
9
+ bat = "#{bat_path}\\shopify.bat"
10
+
11
+ script_content = "#{RbConfig.ruby} --disable=gems -I '#{gem}' '#{exe}' %*"
12
+
13
+ FileUtils.mkdir_p(bat_path)
14
+ makefile_content = <<~MAKEFILE
15
+ .PHONY: clean install
16
+
17
+ clean:
18
+ \t rm -f "#{bat}"
19
+
20
+ install: clean
21
+ \t echo "@ECHO OFF"> "#{bat}"
22
+ \t echo "#{script_content}">> "#{bat}"
23
+ MAKEFILE
24
+ else
25
+ script = exe + '.sh'
26
+ symlink = '/usr/local/bin/shopify'
27
+
28
+ script_content = <<~SCRIPT
29
+ #!/usr/bin/env bash
30
+ #{RbConfig.ruby} --disable=gems -I #{gem} #{exe} $@
31
+ SCRIPT
32
+
33
+ File.write(script, script_content)
34
+ FileUtils.chmod("+x", script)
35
+
36
+ makefile_content = <<~MAKEFILE
37
+ .PHONY: clean install
38
+
39
+ clean:
40
+ \t@sudo rm -f #{symlink}
41
+
42
+ install: clean
43
+ \t@sudo ln -s #{script} #{symlink}
44
+ MAKEFILE
45
+ end
26
46
 
27
47
  File.write('Makefile', makefile_content)
@@ -54,8 +54,8 @@ module Node
54
54
  private
55
55
 
56
56
  def check_node
57
- _, stat = @ctx.capture2e('which', 'node')
58
- @ctx.abort(@ctx.message('node.create.error.node_required')) unless stat.success?
57
+ cmd_path = @ctx.which('node')
58
+ @ctx.abort(@ctx.message('node.create.error.node_required')) if cmd_path.nil?
59
59
 
60
60
  version, stat = @ctx.capture2e('node', '-v')
61
61
  @ctx.abort(@ctx.message('node.create.error.node_version_failure')) unless stat.success?
@@ -64,8 +64,8 @@ module Node
64
64
  end
65
65
 
66
66
  def check_npm
67
- _, stat = @ctx.capture2e('which', 'npm')
68
- @ctx.abort(@ctx.message('node.create.error.npm_required')) unless stat.success?
67
+ cmd_path = @ctx.which('npm')
68
+ @ctx.abort(@ctx.message('node.create.error.npm_required')) if cmd_path.nil?
69
69
 
70
70
  version, stat = @ctx.capture2e('npm', '-v')
71
71
  @ctx.abort(@ctx.message('node.create.error.npm_version_failure')) unless stat.success?
@@ -19,10 +19,15 @@ module Node
19
19
  end
20
20
  spin_group.wait
21
21
 
22
- spin_group.add(@ctx.message('node.deploy.heroku.installing')) do |spinner|
22
+ install_message = @ctx.message(
23
+ @ctx.windows? ? 'node.deploy.heroku.installing_windows' : 'node.deploy.heroku.installing'
24
+ )
25
+ spin_group.add(install_message) do |spinner|
23
26
  heroku_service.install
24
27
  spinner.update_title(@ctx.message('node.deploy.heroku.installed'))
25
28
  end
29
+ spin_group.wait
30
+
26
31
  spin_group.add(@ctx.message('node.deploy.heroku.git.checking')) do |spinner|
27
32
  ShopifyCli::Git.init(@ctx)
28
33
  spinner.update_title(@ctx.message('node.deploy.heroku.git.initialized'))
@@ -5,8 +5,8 @@ module Node
5
5
  class Generate
6
6
  class Billing < ShopifyCli::SubCommand
7
7
  BILLING_TYPES = {
8
- 'recurring-billing' => './node_modules/.bin/generate-node-app recurring-billing',
9
- 'one-time-billing' => './node_modules/.bin/generate-node-app one-time-billing',
8
+ 'recurring-billing' => ['./node_modules/.bin/generate-node-app', 'recurring-billing'],
9
+ 'one-time-billing' => ['./node_modules/.bin/generate-node-app', 'one-time-billing'],
10
10
  }
11
11
  def call(args, _name)
12
12
  selected_type = BILLING_TYPES[args[1]]
@@ -18,11 +18,12 @@ module Node
18
18
  end
19
19
  end
20
20
  billing_type_name = BILLING_TYPES.key(selected_type)
21
+ selected_type[0] = File.join(ShopifyCli::Project.current.directory, selected_type[0])
22
+ selected_type = selected_type.join(' ')
23
+
21
24
  spin_group = CLI::UI::SpinGroup.new
22
25
  spin_group.add(@ctx.message('node.generate.billing.generating', billing_type_name)) do |spinner|
23
- Node::Commands::Generate.run_generate(
24
- selected_type, billing_type_name, @ctx
25
- )
26
+ Node::Commands::Generate.run_generate(selected_type, billing_type_name, @ctx)
26
27
  spinner.update_title(@ctx.message('node.generate.billing.generated', billing_type_name))
27
28
  end
28
29
  spin_group.wait
@@ -5,10 +5,10 @@ module Node
5
5
  class Generate
6
6
  class Page < ShopifyCli::SubCommand
7
7
  PAGE_TYPES = {
8
- 'empty-state' => './node_modules/.bin/generate-node-app empty-state-page',
9
- 'two-column' => './node_modules/.bin/generate-node-app two-column-page',
10
- 'annotated' => './node_modules/.bin/generate-node-app settings-page',
11
- 'list' => './node_modules/.bin/generate-node-app list-page',
8
+ 'empty-state' => ['./node_modules/.bin/generate-node-app', 'empty-state-page'],
9
+ 'two-column' => ['./node_modules/.bin/generate-node-app', 'two-column-page'],
10
+ 'annotated' => ['./node_modules/.bin/generate-node-app', 'settings-page'],
11
+ 'list' => ['./node_modules/.bin/generate-node-app', 'list-page'],
12
12
  }
13
13
 
14
14
  options do |parser, flags|
@@ -37,9 +37,12 @@ module Node
37
37
  end
38
38
  end
39
39
  end
40
+ page_type_name = PAGE_TYPES.key(selected_type)
41
+ selected_type[0] = File.join(ShopifyCli::Project.current.directory, selected_type[0])
42
+ selected_type = selected_type.join(' ')
40
43
 
41
44
  spin_group = CLI::UI::SpinGroup.new
42
- spin_group.add(@ctx.message('node.generate.page.generating', selected_type)) do |spinner|
45
+ spin_group.add(@ctx.message('node.generate.page.generating', page_type_name)) do |spinner|
43
46
  Node::Commands::Generate.run_generate("#{selected_type} #{name}", name, @ctx)
44
47
  spinner.update_title(@ctx.message('node.generate.page.generated', name, name))
45
48
  end
@@ -15,9 +15,12 @@ module Node
15
15
  end
16
16
  end
17
17
  end
18
+
19
+ generate_path = File.join(ShopifyCli::Project.current.directory, "node_modules/.bin/generate-node-app")
20
+
18
21
  spin_group = CLI::UI::SpinGroup.new
19
22
  spin_group.add(@ctx.message('node.generate.webhook.generating', selected_type)) do |spinner|
20
- Node::Commands::Generate.run_generate("./node_modules/.bin/generate-node-app webhook #{selected_type}",
23
+ Node::Commands::Generate.run_generate("#{generate_path} webhook #{selected_type}",
21
24
  selected_type, @ctx)
22
25
  spinner.update_title(@ctx.message('node.generate.webhook.generated', selected_type))
23
26
  end
@@ -54,6 +54,7 @@ module Node
54
54
  downloading: "Downloading Heroku CLI…",
55
55
  downloaded: "Downloaded Heroku CLI",
56
56
  installing: "Installing Heroku CLI…",
57
+ installing_windows: "Running Heroku CLI install wizard…",
57
58
  installed: "Installed Heroku CLI",
58
59
  authenticating: "Authenticating with Heroku…",
59
60
  authenticated: "{{v}} Authenticated with Heroku",
@@ -30,6 +30,9 @@ module Rails
30
30
  @ctx.abort(@ctx.message('rails.create.error.invalid_ruby_version')) unless
31
31
  Ruby.version(@ctx).satisfies?('~>2.5')
32
32
 
33
+ check_node
34
+ check_yarn
35
+
33
36
  build(form.name, form.db)
34
37
  set_custom_ua
35
38
  ShopifyCli::Project.write(
@@ -65,12 +68,51 @@ module Rails
65
68
 
66
69
  private
67
70
 
71
+ def check_node
72
+ cmd_path = @ctx.which('node')
73
+ if cmd_path.nil?
74
+ @ctx.abort(@ctx.message('rails.create.error.node_required')) unless @ctx.windows?
75
+ @ctx.puts("{{x}} {{red:" + @ctx.message('rails.create.error.node_required') + "}}")
76
+ @ctx.puts(@ctx.message('rails.create.info.open_new_shell', 'node'))
77
+ raise ShopifyCli::AbortSilent
78
+ end
79
+
80
+ version, stat = @ctx.capture2e('node', '-v')
81
+ unless stat.success?
82
+ @ctx.abort(@ctx.message('rails.create.error.node_version_failure')) unless @ctx.windows?
83
+ # execution stops above if not Windows
84
+ @ctx.puts("{{x}} {{red:" + @ctx.message('rails.create.error.node_version_failure') + "}}")
85
+ @ctx.puts(@ctx.message('rails.create.info.open_new_shell', 'node'))
86
+ raise ShopifyCli::AbortSilent
87
+ end
88
+
89
+ @ctx.done(@ctx.message('rails.create.node_version', version))
90
+ end
91
+
92
+ def check_yarn
93
+ cmd_path = @ctx.which('yarn')
94
+ if cmd_path.nil?
95
+ @ctx.abort(@ctx.message('rails.create.error.yarn_required')) unless @ctx.windows?
96
+ @ctx.puts("{{x}} {{red:" + @ctx.message('rails.create.error.yarn_required') + "}}")
97
+ @ctx.puts(@ctx.message('rails.create.info.open_new_shell', 'yarn'))
98
+ raise ShopifyCli::AbortSilent
99
+ end
100
+
101
+ version, stat = @ctx.capture2e('yarn', '-v')
102
+ unless stat.success?
103
+ @ctx.abort(@ctx.message('rails.create.error.yarn_version_failure')) unless @ctx.windows?
104
+ @ctx.puts("{{x}} {{red:" + @ctx.message('rails.create.error.yarn_version_failure') + "}}")
105
+ @ctx.puts(@ctx.message('rails.create.info.open_new_shell', 'yarn'))
106
+ raise ShopifyCli::AbortSilent
107
+ end
108
+
109
+ @ctx.done(@ctx.message('rails.create.yarn_version', version))
110
+ end
111
+
68
112
  def build(name, db)
69
- install_gem('rails')
70
- CLI::UI::Frame.open(@ctx.message('rails.create.installing_bundler')) do
71
- install_gem('bundler', '~>1.0')
113
+ @ctx.abort(@ctx.message('rails.create.error.install_failure', 'rails')) unless install_gem('rails')
114
+ @ctx.abort(@ctx.message('rails.create.error.install_failure', 'bundler ~>2.0')) unless
72
115
  install_gem('bundler', '~>2.0')
73
- end
74
116
 
75
117
  CLI::UI::Frame.open(@ctx.message('rails.create.generating_app', name)) do
76
118
  new_command = %w(rails new)
@@ -106,6 +148,12 @@ module Rails
106
148
  syscall(%w(rails db:create))
107
149
  syscall(%w(rails db:migrate RAILS_ENV=development))
108
150
  end
151
+
152
+ unless File.exist?(File.join(@ctx.root, 'config/webpacker.yml'))
153
+ CLI::UI::Frame.open(@ctx.message('rails.create.running_webpacker_install')) do
154
+ syscall(%w(rails webpacker:install))
155
+ end
156
+ end
109
157
  end
110
158
 
111
159
  def set_custom_ua
@@ -28,6 +28,7 @@ module Rails
28
28
  end
29
29
 
30
30
  def self.run_generate(script, name, ctx)
31
+ Gem.gem_path(ctx)
31
32
  stat = ctx.system(script)
32
33
  unless stat.success?
33
34
  ctx.abort(response(stat.exitstatus, name, ctx))
@@ -29,9 +29,10 @@ module Rails
29
29
 
30
30
  def generate_command(selected_type)
31
31
  parts = selected_type.downcase.split("_")
32
- env = ShopifyCli::Project.current.env.host
32
+ host = ShopifyCli::Project.current.env.host
33
33
  selected_type = parts[0..-2].join("_") + "/" + parts[-1]
34
- "rails g shopify_app:add_webhook -t #{selected_type} -a #{env}/webhooks/#{selected_type.downcase}"
34
+ command = @ctx.windows? ? "ruby bin\\rails" : "bin/rails"
35
+ "#{command} g shopify_app:add_webhook -t #{selected_type} -a #{host}/webhooks/#{selected_type.downcase}"
35
36
  end
36
37
  end
37
38
  end
@@ -26,12 +26,16 @@ module Rails
26
26
  @ctx.open_url!("#{project.env.host}/login?shop=#{project.env.shop}")
27
27
  end
28
28
  end
29
- Gem.gem_home(@ctx)
30
29
  CLI::UI::Frame.open(@ctx.message('rails.serve.running_server')) do
31
30
  env = ShopifyCli::Project.current.env.to_h
32
31
  env.delete('HOST')
33
32
  env['PORT'] = ShopifyCli::Tunnel::PORT.to_s
34
- @ctx.system('bin/rails server', env: env)
33
+ env['GEM_PATH'] = Gem.gem_path(@ctx)
34
+ if @ctx.windows?
35
+ @ctx.system("ruby bin\\rails server", env: env)
36
+ else
37
+ @ctx.system('bin/rails server', env: env)
38
+ end
35
39
  end
36
40
  end
37
41
 
@@ -15,30 +15,65 @@ module Rails
15
15
  version = args.shift
16
16
  gem = new(ctx: ctx, name: name, version: version)
17
17
  ctx.debug(ctx.message('rails.gem.installed_debug', name, gem.installed?))
18
- gem.install! unless gem.installed?
18
+ gem.installed? ? true : gem.install!
19
19
  end
20
20
 
21
21
  def binary_path_for(ctx, binary)
22
- File.join(gem_home(ctx), 'bin', binary)
22
+ path_to_binary = File.join(gem_home(ctx), 'bin', binary)
23
+ File.exist?(path_to_binary) ? path_to_binary : binary
23
24
  end
24
25
 
25
26
  def gem_home(ctx)
26
27
  ctx.getenv('GEM_HOME') || apply_gem_home(ctx)
27
28
  end
28
29
 
30
+ def gem_path(ctx)
31
+ ctx.getenv('GEM_PATH') || apply_gem_path(ctx)
32
+ end
33
+
29
34
  private
30
35
 
31
36
  def apply_gem_home(ctx)
32
- path = File.join(ctx.getenv('HOME'), '.gem', 'ruby', RUBY_VERSION)
37
+ path = ''
38
+ # extract GEM_HOME from `gem environment home` command
39
+ out, stat = ctx.capture2e('gem', 'environment', 'home')
40
+ path = out&.empty? ? '' : out.strip if stat.success?
41
+ # fallback if return from `gem environment home` is empty (somewhat unlikely)
42
+ path = fallback_gem_home_path(ctx) if path.empty?
43
+ # fallback if path isn't writable (if using a system installed ruby)
44
+ path = fallback_gem_home_path(ctx) unless File.writable?(path)
33
45
  ctx.mkdir_p(path) unless Dir.exist?(path)
46
+ ctx.debug(ctx.message('rails.gem.setting_gem_home', path))
34
47
  ctx.setenv('GEM_HOME', path)
35
48
  end
49
+
50
+ def apply_gem_path(ctx)
51
+ path = ''
52
+ out, stat = ctx.capture2e('gem', 'environment', 'path')
53
+ path = out&.empty? ? '' : out.strip if stat.success?
54
+ # usually GEM_PATH already contains GEM_HOME
55
+ # if gem_home() falls back to our fallback path, we need to add it
56
+ path = gem_home(ctx) + File::PATH_SEPARATOR + path unless path.include?(gem_home(ctx))
57
+ ctx.debug(ctx.message('rails.gem.setting_gem_path', path))
58
+ ctx.setenv('GEM_PATH', path)
59
+ end
60
+
61
+ def fallback_gem_home_path(ctx)
62
+ File.join(ctx.getenv('HOME'), '.gem', 'ruby', RUBY_VERSION)
63
+ end
36
64
  end
37
65
 
38
66
  def installed?
39
- !!Dir.glob("#{self.class.gem_home(ctx)}/gems/#{name}-*").detect do |f|
40
- f =~ %r{/#{Regexp.quote(name)}-\d}
67
+ found = false
68
+ paths = self.class.gem_path(ctx).split(File::PATH_SEPARATOR)
69
+ paths.each do |path|
70
+ ctx.debug(ctx.message('rails.gem.checking_installation_path', "#{path}/gems/", name))
71
+ found = !!Dir.glob("#{path}/gems/#{name}-*").detect do |f|
72
+ gem_satisfies_version?(f)
73
+ end
74
+ break if found
41
75
  end
76
+ found
42
77
  end
43
78
 
44
79
  def install!
@@ -46,11 +81,31 @@ module Rails
46
81
  spin.add(ctx.message('rails.gem.installing', name)) do |spinner|
47
82
  args = %w(gem install)
48
83
  args.push(name)
49
- args.push('-v', version) unless version.nil?
84
+ unless version.nil?
85
+ if ctx.windows? && version.include?('~')
86
+ args.push('-v', "\"#{version}\"")
87
+ else
88
+ args.push('-v', version)
89
+ end
90
+ end
50
91
  ctx.system(*args)
51
92
  spinner.update_title(ctx.message('rails.gem.installed', name))
52
93
  end
53
94
  spin.wait
54
95
  end
96
+
97
+ def gem_satisfies_version?(path)
98
+ if version
99
+ # there was a specific version given during new(), so
100
+ # check version of gem found to determine match
101
+ require 'semantic/semantic'
102
+ found_version, _ = path.match(%r{/#{Regexp.quote(name)}-([\d\.]+)})&.captures
103
+ found_version.nil? ? false : Semantic::Version.new(found_version).satisfies?(version)
104
+ else
105
+ # otherwise ignore the actual version number,
106
+ # just check there's an initial digit
107
+ %r{/#{Regexp.quote(name)}-\d}.match?(path)
108
+ end
109
+ end
55
110
  end
56
111
  end