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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 315f02fbb865e24647f358979a3f135d6a9b5fa58d27d447cc92cc9462ba80b6
4
- data.tar.gz: 3fd528b19bbfb0edfeb3b05aff689a4c4bc52ef9c2ac218566b1c0be8e071ade
3
+ metadata.gz: 24a9ee78c591a6446ac1bb749225497c75470e12e4fe822d96e975870ac88152
4
+ data.tar.gz: 2b8ae0acbf1237b3312957128130f399a516a3a19d52d45f63c7b0677952e4f2
5
5
  SHA512:
6
- metadata.gz: 464041788694b06f9ef754a01a5de77e198140ad738cc60a7596110f32044b0e8b73ebb405aa210b488ad269b64d3829d51b368cc297617b9c0c8bfc479126c6
7
- data.tar.gz: c48d80a55fc37dd081cb6220919c9f3cef0f0356f75b8befcd8fd1dbf89e11619075f3db9b8ec7cdf387da9e80ee120b39a93b500c839ba816c627cea3dff6ae
6
+ metadata.gz: 55a1a4f6fcf3bf12201105974a619be5178c3c9c6367d8dccf2eef1e7e504880a869a6291dfb5024930e025f63d8cf2f22c8925cf5fc24f9e83d2aa85ed047ed
7
+ data.tar.gz: 5da5d707ceed5ca663e4a68cd4a2ba2afbb232052e4a0542a4e41a1badc0161ba4eda410195b717d21f8fdaea299af771afa6a19310d93b66915996e8579b5fa
@@ -5,7 +5,7 @@ jobs:
5
5
  strategy:
6
6
  fail-fast: false
7
7
  matrix:
8
- ruby: [ "3.2", "3.3", "3.4" ]
8
+ ruby: [ "3.3", "3.4", "4.0" ]
9
9
 
10
10
  runs-on: ubuntu-latest
11
11
  steps:
@@ -34,5 +34,10 @@ jobs:
34
34
  done
35
35
  echo "DOCKER_HOST=tcp://127.0.0.1:8080" >> $GITHUB_ENV
36
36
 
37
+ - name: Build test container image
38
+ run: |
39
+ sudo podman pull ubuntu:24.04
40
+ sudo podman build -t bard-test-server -f spec/acceptance/docker/Dockerfile spec/acceptance/docker
41
+
37
42
  - name: Run tests
38
43
  run: bundle exec rake
data/CLAUDE.md ADDED
@@ -0,0 +1,76 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## What is Bard?
6
+
7
+ Bard is a modular deployment CLI tool for Ruby applications. It provides:
8
+ - SSH-based deployment via git pull
9
+ - GitHub Pages static site deployment
10
+ - Custom pluggable deployment strategies
11
+ - Data syncing (database dumps and file rsync)
12
+ - CI integration (GitHub Actions/Jenkins)
13
+ - Server provisioning
14
+
15
+ ## Development Commands
16
+
17
+ ```bash
18
+ # Install dependencies
19
+ bundle install
20
+
21
+ # Run all tests (RSpec + Cucumber)
22
+ bundle exec rake
23
+
24
+ # Run only RSpec tests
25
+ bundle exec rspec
26
+
27
+ # Run a single spec file
28
+ bundle exec rspec spec/bard/target_spec.rb
29
+
30
+ # Run a specific test by line number
31
+ bundle exec rspec spec/bard/target_spec.rb:42
32
+
33
+ # Run Cucumber features (slow - avoid full suite)
34
+ bundle exec cucumber features/deploy.feature
35
+
36
+ # Run bard from source
37
+ bundle exec bin/bard
38
+ ```
39
+
40
+ ## Architecture
41
+
42
+ ### Core Classes
43
+
44
+ - **`Bard::CLI`** (`lib/bard/cli.rb`) - Thor-based command dispatcher. Commands are modules in `lib/bard/cli/` included into CLI.
45
+ - **`Bard::Config`** (`lib/bard/config.rb`) - DSL parser for `bard.rb` files. Manages targets and settings.
46
+ - **`Bard::Target`** (`lib/bard/target.rb`) - Deployment destination with capabilities (ssh, ping, data). Supports dynamic strategy DSL via `method_missing`.
47
+ - **`Bard::SSHServer`** (`lib/bard/ssh_server.rb`) - SSH connection details (user, host, port, gateway, ssh_key, env).
48
+ - **`Bard::DeployStrategy`** (`lib/bard/deploy_strategy.rb`) - Base class for deployment strategies. Subclasses auto-register via Ruby's `inherited` hook.
49
+
50
+ ### Capability System
51
+
52
+ Targets track enabled capabilities (`:ssh`, `:ping`, `:github_pages`, etc.). Commands call `require_capability!` to ensure the target supports the operation.
53
+
54
+ ### Strategy Auto-Registration
55
+
56
+ Custom strategies subclass `DeployStrategy` and are automatically registered by class name:
57
+ ```ruby
58
+ class Bard::DeployStrategy::Jets < DeployStrategy # registers as :jets
59
+ def deploy; ...; end
60
+ end
61
+ ```
62
+
63
+ ### File Organization
64
+
65
+ - `lib/bard/cli/*.rb` - CLI command modules (deploy, data, ssh, etc.)
66
+ - `lib/bard/ci/*.rb` - CI system integrations (github_actions, jenkins, local)
67
+ - `lib/bard/deploy_strategy/*.rb` - Built-in strategies (ssh, github_pages)
68
+ - `lib/bard/provision/*.rb` - Server provisioning modules
69
+ - `spec/` - RSpec unit tests
70
+ - `features/` - Cucumber integration tests
71
+
72
+ ## Testing Notes
73
+
74
+ - Use `focus: true` in RSpec to run specific tests
75
+ - Cucumber tests use testcontainers and are slow - run specific feature files only
76
+ - SimpleCov tracks coverage across both RSpec and Cucumber runs
data/MIGRATION_GUIDE.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  This guide will help you migrate your Bard configuration from v1.x to v2.0.
4
4
 
5
+ > **Note:** Bard v1.8.0 is a transitional release that supports both v1.x and v2.0 APIs. When using deprecated v1.x patterns, you'll see deprecation warnings indicating what to change. This gives you time to migrate at your own pace while keeping your deployments working.
6
+
5
7
  ## Overview of Changes
6
8
 
7
9
  Bard v2.0 introduces a cleaner, more modular architecture:
@@ -477,22 +479,35 @@ mv bard.rb.backup bard.rb
477
479
  - Check [CUSTOM_STRATEGIES.md](CUSTOM_STRATEGIES.md) for strategy creation
478
480
  - Open an issue at https://github.com/botandrose/bard/issues
479
481
 
480
- ## Transitional Release (v1.9.x)
482
+ ## Transitional Release (v1.8.0)
481
483
 
482
- A transitional release (v1.9.x) will support both v1 and v2 APIs simultaneously with deprecation warnings. This allows gradual migration.
484
+ Bard v1.8.0 is a transitional release that supports both v1.x and v2.0 APIs simultaneously with deprecation warnings. This allows gradual migration.
483
485
 
484
- **Using v1.9.x:**
486
+ **Using v1.8.0:**
485
487
  ```bash
486
488
  # Gemfile
487
- gem 'bard', '~> 1.9'
489
+ gem 'bard', '~> 1.8'
488
490
 
489
491
  bundle update bard
490
492
  ```
491
493
 
492
- v1.9.x will:
493
- - Accept both `server` and `target` (with deprecation warning)
494
- - Accept both old and new SSH configuration styles
495
- - Show deprecation warnings for old API usage
496
- - Support full v2 API
494
+ v1.8.0 will:
495
+ - Accept both `server` and `target` (with deprecation warning for `server`)
496
+ - Accept both old and new SSH configuration styles (with deprecation warnings for separate options)
497
+ - Accept both old `strategy`/`option` calls and new direct strategy methods (with deprecation warnings)
498
+ - Show deprecation warnings for all deprecated v1.x API usage
499
+ - Support full v2.0 API
497
500
 
498
501
  This gives you time to migrate at your own pace while keeping your deployments working.
502
+
503
+ ### Deprecation Warnings
504
+
505
+ When using deprecated patterns, you'll see warnings like:
506
+
507
+ ```
508
+ [DEPRECATION] `server` is deprecated; use `target` instead (will be removed in v2.0) (called from bard.rb:3)
509
+ [DEPRECATION] Separate SSH options are deprecated; pass as keyword arguments to `ssh` instead (will be removed in v2.0) (called from bard.rb:5)
510
+ [DEPRECATION] `strategy` is deprecated; use the strategy method directly (will be removed in v2.0) (called from bard.rb:10)
511
+ ```
512
+
513
+ These warnings help you identify what needs to change before upgrading to v2.0.
data/PLUGINS.md ADDED
@@ -0,0 +1,99 @@
1
+ # Plugin Development
2
+
3
+ Bard uses a plugin system to extend functionality. Plugins can add CLI commands, target methods, and config DSL methods.
4
+
5
+ ## Plugin Structure
6
+
7
+ Plugins live in `lib/bard/plugins/` and are auto-loaded. Command classes auto-register when they subclass `Bard::Plugin::Command`:
8
+
9
+ ```ruby
10
+ # lib/bard/plugins/my_plugin.rb
11
+ require "bard/plugin"
12
+
13
+ class Bard::CLI::MyPlugin < Bard::Plugin::Command
14
+ desc "mycommand", "Description of my command"
15
+ option :verbose, type: :boolean
16
+ def mycommand
17
+ puts "Hello from my plugin!"
18
+ puts config[:production].url
19
+ end
20
+ end
21
+ ```
22
+
23
+ That's it — no registration block needed. The `Command` base class provides:
24
+ - Automatic registration via Ruby's `inherited` hook
25
+ - `desc` and `option` class methods for Thor integration
26
+ - Delegation to the CLI instance (access to `config`, `project_name`, `run!`, `options`, etc.)
27
+
28
+ ## Extending Target and Config
29
+
30
+ To add methods to `Bard::Target` or `Bard::Config`, reopen the class directly:
31
+
32
+ ```ruby
33
+ # lib/bard/plugins/my_plugin.rb
34
+ require "bard/target"
35
+
36
+ class Bard::Target
37
+ def my_feature(url = nil)
38
+ if url.nil?
39
+ @my_feature_url
40
+ else
41
+ @my_feature_url = url
42
+ enable_capability(:my_feature)
43
+ end
44
+ end
45
+ end
46
+
47
+ require "bard/config"
48
+
49
+ class Bard::Config
50
+ def my_global_setting(value = nil)
51
+ if value.nil?
52
+ @my_global_setting
53
+ else
54
+ @my_global_setting = value
55
+ end
56
+ end
57
+ end
58
+ ```
59
+
60
+ ## Adding Strategies
61
+
62
+ Plugins can add deployment strategies or CI runners:
63
+
64
+ ```ruby
65
+ # Custom deploy strategy
66
+ module Bard
67
+ class DeployStrategy
68
+ class MyCloud < DeployStrategy
69
+ def deploy
70
+ # Deployment logic here
71
+ run! "my-cloud deploy #{target.key}"
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ # Custom CI runner
78
+ module Bard
79
+ class CI
80
+ class Runner
81
+ class MyCI < Runner
82
+ def run
83
+ # Start CI run
84
+ end
85
+
86
+ def status
87
+ # Return current status
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ ```
94
+
95
+ Strategies auto-register via Ruby's `inherited` hook. The class name determines the strategy key (e.g., `MyCloud` → `:my_cloud`).
96
+
97
+ ## Plugin Loading
98
+
99
+ Plugins are loaded automatically from `lib/bard/plugins/`. The load order is alphabetical by filename. External plugins are loaded from the project's `lib/bard/plugins/` directory.
data/README.md CHANGED
@@ -12,7 +12,7 @@ end
12
12
  ```
13
13
 
14
14
  ```bash
15
- bard deploy production
15
+ bard deploy
16
16
  ```
17
17
 
18
18
  ## Features
@@ -115,17 +115,21 @@ end
115
115
  ### Deployment
116
116
 
117
117
  ```bash
118
- # Deploy to production
119
- bard deploy production
118
+ # Deploy current branch to production (default)
119
+ bard deploy
120
120
 
121
- # Deploy to staging
122
- bard deploy staging
121
+ # Deploy a specific branch to production
122
+ bard deploy feature-branch
123
+
124
+ # Deploy to a different target
125
+ bard deploy --target=staging
126
+ bard deploy feature-branch --target=staging
123
127
 
124
128
  # Deploy feature branch to staging (no merge)
125
129
  bard stage feature-branch
126
130
 
127
131
  # Skip CI checks
128
- bard deploy production --skip-ci
132
+ bard deploy --skip-ci
129
133
  ```
130
134
 
131
135
  ### Data Management
@@ -459,6 +463,10 @@ See [ARCHITECTURE.md](ARCHITECTURE.md) for detailed architecture documentation.
459
463
 
460
464
  See [CUSTOM_STRATEGIES.md](CUSTOM_STRATEGIES.md) for a step-by-step guide to creating custom deployment strategies.
461
465
 
466
+ ## Plugin Development
467
+
468
+ See [PLUGINS.md](PLUGINS.md) for a guide to creating plugins.
469
+
462
470
  ## Development
463
471
 
464
472
  ```bash
data/Rakefile CHANGED
@@ -1,7 +1,9 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
+ require "cucumber/rake/task"
3
4
 
4
5
  RSpec::Core::RakeTask.new(:spec)
6
+ Cucumber::Rake::Task.new(:cucumber)
5
7
 
6
- task :default => :spec
8
+ task :default => [:spec, :cucumber]
7
9
 
data/bard.gemspec CHANGED
@@ -15,11 +15,11 @@ Gem::Specification.new do |spec|
15
15
  spec.files = `git ls-files -z`.split("\x0")
16
16
  spec.executables = ["bard"]
17
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.required_ruby_version = ">= 3.3"
18
19
  spec.require_paths = ["lib"]
19
20
 
20
21
  spec.add_dependency "thor", ">= 0.19.0"
21
22
  spec.add_dependency "rvm"
22
- spec.add_dependency "term-ansicolor", ">= 1.0.3"
23
23
  spec.add_dependency "rbnacl"
24
24
  spec.add_dependency "base64"
25
25
 
@@ -28,4 +28,5 @@ Gem::Specification.new do |spec|
28
28
  spec.add_development_dependency "debug"
29
29
  spec.add_development_dependency "cucumber"
30
30
  spec.add_development_dependency "testcontainers"
31
+ spec.add_development_dependency "ostruct"
31
32
  end
data/cucumber.yml ADDED
@@ -0,0 +1 @@
1
+ default: --publish-quiet
@@ -0,0 +1,63 @@
1
+ @server
2
+ Feature: bard ci
3
+ Run continuous integration tests.
4
+
5
+ Background:
6
+ Given a test server is running
7
+
8
+ Scenario: no CI configured error
9
+ When I run expecting failure: bard ci
10
+ Then the output should contain "No CI found"
11
+ And the output should contain "Re-run with --skip-ci to bypass CI"
12
+
13
+ Scenario: local CI runs successfully
14
+ Given a local CI script that passes
15
+ When I run: bard ci --local-ci
16
+ Then the output should contain "Continuous integration: starting build"
17
+ And the output should contain "Continuous integration: success!"
18
+
19
+ Scenario: local CI reports failure
20
+ Given a local CI script that fails with "Test failed: expected 1 but got 2"
21
+ When I run expecting failure: bard ci --local-ci
22
+ Then the output should contain "Test failed: expected 1 but got 2"
23
+ And the output should contain "Automated tests failed!"
24
+
25
+ Scenario: --ci option runs specified CI runner
26
+ Given a local CI script that passes
27
+ When I run: bard ci --ci=local
28
+ Then the output should contain "Continuous integration: starting build"
29
+ And the output should contain "Continuous integration: success!"
30
+
31
+ Scenario: --ci option reports failure from specified runner
32
+ Given a local CI script that fails with "Custom runner failed"
33
+ When I run expecting failure: bard ci --ci=local
34
+ Then the output should contain "Custom runner failed"
35
+ And the output should contain "Automated tests failed!"
36
+
37
+ Scenario: deploy passes --ci option to CI
38
+ Given a local CI script that passes
39
+ And I create a file "ci-option-test.txt" with content "ci option test"
40
+ And I commit the changes with message "Add CI option test file"
41
+ When I run: bard deploy --ci=local
42
+ Then the output should contain "Continuous integration: starting build"
43
+ And the output should contain "Continuous integration: success!"
44
+ And the output should contain "Deploy Succeeded"
45
+
46
+ Scenario: deploy runs CI before deploying
47
+ Given a local CI script that passes
48
+ And I create a file "ci-test.txt" with content "ci test"
49
+ And I commit the changes with message "Add CI test file"
50
+ When I run: bard deploy --local-ci
51
+ Then the output should contain "Continuous integration: starting build"
52
+ And the output should contain "Continuous integration: success!"
53
+ And the output should contain "Deploy Succeeded"
54
+
55
+ Scenario: deploy aborts if CI fails
56
+ Given a local CI script that fails with "Build failed"
57
+ And I create a file "ci-fail.txt" with content "ci fail test"
58
+ And I commit the changes with message "Add CI fail test file"
59
+ When I run expecting failure: bard deploy --local-ci
60
+ Then the output should contain "Continuous integration: starting build"
61
+ And the output should contain "Build failed"
62
+ And the output should contain "Automated tests failed!"
63
+ And the output should not contain "Deploy Succeeded"
@@ -0,0 +1,13 @@
1
+ @server
2
+ Feature: bard data
3
+ Copy database from a remote server to local.
4
+
5
+ Background:
6
+ Given a test server is running
7
+
8
+ Scenario: copies database from production to local
9
+ When I run: bard data
10
+ Then the output should contain "Dumping production database to file"
11
+ And the output should contain "Transfering file from production to local"
12
+ And the output should contain "Loading file into local database"
13
+ And a file "db/data.sql.gz" should exist locally
@@ -0,0 +1,14 @@
1
+ @server
2
+ Feature: bard deploy
3
+ Deploy code changes to a remote server.
4
+
5
+ Background:
6
+ Given a test server is running
7
+
8
+ Scenario: deploys code changes to the remote server
9
+ Given I create a file "DEPLOYED.txt" with content "deployed by bard"
10
+ And I commit the changes with message "Add deployed marker"
11
+ When I run: bard deploy --skip-ci
12
+ Then the output should contain "Deploy Succeeded"
13
+ When I run: bard run "cat DEPLOYED.txt"
14
+ Then the output should contain "deployed by bard"
@@ -0,0 +1,89 @@
1
+ @server
2
+ Feature: bard deploy git workflow
3
+ Git workflow behaviors during deploy.
4
+
5
+ Background:
6
+ Given a test server is running
7
+
8
+ Scenario: deploy on master pushes unpushed commits
9
+ Given I create a file "local-only.txt" with content "local commit"
10
+ And I commit the changes with message "Add local only file"
11
+ When I run: bard deploy --skip-ci
12
+ Then the output should contain "Deploy Succeeded"
13
+ When I run: bard run "cat local-only.txt"
14
+ Then the output should contain "local commit"
15
+
16
+ Scenario: feature branch fast-forward merge
17
+ Given I create and switch to branch "feature-branch"
18
+ And I create a file "feature.txt" with content "feature content"
19
+ And I commit the changes with message "Add feature"
20
+ When I run: bard deploy --skip-ci
21
+ Then the output should contain "Deploy Succeeded"
22
+ And I should be on branch "master"
23
+ And branch "feature-branch" should not exist locally
24
+ And branch "feature-branch" should not exist on origin
25
+ When I run: bard run "cat feature.txt"
26
+ Then the output should contain "feature content"
27
+
28
+ Scenario: feature branch requires rebase
29
+ Given I create and switch to branch "feature-branch"
30
+ And I create a file "feature.txt" with content "feature content"
31
+ And I commit the changes with message "Add feature"
32
+ And master has an additional commit from another source
33
+ When I run: bard deploy --skip-ci
34
+ Then the output should contain "The master branch has advanced"
35
+ And the output should contain "Attempting rebase"
36
+ And the output should contain "Deploy Succeeded"
37
+ And I should be on branch "master"
38
+ When I run: bard run "cat feature.txt"
39
+ Then the output should contain "feature content"
40
+ When I run: bard run "cat remote-change.txt"
41
+ Then the output should contain "remote change"
42
+
43
+ Scenario: feature branch rebase conflict
44
+ Given I create and switch to branch "feature-branch"
45
+ And I create a file "conflict.txt" with content "feature content"
46
+ And I commit the changes with message "Add conflicting file"
47
+ And master has a conflicting commit to "conflict.txt"
48
+ When I run expecting failure: bard deploy --skip-ci
49
+ Then the output should contain "The master branch has advanced"
50
+ And the output should contain "Attempting rebase"
51
+ And the output should contain "Running command failed"
52
+
53
+ Scenario: branch cleanup after deploy
54
+ Given I create and switch to branch "cleanup-test"
55
+ And I create a file "cleanup.txt" with content "cleanup test"
56
+ And I commit the changes with message "Add cleanup test file"
57
+ And I push branch "cleanup-test" to origin
58
+ When I run: bard deploy --skip-ci
59
+ Then the output should contain "Deleting branch: cleanup-test"
60
+ And the output should contain "Deploy Succeeded"
61
+ And I should be on branch "master"
62
+ And branch "cleanup-test" should not exist locally
63
+ And branch "cleanup-test" should not exist on origin
64
+
65
+ Scenario: deploy a branch without checking it out
66
+ Given I create and switch to branch "feature-branch"
67
+ And I create a file "feature.txt" with content "feature content"
68
+ And I commit the changes with message "Add feature"
69
+ And I switch to branch "master"
70
+ When I run: bard deploy feature-branch --skip-ci
71
+ Then the output should contain "Deploy Succeeded"
72
+ And I should be on branch "master"
73
+ And branch "feature-branch" should not exist locally
74
+ When I run: bard run "cat feature.txt"
75
+ Then the output should contain "feature content"
76
+
77
+ Scenario: deploy a branch that requires rebase without checking it out
78
+ Given I create and switch to branch "feature-branch"
79
+ And I create a file "feature.txt" with content "feature content"
80
+ And I commit the changes with message "Add feature"
81
+ And I switch to branch "master"
82
+ And master has an additional commit from another source
83
+ When I run: bard deploy feature-branch --skip-ci
84
+ Then the output should contain "The master branch has advanced"
85
+ And the output should contain "Attempting rebase"
86
+ And the output should contain "Deploy Succeeded"
87
+ And I should be on branch "master"
88
+ When I run: bard run "cat feature.txt"
89
+ Then the output should contain "feature content"
@@ -0,0 +1,14 @@
1
+ @server
2
+ Feature: bard run
3
+ Execute commands on a remote server.
4
+
5
+ Background:
6
+ Given a test server is running
7
+
8
+ Scenario: executes a command on the remote server
9
+ When I run: bard run "echo hello"
10
+ Then the output should contain "hello"
11
+
12
+ Scenario: operates in the configured path
13
+ When I run: bard run "pwd"
14
+ Then the output should contain "testproject"
@@ -0,0 +1,136 @@
1
+ Given /^a test server is running$/ do
2
+ raise "Test server failed to start" unless @container && @ssh_port
3
+ end
4
+
5
+ When /^I run: bard (.+)$/ do |command|
6
+ run_bard(command)
7
+ unless @status.success?
8
+ raise "Command failed with status: #{@status}\nOutput: #{@stdout}"
9
+ end
10
+ end
11
+
12
+ When /^I run expecting failure: bard (.+)$/ do |command|
13
+ run_bard(command)
14
+ unless !@status.success?
15
+ raise "Command succeeded but was expected to fail\nOutput: #{@stdout}"
16
+ end
17
+ end
18
+
19
+ Then /^the output should contain "([^\"]+)"$/ do |expected|
20
+ expect(@stdout).to include(expected)
21
+ end
22
+
23
+ Given /^I create a file "([^\"]+)" with content "([^\"]+)"$/ do |filename, content|
24
+ Dir.chdir(@test_dir) do
25
+ File.write(filename, content)
26
+ end
27
+ end
28
+
29
+ Given /^I commit the changes with message "([^\"]+)"$/ do |message|
30
+ Dir.chdir(@test_dir) do
31
+ system("git add -A", out: File::NULL, err: File::NULL)
32
+ system("git commit -m '#{message}'", out: File::NULL, err: File::NULL)
33
+ end
34
+ end
35
+
36
+ Then /^a file "([^\"]+)" should exist locally$/ do |filename|
37
+ path = File.join(@test_dir, filename)
38
+ expect(File.exist?(path)).to be(true), "Expected file #{filename} to exist at #{path}"
39
+ end
40
+
41
+ # Branch management
42
+ Given /^I create and switch to branch "([^"]+)"$/ do |branch_name|
43
+ Dir.chdir(@test_dir) do
44
+ system("git checkout -b #{branch_name}", out: File::NULL, err: File::NULL)
45
+ end
46
+ end
47
+
48
+ Then /^I should be on branch "([^"]+)"$/ do |expected_branch|
49
+ Dir.chdir(@test_dir) do
50
+ current = `git rev-parse --abbrev-ref HEAD`.chomp
51
+ expect(current).to eq(expected_branch)
52
+ end
53
+ end
54
+
55
+ Given /^I push branch "([^"]+)" to origin$/ do |branch_name|
56
+ Dir.chdir(@test_dir) do
57
+ system("git push -u origin #{branch_name}", out: File::NULL, err: File::NULL)
58
+ end
59
+ end
60
+
61
+ Then /^branch "([^"]+)" should not exist locally$/ do |branch_name|
62
+ Dir.chdir(@test_dir) do
63
+ result = system("git rev-parse --verify #{branch_name}", out: File::NULL, err: File::NULL)
64
+ expect(result).to be(false), "Expected branch #{branch_name} to not exist locally"
65
+ end
66
+ end
67
+
68
+ Then /^branch "([^"]+)" should not exist on origin$/ do |branch_name|
69
+ Dir.chdir(@test_dir) do
70
+ system("git fetch --prune origin", out: File::NULL, err: File::NULL)
71
+ result = system("git rev-parse --verify origin/#{branch_name}", out: File::NULL, err: File::NULL)
72
+ expect(result).to be(false), "Expected branch #{branch_name} to not exist on origin"
73
+ end
74
+ end
75
+
76
+ # Simulating remote changes
77
+ Given /^master has an additional commit from another source$/ do
78
+ run_ssh "cd ~/testproject && git pull origin master"
79
+ run_ssh "cd ~/testproject && echo 'remote change' > remote-change.txt"
80
+ run_ssh "cd ~/testproject && git add remote-change.txt"
81
+ run_ssh "cd ~/testproject && git commit -m 'Remote commit on master'"
82
+ run_ssh "cd ~/testproject && git push origin master"
83
+
84
+ Dir.chdir(@test_dir) do
85
+ system("git fetch origin", out: File::NULL, err: File::NULL)
86
+ end
87
+ end
88
+
89
+ Given /^master has a conflicting commit to "([^"]+)"$/ do |filename|
90
+ run_ssh "cd ~/testproject && git pull origin master"
91
+ run_ssh "cd ~/testproject && echo 'conflicting content from remote' > #{filename}"
92
+ run_ssh "cd ~/testproject && git add #{filename}"
93
+ run_ssh "cd ~/testproject && git commit -m 'Remote conflicting commit'"
94
+ run_ssh "cd ~/testproject && git push origin master"
95
+
96
+ Dir.chdir(@test_dir) do
97
+ system("git fetch origin", out: File::NULL, err: File::NULL)
98
+ end
99
+ end
100
+
101
+ # CI setup
102
+ Given /^a local CI script that passes$/ do
103
+ Dir.chdir(@test_dir) do
104
+ File.write("bin/rake", <<~'SCRIPT')
105
+ #!/bin/bash
106
+ case "$1" in
107
+ ci) echo "All tests passed!"; exit 0 ;;
108
+ esac
109
+ SCRIPT
110
+ FileUtils.chmod(0o755, "bin/rake")
111
+ end
112
+ end
113
+
114
+ Given /^a local CI script that fails with "([^"]+)"$/ do |error_message|
115
+ Dir.chdir(@test_dir) do
116
+ File.write("bin/rake", <<~SCRIPT)
117
+ #!/bin/bash
118
+ case "$1" in
119
+ ci) echo "#{error_message}"; exit 1 ;;
120
+ esac
121
+ SCRIPT
122
+ FileUtils.chmod(0o755, "bin/rake")
123
+ end
124
+ end
125
+
126
+ Given /^I switch to branch "([^"]+)"$/ do |branch_name|
127
+ Dir.chdir(@test_dir) do
128
+ system("git checkout #{branch_name}", out: File::NULL, err: File::NULL)
129
+ end
130
+ end
131
+
132
+ # Output negation
133
+ Then /^the output should not contain "([^"]+)"$/ do |unexpected|
134
+ expect(@stdout).not_to include(unexpected)
135
+ end
136
+