bard 2.0.3 → 2.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e6fc9147c8bb2b63efad9e658b6756ee0b69e6c317ca25084700dfeaddf29dbd
4
- data.tar.gz: 948578a41a02a1a54fa29313013838e6bf3f93b35e2c635d75d4ece58e6ff6b6
3
+ metadata.gz: a48fc76ba8d93a7cbf4527db8645112863b1f21fd76616b260e43d70df8cf789
4
+ data.tar.gz: a8ccdfeb4872d4659f27018637395e111286af2c75cc7b4b31fde32744f4919a
5
5
  SHA512:
6
- metadata.gz: 3cd2e967bf7a6f5d3a7b89e60c9fc957f75c7b9f1db06b80e36d20be041c3420a667bb93c702c2f4704e07a9ce847b6bb114df3d0e5196aeaca28afe8015f4eb
7
- data.tar.gz: 2cc75c7bfb471373fd2cfebefa7ab3c115d68b9540591cb48053820b32fa0eb0e99f1a57d3024b65b3e801063b7569e30cd031cc1d39d4788712fdb746e144ad
6
+ metadata.gz: 6be5034adb2ce0c03540eef11b46bd00aba88ce4f333fd31d0fb914ee2568c6d355942efd3380b066c909c891c5b9d3fb8d4d3cf5f4cd2854e9d2f87d7f04ba4
7
+ data.tar.gz: b410e47c51b5c7d968cf3a633da99d54ef3908c58b517c36ad85389bc7f533e69efc7e35ebb96eb70cb86181e5c836b25c3393f30e4d04dd9518f036247d0ba8
@@ -5,17 +5,16 @@ require "bard/plugins/ssh"
5
5
  module Bard
6
6
  class DeployStrategy
7
7
  class SSH < DeployStrategy
8
- def deploy(clone: nil, branch: nil, force: false)
8
+ def deploy(clone: nil, branch: "master", force: false)
9
9
  target.require_capability!(:ssh)
10
10
 
11
11
  if clone
12
- target.run! "git clone git@github.com:botandrosedesign/#{clone} #{target.path}", home: true
12
+ target.run! "git clone --branch #{branch} git@github.com:botandrosedesign/#{clone} #{target.path}", home: true
13
13
  Bard::Copy.file "config/master.key", from: target.config[:local], to: target
14
14
  elsif force
15
15
  target.run! "git fetch origin #{branch}"
16
16
  target.run! "git checkout -f origin/#{branch}"
17
17
  else
18
- branch ||= target.instance_variable_get(:@branch) || "master"
19
18
  target.run! "git pull --ff-only origin #{branch}"
20
19
  end
21
20
 
@@ -39,7 +39,7 @@ module Bard
39
39
  @target = target
40
40
  end
41
41
 
42
- def deploy(clone: nil, branch: nil, force: false)
42
+ def deploy(clone: nil, branch: "master", force: false)
43
43
  raise NotImplementedError, "Subclasses must implement #deploy"
44
44
  end
45
45
 
@@ -121,9 +121,23 @@ class Bard::CLI
121
121
 
122
122
  target = config[:staging]
123
123
  strategy = target.deploy_strategy_instance
124
- strategy.deploy(branch: branch, force: true)
125
124
 
126
- puts green("Stage Succeeded")
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
127
141
 
128
142
  ping :staging
129
143
  rescue Bard::Command::Error => e
@@ -131,6 +145,12 @@ class Bard::CLI
131
145
  exit 1
132
146
  end
133
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
+
134
154
  option :"local-ci", type: :boolean
135
155
  option :ci, type: :string
136
156
  option :status, type: :boolean
@@ -31,6 +31,15 @@ module Bard
31
31
  return false if git_dir.empty? || common_dir.empty?
32
32
  File.expand_path(git_dir) != File.expand_path(common_dir)
33
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
34
43
  end
35
44
  end
36
45
 
@@ -47,6 +47,12 @@ module Bard
47
47
  end
48
48
 
49
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" : ""
50
56
  system "rm -rf #{@build_dir.sub(@sha, "*")}"
51
57
  run! <<~SH
52
58
  set -e
@@ -78,9 +84,10 @@ module Bard
78
84
 
79
85
  cd $BUILD
80
86
  echo copying...
81
- wget -nv -r -l inf --no-remove-listing -FEnH --reject-regex "(\\.*)\\?(.*)" http://localhost:$PORT/ 2>&1
87
+ wget -nv -r -l inf #{convert_links} --no-remove-listing -FEnH --reject-regex "(\\.*)\\?(.*)" http://localhost:$PORT/ 2>&1
82
88
 
83
- echo #{@domain} > CNAME
89
+ #{relocate_step}
90
+ #{cname_step}
84
91
  SH
85
92
  ensure
86
93
  # cleanup
@@ -1,16 +1,21 @@
1
1
  require "bard/plugins/github_pages/strategy"
2
+ require "bard/plugins/git"
2
3
  require "bard/config"
3
4
  require "bard/target"
4
5
 
5
6
  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\./, "")
7
+ def github_pages(url = nil)
8
+ hostname = if url
9
+ uri = url.start_with?("http") ? URI.parse(url) : URI.parse("https://#{url}")
10
+ uri.hostname.sub(/^www\./, "")
11
+ else
12
+ Bard::Git.github_pages_url
13
+ end
9
14
 
10
15
  remove_target :production
11
16
  target :production do
12
17
  github_pages url
13
- url(hostname) if hostname
18
+ url hostname
14
19
  end
15
20
 
16
21
  backup(false) if respond_to?(:backup)
@@ -18,12 +23,12 @@ class Bard::Config
18
23
  end
19
24
 
20
25
  class Bard::Target
21
- def github_pages(url = nil)
22
- if url.nil?
26
+ def github_pages(*args)
27
+ if args.empty?
23
28
  @github_pages_url
24
29
  else
25
30
  @deploy_strategy = :github_pages
26
- @github_pages_url = url
31
+ @github_pages_url = args.first
27
32
  enable_capability(:github_pages)
28
33
  end
29
34
  end
data/lib/bard/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Bard
2
- VERSION = "2.0.3"
2
+ VERSION = "2.2.0"
3
3
  end
4
4
 
@@ -17,10 +17,12 @@ describe "bard stage" do
17
17
  allow(cli).to receive(:ping)
18
18
  allow(cli).to receive(:green).and_return("")
19
19
  allow(cli).to receive(:red).and_return("")
20
- allow(cli).to receive(:yellow).and_return("")
20
+ allow(cli).to receive(:yellow) { |s| s }
21
21
  allow(Bard::Git).to receive(:current_branch).and_return("main")
22
22
  allow(config).to receive(:[]).with(:staging).and_return(staging_server)
23
23
  allow(config).to receive(:[]).with(:production).and_return(production_server)
24
+ allow(cli).to receive(:project_name).and_return("acme")
25
+ allow(cli).to receive(:staging_provisioned?).and_return(true)
24
26
  end
25
27
 
26
28
  describe "#stage" do
@@ -28,6 +30,49 @@ describe "bard stage" do
28
30
  expect(cli).to respond_to(:stage)
29
31
  end
30
32
 
33
+ context "when the staging site has been reaped" do
34
+ before do
35
+ allow(cli).to receive(:staging_provisioned?).and_return(false)
36
+ allow(cli).to receive(:invoke)
37
+ allow($stdin).to receive(:tty?).and_return(false)
38
+ end
39
+
40
+ it "provisions from scratch by cloning and checking out the branch" do
41
+ expect(staging_strategy).to receive(:deploy).with(clone: "acme", branch: "main")
42
+ cli.stage
43
+ end
44
+
45
+ it "restores data from production by default (Enter/no decline)" do
46
+ allow(staging_strategy).to receive(:deploy)
47
+ allow($stdin).to receive(:tty?).and_return(true)
48
+ allow(cli).to receive(:no?).and_return(false)
49
+ expect(cli).to receive(:invoke).with(:data, [], from: "production", to: "staging")
50
+ cli.stage
51
+ end
52
+
53
+ it "skips the restore when the user declines" do
54
+ allow(staging_strategy).to receive(:deploy)
55
+ allow($stdin).to receive(:tty?).and_return(true)
56
+ allow(cli).to receive(:no?).and_return(true)
57
+ expect(cli).not_to receive(:invoke)
58
+ cli.stage
59
+ end
60
+
61
+ it "prints restore instructions when non-interactive" do
62
+ allow(staging_strategy).to receive(:deploy)
63
+ expect(cli).to receive(:puts).with(/bard data --from production --to staging/)
64
+ cli.stage
65
+ end
66
+ end
67
+
68
+ context "when the staging site already exists" do
69
+ it "does not prompt to restore data" do
70
+ allow($stdin).to receive(:tty?).and_return(true)
71
+ expect(cli).not_to receive(:invoke)
72
+ cli.stage
73
+ end
74
+ end
75
+
31
76
  context "when production server is defined" do
32
77
  it "pushes branch and stages it" do
33
78
  expect(cli).to receive(:run!).with("git push -u origin main", verbose: true)
@@ -147,4 +147,26 @@ describe Bard::Config do
147
147
  end
148
148
  end
149
149
  end
150
+
151
+ context "with github_pages directive and no domain" do
152
+ subject { described_class.new("test", source: "github_pages") }
153
+
154
+ before do
155
+ allow(Bard::Git).to receive(:github_pages_url).and_return("https://acme.github.io/widgets/")
156
+ end
157
+
158
+ describe "#target" do
159
+ it "creates a production target with github_pages enabled and no custom domain" do
160
+ production = subject[:production]
161
+ expect(production).not_to be_nil
162
+ expect(production.has_capability?(:github_pages)).to be true
163
+ expect(production.github_pages).to be_nil
164
+ expect(production.ssh).to be_nil
165
+ end
166
+
167
+ it "uses the derived github.io url for pinging" do
168
+ expect(subject[:production].url).to eq "https://acme.github.io/widgets/"
169
+ end
170
+ end
171
+ end
150
172
  end
@@ -0,0 +1,58 @@
1
+ require "spec_helper"
2
+ require "bard/plugins/github_pages/strategy"
3
+ require "bard/plugins/github_pages"
4
+
5
+ describe Bard::DeployStrategy::GithubPages do
6
+ let(:config) { double("config", project_name: "testapp") }
7
+
8
+ def build_shell_for(target)
9
+ strategy = described_class.new(target)
10
+ strategy.instance_variable_set(:@sha, "abc123")
11
+ strategy.instance_variable_set(:@build_dir, "tmp/github-build-abc123")
12
+ strategy.instance_variable_set(:@port, 4321)
13
+ strategy.instance_variable_set(:@domain, strategy.send(:extract_domain))
14
+
15
+ captured = []
16
+ allow(strategy).to receive(:run!) { |cmd| captured << cmd }
17
+ allow(strategy).to receive(:system)
18
+ strategy.send(:build_site)
19
+ captured.join("\n")
20
+ end
21
+
22
+ context "with no custom domain (project page under /<repo>/)" do
23
+ let(:target) do
24
+ Bard::Target.new(:production, config).tap { |t| t.github_pages(nil) }
25
+ end
26
+
27
+ it "mirrors with --convert-links so the tree is relocatable under the subpath" do
28
+ expect(build_shell_for(target)).to match(/wget .*\s-k\s/)
29
+ end
30
+
31
+ it "strips the localhost prefix wget bakes into un-crawled links" do
32
+ expect(build_shell_for(target))
33
+ .to include(%(sed -i "s#http://localhost:$PORT/#/#g"))
34
+ end
35
+
36
+ it "does not write a CNAME" do
37
+ expect(build_shell_for(target)).not_to match(/> CNAME/)
38
+ end
39
+ end
40
+
41
+ context "with a custom domain (served at the domain root)" do
42
+ let(:target) do
43
+ Bard::Target.new(:production, config).tap { |t| t.github_pages("example.com") }
44
+ end
45
+
46
+ it "does not add --convert-links" do
47
+ expect(build_shell_for(target)).not_to match(/wget .*\s-k\s/)
48
+ end
49
+
50
+ it "does not run the localhost strip" do
51
+ expect(build_shell_for(target)).not_to include("localhost:$PORT/#")
52
+ end
53
+
54
+ it "writes the CNAME" do
55
+ expect(build_shell_for(target)).to match(/echo example\.com > CNAME/)
56
+ end
57
+ end
58
+ end
@@ -38,15 +38,13 @@ describe Bard::DeployStrategy::SSH do
38
38
  strategy.deploy
39
39
  end
40
40
 
41
- it "uses configured branch if specified" do
42
- target.instance_variable_set(:@branch, "main")
43
-
41
+ it "pulls the given branch when one is specified" do
44
42
  expect(target).to receive(:run!)
45
43
  .with("git pull --ff-only origin main")
46
44
 
47
45
  allow(target).to receive(:run!).with(/bin\/setup/)
48
46
 
49
- strategy.deploy
47
+ strategy.deploy(branch: "main")
50
48
  end
51
49
 
52
50
  context "with force: true" do
@@ -68,9 +66,9 @@ describe Bard::DeployStrategy::SSH do
68
66
  allow(Bard::Copy).to receive(:file)
69
67
  end
70
68
 
71
- it "clones the repository" do
69
+ it "clones the repository, defaulting to master" do
72
70
  expect(target).to receive(:run!)
73
- .with("git clone git@github.com:botandrosedesign/testapp /app", home: true)
71
+ .with("git clone --branch master git@github.com:botandrosedesign/testapp /app", home: true)
74
72
  allow(target).to receive(:run!).with("bin/setup")
75
73
  allow(target).to receive(:run!).with("bard setup")
76
74
 
@@ -104,6 +102,18 @@ describe Bard::DeployStrategy::SSH do
104
102
 
105
103
  strategy.deploy(clone: "testapp")
106
104
  end
105
+
106
+ it "clones the requested branch directly when provisioning from scratch" do
107
+ allow(target).to receive(:run!).with("bin/setup")
108
+ allow(target).to receive(:run!).with("bard setup")
109
+
110
+ expect(target).to receive(:run!)
111
+ .with("git clone --branch feature-x git@github.com:botandrosedesign/testapp /app", home: true)
112
+ expect(target).not_to receive(:run!).with(/git fetch/)
113
+ expect(target).not_to receive(:run!).with(/git checkout/)
114
+
115
+ strategy.deploy(clone: "testapp", branch: "feature-x")
116
+ end
107
117
  end
108
118
  end
109
119
 
@@ -61,5 +61,41 @@ describe Bard::Git do
61
61
  expect(Bard::Git.sha_of("ref")).to be_nil
62
62
  end
63
63
  end
64
+
65
+ describe ".github_pages_url" do
66
+ def stub_origin(remote)
67
+ allow(Bard::Git).to receive(:`).with("git remote get-url origin").and_return("#{remote}\n")
68
+ end
69
+
70
+ it "derives a project pages url from an ssh remote" do
71
+ stub_origin("git@github.com:botandrose/bard")
72
+ expect(Bard::Git.github_pages_url).to eq("https://botandrose.github.io/bard/")
73
+ end
74
+
75
+ it "derives a project pages url from an https remote with a .git suffix" do
76
+ stub_origin("https://github.com/botandrose/bard.git")
77
+ expect(Bard::Git.github_pages_url).to eq("https://botandrose.github.io/bard/")
78
+ end
79
+
80
+ it "lowercases the owner in the pages host" do
81
+ stub_origin("git@github.com:BotAndRose/Bard.git")
82
+ expect(Bard::Git.github_pages_url).to eq("https://botandrose.github.io/Bard/")
83
+ end
84
+
85
+ it "uses the root url for a user/org pages repo" do
86
+ stub_origin("git@github.com:botandrose/botandrose.github.io")
87
+ expect(Bard::Git.github_pages_url).to eq("https://botandrose.github.io/")
88
+ end
89
+
90
+ it "raises for a non-github remote" do
91
+ stub_origin("git@gitlab.com:botandrose/bard.git")
92
+ expect { Bard::Git.github_pages_url }.to raise_error(/not a github\.com repository/)
93
+ end
94
+
95
+ it "raises when there is no origin remote" do
96
+ allow(Bard::Git).to receive(:`).with("git remote get-url origin").and_return("\n")
97
+ expect { Bard::Git.github_pages_url }.to raise_error(/not a github\.com repository/)
98
+ end
99
+ end
64
100
  end
65
101
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bard
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.3
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Micah Geisel
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-07-01 00:00:00.000000000 Z
10
+ date: 2026-07-06 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: thor
@@ -254,6 +254,7 @@ files:
254
254
  - spec/bard/command_spec.rb
255
255
  - spec/bard/config_spec.rb
256
256
  - spec/bard/copy_spec.rb
257
+ - spec/bard/deploy_strategy/github_pages_spec.rb
257
258
  - spec/bard/deploy_strategy/ssh_spec.rb
258
259
  - spec/bard/deploy_strategy_spec.rb
259
260
  - spec/bard/dynamic_dsl_spec.rb
@@ -323,6 +324,7 @@ test_files:
323
324
  - spec/bard/command_spec.rb
324
325
  - spec/bard/config_spec.rb
325
326
  - spec/bard/copy_spec.rb
327
+ - spec/bard/deploy_strategy/github_pages_spec.rb
326
328
  - spec/bard/deploy_strategy/ssh_spec.rb
327
329
  - spec/bard/deploy_strategy_spec.rb
328
330
  - spec/bard/dynamic_dsl_spec.rb