bard 2.0.0.beta → 2.0.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 (174) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +6 -1
  3. data/CLAUDE.md +76 -0
  4. data/MIGRATION_GUIDE.md +24 -9
  5. data/PLUGINS.md +99 -0
  6. data/README.md +14 -6
  7. data/Rakefile +3 -1
  8. data/bard.gemspec +2 -1
  9. data/cucumber.yml +1 -0
  10. data/features/ci.feature +63 -0
  11. data/features/data.feature +13 -0
  12. data/features/deploy.feature +14 -0
  13. data/features/deploy_git_workflow.feature +89 -0
  14. data/features/run.feature +14 -0
  15. data/features/step_definitions/bard_steps.rb +136 -0
  16. data/features/support/bard-coverage +16 -0
  17. data/features/support/env.rb +14 -39
  18. data/features/support/test_server.rb +216 -0
  19. data/lib/bard/cli.rb +14 -31
  20. data/lib/bard/command.rb +10 -69
  21. data/lib/bard/config.rb +40 -183
  22. data/lib/bard/copy.rb +28 -103
  23. data/lib/bard/plugins/data.rb +56 -0
  24. data/lib/bard/{ci → plugins/deploy/ci}/github_actions.rb +3 -4
  25. data/lib/bard/plugins/deploy/ci/jenkins.rb +176 -0
  26. data/lib/bard/{ci → plugins/deploy/ci}/local.rb +7 -7
  27. data/lib/bard/{ci → plugins/deploy/ci}/runner.rb +38 -4
  28. data/lib/bard/plugins/deploy/ci.rb +38 -0
  29. data/lib/bard/plugins/deploy/ssh_strategy.rb +27 -0
  30. data/lib/bard/{deploy_strategy.rb → plugins/deploy/strategy.rb} +1 -1
  31. data/lib/bard/plugins/deploy.rb +240 -0
  32. data/lib/bard/{git.rb → plugins/git.rb} +6 -3
  33. data/lib/bard/{github.rb → plugins/github.rb} +4 -6
  34. data/lib/bard/{deploy_strategy/github_pages.rb → plugins/github_pages/strategy.rb} +13 -6
  35. data/lib/bard/plugins/github_pages.rb +30 -0
  36. data/lib/bard/plugins/hurt.rb +13 -0
  37. data/{install_files → lib/bard/plugins/install}/.github/dependabot.yml +2 -1
  38. data/{install_files → lib/bard/plugins/install}/.github/workflows/cache-ci.yml +1 -1
  39. data/{install_files → lib/bard/plugins/install}/.github/workflows/ci.yml +2 -2
  40. data/lib/bard/plugins/install.rb +9 -0
  41. data/lib/bard/plugins/open.rb +20 -0
  42. data/lib/bard/{ping.rb → plugins/ping/check.rb} +4 -4
  43. data/lib/bard/plugins/ping/target_methods.rb +23 -0
  44. data/lib/bard/plugins/ping.rb +10 -0
  45. data/lib/bard/plugins/run.rb +19 -0
  46. data/lib/bard/plugins/setup.rb +54 -0
  47. data/lib/bard/plugins/ssh/connection.rb +75 -0
  48. data/lib/bard/plugins/ssh/copy.rb +95 -0
  49. data/lib/bard/{ssh_server.rb → plugins/ssh/server.rb} +17 -42
  50. data/lib/bard/plugins/ssh/target_methods.rb +20 -0
  51. data/lib/bard/plugins/ssh.rb +10 -0
  52. data/lib/bard/plugins/url/target_methods.rb +23 -0
  53. data/lib/bard/plugins/url.rb +1 -0
  54. data/lib/bard/plugins/vim.rb +6 -0
  55. data/lib/bard/retryable.rb +25 -0
  56. data/lib/bard/secrets.rb +10 -0
  57. data/lib/bard/target.rb +27 -185
  58. data/lib/bard/version.rb +1 -1
  59. data/lib/bard.rb +1 -3
  60. data/spec/acceptance/docker/Dockerfile +3 -2
  61. data/spec/bard/capability_spec.rb +8 -50
  62. data/spec/bard/ci/github_actions_spec.rb +117 -14
  63. data/spec/bard/ci/jenkins_spec.rb +139 -0
  64. data/spec/bard/ci/runner_spec.rb +61 -0
  65. data/spec/bard/ci_spec.rb +1 -1
  66. data/spec/bard/cli/ci_spec.rb +34 -27
  67. data/spec/bard/cli/data_spec.rb +7 -26
  68. data/spec/bard/cli/deploy_spec.rb +87 -46
  69. data/spec/bard/cli/hurt_spec.rb +3 -9
  70. data/spec/bard/cli/install_spec.rb +5 -11
  71. data/spec/bard/cli/master_key_spec.rb +5 -19
  72. data/spec/bard/cli/open_spec.rb +14 -30
  73. data/spec/bard/cli/ping_spec.rb +8 -23
  74. data/spec/bard/cli/run_spec.rb +27 -21
  75. data/spec/bard/cli/setup_spec.rb +10 -27
  76. data/spec/bard/cli/ssh_spec.rb +10 -25
  77. data/spec/bard/cli/stage_spec.rb +28 -23
  78. data/spec/bard/cli/vim_spec.rb +3 -9
  79. data/spec/bard/command_spec.rb +1 -8
  80. data/spec/bard/config_spec.rb +78 -98
  81. data/spec/bard/copy_spec.rb +54 -18
  82. data/spec/bard/deploy_strategy/ssh_spec.rb +65 -7
  83. data/spec/bard/deploy_strategy_spec.rb +1 -1
  84. data/spec/bard/dynamic_dsl_spec.rb +18 -98
  85. data/spec/bard/git_spec.rb +9 -5
  86. data/spec/bard/github_spec.rb +2 -2
  87. data/spec/bard/ping_spec.rb +5 -5
  88. data/spec/bard/ssh_copy_spec.rb +44 -0
  89. data/spec/bard/ssh_server_spec.rb +8 -101
  90. data/spec/bard/target_spec.rb +66 -109
  91. data/spec/spec_helper.rb +6 -1
  92. metadata +79 -143
  93. data/README.rdoc +0 -15
  94. data/features/bard_check.feature +0 -94
  95. data/features/bard_deploy.feature +0 -18
  96. data/features/bard_pull.feature +0 -112
  97. data/features/bard_push.feature +0 -112
  98. data/features/podman_testcontainers.feature +0 -16
  99. data/features/step_definitions/check_steps.rb +0 -47
  100. data/features/step_definitions/git_steps.rb +0 -73
  101. data/features/step_definitions/global_steps.rb +0 -56
  102. data/features/step_definitions/podman_steps.rb +0 -23
  103. data/features/step_definitions/rails_steps.rb +0 -44
  104. data/features/step_definitions/submodule_steps.rb +0 -110
  105. data/features/support/grit_ext.rb +0 -13
  106. data/features/support/io.rb +0 -32
  107. data/features/support/podman.rb +0 -153
  108. data/lib/bard/ci/jenkins.rb +0 -105
  109. data/lib/bard/ci/retryable.rb +0 -27
  110. data/lib/bard/ci.rb +0 -50
  111. data/lib/bard/cli/ci.rb +0 -66
  112. data/lib/bard/cli/command.rb +0 -26
  113. data/lib/bard/cli/data.rb +0 -45
  114. data/lib/bard/cli/deploy.rb +0 -85
  115. data/lib/bard/cli/hurt.rb +0 -20
  116. data/lib/bard/cli/install.rb +0 -16
  117. data/lib/bard/cli/master_key.rb +0 -17
  118. data/lib/bard/cli/new.rb +0 -101
  119. data/lib/bard/cli/new_rails_template.rb +0 -197
  120. data/lib/bard/cli/open.rb +0 -22
  121. data/lib/bard/cli/ping.rb +0 -18
  122. data/lib/bard/cli/provision.rb +0 -34
  123. data/lib/bard/cli/run.rb +0 -24
  124. data/lib/bard/cli/setup.rb +0 -56
  125. data/lib/bard/cli/ssh.rb +0 -14
  126. data/lib/bard/cli/stage.rb +0 -27
  127. data/lib/bard/cli/vim.rb +0 -13
  128. data/lib/bard/default_config.rb +0 -35
  129. data/lib/bard/deploy_strategy/ssh.rb +0 -19
  130. data/lib/bard/github_pages.rb +0 -134
  131. data/lib/bard/provision/app.rb +0 -10
  132. data/lib/bard/provision/apt.rb +0 -16
  133. data/lib/bard/provision/authorizedkeys.rb +0 -25
  134. data/lib/bard/provision/data.rb +0 -27
  135. data/lib/bard/provision/deploy.rb +0 -10
  136. data/lib/bard/provision/http.rb +0 -16
  137. data/lib/bard/provision/logrotation.rb +0 -30
  138. data/lib/bard/provision/masterkey.rb +0 -18
  139. data/lib/bard/provision/mysql.rb +0 -22
  140. data/lib/bard/provision/passenger.rb +0 -37
  141. data/lib/bard/provision/repo.rb +0 -72
  142. data/lib/bard/provision/rvm.rb +0 -22
  143. data/lib/bard/provision/ssh.rb +0 -72
  144. data/lib/bard/provision/swapfile.rb +0 -21
  145. data/lib/bard/provision/user.rb +0 -42
  146. data/lib/bard/provision.rb +0 -16
  147. data/lib/bard/server.rb +0 -117
  148. data/spec/bard/cli/command_spec.rb +0 -50
  149. data/spec/bard/cli/new_spec.rb +0 -73
  150. data/spec/bard/cli/provision_spec.rb +0 -42
  151. data/spec/bard/github_pages_spec.rb +0 -143
  152. data/spec/bard/provision/app_spec.rb +0 -33
  153. data/spec/bard/provision/apt_spec.rb +0 -39
  154. data/spec/bard/provision/authorizedkeys_spec.rb +0 -40
  155. data/spec/bard/provision/data_spec.rb +0 -54
  156. data/spec/bard/provision/deploy_spec.rb +0 -33
  157. data/spec/bard/provision/http_spec.rb +0 -57
  158. data/spec/bard/provision/logrotation_spec.rb +0 -34
  159. data/spec/bard/provision/masterkey_spec.rb +0 -63
  160. data/spec/bard/provision/mysql_spec.rb +0 -55
  161. data/spec/bard/provision/passenger_spec.rb +0 -81
  162. data/spec/bard/provision/repo_spec.rb +0 -208
  163. data/spec/bard/provision/rvm_spec.rb +0 -49
  164. data/spec/bard/provision/ssh_spec.rb +0 -229
  165. data/spec/bard/provision/swapfile_spec.rb +0 -32
  166. data/spec/bard/provision/user_spec.rb +0 -103
  167. data/spec/bard/provision_spec.rb +0 -28
  168. data/spec/bard/server_spec.rb +0 -127
  169. /data/lib/bard/{ci → plugins/deploy/ci}/state.rb +0 -0
  170. /data/{install_files → lib/bard/plugins/install}/apt_dependencies.rb +0 -0
  171. /data/{install_files → lib/bard/plugins/install}/ci +0 -0
  172. /data/{install_files → lib/bard/plugins/install}/setup +0 -0
  173. /data/{install_files → lib/bard/plugins/install}/specified_bundler.rb +0 -0
  174. /data/{install_files → lib/bard/plugins/install}/specified_ruby.rb +0 -0
@@ -1,17 +1,48 @@
1
- require "bard/ci/state"
2
- require "bard/ci/retryable"
1
+ require "bard/plugins/deploy/ci/state"
2
+ require "bard/retryable"
3
3
 
4
4
  module Bard
5
5
  class CI
6
6
  class Runner < Struct.new(:project_name, :branch, :sha)
7
- include Retryable
7
+ include Bard::Retryable
8
+
9
+ @runners = {}
10
+
11
+ class << self
12
+ attr_reader :runners
13
+
14
+ def inherited(subclass)
15
+ super
16
+ name = extract_runner_name(subclass)
17
+ runners[name] = subclass if name
18
+ end
19
+
20
+ def [](name)
21
+ runners[name.to_sym]
22
+ end
23
+
24
+ # Returns the last registered runner (most recently loaded wins)
25
+ def default
26
+ runners.values.last
27
+ end
28
+
29
+ private
30
+
31
+ def extract_runner_name(klass)
32
+ klass.name&.split("::")&.last
33
+ &.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
34
+ &.gsub(/([a-z\d])([A-Z])/, '\1_\2')
35
+ &.downcase
36
+ &.to_sym
37
+ end
38
+ end
8
39
 
9
40
  def run
10
41
  start
11
42
  @start_time = Time.new.to_i
12
43
  @last_time_elapsed = get_last_time_elapsed
13
44
  save_state
14
- wait_until_started if respond_to?(:wait_until_started)
45
+ wait_until_started
15
46
 
16
47
  poll_until_complete { |elapsed, last_time| yield elapsed, last_time }
17
48
 
@@ -62,6 +93,9 @@ module Bard
62
93
  raise NotImplementedError, "#{self.class}#success? not implemented"
63
94
  end
64
95
 
96
+ def wait_until_started
97
+ end
98
+
65
99
  def get_last_time_elapsed
66
100
  nil
67
101
  end
@@ -0,0 +1,38 @@
1
+ require "forwardable"
2
+ require "bard/plugins/deploy/ci/runner"
3
+
4
+ module Bard
5
+ class CI
6
+ def initialize(project_name, branch, runner_name: nil)
7
+ @project_name = project_name
8
+ @branch = branch
9
+ @runner_name = runner_name
10
+ end
11
+
12
+ extend Forwardable
13
+ delegate [:run, :resume, :exists?, :console, :status] => :runner
14
+
15
+ private
16
+
17
+ def runner
18
+ @runner ||= choose_runner_class.new(@project_name, @branch, sha)
19
+ end
20
+
21
+ def sha
22
+ @sha ||= `git rev-parse #{@branch}`.chomp
23
+ end
24
+
25
+ def choose_runner_class
26
+ if @runner_name
27
+ runner_class = Runner[@runner_name]
28
+ raise "Unknown CI runner: #{@runner_name}" unless runner_class
29
+ runner_class
30
+ else
31
+ runner_class = Runner.default
32
+ raise "No CI runner available" unless runner_class
33
+ runner_class
34
+ end
35
+ end
36
+ end
37
+ end
38
+
@@ -0,0 +1,27 @@
1
+ require "bard/plugins/deploy/strategy"
2
+ require "bard/copy"
3
+ require "bard/plugins/ssh"
4
+
5
+ module Bard
6
+ class DeployStrategy
7
+ class SSH < DeployStrategy
8
+ def deploy(clone: nil, branch: nil, force: false)
9
+ target.require_capability!(:ssh)
10
+
11
+ if clone
12
+ target.run! "git clone git@github.com:botandrosedesign/#{clone} #{target.path}", home: true
13
+ Bard::Copy.file "config/master.key", from: target.config[:local], to: target
14
+ elsif force
15
+ target.run! "git fetch origin #{branch}"
16
+ target.run! "git checkout -f origin/#{branch}"
17
+ else
18
+ branch ||= target.instance_variable_get(:@branch) || "master"
19
+ target.run! "git pull --ff-only origin #{branch}"
20
+ end
21
+
22
+ target.run! "bin/setup"
23
+ target.run! "bard setup" if clone
24
+ end
25
+ end
26
+ end
27
+ end
@@ -39,7 +39,7 @@ module Bard
39
39
  @target = target
40
40
  end
41
41
 
42
- def deploy
42
+ def deploy(clone: nil, branch: nil, force: false)
43
43
  raise NotImplementedError, "Subclasses must implement #deploy"
44
44
  end
45
45
 
@@ -0,0 +1,240 @@
1
+ require "bard/plugins/ping"
2
+ require "bard/plugins/git"
3
+ require "bard/command"
4
+ require "bard/plugins/deploy/strategy"
5
+ require "bard/plugins/deploy/ssh_strategy"
6
+ require "bard/plugins/deploy/ci"
7
+ require "bard/plugins/deploy/ci/jenkins"
8
+ require "bard/plugins/deploy/ci/local"
9
+ require "bard/plugins/deploy/ci/github_actions"
10
+ require "tmpdir"
11
+
12
+ class Bard::CLI
13
+ option :"skip-ci", type: :boolean
14
+ option :"local-ci", type: :boolean
15
+ option :ci, type: :string
16
+ option :clone, type: :boolean
17
+ option :target, type: :string, default: "production"
18
+ desc "deploy [BRANCH]", "deploys branch to target (default: current branch to production)"
19
+ def deploy(branch = nil)
20
+ branch ||= Bard::Git.current_branch
21
+
22
+ if branch == "master"
23
+ if !Bard::Git.up_to_date_with_remote?(branch)
24
+ run! "git push origin #{branch}:#{branch}"
25
+ end
26
+ invoke :ci, [branch], options.slice("local-ci", "ci") unless options["skip-ci"] || config.ci == false
27
+
28
+ else
29
+ run! "git fetch origin"
30
+ if Bard::Git.current_branch != "master" && !Bard::Git.in_linked_worktree?
31
+ run! "git fetch origin master:master"
32
+ end
33
+
34
+ unless Bard::Git.fast_forward_merge?("origin/master", branch)
35
+ puts "The master branch has advanced. Attempting rebase..."
36
+ if branch == Bard::Git.current_branch
37
+ run! "git rebase origin/master"
38
+ else
39
+ tmpdir = Dir.mktmpdir("bard-rebase")
40
+ begin
41
+ run! "git worktree add --detach #{tmpdir} #{branch}"
42
+ success = Dir.chdir(tmpdir) { system("git rebase origin/master") }
43
+ rebased_sha = Dir.chdir(tmpdir) { `git rev-parse HEAD`.strip } if success
44
+ run! "git worktree remove #{tmpdir} --force"
45
+ unless success
46
+ puts red("!!! ") + "Rebase failed due to conflicts."
47
+ puts "Please rebase #{branch} manually:"
48
+ puts " git checkout #{branch}"
49
+ puts " git rebase origin/master"
50
+ exit 1
51
+ end
52
+ run! "git branch -f #{branch} #{rebased_sha}"
53
+ ensure
54
+ FileUtils.rm_rf(tmpdir) if Dir.exist?(tmpdir)
55
+ end
56
+ end
57
+ end
58
+
59
+ run! "git push -f origin #{branch}:#{branch}"
60
+
61
+ invoke :ci, [branch], options.slice("local-ci", "ci") unless options["skip-ci"] || config.ci == false
62
+
63
+ run! "git push origin #{branch}:master"
64
+ if Bard::Git.current_branch == "master"
65
+ run! "git pull --ff-only origin master"
66
+ elsif Bard::Git.in_linked_worktree?
67
+ run! "git fetch origin master"
68
+ else
69
+ run! "git fetch origin master:master"
70
+ end
71
+ end
72
+
73
+ if `git remote` =~ /\bgithub\b/
74
+ run! "git push github"
75
+ end
76
+
77
+ to = options[:target].to_sym
78
+
79
+ target = config[to]
80
+ strategy = target.deploy_strategy_instance
81
+ if options[:clone]
82
+ strategy.deploy(clone: project_name)
83
+ else
84
+ strategy.deploy
85
+ end
86
+
87
+ puts green("Deploy Succeeded")
88
+
89
+ if branch != "master"
90
+ puts "Deleting branch: #{branch}"
91
+ run! "git push --delete origin #{branch}"
92
+
93
+ if branch == Bard::Git.current_branch && Bard::Git.in_linked_worktree?
94
+ worktree_path = Dir.pwd
95
+ main_checkout = File.dirname(File.expand_path(`git rev-parse --git-common-dir`.chomp))
96
+ Dir.chdir(main_checkout)
97
+ run! "git worktree remove #{worktree_path}"
98
+ run! "git branch -D #{branch}"
99
+ puts "Worktree removed. Run: cd #{main_checkout}"
100
+ else
101
+ if branch == Bard::Git.current_branch
102
+ run! "git checkout master"
103
+ end
104
+ run! "git branch -D #{branch}"
105
+ end
106
+ end
107
+
108
+ ping to
109
+ rescue Bard::Command::Error => e
110
+ puts red("!!! ") + "Running command failed: #{yellow(e.message)}"
111
+ exit 1
112
+ end
113
+
114
+ desc "stage [branch=HEAD]", "pushes current branch, and stages it"
115
+ def stage(branch = Bard::Git.current_branch)
116
+ if config[:production] == config[:staging]
117
+ raise Thor::Error.new("`bard stage` is disabled until a production target is defined. Until then, please use `bard deploy` to deploy to the staging target.")
118
+ end
119
+
120
+ run! "git push -u origin #{branch}", verbose: true
121
+
122
+ target = config[:staging]
123
+ strategy = target.deploy_strategy_instance
124
+ strategy.deploy(branch: branch, force: true)
125
+
126
+ puts green("Stage Succeeded")
127
+
128
+ ping :staging
129
+ rescue Bard::Command::Error => e
130
+ puts red("!!! ") + "Running command failed: #{yellow(e.message)}"
131
+ exit 1
132
+ end
133
+
134
+ option :"local-ci", type: :boolean
135
+ option :ci, type: :string
136
+ option :status, type: :boolean
137
+ option :resume, type: :boolean
138
+ desc "ci [branch=HEAD]", "runs ci against BRANCH"
139
+ def ci(branch = Bard::Git.current_branch)
140
+ runner_name = if options["local-ci"]
141
+ :local
142
+ elsif options["ci"]
143
+ options["ci"].to_sym
144
+ else
145
+ config.ci
146
+ end
147
+ ci = Bard::CI.new(project_name, branch, runner_name: runner_name)
148
+ unless ci.exists?
149
+ puts red("No CI found for #{project_name}!")
150
+ puts "Re-run with --skip-ci to bypass CI, if you absolutely must, and know what you're doing."
151
+ exit 1
152
+ end
153
+
154
+ return puts ci.status if options["status"]
155
+
156
+ if options["resume"]
157
+ puts "Continuous integration: resuming build..."
158
+ success = ci.resume do |elapsed_time, last_time|
159
+ if last_time
160
+ percentage = (elapsed_time.to_f / last_time.to_f * 100).to_i
161
+ output = " Estimated completion: #{percentage}%"
162
+ else
163
+ output = " No estimated completion time. Elapsed time: #{elapsed_time} sec"
164
+ end
165
+ print "\x08" * output.length
166
+ print output
167
+ $stdout.flush
168
+ end
169
+ else
170
+ puts "Continuous integration: starting build on #{branch}..."
171
+
172
+ success = ci.run do |elapsed_time, last_time|
173
+ if last_time
174
+ percentage = (elapsed_time.to_f / last_time.to_f * 100).to_i
175
+ output = " Estimated completion: #{percentage}%"
176
+ else
177
+ output = " No estimated completion time. Elapsed time: #{elapsed_time} sec"
178
+ end
179
+ print "\x08" * output.length
180
+ print output
181
+ $stdout.flush
182
+ end
183
+ end
184
+
185
+ if success
186
+ puts
187
+ puts "Continuous integration: success!"
188
+ else
189
+ puts
190
+ puts ci.console
191
+ puts red("Automated tests failed!")
192
+ exit 1
193
+ end
194
+ end
195
+
196
+ option :from, default: "production"
197
+ option :to, default: "local"
198
+ desc "master_key --from=production --to=local", "copy master key from from to to"
199
+ def master_key
200
+ from = config[options[:from]]
201
+ to = config[options[:to]]
202
+ Bard::Copy.file "config/master.key", from: from, to: to
203
+ end
204
+ end
205
+
206
+ require "bard/config"
207
+
208
+ class Bard::Config
209
+ def ci(system = nil)
210
+ if system.nil?
211
+ @ci_system
212
+ else
213
+ @ci_system = system
214
+ end
215
+ end
216
+ end
217
+
218
+ require "bard/target"
219
+
220
+ class Bard::Target
221
+ def deploy_strategy
222
+ @deploy_strategy
223
+ end
224
+
225
+ def deploy_strategy_instance
226
+ strategy = @deploy_strategy
227
+ strategy ||= :ssh if has_capability?(:ssh)
228
+ raise "No deployment strategy configured for target #{key}" unless strategy
229
+
230
+ strategy_class = Bard::DeployStrategy[strategy]
231
+ raise "Unknown deployment strategy: #{strategy}" unless strategy_class
232
+
233
+ strategy_class.new(self)
234
+ end
235
+
236
+ def strategy_options(strategy_name)
237
+ @strategy_options_hash ||= {}
238
+ @strategy_options_hash[strategy_name] || {}
239
+ end
240
+ end
@@ -21,12 +21,15 @@ module Bard
21
21
 
22
22
  def sha_of ref
23
23
  sha = `git rev-parse #{ref} 2>/dev/null`.chomp
24
- return sha if command_succeeded?
24
+ return sha if $?.success?
25
25
  nil # Branch doesn't exist
26
26
  end
27
27
 
28
- def command_succeeded?
29
- $?.success?
28
+ def in_linked_worktree?
29
+ git_dir = `git rev-parse --git-dir 2>/dev/null`.chomp
30
+ common_dir = `git rev-parse --git-common-dir 2>/dev/null`.chomp
31
+ return false if git_dir.empty? || common_dir.empty?
32
+ File.expand_path(git_dir) != File.expand_path(common_dir)
30
33
  end
31
34
  end
32
35
  end
@@ -2,11 +2,12 @@ require "net/http"
2
2
  require "json"
3
3
  require "base64"
4
4
  require "rbnacl"
5
- require "bard/ci/retryable"
5
+ require "bard/retryable"
6
+ require "bard/secrets"
6
7
 
7
8
  module Bard
8
9
  class Github < Struct.new(:project_name)
9
- include CI::Retryable
10
+ include Retryable
10
11
 
11
12
  def initialize(project_name, api_key: nil)
12
13
  super(project_name)
@@ -107,10 +108,7 @@ module Bard
107
108
  private
108
109
 
109
110
  def api_key
110
- @api_key ||= begin
111
- raw = `git ls-remote -t git@github.com:botandrosedesign/secrets`
112
- raw[/github-apikey\|(.+)$/, 1]
113
- end
111
+ @api_key ||= Bard::Secrets.fetch("github-apikey")
114
112
  end
115
113
 
116
114
  def request path, &block
@@ -1,5 +1,5 @@
1
- require "bard/deploy_strategy"
2
- require "bard/git"
1
+ require "bard/plugins/deploy/strategy"
2
+ require "bard/plugins/git"
3
3
  require "fileutils"
4
4
  require "uri"
5
5
 
@@ -8,11 +8,10 @@ module Bard
8
8
  class GithubPages < DeployStrategy
9
9
  def initialize(target, url = nil, **options)
10
10
  super(target)
11
- @url = url
11
+ @url = url || target.github_pages
12
12
  @options = options
13
13
 
14
- # Auto-configure ping URL if provided
15
- target.ping(url) if url
14
+ target.url(url) if url
16
15
  end
17
16
 
18
17
  def deploy
@@ -65,7 +64,15 @@ module Bard
65
64
  ensure
66
65
  # cleanup
67
66
  run! <<~SH
68
- cat tmp/pids/server.pid | xargs -I {} kill {}
67
+ PID=$(cat tmp/pids/server.pid 2>/dev/null)
68
+ if [ -n "$PID" ]; then
69
+ kill $PID 2>/dev/null
70
+ for i in 1 2 3 4 5; do
71
+ kill -0 $PID 2>/dev/null || break
72
+ sleep 1
73
+ done
74
+ kill -9 $PID 2>/dev/null || true
75
+ fi
69
76
  rm -rf public/assets
70
77
  SH
71
78
  end
@@ -0,0 +1,30 @@
1
+ require "bard/plugins/github_pages/strategy"
2
+ require "bard/config"
3
+ require "bard/target"
4
+
5
+ class Bard::Config
6
+ def github_pages(url)
7
+ uri = url.start_with?("http") ? URI.parse(url) : URI.parse("https://#{url}")
8
+ hostname = uri.hostname.sub(/^www\./, "")
9
+
10
+ remove_target :production
11
+ target :production do
12
+ github_pages url
13
+ url(hostname) if hostname
14
+ end
15
+
16
+ backup(false) if respond_to?(:backup)
17
+ end
18
+ end
19
+
20
+ class Bard::Target
21
+ def github_pages(url = nil)
22
+ if url.nil?
23
+ @github_pages_url
24
+ else
25
+ @deploy_strategy = :github_pages
26
+ @github_pages_url = url
27
+ enable_capability(:github_pages)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ class Bard::CLI
2
+ desc "hurt <command>", "reruns a command until it fails"
3
+ def hurt(*args)
4
+ (1..).each do |count|
5
+ puts "Running attempt #{count}"
6
+ system *args
7
+ unless $?.success?
8
+ puts "Ran #{count-1} times before failing"
9
+ break
10
+ end
11
+ end
12
+ end
13
+ end
@@ -4,6 +4,8 @@ updates:
4
4
  directory: "/"
5
5
  schedule:
6
6
  interval: "weekly"
7
+ cooldown:
8
+ default-days: 7
7
9
  allow:
8
10
  - dependency-type: "all"
9
11
  rebase-strategy: "disabled"
@@ -14,4 +16,3 @@ updates:
14
16
  update-types:
15
17
  - "minor"
16
18
  - "patch"
17
-
@@ -6,7 +6,7 @@ on:
6
6
  - cron: '0 0 */3 * *'
7
7
  jobs:
8
8
  build_ruby_cache:
9
- runs-on: ubuntu-22.04
9
+ runs-on: ubuntu-24.04
10
10
  timeout-minutes: 30
11
11
  steps:
12
12
  - uses: actions/checkout@v3
@@ -12,7 +12,7 @@ permissions:
12
12
 
13
13
  jobs:
14
14
  test:
15
- runs-on: ubuntu-22.04${{ github.actor != 'dependabot[bot]' && '-16core' || '' }}
15
+ runs-on: ubuntu-24.04${{ github.actor != 'dependabot[bot]' && '-8core' || '' }}
16
16
  timeout-minutes: 30
17
17
  env:
18
18
  RAILS_ENV: test
@@ -47,7 +47,7 @@ jobs:
47
47
 
48
48
  autodeploy-dependabot-prs:
49
49
  needs: test
50
- runs-on: ubuntu-22.04
50
+ runs-on: ubuntu-24.04
51
51
  timeout-minutes: 30
52
52
  if: github.actor == 'dependabot[bot]'
53
53
  steps:
@@ -0,0 +1,9 @@
1
+ class Bard::CLI
2
+ desc "install", "copies bin/setup and bin/ci scripts into current project."
3
+ def install
4
+ install_files_path = File.expand_path("install", __dir__)
5
+
6
+ system "cp -R #{install_files_path}/* bin/"
7
+ system "cp -R #{install_files_path}/.github ./"
8
+ end
9
+ end
@@ -0,0 +1,20 @@
1
+ require "bard/plugins/url"
2
+
3
+ class Bard::CLI
4
+ desc "open [target=production]", "opens the url in the web browser."
5
+ def open(target = :production)
6
+ exec "xdg-open #{open_url target}"
7
+ end
8
+
9
+ no_commands do
10
+ def open_url(target)
11
+ if target.to_sym == :ci
12
+ "https://github.com/botandrosedesign/#{project_name}/actions/workflows/ci.yml"
13
+ else
14
+ t = config[target]
15
+ t.require_capability!(:url)
16
+ t.url
17
+ end
18
+ end
19
+ end
20
+ end
@@ -2,13 +2,13 @@ require "net/http"
2
2
  require "uri"
3
3
 
4
4
  module Bard
5
- class Ping < Struct.new(:server)
6
- def self.call server
7
- new(server).call
5
+ class Ping < Struct.new(:target)
6
+ def self.call target
7
+ new(target).call
8
8
  end
9
9
 
10
10
  def call
11
- server.ping.reject { |url| reachable?(url) }
11
+ target.ping.reject { |url| reachable?(url) }
12
12
  end
13
13
 
14
14
  private
@@ -0,0 +1,23 @@
1
+ require "bard/target"
2
+ require "bard/plugins/url/target_methods"
3
+ require "bard/plugins/ping/check"
4
+
5
+ class Bard::Target
6
+ def ping(*urls)
7
+ if urls.empty?
8
+ @ping_urls || [url].compact
9
+ elsif urls.first == false
10
+ @ping_urls = []
11
+ else
12
+ @ping_urls = urls.flatten.map { |u| normalize_url(u) }
13
+ end
14
+ end
15
+
16
+ def ping!
17
+ require_capability!(:url)
18
+ failed_urls = Bard::Ping.call(self)
19
+ if failed_urls.any?
20
+ raise "Ping failed for: #{failed_urls.join(", ")}"
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,10 @@
1
+ require "bard/plugins/ping/target_methods"
2
+
3
+ class Bard::CLI
4
+ desc "ping [target=production]", "hits the target over http to verify that its up."
5
+ def ping(target = :production)
6
+ down_urls = Bard::Ping.call(config[target])
7
+ down_urls.each { |url| puts "#{url} is down!" }
8
+ exit 1 if down_urls.any?
9
+ end
10
+ end
@@ -0,0 +1,19 @@
1
+ require "bard/command"
2
+
3
+ class Bard::CLI
4
+ # HACK: we don't use Thor::Base#run, so its okay to stomp on it here
5
+ original_verbose, $VERBOSE = $VERBOSE, nil
6
+ Thor::THOR_RESERVED_WORDS -= ["run"]
7
+ $VERBOSE = original_verbose
8
+
9
+ option :target, type: :string, default: "production"
10
+ option :home, type: :boolean
11
+ desc "run <command>", "run the given command on the specified target"
12
+ def run(*args)
13
+ target = config[options[:target].to_sym]
14
+ target.run!(*args.join(" "), verbose: true, home: options[:home])
15
+ rescue Bard::Command::Error => e
16
+ puts red("!!! ") + "Running command failed: #{yellow(e.message)}"
17
+ exit 1
18
+ end
19
+ end