bard 2.2.0 → 3.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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -487
  3. data/Rakefile +1 -4
  4. data/bard.gemspec +2 -11
  5. data/lib/bard/config.rb +22 -20
  6. data/lib/bard/plugins/ping/target_methods.rb +0 -9
  7. data/lib/bard/plugins/ssh/connection.rb +2 -35
  8. data/lib/bard/target.rb +0 -15
  9. data/lib/bard/version.rb +1 -1
  10. data/lib/bard.rb +2 -1
  11. data/spec/bard/capability_spec.rb +0 -8
  12. data/spec/bard/config_spec.rb +30 -67
  13. data/spec/bard/target_spec.rb +1 -36
  14. metadata +4 -225
  15. data/ARCHITECTURE.md +0 -957
  16. data/CUSTOM_STRATEGIES.md +0 -701
  17. data/MIGRATION_GUIDE.md +0 -513
  18. data/PLUGINS.md +0 -99
  19. data/bin/bard +0 -4
  20. data/features/ci.feature +0 -63
  21. data/features/data.feature +0 -13
  22. data/features/deploy.feature +0 -14
  23. data/features/deploy_git_workflow.feature +0 -89
  24. data/features/run.feature +0 -14
  25. data/features/step_definitions/bard_steps.rb +0 -136
  26. data/features/support/bard-coverage +0 -16
  27. data/features/support/env.rb +0 -22
  28. data/features/support/test_server.rb +0 -216
  29. data/lib/bard/cli.rb +0 -44
  30. data/lib/bard/command.rb +0 -31
  31. data/lib/bard/copy.rb +0 -45
  32. data/lib/bard/plugins/data.rb +0 -56
  33. data/lib/bard/plugins/deploy/ci/github_actions.rb +0 -202
  34. data/lib/bard/plugins/deploy/ci/jenkins.rb +0 -176
  35. data/lib/bard/plugins/deploy/ci/local.rb +0 -57
  36. data/lib/bard/plugins/deploy/ci/runner.rb +0 -112
  37. data/lib/bard/plugins/deploy/ci/state.rb +0 -40
  38. data/lib/bard/plugins/deploy/ci.rb +0 -38
  39. data/lib/bard/plugins/deploy/ssh_strategy.rb +0 -26
  40. data/lib/bard/plugins/deploy/strategy.rb +0 -60
  41. data/lib/bard/plugins/deploy.rb +0 -260
  42. data/lib/bard/plugins/git.rb +0 -45
  43. data/lib/bard/plugins/github.rb +0 -152
  44. data/lib/bard/plugins/github_pages/strategy.rb +0 -170
  45. data/lib/bard/plugins/github_pages.rb +0 -35
  46. data/lib/bard/plugins/hurt.rb +0 -13
  47. data/lib/bard/plugins/install/.github/dependabot.yml +0 -18
  48. data/lib/bard/plugins/install/.github/workflows/cache-ci.yml +0 -15
  49. data/lib/bard/plugins/install/.github/workflows/ci.yml +0 -74
  50. data/lib/bard/plugins/install/apt_dependencies.rb +0 -33
  51. data/lib/bard/plugins/install/ci +0 -5
  52. data/lib/bard/plugins/install/setup +0 -13
  53. data/lib/bard/plugins/install/specified_bundler.rb +0 -15
  54. data/lib/bard/plugins/install/specified_ruby.rb +0 -59
  55. data/lib/bard/plugins/install.rb +0 -9
  56. data/lib/bard/plugins/open.rb +0 -20
  57. data/lib/bard/plugins/ping/check.rb +0 -66
  58. data/lib/bard/plugins/ping.rb +0 -10
  59. data/lib/bard/plugins/run.rb +0 -19
  60. data/lib/bard/plugins/setup.rb +0 -75
  61. data/lib/bard/plugins/ssh/copy.rb +0 -95
  62. data/lib/bard/plugins/ssh.rb +0 -10
  63. data/lib/bard/plugins/vim.rb +0 -6
  64. data/lib/bard/retryable.rb +0 -25
  65. data/lib/bard/secrets.rb +0 -10
  66. data/spec/acceptance/.gitignore +0 -4
  67. data/spec/acceptance/docker/Dockerfile +0 -36
  68. data/spec/acceptance/docker/test_key +0 -27
  69. data/spec/acceptance/docker/test_key.pub +0 -1
  70. data/spec/bard/ci/github_actions_spec.rb +0 -138
  71. data/spec/bard/ci/jenkins_spec.rb +0 -139
  72. data/spec/bard/ci/runner_spec.rb +0 -61
  73. data/spec/bard/ci_spec.rb +0 -10
  74. data/spec/bard/cli/ci_spec.rb +0 -170
  75. data/spec/bard/cli/data_spec.rb +0 -58
  76. data/spec/bard/cli/deploy_spec.rb +0 -243
  77. data/spec/bard/cli/hurt_spec.rb +0 -17
  78. data/spec/bard/cli/install_spec.rb +0 -19
  79. data/spec/bard/cli/master_key_spec.rb +0 -38
  80. data/spec/bard/cli/open_spec.rb +0 -51
  81. data/spec/bard/cli/ping_spec.rb +0 -32
  82. data/spec/bard/cli/run_spec.rb +0 -57
  83. data/spec/bard/cli/setup_spec.rb +0 -99
  84. data/spec/bard/cli/ssh_spec.rb +0 -40
  85. data/spec/bard/cli/stage_spec.rb +0 -127
  86. data/spec/bard/cli/vim_spec.rb +0 -28
  87. data/spec/bard/command_spec.rb +0 -30
  88. data/spec/bard/copy_spec.rb +0 -69
  89. data/spec/bard/deploy_strategy/github_pages_spec.rb +0 -58
  90. data/spec/bard/deploy_strategy/ssh_spec.rb +0 -135
  91. data/spec/bard/deploy_strategy_spec.rb +0 -107
  92. data/spec/bard/dynamic_dsl_spec.rb +0 -46
  93. data/spec/bard/git_spec.rb +0 -101
  94. data/spec/bard/github_spec.rb +0 -45
  95. data/spec/bard/ping_spec.rb +0 -60
  96. data/spec/bard/ssh_copy_spec.rb +0 -44
@@ -1,202 +0,0 @@
1
- require "time"
2
- require "bard/plugins/github"
3
- require "bard/plugins/deploy/ci/runner"
4
-
5
- module Bard
6
- class CI
7
- class GithubActions < Runner
8
- def exists?
9
- File.exist?(".github/workflows/ci.yml")
10
- end
11
-
12
- def console
13
- @run.console
14
- end
15
-
16
- def status
17
- last_run = api.last_run
18
- if last_run.building?
19
- "Building..."
20
- elsif last_run.success?
21
- "Succeeded!"
22
- elsif last_run.failure?
23
- "Failed!\n\n#{last_run.console}"
24
- else
25
- raise "Unknown job status: #{last_run.inspect}"
26
- end
27
- end
28
-
29
- protected
30
-
31
- def start
32
- @run = api.create_run!(branch)
33
- end
34
-
35
- def get_last_time_elapsed
36
- api.last_successful_run&.time_elapsed
37
- end
38
-
39
- def building?
40
- @run.building?
41
- end
42
-
43
- def success?
44
- @run.success?
45
- end
46
-
47
- def get_state_data
48
- {
49
- "project_name" => project_name,
50
- "branch" => branch,
51
- "run_id" => @run.id,
52
- "start_time" => @start_time,
53
- "last_time_elapsed" => @last_time_elapsed
54
- }
55
- end
56
-
57
- def restore_state(data)
58
- @run = api.find_run(data["run_id"])
59
- @start_time = data["start_time"]
60
- @last_time_elapsed = data["last_time_elapsed"]
61
- end
62
-
63
- def poll_until_complete
64
- while @run.building?
65
- elapsed_time = Time.new.to_i - @start_time
66
- yield elapsed_time, @last_time_elapsed
67
- save_state
68
- sleep(2)
69
- @run = api.find_run(@run.id)
70
- end
71
- end
72
-
73
- private
74
-
75
- def api
76
- @api ||= API.new(project_name)
77
- end
78
-
79
- class API < Struct.new(:project_name)
80
- def last_run
81
- response = client.get("actions/runs", event: "workflow_dispatch", per_page: 1)
82
- if json = response["workflow_runs"][0]
83
- Run.new(self, json)
84
- end
85
- end
86
-
87
- def last_successful_run
88
- successful_runs = client.get("actions/runs", event: "workflow_dispatch", status: "success", per_page: 1)
89
- if json = successful_runs["workflow_runs"][0]
90
- Run.new(self, json)
91
- end
92
- end
93
-
94
- def find_run id
95
- json = client.get("actions/runs/#{id}")
96
- Run.new(self, json)
97
- end
98
-
99
- def create_run! branch
100
- start_time = Time.now
101
- client.post("actions/workflows/ci.yml/dispatches", ref: branch, inputs: { "git-ref": branch })
102
- sha = `git rev-parse #{branch}`.chomp
103
-
104
- loop do
105
- runs = client.get("actions/runs", event: "workflow_dispatch", head_sha: sha, created: ">#{start_time.iso8601}")
106
- if json = runs["workflow_runs"].first
107
- return Run.new(self, json)
108
- end
109
- sleep 1
110
- end
111
- end
112
-
113
- def find_job_by_run_id run_id
114
- jobs = client.get("actions/runs/#{run_id}/jobs", filter: "latest", per_page: 1)
115
- job_json = jobs["jobs"][0]
116
- Job.new(self, job_json)
117
- end
118
-
119
- def download_logs_by_job_id job_id
120
- client.get("actions/jobs/#{job_id}/logs")
121
- end
122
-
123
- private
124
-
125
- def client
126
- @client ||= Bard::Github.new(project_name)
127
- end
128
- end
129
-
130
- class Run < Struct.new(:api, :json)
131
- def id
132
- json["id"]
133
- end
134
-
135
- def time_elapsed
136
- job.time_elapsed
137
- end
138
-
139
- def building?
140
- %w[in_progress queued requested waiting pending]
141
- .include?(json["status"])
142
- end
143
-
144
- def success?
145
- status == "completed" && conclusion == "success"
146
- end
147
-
148
- def failure?
149
- conclusion == "failure"
150
- end
151
-
152
- def job
153
- @job ||= api.find_job_by_run_id(id)
154
- end
155
-
156
- def console
157
- job.logs
158
- end
159
-
160
- def branch
161
- json["head_branch"]
162
- end
163
-
164
- def sha
165
- json["head_sha"]
166
- end
167
-
168
- def status
169
- json["status"]
170
- end
171
-
172
- def conclusion
173
- json["conclusion"]
174
- end
175
-
176
- def started_at
177
- Time.parse(json["run_started_at"])
178
- end
179
-
180
- def updated_at
181
- Time.parse(json["updated_at"])
182
- end
183
- end
184
-
185
- class Job < Struct.new(:api, :json)
186
- def id
187
- json["id"]
188
- end
189
-
190
- def time_elapsed
191
- Time.parse(json["completed_at"]).to_i -
192
- Time.parse(json["started_at"]).to_i
193
- end
194
-
195
- def logs
196
- @logs ||= api.download_logs_by_job_id(id)
197
- end
198
- end
199
- end
200
- end
201
- end
202
-
@@ -1,176 +0,0 @@
1
- require "json"
2
- require "bard/plugins/deploy/ci/runner"
3
- require "bard/secrets"
4
-
5
- module Bard
6
- class CI
7
- class Jenkins < Runner
8
- def exists?
9
- `curl -s -I #{ci_host}/` =~ /\b200 OK\b/ or create!
10
- end
11
-
12
- private def create!
13
- git_url = `git remote get-url origin`.strip
14
- config = JOB_CONFIG_XML.sub("GIT_URL", git_url)
15
- if File.exist?("config/master.key")
16
- master_key = File.read("config/master.key").strip
17
- master_key_step = <<~XML.chomp
18
- <hudson.tasks.Shell>
19
- <command>echo #{master_key} > config/master.key</command>
20
- </hudson.tasks.Shell>
21
-
22
- XML
23
- config = config.sub("<builders>\n", "<builders>\n #{master_key_step}")
24
- end
25
- `curl -s -X POST "http://#{auth}@ci.botandrose.com/createItem?name=#{project_name}" -H "Content-Type: application/xml" -d '#{config}'`
26
- end
27
-
28
- def console
29
- raw = `curl -s #{ci_host}/lastBuild/console`
30
- raw[%r{<pre.*?>(.+)</pre>}m, 1]
31
- end
32
-
33
- attr_accessor :last_response
34
-
35
- protected
36
-
37
- def wait_until_started
38
- sleep(2) until started?
39
- end
40
-
41
- def start
42
- command = "curl -s -I -X POST -L '#{ci_host}/buildWithParameters?GIT_REF=#{branch}'"
43
- output = `#{command}`
44
- @queueId = output[%r{Location: .+/queue/item/(\d+)/}, 1].to_i
45
- end
46
-
47
- def building?
48
- retry_with_backoff do
49
- self.last_response = `curl -s #{ci_host}/#{job_id}/api/json?tree=building,result`
50
- raise "Blank response from CI" if last_response.empty?
51
- end
52
- last_response.include? '"building":true'
53
- end
54
-
55
- def success?
56
- last_response.include? '"result":"SUCCESS"'
57
- end
58
-
59
- def get_state_data
60
- {
61
- "project_name" => project_name,
62
- "branch" => branch,
63
- "queue_id" => @queueId,
64
- "job_id" => @job_id,
65
- "start_time" => @start_time,
66
- "last_time_elapsed" => @last_time_elapsed
67
- }
68
- end
69
-
70
- def restore_state(data)
71
- @queueId = data["queue_id"]
72
- @job_id = data["job_id"]
73
- @start_time = data["start_time"]
74
- @last_time_elapsed = data["last_time_elapsed"]
75
- end
76
-
77
- private
78
-
79
- def get_last_time_elapsed
80
- response = retry_with_backoff do
81
- response = `curl -s #{ci_host}/lastStableBuild/api/xml`
82
- raise "Blank response from CI" if response.empty?
83
- response
84
- end
85
- response.match(/<duration>(\d+)<\/duration>/)
86
- $1 ? $1.to_i / 1000 : nil
87
- rescue => e
88
- puts " Warning: Could not get last build duration: #{e.message}"
89
- nil
90
- end
91
-
92
- def auth
93
- @auth ||= "#{Bard::Secrets.fetch("jenkins-user")}:#{Bard::Secrets.fetch("jenkins-token")}"
94
- end
95
-
96
- def ci_host
97
- "http://#{auth}@ci.botandrose.com/job/#{project_name}"
98
- end
99
-
100
- def started?
101
- retry_with_backoff do
102
- command = "curl -s -g '#{ci_host}/api/json?depth=1&tree=builds[queueId,number]'"
103
- output = `#{command}`
104
- raise "Blank response from CI" if output.empty?
105
- builds = JSON.parse(output)["builds"]
106
- raise "Build not found in builds list" if builds.empty?
107
- builds.first["queueId"] == @queueId
108
- end
109
- end
110
-
111
- def job_id
112
- @job_id ||= begin
113
- retry_with_backoff do
114
- output = `curl -s -g '#{ci_host}/api/json?depth=1&tree=builds[queueId,number]'`
115
- raise "Blank response from CI" if output.empty?
116
- builds = JSON.parse(output)["builds"]
117
- build = builds.find { |b| b["queueId"] == @queueId }
118
- build["number"]
119
- end
120
- end
121
- end
122
- JOB_CONFIG_XML = <<~XML
123
- <?xml version="1.0" encoding="UTF-8"?>
124
- <project>
125
- <actions/>
126
- <description></description>
127
- <keepDependencies>false</keepDependencies>
128
- <properties>
129
- <hudson.model.ParametersDefinitionProperty>
130
- <parameterDefinitions>
131
- <hudson.model.StringParameterDefinition>
132
- <name>GIT_REF</name>
133
- <description></description>
134
- <defaultValue>master</defaultValue>
135
- </hudson.model.StringParameterDefinition>
136
- </parameterDefinitions>
137
- </hudson.model.ParametersDefinitionProperty>
138
- </properties>
139
- <scm class="hudson.plugins.git.GitSCM" plugin="git@3.3.0">
140
- <configVersion>2</configVersion>
141
- <userRemoteConfigs>
142
- <hudson.plugins.git.UserRemoteConfig>
143
- <url>GIT_URL</url>
144
- </hudson.plugins.git.UserRemoteConfig>
145
- </userRemoteConfigs>
146
- <branches>
147
- <hudson.plugins.git.BranchSpec>
148
- <name>$GIT_REF</name>
149
- </hudson.plugins.git.BranchSpec>
150
- </branches>
151
- <doGenerateSubmoduleConfigurations>false</doGenerateSubmoduleConfigurations>
152
- <submoduleCfg class="list"/>
153
- <extensions/>
154
- </scm>
155
- <canRoam>true</canRoam>
156
- <disabled>false</disabled>
157
- <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
158
- <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
159
- <triggers/>
160
- <concurrentBuild>false</concurrentBuild>
161
- <builders>
162
- <hudson.tasks.Shell>
163
- <command>bash -l -c bin/setup</command>
164
- </hudson.tasks.Shell>
165
- <hudson.tasks.Shell>
166
- <command>bash -l -c bin/ci</command>
167
- </hudson.tasks.Shell>
168
- </builders>
169
- <publishers/>
170
- <buildWrappers/>
171
- </project>
172
- XML
173
- end
174
- end
175
- end
176
-
@@ -1,57 +0,0 @@
1
- require "tempfile"
2
- require "bard/plugins/deploy/ci/runner"
3
-
4
- module Bard
5
- class CI
6
- class Local < Runner
7
- def exists?
8
- true
9
- end
10
-
11
- def console
12
- @console
13
- end
14
-
15
- protected
16
-
17
- def start
18
- @output_file = Tempfile.new("bard-ci")
19
- @wait_thread = Process.detach(spawn("CLEAN=true bin/rake ci", [:out, :err] => @output_file))
20
- end
21
-
22
- def building?
23
- ![nil, false].include?(@wait_thread.status)
24
- end
25
-
26
- def success?
27
- @wait_thread.value.success?
28
- end
29
-
30
- def get_state_data
31
- {
32
- "project_name" => project_name,
33
- "branch" => branch,
34
- "start_time" => @start_time
35
- }
36
- end
37
-
38
- def restore_state(data)
39
- raise "Cannot resume local CI: process is no longer running. Start a new build with 'bard ci'."
40
- end
41
-
42
- def poll_until_complete
43
- while building?
44
- elapsed_time = Time.new.to_i - @start_time
45
- yield elapsed_time, nil
46
- save_state
47
- sleep(2)
48
- end
49
-
50
- @output_file.rewind
51
- @console = @output_file.read
52
- @output_file.close!
53
- end
54
- end
55
- end
56
- end
57
-
@@ -1,112 +0,0 @@
1
- require "bard/plugins/deploy/ci/state"
2
- require "bard/retryable"
3
-
4
- module Bard
5
- class CI
6
- class Runner < Struct.new(:project_name, :branch, :sha)
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
39
-
40
- def run
41
- start
42
- @start_time = Time.new.to_i
43
- @last_time_elapsed = get_last_time_elapsed
44
- save_state
45
- wait_until_started
46
-
47
- poll_until_complete { |elapsed, last_time| yield elapsed, last_time }
48
-
49
- state.delete
50
- success?
51
- end
52
-
53
- def resume
54
- saved_state = state.load
55
- raise "No saved CI state found for #{project_name}. Start a new build with 'bard ci'." if saved_state.nil?
56
-
57
- restore_state(saved_state)
58
- poll_until_complete { |elapsed, last_time| yield elapsed, last_time }
59
-
60
- state.delete
61
- success?
62
- end
63
-
64
- protected
65
-
66
- def poll_until_complete
67
- while building?
68
- elapsed_time = Time.new.to_i - @start_time
69
- yield elapsed_time, @last_time_elapsed
70
- save_state
71
- sleep(2)
72
- end
73
- end
74
-
75
- def save_state
76
- state.save(get_state_data)
77
- end
78
-
79
- def state
80
- @state ||= State.new(project_name)
81
- end
82
-
83
- # Abstract methods - override in subclasses
84
- def start
85
- raise NotImplementedError, "#{self.class}#start not implemented"
86
- end
87
-
88
- def building?
89
- raise NotImplementedError, "#{self.class}#building? not implemented"
90
- end
91
-
92
- def success?
93
- raise NotImplementedError, "#{self.class}#success? not implemented"
94
- end
95
-
96
- def wait_until_started
97
- end
98
-
99
- def get_last_time_elapsed
100
- nil
101
- end
102
-
103
- def get_state_data
104
- raise NotImplementedError, "#{self.class}#get_state_data not implemented"
105
- end
106
-
107
- def restore_state(data)
108
- raise NotImplementedError, "#{self.class}#restore_state not implemented"
109
- end
110
- end
111
- end
112
- end
@@ -1,40 +0,0 @@
1
- require "json"
2
- require "fileutils"
3
-
4
- module Bard
5
- class CI
6
- class State
7
- def initialize project_name
8
- @project_name = project_name
9
- end
10
-
11
- def save data
12
- FileUtils.mkdir_p(state_dir)
13
- File.write(state_file, JSON.generate(data))
14
- end
15
-
16
- def load
17
- return nil unless File.exist?(state_file)
18
- JSON.parse(File.read(state_file))
19
- end
20
-
21
- def delete
22
- File.delete(state_file) if File.exist?(state_file)
23
- end
24
-
25
- def exists?
26
- File.exist?(state_file)
27
- end
28
-
29
- private
30
-
31
- def state_dir
32
- File.join(Dir.pwd, "tmp", "bard", "ci")
33
- end
34
-
35
- def state_file
36
- File.join(state_dir, "#{@project_name}.json")
37
- end
38
- end
39
- end
40
- end
@@ -1,38 +0,0 @@
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
-
@@ -1,26 +0,0 @@
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: "master", force: false)
9
- target.require_capability!(:ssh)
10
-
11
- if clone
12
- target.run! "git clone --branch #{branch} 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
- target.run! "git pull --ff-only origin #{branch}"
19
- end
20
-
21
- target.run! "bin/setup"
22
- target.run! "bard setup" if clone
23
- end
24
- end
25
- end
26
- end