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,60 +0,0 @@
1
- require "bard/command"
2
-
3
- module Bard
4
- class DeployStrategy
5
- @strategies = {}
6
-
7
- class << self
8
- attr_reader :strategies
9
-
10
- def inherited(subclass)
11
- super
12
- # Extract strategy name from class name
13
- # e.g., Bard::DeployStrategy::SSH -> :ssh
14
- name = extract_strategy_name(subclass)
15
- strategies[name] = subclass
16
- end
17
-
18
- def [](name)
19
- strategies[name.to_sym]
20
- end
21
-
22
- private
23
-
24
- def extract_strategy_name(klass)
25
- # Get the class name without module prefix
26
- class_name = klass.name.split('::').last
27
- # Convert from CamelCase to snake_case
28
- class_name
29
- .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
30
- .gsub(/([a-z\d])([A-Z])/, '\1_\2')
31
- .downcase
32
- .to_sym
33
- end
34
- end
35
-
36
- attr_reader :target
37
-
38
- def initialize(target)
39
- @target = target
40
- end
41
-
42
- def deploy(clone: nil, branch: "master", force: false)
43
- raise NotImplementedError, "Subclasses must implement #deploy"
44
- end
45
-
46
- # Helper methods for strategies
47
- def run!(command)
48
- Command.run!(command)
49
- end
50
-
51
- def run(command)
52
- Command.run(command)
53
- end
54
-
55
- def system!(command)
56
- result = Kernel.system(command)
57
- raise "Command failed: #{command}" unless result
58
- end
59
- end
60
- end
@@ -1,260 +0,0 @@
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
-
125
- if staging_provisioned?(target)
126
- strategy.deploy(branch: branch, force: true)
127
- puts green("Stage Succeeded")
128
-
129
- else # clone from scratch
130
- puts yellow("Staging site for #{project_name} not found — provisioning it from scratch…")
131
- strategy.deploy(clone: project_name, branch: branch)
132
- puts green("Stage Succeeded")
133
-
134
- puts "#{project_name} was rebuilt from scratch; its database and files are empty."
135
- if $stdin.tty? && !no?("Restore #{project_name}'s data from production now? (bard data --from production --to staging) [Y/n]")
136
- invoke :data, [], from: "production", to: "staging"
137
- else
138
- puts "Run #{yellow("bard data --from production --to staging")} to restore its data."
139
- end
140
- end
141
-
142
- ping :staging
143
- rescue Bard::Command::Error => e
144
- puts red("!!! ") + "Running command failed: #{yellow(e.message)}"
145
- exit 1
146
- end
147
-
148
- no_commands do
149
- def staging_provisioned?(target)
150
- !!target.run("test -e #{target.path}/.git", home: true)
151
- end
152
- end
153
-
154
- option :"local-ci", type: :boolean
155
- option :ci, type: :string
156
- option :status, type: :boolean
157
- option :resume, type: :boolean
158
- desc "ci [branch=HEAD]", "runs ci against BRANCH"
159
- def ci(branch = Bard::Git.current_branch)
160
- runner_name = if options["local-ci"]
161
- :local
162
- elsif options["ci"]
163
- options["ci"].to_sym
164
- else
165
- config.ci
166
- end
167
- ci = Bard::CI.new(project_name, branch, runner_name: runner_name)
168
- unless ci.exists?
169
- puts red("No CI found for #{project_name}!")
170
- puts "Re-run with --skip-ci to bypass CI, if you absolutely must, and know what you're doing."
171
- exit 1
172
- end
173
-
174
- return puts ci.status if options["status"]
175
-
176
- if options["resume"]
177
- puts "Continuous integration: resuming build..."
178
- success = ci.resume do |elapsed_time, last_time|
179
- if last_time
180
- percentage = (elapsed_time.to_f / last_time.to_f * 100).to_i
181
- output = " Estimated completion: #{percentage}%"
182
- else
183
- output = " No estimated completion time. Elapsed time: #{elapsed_time} sec"
184
- end
185
- print "\x08" * output.length
186
- print output
187
- $stdout.flush
188
- end
189
- else
190
- puts "Continuous integration: starting build on #{branch}..."
191
-
192
- success = ci.run do |elapsed_time, last_time|
193
- if last_time
194
- percentage = (elapsed_time.to_f / last_time.to_f * 100).to_i
195
- output = " Estimated completion: #{percentage}%"
196
- else
197
- output = " No estimated completion time. Elapsed time: #{elapsed_time} sec"
198
- end
199
- print "\x08" * output.length
200
- print output
201
- $stdout.flush
202
- end
203
- end
204
-
205
- if success
206
- puts
207
- puts "Continuous integration: success!"
208
- else
209
- puts
210
- puts ci.console
211
- puts red("Automated tests failed!")
212
- exit 1
213
- end
214
- end
215
-
216
- option :from, default: "production"
217
- option :to, default: "local"
218
- desc "master_key --from=production --to=local", "copy master key from from to to"
219
- def master_key
220
- from = config[options[:from]]
221
- to = config[options[:to]]
222
- Bard::Copy.file "config/master.key", from: from, to: to
223
- end
224
- end
225
-
226
- require "bard/config"
227
-
228
- class Bard::Config
229
- def ci(system = nil)
230
- if system.nil?
231
- @ci_system
232
- else
233
- @ci_system = system
234
- end
235
- end
236
- end
237
-
238
- require "bard/target"
239
-
240
- class Bard::Target
241
- def deploy_strategy
242
- @deploy_strategy
243
- end
244
-
245
- def deploy_strategy_instance
246
- strategy = @deploy_strategy
247
- strategy ||= :ssh if has_capability?(:ssh)
248
- raise "No deployment strategy configured for target #{key}" unless strategy
249
-
250
- strategy_class = Bard::DeployStrategy[strategy]
251
- raise "Unknown deployment strategy: #{strategy}" unless strategy_class
252
-
253
- strategy_class.new(self)
254
- end
255
-
256
- def strategy_options(strategy_name)
257
- @strategy_options_hash ||= {}
258
- @strategy_options_hash[strategy_name] || {}
259
- end
260
- end
@@ -1,45 +0,0 @@
1
- module Bard
2
- module Git
3
- module_function
4
-
5
- def current_branch
6
- ref = `git symbolic-ref HEAD 2>&1`.chomp
7
- return false if ref =~ /^fatal:/
8
- ref.sub(/refs\/heads\//, '') # refs/heads/master ... we want "master"
9
- end
10
-
11
- def fast_forward_merge?(root, branch)
12
- root_head = sha_of(root)
13
- branch_head = sha_of(branch)
14
- common_ancestor = `git merge-base #{root_head} #{branch_head}`.chomp
15
- common_ancestor == root_head
16
- end
17
-
18
- def up_to_date_with_remote? branch
19
- sha_of(branch) == sha_of("origin/#{branch}")
20
- end
21
-
22
- def sha_of ref
23
- sha = `git rev-parse #{ref} 2>/dev/null`.chomp
24
- return sha if $?.success?
25
- nil # Branch doesn't exist
26
- end
27
-
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)
33
- end
34
-
35
- def github_pages_url
36
- remote = `git remote get-url origin`.chomp
37
- match = remote.match(%r{github\.com[:/]+([^/]+?)/(.+?)(?:\.git)?/?\z})
38
- raise "Could not derive a github.io URL: origin remote #{remote.inspect} is not a github.com repository" unless match
39
- owner, repo = match[1], match[2]
40
- host = "#{owner.downcase}.github.io"
41
- repo.casecmp?(host) ? "https://#{host}/" : "https://#{host}/#{repo}/"
42
- end
43
- end
44
- end
45
-
@@ -1,152 +0,0 @@
1
- require "net/http"
2
- require "json"
3
- require "base64"
4
- require "rbnacl"
5
- require "bard/retryable"
6
- require "bard/secrets"
7
-
8
- module Bard
9
- class Github < Struct.new(:project_name)
10
- include Retryable
11
-
12
- def initialize(project_name, api_key: nil)
13
- super(project_name)
14
- @api_key = api_key
15
- end
16
-
17
- def get path, params={}
18
- request(path) do |uri|
19
- uri.query = URI.encode_www_form(params)
20
- Net::HTTP::Get.new(uri)
21
- end
22
- end
23
-
24
- def post path, params={}
25
- request(path) do |uri|
26
- Net::HTTP::Post.new(uri).tap do |r|
27
- r.body = JSON.dump(params)
28
- end
29
- end
30
- end
31
-
32
- def put path, params={}
33
- request(path) do |uri|
34
- Net::HTTP::Put.new(uri).tap do |r|
35
- r.body = JSON.dump(params)
36
- end
37
- end
38
- end
39
-
40
- def patch path, params={}
41
- request(path) do |uri|
42
- Net::HTTP::Patch.new(uri).tap do |r|
43
- r.body = JSON.dump(params)
44
- end
45
- end
46
- end
47
-
48
- def delete path, params={}
49
- request(path) do |uri|
50
- Net::HTTP::Delete.new(uri).tap do |r|
51
- r.body = JSON.dump(params)
52
- end
53
- end
54
- end
55
-
56
- def read_file path, branch: "master"
57
- metadata = get("contents/#{path}", ref: branch)
58
- Base64.decode64(metadata["content"])
59
- end
60
-
61
- def add_deploy_key title:, key:
62
- post("keys", title:, key:)
63
- end
64
-
65
- def add_master_key master_key
66
- response = get("actions/secrets/public-key")
67
- public_key, public_key_id = response.values_at("key", "key_id")
68
-
69
- def encrypt_secret(encoded_public_key, secret)
70
- decoded_key = Base64.decode64(encoded_public_key)
71
- public_key = RbNaCl::PublicKey.new(decoded_key)
72
- box = RbNaCl::Boxes::Sealed.from_public_key(public_key)
73
- encrypted_secret = box.encrypt(secret)
74
- Base64.strict_encode64(encrypted_secret)
75
- end
76
-
77
- encrypted_master_key = encrypt_secret(public_key, master_key)
78
-
79
- put("actions/secrets/RAILS_MASTER_KEY", {
80
- encrypted_value: encrypted_master_key,
81
- key_id: public_key_id,
82
- })
83
- end
84
-
85
- def add_master_branch_protection
86
- put("branches/master/protection", {
87
- required_status_checks: {
88
- strict: false,
89
- contexts: [],
90
- },
91
- enforce_admins: nil,
92
- required_pull_request_reviews: nil,
93
- restrictions: nil,
94
- })
95
- end
96
-
97
- def create_repo
98
- post("https://api.github.com/orgs/botandrosedesign/repos", {
99
- name: project_name,
100
- private: true,
101
- })
102
- end
103
-
104
- def delete_repo
105
- delete(nil)
106
- end
107
-
108
- private
109
-
110
- def api_key
111
- @api_key ||= Bard::Secrets.fetch("github-apikey")
112
- end
113
-
114
- def request path, &block
115
- uri = if path =~ /^http/
116
- URI(path)
117
- else
118
- base = "https://api.github.com/repos/botandrosedesign/#{project_name}"
119
- if path
120
- URI(File.join(base, path))
121
- else
122
- URI(base)
123
- end
124
- end
125
-
126
- retry_with_backoff do
127
- req = nil
128
- response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
129
- req = block.call(uri)
130
- req["Accept"] = "application/vnd.github+json"
131
- req["Authorization"] = "Bearer #{api_key}"
132
- req["X-GitHub-Api-Version"] = "2022-11-28"
133
- http.request(req)
134
- end
135
-
136
- case response
137
- when Net::HTTPRedirection then
138
- Net::HTTP.get(URI(response["Location"]))
139
- when Net::HTTPSuccess then
140
- if response["Content-Type"].to_s.include?("/json")
141
- JSON.load(response.body)
142
- else
143
- response.body
144
- end
145
- else
146
- raise [req.method, req.uri, req.to_hash, response, response.body].inspect
147
- end
148
- end
149
- end
150
- end
151
- end
152
-
@@ -1,170 +0,0 @@
1
- require "bard/plugins/deploy/strategy"
2
- require "bard/plugins/git"
3
- require "fileutils"
4
- require "socket"
5
- require "uri"
6
-
7
- module Bard
8
- class DeployStrategy
9
- class GithubPages < DeployStrategy
10
- def initialize(target, url = nil, **options)
11
- super(target)
12
- @url = url || target.github_pages
13
- @options = options
14
-
15
- target.url(url) if url
16
- end
17
-
18
- def deploy
19
- @sha = Git.sha_of(Git.current_branch)
20
- @build_dir = "tmp/github-build-#{@sha}"
21
- @branch = "gh-pages"
22
- @domain = extract_domain
23
- @port = pick_free_port
24
-
25
- puts "Starting deployment to GitHub Pages..."
26
-
27
- build_site
28
- tree_sha = create_tree_from_build
29
- new_commit = create_commit(tree_sha)
30
- commit_and_push(new_commit)
31
- end
32
-
33
- private
34
-
35
- def extract_domain
36
- return nil unless @url
37
- domain = @url
38
- domain = URI.parse(domain).hostname if domain =~ /^http/
39
- domain
40
- end
41
-
42
- def pick_free_port
43
- server = TCPServer.new("127.0.0.1", 0)
44
- port = server.addr[1]
45
- server.close
46
- port
47
- end
48
-
49
- def build_site
50
- # No custom domain => the site serves under /<repo>/, so make the mirror relocatable:
51
- # -k rewrites absolute links to relative, and the sed strips the localhost prefix wget
52
- # bakes into any links it couldn't crawl.
53
- convert_links = @domain ? "" : "-k"
54
- relocate_step = @domain ? "" : %(find . -type f \\( -name '*.html' -o -name '*.css' \\) -exec sed -i "s#http://localhost:$PORT/#/#g" {} +)
55
- cname_step = @domain ? "echo #{@domain} > CNAME" : ""
56
- system "rm -rf #{@build_dir.sub(@sha, "*")}"
57
- run! <<~SH
58
- set -e
59
- PORT=#{@port}
60
-
61
- OUTPUT=$(bundle exec rake assets:clean assets:precompile 2>&1) || echo "$OUTPUT"
62
-
63
- BUILD=#{@build_dir}
64
- rm -rf $BUILD
65
- mkdir -p $BUILD
66
- cp -R public/assets $BUILD/
67
-
68
- # Start rails in the foreground, backgrounded — NOT as a daemon —
69
- # so a port-bind failure surfaces instead of being swallowed by fork.
70
- rm -f tmp/pids/server.pid
71
- RAILS_ENV=production bundle exec rails s -p $PORT -P tmp/pids/server.pid >tmp/pids/server.log 2>&1 &
72
- RAILS_PID=$!
73
-
74
- echo waiting...
75
- for i in $(seq 1 30); do
76
- if ! kill -0 $RAILS_PID 2>/dev/null; then
77
- echo "Rails server failed to start on port $PORT:"
78
- cat tmp/pids/server.log
79
- exit 1
80
- fi
81
- curl -sf http://localhost:$PORT >/dev/null 2>&1 && break
82
- sleep 1
83
- done
84
-
85
- cd $BUILD
86
- echo copying...
87
- wget -nv -r -l inf #{convert_links} --no-remove-listing -FEnH --reject-regex "(\\.*)\\?(.*)" http://localhost:$PORT/ 2>&1
88
-
89
- #{relocate_step}
90
- #{cname_step}
91
- SH
92
- ensure
93
- # cleanup
94
- run! <<~SH
95
- PID=$(cat tmp/pids/server.pid 2>/dev/null)
96
- if [ -n "$PID" ]; then
97
- kill $PID 2>/dev/null
98
- for i in 1 2 3 4 5; do
99
- kill -0 $PID 2>/dev/null || break
100
- sleep 1
101
- done
102
- kill -9 $PID 2>/dev/null || true
103
- fi
104
- rm -rf public/assets
105
- SH
106
- end
107
-
108
- def create_tree_from_build
109
- # Create temporary index
110
- git_index_file = ".git/tmp-index"
111
- git = "GIT_INDEX_FILE=#{git_index_file} git"
112
- run! "#{git} read-tree --empty"
113
-
114
- # Add build files to temporary index
115
- Dir.chdir(@build_dir) do
116
- Dir.glob('**/*', File::FNM_DOTMATCH).each do |file|
117
- next if file == '.' || file == '..'
118
- if File.file?(file)
119
- run! "#{git} update-index --add --cacheinfo 100644 $(#{git} hash-object -w #{file}) #{file}"
120
- end
121
- end
122
- end
123
-
124
- # Create tree object from index
125
- tree_sha = `#{git} write-tree`.chomp
126
-
127
- # Clean up temporary index
128
- FileUtils.rm_f(git_index_file)
129
-
130
- tree_sha
131
- end
132
-
133
- def create_commit(tree_sha)
134
- # Get parent commit if branch exists
135
- parent = get_parent_commit
136
-
137
- # Create commit object
138
- message = "'Deploying to #{@branch} from @ #{@sha} 🚀'"
139
- args = ['commit-tree', tree_sha]
140
- args += ['-p', parent] if parent
141
- args += ['-m', message]
142
-
143
- commit_sha = `git #{args.join(' ')}`.chomp
144
-
145
- commit_sha
146
- end
147
-
148
- def get_parent_commit
149
- Git.sha_of("#{@branch}^{commit}")
150
- end
151
-
152
- def commit_and_push(commit_sha)
153
- if branch_exists?
154
- run! "git update-ref refs/heads/#{@branch} #{commit_sha}"
155
- else
156
- run! "git branch #{@branch} #{commit_sha}"
157
- end
158
- run! "git push -f origin #{@branch}:refs/heads/#{@branch}"
159
- end
160
-
161
- def branch_exists?
162
- system("git show-ref --verify --quiet refs/heads/#{@branch}")
163
- end
164
-
165
- def cleanup
166
- # Cleanup method for tests
167
- end
168
- end
169
- end
170
- end