bard 1.8.0 → 1.9.1

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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +76 -0
  3. data/PLUGINS.md +114 -0
  4. data/README.md +14 -6
  5. data/features/ci.feature +62 -0
  6. data/features/deploy_git_workflow.feature +88 -0
  7. data/features/step_definitions/bard_steps.rb +96 -0
  8. data/features/support/bard-coverage +16 -0
  9. data/features/support/env.rb +10 -1
  10. data/features/support/test_server.rb +2 -1
  11. data/lib/bard/ci/github_actions.rb +1 -2
  12. data/lib/bard/ci/jenkins.rb +82 -11
  13. data/lib/bard/ci/local.rb +6 -6
  14. data/lib/bard/ci/runner.rb +35 -1
  15. data/lib/bard/ci.rb +11 -23
  16. data/lib/bard/cli/ci.rb +45 -38
  17. data/lib/bard/cli/deploy.rb +40 -8
  18. data/lib/bard/cli/hurt.rb +10 -15
  19. data/lib/bard/cli/install.rb +7 -12
  20. data/lib/bard/cli/open.rb +12 -16
  21. data/lib/bard/cli/ping.rb +8 -14
  22. data/lib/bard/cli/run.rb +5 -3
  23. data/lib/bard/cli/stage.rb +10 -1
  24. data/lib/bard/cli/vim.rb +5 -10
  25. data/lib/bard/cli.rb +13 -11
  26. data/lib/bard/command.rb +3 -3
  27. data/lib/bard/config.rb +4 -14
  28. data/lib/bard/github.rb +2 -4
  29. data/lib/bard/plugin.rb +99 -0
  30. data/lib/bard/plugins/backup.rb +19 -0
  31. data/lib/bard/plugins/github_pages.rb +34 -0
  32. data/lib/bard/plugins/hurt.rb +5 -0
  33. data/lib/bard/plugins/install.rb +5 -0
  34. data/lib/bard/plugins/jenkins.rb +6 -0
  35. data/lib/bard/plugins/new.rb +5 -0
  36. data/lib/bard/plugins/ping.rb +6 -0
  37. data/lib/bard/plugins/provision.rb +5 -0
  38. data/lib/bard/plugins/vim.rb +5 -0
  39. data/lib/bard/provision/ssh.rb +9 -2
  40. data/lib/bard/secrets.rb +10 -0
  41. data/lib/bard/server.rb +3 -2
  42. data/lib/bard/target.rb +3 -2
  43. data/lib/bard/version.rb +1 -1
  44. data/spec/bard/ci/github_actions_spec.rb +116 -13
  45. data/spec/bard/ci/jenkins_spec.rb +139 -0
  46. data/spec/bard/ci/runner_spec.rb +61 -0
  47. data/spec/bard/cli/ci_spec.rb +34 -8
  48. data/spec/bard/cli/deploy_spec.rb +20 -8
  49. data/spec/bard/cli/hurt_spec.rb +2 -2
  50. data/spec/bard/cli/install_spec.rb +4 -4
  51. data/spec/bard/cli/open_spec.rb +10 -8
  52. data/spec/bard/cli/ping_spec.rb +5 -5
  53. data/spec/bard/cli/run_spec.rb +20 -1
  54. data/spec/bard/cli/stage_spec.rb +20 -0
  55. data/spec/bard/cli/vim_spec.rb +5 -5
  56. data/spec/bard/config_spec.rb +12 -0
  57. data/spec/bard/github_spec.rb +1 -1
  58. data/spec/bard/plugin_spec.rb +79 -0
  59. data/spec/bard/provision/ssh_spec.rb +17 -4
  60. data/spec/spec_helper.rb +6 -1
  61. metadata +27 -3
  62. data/README.rdoc +0 -15
@@ -0,0 +1,139 @@
1
+ require "spec_helper"
2
+ require "bard/ci/jenkins"
3
+
4
+ RSpec.describe Bard::CI::Jenkins do
5
+ let(:jenkins) { described_class.new("test-project", "master", "abc123") }
6
+
7
+ before do
8
+ allow(Bard::Secrets).to receive(:fetch).with("jenkins-user").and_return("micah")
9
+ allow(Bard::Secrets).to receive(:fetch).with("jenkins-token").and_return("fake-token")
10
+ end
11
+
12
+ describe "#get_last_time_elapsed" do
13
+ it "returns the duration in seconds from the last stable build" do
14
+ xml = "<build><duration>120000</duration></build>"
15
+ allow(jenkins).to receive(:`).with("curl -s http://micah:fake-token@ci.botandrose.com/job/test-project/lastStableBuild/api/xml").and_return(xml)
16
+
17
+ result = jenkins.send(:get_last_time_elapsed)
18
+ expect(result).to eq 120
19
+ end
20
+ end
21
+
22
+ describe "#run" do
23
+ let(:ci_url) { "http://micah:fake-token@ci.botandrose.com/job/test-project" }
24
+
25
+ before do
26
+ allow(jenkins).to receive(:sleep)
27
+ state = instance_double(Bard::CI::State, save: nil, delete: nil)
28
+ allow(jenkins).to receive(:state).and_return(state)
29
+ end
30
+
31
+ it "waits until the build has started before polling" do
32
+ allow(jenkins).to receive(:`).with("curl -s -I -X POST -L '#{ci_url}/buildWithParameters?GIT_REF=master'").and_return("Location: http://ci.botandrose.com/queue/item/99/\r\n")
33
+ allow(jenkins).to receive(:`).with("curl -s #{ci_url}/lastStableBuild/api/xml").and_return("<build><duration>60000</duration></build>")
34
+ allow(jenkins).to receive(:`).with("curl -s -g '#{ci_url}/api/json?depth=1&tree=builds[queueId,number]'").and_return(
35
+ '{"builds":[{"queueId":1,"number":1}]}',
36
+ '{"builds":[{"queueId":99,"number":5}]}',
37
+ '{"builds":[{"queueId":99,"number":5}]}'
38
+ )
39
+ allow(jenkins).to receive(:`).with("curl -s #{ci_url}/5/api/json?tree=building,result").and_return('{"building":false,"result":"SUCCESS"}')
40
+
41
+ result = jenkins.run { |elapsed, last_time| }
42
+ expect(result).to eq true
43
+ end
44
+ end
45
+
46
+ describe "#started?" do
47
+ before do
48
+ jenkins.instance_variable_set(:@queueId, 99)
49
+ end
50
+
51
+ it "retries when builds list is empty (job just created)" do
52
+ allow(jenkins).to receive(:`).with(/api\/json\?depth=1/).and_return(
53
+ '{"builds":[]}',
54
+ '{"builds":[{"queueId":99,"number":1}]}'
55
+ )
56
+ allow(jenkins).to receive(:sleep)
57
+
58
+ expect(jenkins.send(:started?)).to eq true
59
+ end
60
+ end
61
+
62
+ describe "#exists?" do
63
+ it "returns truthy when the job exists" do
64
+ allow(jenkins).to receive(:`).with(/curl -s -I/).and_return("HTTP/1.1 200 OK\r\n")
65
+
66
+ expect(jenkins.exists?).to be_truthy
67
+ end
68
+
69
+ it "auto-creates the job when it does not exist" do
70
+ allow(jenkins).to receive(:`).with(/curl -s -I/).and_return("HTTP/1.1 404 Not Found\r\n")
71
+ allow(jenkins).to receive(:`).with("git remote get-url origin").and_return("git@gitlab.com:botandrose/test-project.git\n")
72
+ allow(File).to receive(:exist?).with("config/master.key").and_return(false)
73
+ expect(jenkins).to receive(:`).with(/curl -s -X POST.*createItem\?name=test-project.*Content-Type: application\/xml/)
74
+
75
+ jenkins.exists?
76
+ end
77
+
78
+ it "includes a master key build step when config/master.key exists" do
79
+ allow(jenkins).to receive(:`).with(/curl -s -I/).and_return("HTTP/1.1 404 Not Found\r\n")
80
+ allow(jenkins).to receive(:`).with("git remote get-url origin").and_return("git@gitlab.com:botandrose/test-project.git\n")
81
+ allow(File).to receive(:exist?).with("config/master.key").and_return(true)
82
+ allow(File).to receive(:read).with("config/master.key").and_return("abc123secret")
83
+
84
+ config_xml = nil
85
+ allow(jenkins).to receive(:`).with(/createItem/) do |cmd|
86
+ config_xml = cmd
87
+ ""
88
+ end
89
+
90
+ jenkins.exists?
91
+ expect(config_xml).to include("abc123secret")
92
+ expect(config_xml).to include("config/master.key")
93
+ end
94
+ end
95
+
96
+ describe "#building? and #success?" do
97
+ before do
98
+ jenkins.instance_variable_set(:@job_id, 42)
99
+ end
100
+
101
+ it "detects a successful build" do
102
+ allow(jenkins).to receive(:`).with("curl -s http://micah:fake-token@ci.botandrose.com/job/test-project/42/api/json?tree=building,result").and_return('{"building":false,"result":"SUCCESS"}')
103
+
104
+ expect(jenkins.send(:building?)).to eq false
105
+ expect(jenkins.send(:success?)).to eq true
106
+ end
107
+
108
+ it "detects a failed build" do
109
+ allow(jenkins).to receive(:`).with("curl -s http://micah:fake-token@ci.botandrose.com/job/test-project/42/api/json?tree=building,result").and_return('{"building":false,"result":"FAILURE"}')
110
+
111
+ expect(jenkins.send(:building?)).to eq false
112
+ expect(jenkins.send(:success?)).to eq false
113
+ end
114
+
115
+ it "detects a build in progress" do
116
+ allow(jenkins).to receive(:`).with("curl -s http://micah:fake-token@ci.botandrose.com/job/test-project/42/api/json?tree=building,result").and_return('{"building":true,"result":null}')
117
+
118
+ expect(jenkins.send(:building?)).to eq true
119
+ end
120
+
121
+ it "handles JSON with spaces in keys" do
122
+ allow(jenkins).to receive(:`).with("curl -s http://micah:fake-token@ci.botandrose.com/job/test-project/42/api/json?tree=building,result").and_return('{"_class":"hudson.model.FreeStyleBuild","building":false,"result":"SUCCESS"}')
123
+
124
+ expect(jenkins.send(:building?)).to eq false
125
+ expect(jenkins.send(:success?)).to eq true
126
+ end
127
+
128
+ it "success? reflects the last response from building?" do
129
+ allow(jenkins).to receive(:`).with("curl -s http://micah:fake-token@ci.botandrose.com/job/test-project/42/api/json?tree=building,result").and_return(
130
+ '{"building":true,"result":null}',
131
+ '{"building":false,"result":"SUCCESS"}'
132
+ )
133
+
134
+ jenkins.send(:building?) # first call — still building
135
+ jenkins.send(:building?) # second call — done
136
+ expect(jenkins.send(:success?)).to eq true
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,61 @@
1
+ require "bard/ci/runner"
2
+
3
+ RSpec.describe Bard::CI::Runner do
4
+ describe ".runners" do
5
+ it "is a hash registry" do
6
+ expect(described_class.runners).to be_a(Hash)
7
+ end
8
+ end
9
+
10
+ describe ".[]" do
11
+ before do
12
+ require "bard/ci/local"
13
+ require "bard/ci/github_actions"
14
+ end
15
+
16
+ it "looks up runners by name" do
17
+ expect(described_class[:local]).to eq Bard::CI::Local
18
+ expect(described_class[:github_actions]).to eq Bard::CI::GithubActions
19
+ end
20
+
21
+ it "returns nil for unknown runners" do
22
+ expect(described_class[:nonexistent]).to be_nil
23
+ end
24
+ end
25
+
26
+ describe ".default" do
27
+ it "returns the last registered runner" do
28
+ # Whatever was registered last in the current test run
29
+ expect(described_class.default).to be_a(Class)
30
+ expect(described_class.default.ancestors).to include(Bard::CI::Runner)
31
+ end
32
+ end
33
+
34
+ describe "auto-registration via inherited" do
35
+ it "registers subclasses automatically" do
36
+ eval <<-RUBY
37
+ module Bard
38
+ class CI
39
+ class SpecTestRunner < Runner
40
+ end
41
+ end
42
+ end
43
+ RUBY
44
+
45
+ expect(described_class[:spec_test_runner]).to eq Bard::CI::SpecTestRunner
46
+ end
47
+
48
+ it "newly registered runner becomes the default" do
49
+ eval <<-RUBY
50
+ module Bard
51
+ class CI
52
+ class AnotherTestRunner < Runner
53
+ end
54
+ end
55
+ end
56
+ RUBY
57
+
58
+ expect(described_class.default).to eq Bard::CI::AnotherTestRunner
59
+ end
60
+ end
61
+ end
@@ -2,6 +2,7 @@ require "spec_helper"
2
2
  require "bard/cli"
3
3
  require "bard/cli/ci"
4
4
  require "thor"
5
+ require "ostruct"
5
6
 
6
7
  class TestCICLI < Thor
7
8
  include Bard::CLI::CI
@@ -16,6 +17,10 @@ class TestCICLI < Thor
16
17
  def project_name
17
18
  "test_project"
18
19
  end
20
+
21
+ def config
22
+ @config ||= OpenStruct.new(ci: nil)
23
+ end
19
24
  end
20
25
 
21
26
  describe Bard::CLI::CI do
@@ -101,12 +106,9 @@ describe Bard::CLI::CI do
101
106
  it "shows error message and exits" do
102
107
  allow(cli).to receive(:options).and_return({})
103
108
  allow(ci_runner).to receive(:exists?).and_return(false)
109
+ allow(cli).to receive(:exit).with(1).and_raise(SystemExit)
104
110
 
105
- expect(cli).to receive(:puts) # "No CI found for test_project!"
106
- expect(cli).to receive(:puts) # "Re-run with --skip-ci to bypass CI..."
107
- expect(cli).to receive(:exit).with(1)
108
-
109
- cli.ci
111
+ expect { cli.ci }.to raise_error(SystemExit)
110
112
  end
111
113
  end
112
114
 
@@ -116,7 +118,7 @@ describe Bard::CLI::CI do
116
118
  allow(ci_runner).to receive(:exists?).and_return(true)
117
119
  allow(ci_runner).to receive(:run).and_return(true)
118
120
 
119
- expect(Bard::CI).to receive(:new).with("test_project", "develop", local: nil)
121
+ expect(Bard::CI).to receive(:new).with("test_project", "develop", runner_name: nil)
120
122
  expect(cli).to receive(:puts).with("Continuous integration: starting build on develop...")
121
123
 
122
124
  cli.ci("develop")
@@ -124,12 +126,36 @@ describe Bard::CLI::CI do
124
126
  end
125
127
 
126
128
  context "with local-ci option" do
127
- it "passes local option to CI runner" do
129
+ it "passes local runner_name to CI" do
128
130
  allow(cli).to receive(:options).and_return({ "local-ci" => true })
129
131
  allow(ci_runner).to receive(:exists?).and_return(true)
130
132
  allow(ci_runner).to receive(:run).and_return(true)
131
133
 
132
- expect(Bard::CI).to receive(:new).with("test_project", "feature-branch", local: true)
134
+ expect(Bard::CI).to receive(:new).with("test_project", "feature-branch", runner_name: :local)
135
+
136
+ cli.ci
137
+ end
138
+ end
139
+
140
+ context "with ci option" do
141
+ it "passes specified runner_name to CI" do
142
+ allow(cli).to receive(:options).and_return({ "ci" => "jenkins" })
143
+ allow(ci_runner).to receive(:exists?).and_return(true)
144
+ allow(ci_runner).to receive(:run).and_return(true)
145
+
146
+ expect(Bard::CI).to receive(:new).with("test_project", "feature-branch", runner_name: :jenkins)
147
+
148
+ cli.ci
149
+ end
150
+ end
151
+
152
+ context "with both local-ci and ci options" do
153
+ it "local-ci takes precedence" do
154
+ allow(cli).to receive(:options).and_return({ "local-ci" => true, "ci" => "jenkins" })
155
+ allow(ci_runner).to receive(:exists?).and_return(true)
156
+ allow(ci_runner).to receive(:run).and_return(true)
157
+
158
+ expect(Bard::CI).to receive(:new).with("test_project", "feature-branch", runner_name: :local)
133
159
 
134
160
  cli.ci
135
161
  end
@@ -26,6 +26,7 @@ describe Bard::CLI::Deploy do
26
26
 
27
27
  before do
28
28
  allow(cli).to receive(:config).and_return(config)
29
+ allow(cli).to receive(:options).and_return({ target: "production" })
29
30
  allow(cli).to receive(:puts)
30
31
  allow(cli).to receive(:exit)
31
32
  allow(cli).to receive(:run!)
@@ -48,7 +49,7 @@ describe Bard::CLI::Deploy do
48
49
  context "when on master branch" do
49
50
  before do
50
51
  allow(Bard::Git).to receive(:current_branch).and_return("master")
51
- allow(cli).to receive(:options).and_return({})
52
+ allow(cli).to receive(:options).and_return({ target: "production" })
52
53
  end
53
54
 
54
55
  context "when up to date with remote" do
@@ -79,7 +80,7 @@ describe Bard::CLI::Deploy do
79
80
 
80
81
  context "with skip-ci option" do
81
82
  it "skips CI step" do
82
- allow(cli).to receive(:options).and_return({ "skip-ci" => true })
83
+ allow(cli).to receive(:options).and_return({ "skip-ci" => true, target: "production" })
83
84
 
84
85
  expect(cli).not_to receive(:invoke).with(:ci, anything, anything)
85
86
  expect(production_server).to receive(:run!).with("git pull origin master && bin/setup")
@@ -91,16 +92,16 @@ describe Bard::CLI::Deploy do
91
92
 
92
93
  context "when on feature branch" do
93
94
  before do
94
- allow(cli).to receive(:options).and_return({})
95
+ allow(cli).to receive(:options).and_return({ target: "production" })
95
96
  end
96
97
 
97
98
  context "with fast-forward merge possible" do
98
99
  it "fetches master, pushes branch, runs CI, merges to master, and deploys" do
99
- expect(cli).to receive(:run!).with("git fetch origin master:master")
100
+ expect(cli).to receive(:run!).with("git fetch origin")
101
+ expect(cli).to receive(:run!).with("git fetch origin master:master").twice
100
102
  expect(cli).to receive(:run!).with("git push -f origin feature-branch:feature-branch")
101
103
  expect(cli).to receive(:invoke).with(:ci, ["feature-branch"], {})
102
104
  expect(cli).to receive(:run!).with("git push origin feature-branch:master")
103
- expect(cli).to receive(:run!).with("git fetch origin master:master")
104
105
  expect(production_server).to receive(:run!).with("git pull origin master && bin/setup")
105
106
 
106
107
  cli.deploy
@@ -141,7 +142,7 @@ describe Bard::CLI::Deploy do
141
142
 
142
143
  context "with clone option" do
143
144
  it "clones repository and sets up application" do
144
- allow(cli).to receive(:options).and_return({ clone: true })
145
+ allow(cli).to receive(:options).and_return({ clone: true, target: "production" })
145
146
 
146
147
  expect(production_server).to receive(:run!).with("git clone git@github.com:botandrosedesign/test_project /var/www/test_project", home: true)
147
148
  expect(cli).to receive(:invoke).with(:master_key, [], from: "local", to: :production)
@@ -168,13 +169,14 @@ describe Bard::CLI::Deploy do
168
169
 
169
170
  before do
170
171
  allow(config).to receive(:[]).with(:staging).and_return(staging_server)
172
+ allow(cli).to receive(:options).and_return({ target: "staging" })
171
173
  end
172
174
 
173
175
  it "deploys to specified target" do
174
176
  expect(staging_server).to receive(:run!).with("git pull origin master && bin/setup")
175
177
  expect(cli).to receive(:ping).with(:staging)
176
178
 
177
- cli.deploy(:staging)
179
+ cli.deploy
178
180
  end
179
181
  end
180
182
 
@@ -191,12 +193,22 @@ describe Bard::CLI::Deploy do
191
193
 
192
194
  context "with local-ci option" do
193
195
  it "passes local-ci option to CI invocation" do
194
- allow(cli).to receive(:options).and_return({ "local-ci" => true })
196
+ allow(cli).to receive(:options).and_return({ "local-ci" => true, target: "production" })
195
197
 
196
198
  expect(cli).to receive(:invoke).with(:ci, ["feature-branch"], { "local-ci" => true })
197
199
 
198
200
  cli.deploy
199
201
  end
200
202
  end
203
+
204
+ context "with ci option" do
205
+ it "passes ci option to CI invocation" do
206
+ allow(cli).to receive(:options).and_return({ "ci" => "jenkins", target: "production" })
207
+
208
+ expect(cli).to receive(:invoke).with(:ci, ["feature-branch"], { "ci" => "jenkins" })
209
+
210
+ cli.deploy
211
+ end
212
+ end
201
213
  end
202
214
  end
@@ -4,7 +4,7 @@ require "bard/cli/hurt"
4
4
  require "thor"
5
5
 
6
6
  class TestHurtCLI < Thor
7
- include Bard::CLI::Hurt
7
+ Bard::CLI::Hurt.setup(self)
8
8
  end
9
9
 
10
10
  describe Bard::CLI::Hurt do
@@ -20,4 +20,4 @@ describe Bard::CLI::Hurt do
20
20
  expect(cli).to respond_to(:hurt)
21
21
  end
22
22
  end
23
- end
23
+ end
@@ -4,7 +4,7 @@ require "bard/cli/install"
4
4
  require "thor"
5
5
 
6
6
  class TestInstallCLI < Thor
7
- include Bard::CLI::Install
7
+ Bard::CLI::Install.setup(self)
8
8
  end
9
9
 
10
10
  describe Bard::CLI::Install do
@@ -16,10 +16,10 @@ describe Bard::CLI::Install do
16
16
  end
17
17
 
18
18
  it "should copy install files to bin directory" do
19
- expect(cli).to receive(:system).with(/cp -R .*install_files\/\* bin\//)
20
- expect(cli).to receive(:system).with(/cp -R .*install_files\/\.github \.\//)
19
+ expect_any_instance_of(Bard::CLI::Install).to receive(:system).with(/cp -R .*install_files\/\* bin\//)
20
+ expect_any_instance_of(Bard::CLI::Install).to receive(:system).with(/cp -R .*install_files\/\.github \.\//)
21
21
 
22
22
  cli.install
23
23
  end
24
24
  end
25
- end
25
+ end
@@ -4,7 +4,7 @@ require "bard/cli/open"
4
4
  require "thor"
5
5
 
6
6
  class TestOpenCLI < Thor
7
- include Bard::CLI::Open
7
+ Bard::CLI::Open.setup(self)
8
8
 
9
9
  attr_reader :config
10
10
 
@@ -25,7 +25,7 @@ describe Bard::CLI::Open do
25
25
 
26
26
  before do
27
27
  allow(cli).to receive(:config).and_return(config)
28
- allow(cli).to receive(:exec)
28
+ allow_any_instance_of(Bard::CLI::Open).to receive(:exec)
29
29
  end
30
30
 
31
31
  describe "#open" do
@@ -34,7 +34,7 @@ describe Bard::CLI::Open do
34
34
  end
35
35
 
36
36
  it "should open production server URL by default" do
37
- expect(cli).to receive(:exec).with("xdg-open https://example.com")
37
+ expect_any_instance_of(Bard::CLI::Open).to receive(:exec).with("xdg-open https://example.com")
38
38
 
39
39
  cli.open
40
40
  end
@@ -43,13 +43,13 @@ describe Bard::CLI::Open do
43
43
  staging_server = double("staging", ping: ["https://staging.example.com"])
44
44
  allow(config).to receive(:[]).with(:staging).and_return(staging_server)
45
45
 
46
- expect(cli).to receive(:exec).with("xdg-open https://staging.example.com")
46
+ expect_any_instance_of(Bard::CLI::Open).to receive(:exec).with("xdg-open https://staging.example.com")
47
47
 
48
48
  cli.open(:staging)
49
49
  end
50
50
 
51
51
  it "should open CI URL when server is ci" do
52
- expect(cli).to receive(:exec).with("xdg-open https://github.com/botandrosedesign/test_project/actions/workflows/ci.yml")
52
+ expect_any_instance_of(Bard::CLI::Open).to receive(:exec).with("xdg-open https://github.com/botandrosedesign/test_project/actions/workflows/ci.yml")
53
53
 
54
54
  cli.open(:ci)
55
55
  end
@@ -57,11 +57,13 @@ describe Bard::CLI::Open do
57
57
 
58
58
  describe "#open_url" do
59
59
  it "returns CI URL for ci server" do
60
- expect(cli.send(:open_url, :ci)).to eq("https://github.com/botandrosedesign/test_project/actions/workflows/ci.yml")
60
+ command = Bard::CLI::Open.new(cli)
61
+ expect(command.send(:open_url, :ci)).to eq("https://github.com/botandrosedesign/test_project/actions/workflows/ci.yml")
61
62
  end
62
63
 
63
64
  it "returns server ping URL for other servers" do
64
- expect(cli.send(:open_url, :production)).to eq("https://example.com")
65
+ command = Bard::CLI::Open.new(cli)
66
+ expect(command.send(:open_url, :production)).to eq("https://example.com")
65
67
  end
66
68
  end
67
- end
69
+ end
@@ -4,7 +4,7 @@ require "bard/cli/ping"
4
4
  require "thor"
5
5
 
6
6
  class TestPingCLI < Thor
7
- include Bard::CLI::Ping
7
+ Bard::CLI::Ping.setup(self)
8
8
 
9
9
  attr_reader :config
10
10
 
@@ -21,8 +21,8 @@ describe Bard::CLI::Ping do
21
21
 
22
22
  before do
23
23
  allow(cli).to receive(:config).and_return(config)
24
- allow(cli).to receive(:puts)
25
- allow(cli).to receive(:exit)
24
+ allow_any_instance_of(Bard::CLI::Ping).to receive(:puts)
25
+ allow_any_instance_of(Bard::CLI::Ping).to receive(:exit)
26
26
  end
27
27
 
28
28
  describe "#ping" do
@@ -39,9 +39,9 @@ describe Bard::CLI::Ping do
39
39
  it "should print down URLs when they exist" do
40
40
  down_urls = ["https://down.example.com"]
41
41
  allow(Bard::Ping).to receive(:call).and_return(down_urls)
42
- expect(cli).to receive(:puts).with("https://down.example.com is down!")
42
+ expect_any_instance_of(Bard::CLI::Ping).to receive(:puts).with("https://down.example.com is down!")
43
43
 
44
44
  cli.ping
45
45
  end
46
46
  end
47
- end
47
+ end
@@ -25,6 +25,7 @@ describe Bard::CLI::Run do
25
25
  allow(cli).to receive(:exit)
26
26
  allow(cli).to receive(:red).and_return("")
27
27
  allow(cli).to receive(:yellow).and_return("")
28
+ allow(cli).to receive(:options).and_return({ target: "production" })
28
29
  end
29
30
 
30
31
  describe "#run" do
@@ -33,7 +34,25 @@ describe Bard::CLI::Run do
33
34
  end
34
35
 
35
36
  it "should run command on production server" do
36
- expect(server).to receive(:run!).with("ls -la", verbose: true)
37
+ expect(server).to receive(:run!).with("ls -la", verbose: true, home: nil)
38
+
39
+ cli.run("ls", "-la")
40
+ end
41
+
42
+ it "should run command on specified target" do
43
+ staging_server = double("staging_server")
44
+ allow(cli).to receive(:config).and_return(config.merge(staging: staging_server))
45
+ allow(cli).to receive(:options).and_return({ target: "staging" })
46
+
47
+ expect(staging_server).to receive(:run!).with("ls -la", verbose: true, home: nil)
48
+
49
+ cli.run("ls", "-la")
50
+ end
51
+
52
+ it "should pass home option to target" do
53
+ allow(cli).to receive(:options).and_return({ target: "production", home: true })
54
+
55
+ expect(server).to receive(:run!).with("ls -la", verbose: true, home: true)
37
56
 
38
57
  cli.run("ls", "-la")
39
58
  end
@@ -55,6 +55,26 @@ describe Bard::CLI::Stage do
55
55
  end
56
56
  end
57
57
 
58
+ context "when staging target has a deploy strategy" do
59
+ let(:strategy_instance) { double("strategy") }
60
+ let(:staging_server) { double("staging", deploy_strategy: :fake) }
61
+
62
+ before do
63
+ allow(staging_server).to receive(:respond_to?).with(:deploy_strategy).and_return(true)
64
+ allow(staging_server).to receive(:deploy_strategy_instance).and_return(strategy_instance)
65
+ allow(cli).to receive(:require).with("bard/deploy_strategy/fake").and_return(true)
66
+ end
67
+
68
+ it "uses the deploy strategy instead of SSH" do
69
+ expect(cli).to receive(:run!).with("git push -u origin main", verbose: true)
70
+ expect(strategy_instance).to receive(:deploy)
71
+ expect(staging_server).not_to receive(:run!)
72
+ expect(cli).to receive(:ping).with(:staging)
73
+
74
+ cli.stage
75
+ end
76
+ end
77
+
58
78
  context "when production server is not defined" do
59
79
  let(:servers) { { staging: staging_server } }
60
80
 
@@ -4,14 +4,14 @@ require "bard/cli/vim"
4
4
  require "thor"
5
5
 
6
6
  class TestVimCLI < Thor
7
- include Bard::CLI::Vim
7
+ Bard::CLI::Vim.setup(self)
8
8
  end
9
9
 
10
10
  describe Bard::CLI::Vim do
11
11
  let(:cli) { TestVimCLI.new }
12
12
 
13
13
  before do
14
- allow(cli).to receive(:exec)
14
+ allow_any_instance_of(Bard::CLI::Vim).to receive(:exec)
15
15
  end
16
16
 
17
17
  describe "#vim" do
@@ -20,15 +20,15 @@ describe Bard::CLI::Vim do
20
20
  end
21
21
 
22
22
  it "should exec vim with git diff files by default" do
23
- expect(cli).to receive(:exec).with("vim -p `(git diff master --name-only; git ls-files --others --exclude-standard) | grep -v '^app/assets/images/' | grep -v '^app/assets/stylesheets/' | while read f; do [ -f \"$f\" ] && ! file -b \"$f\" | grep -q \"binary\" && echo \"$f\"; done | tac`")
23
+ expect_any_instance_of(Bard::CLI::Vim).to receive(:exec).with("vim -p `(git diff master --name-only; git ls-files --others --exclude-standard) | grep -v '^app/assets/images/' | grep -v '^app/assets/stylesheets/' | while read f; do [ -f \"$f\" ] && ! file -b \"$f\" | grep -q \"binary\" && echo \"$f\"; done | tac`")
24
24
 
25
25
  cli.vim
26
26
  end
27
27
 
28
28
  it "should exec vim with specified branch" do
29
- expect(cli).to receive(:exec).with("vim -p `(git diff develop --name-only; git ls-files --others --exclude-standard) | grep -v '^app/assets/images/' | grep -v '^app/assets/stylesheets/' | while read f; do [ -f \"$f\" ] && ! file -b \"$f\" | grep -q \"binary\" && echo \"$f\"; done | tac`")
29
+ expect_any_instance_of(Bard::CLI::Vim).to receive(:exec).with("vim -p `(git diff develop --name-only; git ls-files --others --exclude-standard) | grep -v '^app/assets/images/' | grep -v '^app/assets/stylesheets/' | while read f; do [ -f \"$f\" ] && ! file -b \"$f\" | grep -q \"binary\" && echo \"$f\"; done | tac`")
30
30
 
31
31
  cli.vim("develop")
32
32
  end
33
33
  end
34
- end
34
+ end
@@ -148,6 +148,18 @@ describe Bard::Config do
148
148
  end
149
149
  end
150
150
 
151
+ context "with target overriding default server" do
152
+ subject { described_class.new("tracker", source: <<~SOURCE) }
153
+ target :staging do
154
+ ssh false
155
+ end
156
+ SOURCE
157
+
158
+ it "replaces the default Server with a Target" do
159
+ expect(subject[:staging]).to be_a(Bard::Target)
160
+ end
161
+ end
162
+
151
163
  context "with github_pages directive" do
152
164
  subject { described_class.new("test", source: "github_pages 'example.com'") }
153
165
 
@@ -5,7 +5,7 @@ describe Bard::Github do
5
5
  let(:github) { Bard::Github.new("test-project") }
6
6
 
7
7
  before do
8
- allow(github).to receive(:`).with("git ls-remote -t git@github.com:botandrosedesign/secrets").and_return("github-apikey|12345")
8
+ allow(Bard::Secrets).to receive(:fetch).with("github-apikey").and_return("12345")
9
9
  end
10
10
 
11
11
  describe "#get" do