discharger 0.3.4 → 0.4.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 +4 -4
- data/CHANGELOG.md +14 -4
- data/README.md +32 -1
- data/lib/discharger/setup_runner/command_factory.rb +45 -8
- data/lib/discharger/setup_runner/command_registry.rb +27 -0
- data/lib/discharger/setup_runner/commands/docker_command.rb +7 -1
- data/lib/discharger/setup_runner/commands/github_packages_command.rb +137 -0
- data/lib/discharger/setup_runner/configuration.rb +15 -1
- data/lib/discharger/setup_runner/runner.rb +15 -1
- data/lib/discharger/task.rb +163 -81
- data/lib/discharger/version.rb +1 -1
- data/lib/generators/discharger/install/templates/setup.yml +16 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3b0eda964739589ae49d73ea992ff80d8e782b3de287ca19fcb1a82c605d1638
|
|
4
|
+
data.tar.gz: 5d86440df15d87cb9a66b5133dc1d70d8f736bd0644a7b9f57b5a4d2a3428e89
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 82c6912aee9e99edeb92304f26a0443983495b4aa8b3152cea7497da0da895ad892b8d392bdd25d7a1d559176c6a412589fd9b2dab0bc8fae9fb1c28019602ad
|
|
7
|
+
data.tar.gz: 48a5949f5177ace0de43d0788ce3f45a6dd24b558478704014b5234c6625e23737c209f831550ed224c1ca6b6869f08d0fdb8c30515f1af6efebafeb129c80db
|
data/CHANGELOG.md
CHANGED
|
@@ -5,14 +5,24 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
|
6
6
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
|
7
7
|
|
|
8
|
-
## [0.
|
|
8
|
+
## [0.4.0] - 2026-09-24
|
|
9
9
|
|
|
10
10
|
### Added
|
|
11
11
|
|
|
12
|
-
-
|
|
12
|
+
- pr_label option opening the finalize and version-bump PRs via gh with that label (08e1d10)
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Auto-deploy releases tag the newest commit that changed the version's changelog section instead of requiring it at HEAD, and no longer open a production PR (476fe1b)
|
|
17
|
+
- Auto-deploy releases merge the release tag into the production branch (a7a1b49)
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- retain_changelogs reaches Reissue, so finalize writes the archived changelog file (2a90edf)
|
|
22
|
+
- release:prepare branches from origin, so a stale local branch no longer yields an empty changelog (8eb5fd3)
|
|
23
|
+
|
|
24
|
+
## [0.3.5] - 2026-08-10
|
|
15
25
|
|
|
16
26
|
### Added
|
|
17
27
|
|
|
18
|
-
-
|
|
28
|
+
- github_packages setup step storing bundler credentials for a private GitHub Packages gem source via the gh CLI (2cff4b6)
|
data/README.md
CHANGED
|
@@ -187,6 +187,16 @@ end
|
|
|
187
187
|
|
|
188
188
|
This allows you to use environment variables to override the default branch names, or set project-specific defaults. The `DISCHARGER_BUILD_BRANCH` environment variable (shown above) provides a runtime override specifically for the build task.
|
|
189
189
|
|
|
190
|
+
### Auto-deploy Staging
|
|
191
|
+
|
|
192
|
+
For projects whose CI deploys the working branch to staging on every merge, set `task.auto_deploy_staging = true` and skip `rake release:stage`. Once the finalize PR merges, `rake release`:
|
|
193
|
+
|
|
194
|
+
1. Tags the newest commit that changed the current version's dated changelog section, so work merged after finalizing (or re-finalizing for a hotfix) is left out.
|
|
195
|
+
2. Pushes the tag. Production deploys from `v*` tags.
|
|
196
|
+
3. Merges the tag into `production_branch` so it tracks production. If that push is rejected, the release still completes with a warning.
|
|
197
|
+
|
|
198
|
+
Production ships the tagged commit, which can be older than what staging last ran.
|
|
199
|
+
|
|
190
200
|
## Development Setup Automation
|
|
191
201
|
|
|
192
202
|
Discharger includes a setup script that automates your development environment configuration. When you run the install generator, it creates a `bin/setup` script and a `config/setup.yml` configuration file.
|
|
@@ -274,6 +284,7 @@ The `steps` array specifies which built-in setup commands to run. Available comm
|
|
|
274
284
|
- `brew` - Install Homebrew dependencies
|
|
275
285
|
- `asdf` - Setup version management with asdf
|
|
276
286
|
- `git` - Configure git settings
|
|
287
|
+
- `github_packages` - Store bundler credentials for a private GitHub Packages gem source using the GitHub CLI (run before `bundler`; requires a `github_packages.source` config entry)
|
|
277
288
|
- `bundler` - Install Ruby gems
|
|
278
289
|
- `yarn` - Install JavaScript packages
|
|
279
290
|
- `config` - Copy configuration files
|
|
@@ -294,7 +305,7 @@ steps:
|
|
|
294
305
|
- yarn
|
|
295
306
|
```
|
|
296
307
|
|
|
297
|
-
Leave the array empty or omit it entirely to run all available steps.
|
|
308
|
+
Leave the array empty or omit it entirely to run all available steps, in the order listed above. Custom commands registered through `Discharger::SetupRunner.register_command` run last.
|
|
298
309
|
|
|
299
310
|
### Adding Custom Commands
|
|
300
311
|
|
|
@@ -321,6 +332,26 @@ Each custom step can include:
|
|
|
321
332
|
- `description` - A description shown during setup
|
|
322
333
|
- `command` - The command to execute
|
|
323
334
|
- `condition` - An optional Ruby expression that must evaluate to true for the command to run
|
|
335
|
+
- `name` - An optional name that lets the step be scheduled in the `steps` list
|
|
336
|
+
|
|
337
|
+
Custom steps run after the built-in steps, in the order listed. To run one
|
|
338
|
+
earlier, give it a `name` and list that name in `steps`:
|
|
339
|
+
|
|
340
|
+
```yaml
|
|
341
|
+
custom_steps:
|
|
342
|
+
- name: import_certs
|
|
343
|
+
description: "Import development certificates"
|
|
344
|
+
command: "bin/import-certs"
|
|
345
|
+
|
|
346
|
+
steps:
|
|
347
|
+
- brew
|
|
348
|
+
- import_certs # runs between brew and bundler
|
|
349
|
+
- bundler
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
A named step listed in `steps` runs only at that position; named steps that
|
|
353
|
+
are not listed keep the default run-last behavior. A name that collides with
|
|
354
|
+
a built-in step resolves to the built-in, and a warning is logged.
|
|
324
355
|
|
|
325
356
|
### Creating Custom Command Classes
|
|
326
357
|
|
|
@@ -25,32 +25,69 @@ module Discharger
|
|
|
25
25
|
|
|
26
26
|
def create_all_commands
|
|
27
27
|
commands = []
|
|
28
|
+
scheduled = []
|
|
28
29
|
|
|
29
|
-
# Create built-in commands from steps
|
|
30
|
+
# Create built-in and named custom commands from steps
|
|
30
31
|
if config.steps.any?
|
|
32
|
+
named_custom = named_custom_steps
|
|
31
33
|
config.steps.each do |step|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
name = step.to_s
|
|
35
|
+
if CommandRegistry.get(name)
|
|
36
|
+
if named_custom.key?(name)
|
|
37
|
+
logger&.warn "custom step name #{name.inspect} is shadowed by a built-in command; the built-in will run"
|
|
38
|
+
end
|
|
39
|
+
command = create_command(name)
|
|
40
|
+
commands << command if command
|
|
41
|
+
elsif (step_config = named_custom[name])
|
|
42
|
+
scheduled << step_config
|
|
43
|
+
commands << build_custom_command(step_config)
|
|
44
|
+
else
|
|
45
|
+
logger&.warn "unknown step #{name.inspect}; not a built-in or named custom step"
|
|
46
|
+
end
|
|
34
47
|
end
|
|
35
48
|
else
|
|
36
49
|
# If no steps specified, create all registered commands
|
|
37
|
-
CommandRegistry.
|
|
50
|
+
CommandRegistry.ordered_names.each do |name|
|
|
38
51
|
command = create_command(name)
|
|
39
52
|
commands << command if command
|
|
40
53
|
end
|
|
41
54
|
end
|
|
42
55
|
|
|
43
|
-
# Create custom commands
|
|
56
|
+
# Create the remaining custom commands
|
|
44
57
|
if config.respond_to?(:custom_steps) && config.custom_steps.any?
|
|
45
|
-
require_relative "commands/custom_command"
|
|
46
58
|
config.custom_steps.each do |step_config|
|
|
47
|
-
|
|
48
|
-
commands <<
|
|
59
|
+
next if scheduled.any? { |s| s.equal?(step_config) }
|
|
60
|
+
commands << build_custom_command(step_config)
|
|
49
61
|
end
|
|
50
62
|
end
|
|
51
63
|
|
|
52
64
|
commands
|
|
53
65
|
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
# Custom steps with a "name" key can be scheduled by that name in
|
|
70
|
+
# steps:. First entry wins a contested name; later duplicates keep the
|
|
71
|
+
# default run-last behavior.
|
|
72
|
+
def named_custom_steps
|
|
73
|
+
return {} unless config.respond_to?(:custom_steps)
|
|
74
|
+
|
|
75
|
+
config.custom_steps.each_with_object({}) do |step_config, named|
|
|
76
|
+
name = step_config["name"].to_s
|
|
77
|
+
next if name.empty?
|
|
78
|
+
|
|
79
|
+
if named.key?(name)
|
|
80
|
+
logger&.warn "duplicate custom step name #{name.inspect}; only the first entry can be scheduled"
|
|
81
|
+
next
|
|
82
|
+
end
|
|
83
|
+
named[name] = step_config
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def build_custom_command(step_config)
|
|
88
|
+
require_relative "commands/custom_command"
|
|
89
|
+
Commands::CustomCommand.new(config, app_root, logger, step_config)
|
|
90
|
+
end
|
|
54
91
|
end
|
|
55
92
|
end
|
|
56
93
|
end
|
|
@@ -3,6 +3,24 @@
|
|
|
3
3
|
module Discharger
|
|
4
4
|
module SetupRunner
|
|
5
5
|
class CommandRegistry
|
|
6
|
+
# The order built-in steps run in when setup.yml configures no steps.
|
|
7
|
+
# Registration order cannot be relied on here: it comes from enumerating
|
|
8
|
+
# Commands.constants, which shifts with require order. Credentials and
|
|
9
|
+
# toolchains have to be in place before bundler resolves gems.
|
|
10
|
+
DEFAULT_ORDER = %w[
|
|
11
|
+
brew
|
|
12
|
+
asdf
|
|
13
|
+
git
|
|
14
|
+
github_packages
|
|
15
|
+
bundler
|
|
16
|
+
yarn
|
|
17
|
+
config
|
|
18
|
+
docker
|
|
19
|
+
pg_tools
|
|
20
|
+
env
|
|
21
|
+
database
|
|
22
|
+
].freeze
|
|
23
|
+
|
|
6
24
|
class << self
|
|
7
25
|
def register(name, command_class)
|
|
8
26
|
commands[name.to_s] = command_class
|
|
@@ -20,6 +38,12 @@ module Discharger
|
|
|
20
38
|
commands.keys
|
|
21
39
|
end
|
|
22
40
|
|
|
41
|
+
# Registered names in pipeline order, with anything outside
|
|
42
|
+
# DEFAULT_ORDER (custom commands) appended in registration order.
|
|
43
|
+
def ordered_names
|
|
44
|
+
(DEFAULT_ORDER & names) + (names - DEFAULT_ORDER)
|
|
45
|
+
end
|
|
46
|
+
|
|
23
47
|
def clear
|
|
24
48
|
commands.clear
|
|
25
49
|
end
|
|
@@ -41,6 +65,9 @@ module Discharger
|
|
|
41
65
|
command_class = Commands.const_get(const_name)
|
|
42
66
|
next unless command_class < Commands::BaseCommand
|
|
43
67
|
next if command_class == Commands::BaseCommand
|
|
68
|
+
# Not schedulable by name: it takes a step_config argument and is
|
|
69
|
+
# instantiated by CommandFactory from custom_steps entries.
|
|
70
|
+
next if command_class == Commands::CustomCommand
|
|
44
71
|
|
|
45
72
|
# Convert class name to command name (e.g., AsdfCommand -> asdf)
|
|
46
73
|
command_name = const_name.to_s.sub(/Command$/, "").gsub(/([A-Z])/, '_\1').downcase.sub(/^_/, "")
|
|
@@ -104,12 +104,14 @@ module Discharger
|
|
|
104
104
|
log "Checking #{name} container"
|
|
105
105
|
|
|
106
106
|
if system_quiet("docker ps | grep #{name} > /dev/null 2>&1")
|
|
107
|
+
ensure_restart_policy(name)
|
|
107
108
|
log "#{name} container is already running"
|
|
108
109
|
return
|
|
109
110
|
end
|
|
110
111
|
|
|
111
112
|
# Check if container exists but is stopped
|
|
112
113
|
if system_quiet("docker inspect #{name} > /dev/null 2>&1")
|
|
114
|
+
ensure_restart_policy(name)
|
|
113
115
|
log "Starting existing #{name} container"
|
|
114
116
|
unless system_quiet("docker start #{name}")
|
|
115
117
|
log "Removing failed #{name} container"
|
|
@@ -148,7 +150,7 @@ module Discharger
|
|
|
148
150
|
def create_container(name:, port:, image:, internal_port:, env: {}, volume: nil)
|
|
149
151
|
log "Creating new #{name} container"
|
|
150
152
|
|
|
151
|
-
cmd = ["docker", "run", "-d", "--name", name, "-p", "#{port}:#{internal_port}"]
|
|
153
|
+
cmd = ["docker", "run", "-d", "--restart", "unless-stopped", "--name", name, "-p", "#{port}:#{internal_port}"]
|
|
152
154
|
env.each { |k, v| cmd.push("-e", "#{k}=#{v}") }
|
|
153
155
|
cmd.push("-v", volume) if volume
|
|
154
156
|
cmd.push(image)
|
|
@@ -156,6 +158,10 @@ module Discharger
|
|
|
156
158
|
system!(*cmd)
|
|
157
159
|
end
|
|
158
160
|
|
|
161
|
+
def ensure_restart_policy(name)
|
|
162
|
+
system!("docker", "update", "--restart", "unless-stopped", name)
|
|
163
|
+
end
|
|
164
|
+
|
|
159
165
|
def docker_available?
|
|
160
166
|
return true if system_quiet("which docker > /dev/null 2>&1")
|
|
161
167
|
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_command"
|
|
4
|
+
|
|
5
|
+
module Discharger
|
|
6
|
+
module SetupRunner
|
|
7
|
+
module Commands
|
|
8
|
+
# Stores bundler credentials for a private GitHub Packages gem source
|
|
9
|
+
# using the GitHub CLI, so Gemfiles don't need embedded tokens.
|
|
10
|
+
# Credentials are written to the app's .bundle/config — keep .bundle
|
|
11
|
+
# gitignored. Run this step before "bundler" in setup.yml.
|
|
12
|
+
class GithubPackagesCommand < BaseCommand
|
|
13
|
+
# A configured source with nothing scheduled to store its credentials
|
|
14
|
+
# is inert, and the only symptom is bundler failing against the
|
|
15
|
+
# private source. The Runner surfaces this before executing commands.
|
|
16
|
+
# Guarded for duck-typed configs passed to the public Runner API, and
|
|
17
|
+
# quiet when a custom step's command references the source (assumed
|
|
18
|
+
# to handle credentials itself).
|
|
19
|
+
def self.unscheduled_warning(config)
|
|
20
|
+
return unless config.respond_to?(:github_packages)
|
|
21
|
+
|
|
22
|
+
source = config.github_packages&.source
|
|
23
|
+
return if source.to_s.empty?
|
|
24
|
+
if config.respond_to?(:custom_steps) &&
|
|
25
|
+
config.custom_steps.any? { |step| step_handles_source?(step, source) }
|
|
26
|
+
return
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
"github_packages is configured but missing from steps; " \
|
|
30
|
+
"credentials will not be stored and bundler may fail for #{source}"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# A referencing step only counts as handling credentials when it will
|
|
34
|
+
# actually run: CustomCommand skips itself when its condition is
|
|
35
|
+
# false, storing nothing on this run.
|
|
36
|
+
def self.step_handles_source?(step, source)
|
|
37
|
+
return false unless step["command"].to_s.include?(source)
|
|
38
|
+
|
|
39
|
+
require_relative "../condition_evaluator"
|
|
40
|
+
ConditionEvaluator.evaluate(step["condition"])
|
|
41
|
+
end
|
|
42
|
+
private_class_method :step_handles_source?
|
|
43
|
+
|
|
44
|
+
def execute
|
|
45
|
+
unless gh_installed?
|
|
46
|
+
log "GitHub CLI (gh) not found; skipping. Install gh and rerun setup or `bundle install` may fail for #{source}"
|
|
47
|
+
return
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
unless authenticated? || login
|
|
51
|
+
log "Not logged in to GitHub; skipping. Run `gh auth login` and rerun setup."
|
|
52
|
+
return
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
username = gh("api", "user", "--jq", ".login")
|
|
56
|
+
token = gh("auth", "token")
|
|
57
|
+
if username.to_s.empty? || token.to_s.empty?
|
|
58
|
+
log "Could not read GitHub credentials from gh; skipping."
|
|
59
|
+
return
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
store_bundler_credentials(username, token)
|
|
63
|
+
if credentials_valid?(username, token)
|
|
64
|
+
log "Configured bundler credentials for #{source}"
|
|
65
|
+
else
|
|
66
|
+
log "#{source} rejected the stored credentials. Your gh token may lack " \
|
|
67
|
+
"the read:packages scope — run `gh auth refresh -s read:packages` and rerun setup."
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def can_execute?
|
|
72
|
+
!source.to_s.empty?
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def description
|
|
76
|
+
"Configure GitHub Packages credentials"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
protected
|
|
80
|
+
|
|
81
|
+
def source
|
|
82
|
+
config.github_packages&.source
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def gh_installed?
|
|
86
|
+
system_quiet("which gh")
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def authenticated?
|
|
90
|
+
system_quiet("gh auth status")
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def login
|
|
94
|
+
return false unless $stdin.tty?
|
|
95
|
+
log "Logging in to GitHub..."
|
|
96
|
+
system("gh", "auth", "login")
|
|
97
|
+
authenticated?
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def gh(*args)
|
|
101
|
+
require "open3"
|
|
102
|
+
stdout, _stderr, status = Open3.capture3("gh", *args)
|
|
103
|
+
status.success? ? stdout.chomp : nil
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Runs bundle config directly instead of through system! so the
|
|
107
|
+
# token never reaches the spinner display or the debug log.
|
|
108
|
+
def store_bundler_credentials(username, token)
|
|
109
|
+
require "open3"
|
|
110
|
+
_stdout, stderr, status = Open3.capture3(
|
|
111
|
+
"bundle", "config", "set", "--local", source, "#{username}:#{token}"
|
|
112
|
+
)
|
|
113
|
+
raise "bundle config set --local #{source} failed: #{stderr}" unless status.success?
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Probes the source's compact index with the stored credentials.
|
|
117
|
+
# Only a definite 401/403 counts as invalid — network trouble must
|
|
118
|
+
# not fail setup over an unverifiable token.
|
|
119
|
+
def credentials_valid?(username, token)
|
|
120
|
+
require "net/http"
|
|
121
|
+
require "uri"
|
|
122
|
+
uri = URI.join("#{source.chomp("/")}/", "versions")
|
|
123
|
+
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https",
|
|
124
|
+
open_timeout: 5, read_timeout: 10) do |http|
|
|
125
|
+
request = Net::HTTP::Get.new(uri)
|
|
126
|
+
request.basic_auth(username, token)
|
|
127
|
+
http.request(request)
|
|
128
|
+
end
|
|
129
|
+
!%w[401 403].include?(response.code)
|
|
130
|
+
rescue => e
|
|
131
|
+
logger&.debug("Could not verify GitHub Packages credentials: #{e.message}")
|
|
132
|
+
true
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -5,12 +5,13 @@ require "yaml"
|
|
|
5
5
|
module Discharger
|
|
6
6
|
module SetupRunner
|
|
7
7
|
class Configuration
|
|
8
|
-
attr_accessor :app_name, :database, :redis, :services, :steps, :custom_steps, :pre_steps
|
|
8
|
+
attr_accessor :app_name, :database, :redis, :github_packages, :services, :steps, :custom_steps, :pre_steps
|
|
9
9
|
|
|
10
10
|
def initialize
|
|
11
11
|
@app_name = "Application"
|
|
12
12
|
@database = DatabaseConfig.new
|
|
13
13
|
@redis = nil
|
|
14
|
+
@github_packages = nil
|
|
14
15
|
@services = []
|
|
15
16
|
@steps = []
|
|
16
17
|
@custom_steps = []
|
|
@@ -27,6 +28,7 @@ module Discharger
|
|
|
27
28
|
config.app_name = yaml["app_name"] if yaml["app_name"]
|
|
28
29
|
config.database.from_hash(yaml["database"]) if yaml["database"]
|
|
29
30
|
config.redis = RedisConfig.new.tap { |r| r.from_hash(yaml["redis"]) } if yaml["redis"]
|
|
31
|
+
config.github_packages = GithubPackagesConfig.new.tap { |g| g.from_hash(yaml["github_packages"]) } if yaml["github_packages"]
|
|
30
32
|
config.services = yaml["services"] || []
|
|
31
33
|
config.steps = yaml["steps"] || []
|
|
32
34
|
config.custom_steps = yaml["custom_steps"] || []
|
|
@@ -56,6 +58,18 @@ module Discharger
|
|
|
56
58
|
end
|
|
57
59
|
end
|
|
58
60
|
|
|
61
|
+
class GithubPackagesConfig
|
|
62
|
+
attr_accessor :source
|
|
63
|
+
|
|
64
|
+
def initialize
|
|
65
|
+
@source = nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def from_hash(hash)
|
|
69
|
+
@source = hash["source"] if hash["source"]
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
59
73
|
class RedisConfig
|
|
60
74
|
attr_accessor :port, :name, :version
|
|
61
75
|
|
|
@@ -15,7 +15,7 @@ module Discharger
|
|
|
15
15
|
@config = config
|
|
16
16
|
@app_root = app_root || Dir.pwd
|
|
17
17
|
@logger = logger || Logger.new($stdout)
|
|
18
|
-
@command_factory = CommandFactory.new(config, app_root, logger)
|
|
18
|
+
@command_factory = CommandFactory.new(config, @app_root, @logger)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def run
|
|
@@ -25,6 +25,8 @@ module Discharger
|
|
|
25
25
|
puts Rainbow("=" * 50).blue
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
warn_unscheduled_commands
|
|
29
|
+
|
|
28
30
|
FileUtils.chdir app_root do
|
|
29
31
|
execute_commands
|
|
30
32
|
end
|
|
@@ -76,6 +78,18 @@ module Discharger
|
|
|
76
78
|
@commands ||= command_factory.create_all_commands
|
|
77
79
|
end
|
|
78
80
|
|
|
81
|
+
# Checked at run time, after the customization block, so commands
|
|
82
|
+
# scheduled through add_command/insert_command_* count as scheduled.
|
|
83
|
+
def warn_unscheduled_commands
|
|
84
|
+
CommandRegistry.all.each do |klass|
|
|
85
|
+
next unless klass.respond_to?(:unscheduled_warning)
|
|
86
|
+
next if commands.any? { |command| command.is_a?(klass) }
|
|
87
|
+
|
|
88
|
+
message = klass.unscheduled_warning(config)
|
|
89
|
+
logger.warn message if message
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
79
93
|
def execute_commands
|
|
80
94
|
commands.each do |command|
|
|
81
95
|
execute_command(command)
|
data/lib/discharger/task.rb
CHANGED
|
@@ -25,6 +25,7 @@ module Discharger
|
|
|
25
25
|
reissue.clear_fragments = task.clear_fragments
|
|
26
26
|
reissue.tag_pattern = task.tag_pattern
|
|
27
27
|
reissue.runbook_file = task.runbook_file
|
|
28
|
+
reissue.retain_changelogs = task.retain_changelogs
|
|
28
29
|
end
|
|
29
30
|
task.define
|
|
30
31
|
task
|
|
@@ -46,6 +47,7 @@ module Discharger
|
|
|
46
47
|
attr_accessor :app_name
|
|
47
48
|
attr_accessor :commit_identifier
|
|
48
49
|
attr_accessor :pull_request_url
|
|
50
|
+
attr_accessor :pr_label
|
|
49
51
|
attr_accessor :fragment_directory
|
|
50
52
|
attr_accessor :fragment
|
|
51
53
|
attr_accessor :clear_fragments
|
|
@@ -134,32 +136,41 @@ module Discharger
|
|
|
134
136
|
ERROR
|
|
135
137
|
end
|
|
136
138
|
|
|
137
|
-
#
|
|
138
|
-
def
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
# The newest commit that changed the current version's dated changelog section
|
|
140
|
+
def find_release_commit!(branch, output: $stdout)
|
|
141
|
+
version = Object.const_get(version_constant)
|
|
142
|
+
section = /^## \[#{Regexp.escape(version)}\] - \d{4}-\d{2}-\d{2}.*?(?=^## \[|\z)/m
|
|
143
|
+
sha = git_file_commits(branch, changelog_file).find do |candidate_sha|
|
|
144
|
+
released = git_show_at_commit(candidate_sha, changelog_file)&.[](section)
|
|
145
|
+
released && released != git_show_at_commit("#{candidate_sha}^", changelog_file)&.[](section)
|
|
146
|
+
end
|
|
141
147
|
|
|
142
|
-
if
|
|
148
|
+
if sha.nil?
|
|
143
149
|
abort <<~ERROR.bg(:red).white
|
|
144
|
-
Could not
|
|
145
|
-
|
|
146
|
-
HEAD: #{head_sha || "not found"}
|
|
147
|
-
Release commit: #{release_sha || "not found"}
|
|
148
|
-
|
|
149
|
-
Ensure #{branch} exists and #{version_file} has been modified.
|
|
150
|
+
Could not locate a release commit.
|
|
151
|
+
Ensure #{branch} exists and #{changelog_file} has been finalized for #{version}.
|
|
150
152
|
ERROR
|
|
151
153
|
end
|
|
152
154
|
|
|
153
|
-
|
|
155
|
+
sysecho("✓ Release commit: #{sha[0, 8]}".bg(:green).black, output:)
|
|
156
|
+
sha
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def merge_tag_into_production_branch(tag, output: $stdout)
|
|
160
|
+
git = ->(*args) do
|
|
161
|
+
stdout, stderr, status = Open3.capture3("git", *args)
|
|
162
|
+
return stdout.strip if status.success?
|
|
154
163
|
|
|
155
|
-
|
|
156
|
-
|
|
164
|
+
sysecho("Could not merge #{tag} into #{production_branch}: #{stderr.strip}".bg(:yellow).black, output:)
|
|
165
|
+
nil
|
|
166
|
+
end
|
|
157
167
|
|
|
158
|
-
|
|
159
|
-
|
|
168
|
+
return false unless git.call("fetch", "origin", production_branch)
|
|
169
|
+
return false unless (tree = git.call("merge-tree", "--write-tree", "origin/#{production_branch}", tag))
|
|
170
|
+
return false unless (commit = git.call("commit-tree", tree, "-p", "origin/#{production_branch}", "-p", "#{tag}^{commit}", "-m", "Merge tag '#{tag}'"))
|
|
171
|
+
return false unless git.call("push", "origin", "#{commit}:refs/heads/#{production_branch}")
|
|
160
172
|
|
|
161
|
-
|
|
162
|
-
ERROR
|
|
173
|
+
sysecho("✓ Merged #{tag} into #{production_branch}".bg(:green).black, output:)
|
|
163
174
|
end
|
|
164
175
|
|
|
165
176
|
def git_show_version(branch)
|
|
@@ -168,16 +179,57 @@ module Discharger
|
|
|
168
179
|
content[/VERSION\s*=\s*["']([^"']+)["']/, 1]
|
|
169
180
|
end
|
|
170
181
|
|
|
171
|
-
def
|
|
172
|
-
stdout, _, status = Open3.capture3("git", "
|
|
173
|
-
return
|
|
174
|
-
stdout.strip
|
|
182
|
+
def git_file_commits(branch, path)
|
|
183
|
+
stdout, _, status = Open3.capture3("git", "log", "--no-merges", branch, "--format=%H", "--", path)
|
|
184
|
+
return [] unless status.success?
|
|
185
|
+
stdout.lines.map(&:strip)
|
|
175
186
|
end
|
|
176
187
|
|
|
177
|
-
def
|
|
178
|
-
stdout, _, status = Open3.capture3("git", "
|
|
188
|
+
def git_show_at_commit(sha, path)
|
|
189
|
+
stdout, _, status = Open3.capture3("git", "show", "#{sha}:#{path}")
|
|
179
190
|
return nil unless status.success?
|
|
180
|
-
stdout
|
|
191
|
+
stdout
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def ensure_clean_worktree!
|
|
195
|
+
stdout, _, status = Open3.capture3("git", "status", "--porcelain")
|
|
196
|
+
return true if status.success? && stdout.empty?
|
|
197
|
+
|
|
198
|
+
abort "Working tree has uncommitted changes. Commit or stash them before running rake #{name}:prepare."
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def validate_pr_label!
|
|
202
|
+
return true unless pr_label
|
|
203
|
+
|
|
204
|
+
stdout, stderr, status = Open3.capture3("gh", "label", "list", "--search", pr_label, "--json", "name", "--jq", ".[].name")
|
|
205
|
+
return true if status.success? && stdout.lines.map(&:chomp).include?(pr_label)
|
|
206
|
+
|
|
207
|
+
abort <<~ERROR.bg(:red).white
|
|
208
|
+
Could not find GitHub label '#{pr_label}'.
|
|
209
|
+
|
|
210
|
+
#{stderr}
|
|
211
|
+
ERROR
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def create_labeled_pr!(head:, title:, body:)
|
|
215
|
+
if (pr_number = existing_pr_number(working_branch, head))
|
|
216
|
+
return sysecho("Reusing existing PR ##{pr_number} for #{head}.")
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
syscall(
|
|
220
|
+
["gh", "pr", "create", "--base", working_branch, "--head", head, "--title", title, "--body", body, "--label", pr_label]
|
|
221
|
+
)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def ensure_branch_not_ahead!(branch)
|
|
225
|
+
stdout, _, status = Open3.capture3("git", "rev-list", "--count", "origin/#{branch}..#{branch}")
|
|
226
|
+
return true if status.success? && stdout.strip == "0"
|
|
227
|
+
|
|
228
|
+
abort <<~ERROR.bg(:red).white
|
|
229
|
+
Local #{branch} has commits not on origin/#{branch}. Refusing to reset --hard.
|
|
230
|
+
|
|
231
|
+
Push or remove them before retrying.
|
|
232
|
+
ERROR
|
|
181
233
|
end
|
|
182
234
|
|
|
183
235
|
# The post-release steps collected from "Runbook:" commit trailers.
|
|
@@ -258,6 +310,7 @@ module Discharger
|
|
|
258
310
|
unless system("gh --version > /dev/null 2>&1")
|
|
259
311
|
abort "Error: GitHub CLI (gh) is required for the release process but was not found. Install it: https://cli.github.com"
|
|
260
312
|
end
|
|
313
|
+
validate_pr_label!
|
|
261
314
|
|
|
262
315
|
current_version = Object.const_get(version_constant)
|
|
263
316
|
|
|
@@ -265,10 +318,16 @@ module Discharger
|
|
|
265
318
|
# instead of staging_branch (for CI/CD pipelines that auto-deploy staging)
|
|
266
319
|
release_source = auto_deploy_staging ? working_branch : staging_branch
|
|
267
320
|
|
|
321
|
+
release_action = if auto_deploy_staging
|
|
322
|
+
"This will tag the release commit on #{working_branch}, push the tag, and merge it into #{production_branch}."
|
|
323
|
+
else
|
|
324
|
+
"This will tag the current version and push it to the production branch."
|
|
325
|
+
end
|
|
326
|
+
|
|
268
327
|
sysecho <<~MSG
|
|
269
328
|
Releasing version #{current_version} to production.
|
|
270
329
|
|
|
271
|
-
|
|
330
|
+
#{release_action}
|
|
272
331
|
Release source: #{release_source}
|
|
273
332
|
MSG
|
|
274
333
|
sysecho "Are you ready to continue? (Press Enter to continue, Type 'x' and Enter to exit)".bg(:yellow).black
|
|
@@ -283,48 +342,39 @@ module Discharger
|
|
|
283
342
|
|
|
284
343
|
if auto_deploy_staging
|
|
285
344
|
syscall(
|
|
286
|
-
["git fetch origin #{
|
|
345
|
+
["git fetch origin #{working_branch}"],
|
|
287
346
|
["git reset --hard origin/#{working_branch}"]
|
|
288
347
|
)
|
|
289
|
-
|
|
348
|
+
tag_ref = find_release_commit!(release_source)
|
|
290
349
|
else
|
|
291
350
|
syscall(
|
|
292
351
|
["git fetch origin #{release_source}:#{release_source} #{production_branch}:#{production_branch}"]
|
|
293
352
|
)
|
|
294
353
|
validate_version_match!(staging_branch, working_branch)
|
|
295
|
-
end
|
|
296
354
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
if pr_number
|
|
301
|
-
sysecho "Reusing existing PR ##{pr_number} from #{release_source} to #{production_branch}."
|
|
302
|
-
pr_ref = pr_number
|
|
355
|
+
pr_ref = release_source
|
|
356
|
+
if pr_already_merged?(pr_ref)
|
|
357
|
+
sysecho "PR #{pr_ref} is already merged. Continuing..."
|
|
303
358
|
else
|
|
304
359
|
syscall(
|
|
305
|
-
["gh pr
|
|
360
|
+
["gh pr merge #{pr_ref} --merge"]
|
|
306
361
|
)
|
|
307
362
|
end
|
|
308
|
-
end
|
|
309
363
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
else
|
|
313
|
-
syscall(
|
|
314
|
-
["gh pr merge #{pr_ref} --merge"]
|
|
315
|
-
)
|
|
364
|
+
syscall(["git fetch origin #{production_branch}:#{production_branch}"])
|
|
365
|
+
tag_ref = production_branch
|
|
316
366
|
end
|
|
317
367
|
|
|
368
|
+
slack_sha = auto_deploy_staging ? tag_ref[0, 8] : commit_identifier.call
|
|
318
369
|
continue = syscall(
|
|
319
|
-
["git
|
|
320
|
-
["git tag -a v#{current_version} -m 'Release #{current_version}' #{production_branch}"],
|
|
370
|
+
["git tag -a v#{current_version} -m 'Release #{current_version}' #{tag_ref}"],
|
|
321
371
|
["git push origin v#{current_version}"]
|
|
322
372
|
) do
|
|
323
|
-
tasker["#{name}:slack"].invoke("Released #{app_name} #{current_version} (#{
|
|
373
|
+
tasker["#{name}:slack"].invoke("Released #{app_name} #{current_version} (#{slack_sha}) to production.", release_message_channel, ":chipmunk:")
|
|
324
374
|
# Capture the root before replying; each post overwrites last_message_ts.
|
|
325
375
|
thread_ts = last_message_ts
|
|
326
376
|
if thread_ts.present?
|
|
327
|
-
text = File.read(Rails.root.join(changelog_file))
|
|
377
|
+
text = git_show_at_commit(tag_ref, changelog_file) || File.read(Rails.root.join(changelog_file))
|
|
328
378
|
tasker["#{name}:slack"].reenable
|
|
329
379
|
tasker["#{name}:slack"].invoke(text, release_message_channel, ":log:", thread_ts)
|
|
330
380
|
|
|
@@ -335,6 +385,7 @@ module Discharger
|
|
|
335
385
|
end
|
|
336
386
|
|
|
337
387
|
abort "Release failed." unless continue
|
|
388
|
+
merge_tag_into_production_branch("v#{current_version}") if auto_deploy_staging
|
|
338
389
|
|
|
339
390
|
sysecho <<~MSG
|
|
340
391
|
Version #{current_version} released to production.
|
|
@@ -346,20 +397,26 @@ module Discharger
|
|
|
346
397
|
|
|
347
398
|
new_version_branch = `git rev-parse --abbrev-ref HEAD`.strip
|
|
348
399
|
new_version = new_version_branch.split("/").last
|
|
349
|
-
params = {expand: 1, title: "Bump version to #{new_version}"}
|
|
350
|
-
pr_url = "#{pull_request_url}/compare/#{working_branch}...#{new_version_branch}?#{params.to_query}"
|
|
351
400
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
401
|
+
if pr_label
|
|
402
|
+
syscall(["git push origin #{new_version_branch} --force"])
|
|
403
|
+
create_labeled_pr!(head: new_version_branch, title: "Bump version to #{new_version}", body: "")
|
|
404
|
+
else
|
|
405
|
+
params = {expand: 1, title: "Bump version to #{new_version}"}
|
|
406
|
+
pr_url = "#{pull_request_url}/compare/#{working_branch}...#{new_version_branch}?#{params.to_query}"
|
|
355
407
|
|
|
356
|
-
|
|
357
|
-
|
|
408
|
+
syscall(["git push origin #{new_version_branch} --force"]) do
|
|
409
|
+
sysecho <<~MSG
|
|
410
|
+
Branch #{new_version_branch} created.
|
|
358
411
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
412
|
+
Open a PR to #{working_branch} to mark the version and update the chaneglog
|
|
413
|
+
for the next release.
|
|
414
|
+
|
|
415
|
+
Opening PR: #{pr_url}
|
|
416
|
+
MSG
|
|
417
|
+
end.then do |success|
|
|
418
|
+
syscall ["open", pr_url] if success
|
|
419
|
+
end
|
|
363
420
|
end
|
|
364
421
|
end
|
|
365
422
|
|
|
@@ -429,12 +486,19 @@ module Discharger
|
|
|
429
486
|
the release.
|
|
430
487
|
DESC
|
|
431
488
|
task prepare: [:environment] do
|
|
489
|
+
ensure_clean_worktree!
|
|
490
|
+
validate_pr_label!
|
|
491
|
+
|
|
432
492
|
current_version = Object.const_get(version_constant)
|
|
433
493
|
finish_branch = "bump/finish-#{current_version.tr(".", "-")}"
|
|
434
494
|
|
|
435
495
|
syscall(
|
|
436
496
|
["git fetch origin #{working_branch}"],
|
|
437
|
-
["git checkout #{working_branch}"]
|
|
497
|
+
["git checkout #{working_branch}"]
|
|
498
|
+
)
|
|
499
|
+
ensure_branch_not_ahead!(working_branch)
|
|
500
|
+
syscall(
|
|
501
|
+
["git reset --hard origin/#{working_branch}"],
|
|
438
502
|
["git checkout -b #{finish_branch}"]
|
|
439
503
|
)
|
|
440
504
|
sysecho <<~MSG
|
|
@@ -451,34 +515,52 @@ module Discharger
|
|
|
451
515
|
|
|
452
516
|
tasker["reissue:finalize"].invoke
|
|
453
517
|
|
|
454
|
-
params = {
|
|
455
|
-
expand: 1,
|
|
456
|
-
title: "Finish version #{current_version}",
|
|
457
|
-
body: <<~BODY
|
|
458
|
-
Completing development for #{current_version}.
|
|
459
|
-
BODY
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
pr_url = "#{pull_request_url}/compare/#{finish_branch}?#{params.to_query}"
|
|
463
|
-
|
|
464
518
|
next_step = auto_deploy_staging ? "rake #{name}" : "rake #{name}:stage"
|
|
465
519
|
next_step_desc = auto_deploy_staging ? "release to production" : "stage the release branch"
|
|
520
|
+
after_merge = <<~MSG.chomp
|
|
521
|
+
Once the PR is merged, pull down #{working_branch} and run
|
|
522
|
+
'#{next_step}'
|
|
523
|
+
to #{next_step_desc}.
|
|
524
|
+
MSG
|
|
466
525
|
|
|
467
|
-
|
|
526
|
+
if pr_label
|
|
527
|
+
syscall ["git push origin #{finish_branch} --force"]
|
|
528
|
+
create_labeled_pr!(
|
|
529
|
+
head: finish_branch,
|
|
530
|
+
title: "Finish version #{current_version}",
|
|
531
|
+
body: "Completing development for #{current_version}."
|
|
532
|
+
)
|
|
468
533
|
sysecho <<~MSG
|
|
469
|
-
Branch #{finish_branch}
|
|
470
|
-
Open a PR to #{working_branch} to finalize the release.
|
|
534
|
+
Branch #{finish_branch} pushed and its PR labeled '#{pr_label}'.
|
|
471
535
|
|
|
472
|
-
#{
|
|
473
|
-
|
|
474
|
-
Once the PR is merged, pull down #{working_branch} and run
|
|
475
|
-
'#{next_step}'
|
|
476
|
-
to #{next_step_desc}.
|
|
536
|
+
#{after_merge}
|
|
477
537
|
MSG
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
538
|
+
syscall ["git checkout #{working_branch}"]
|
|
539
|
+
else
|
|
540
|
+
params = {
|
|
541
|
+
expand: 1,
|
|
542
|
+
title: "Finish version #{current_version}",
|
|
543
|
+
body: <<~BODY
|
|
544
|
+
Completing development for #{current_version}.
|
|
545
|
+
BODY
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
pr_url = "#{pull_request_url}/compare/#{finish_branch}?#{params.to_query}"
|
|
549
|
+
|
|
550
|
+
continue = syscall ["git push origin #{finish_branch} --force"] do
|
|
551
|
+
sysecho <<~MSG
|
|
552
|
+
Branch #{finish_branch} created.
|
|
553
|
+
Open a PR to #{working_branch} to finalize the release.
|
|
554
|
+
|
|
555
|
+
#{pr_url}
|
|
556
|
+
|
|
557
|
+
#{after_merge}
|
|
558
|
+
MSG
|
|
559
|
+
end
|
|
560
|
+
if continue
|
|
561
|
+
syscall ["git checkout #{working_branch}"],
|
|
562
|
+
["open", pr_url]
|
|
563
|
+
end
|
|
482
564
|
end
|
|
483
565
|
end
|
|
484
566
|
|
data/lib/discharger/version.rb
CHANGED
|
@@ -18,6 +18,14 @@ redis:
|
|
|
18
18
|
name: "redis-your-app"
|
|
19
19
|
version: "latest"
|
|
20
20
|
|
|
21
|
+
# Private GitHub Packages gem source (optional)
|
|
22
|
+
# Stores bundler credentials from the GitHub CLI (gh) so the Gemfile
|
|
23
|
+
# doesn't need an embedded token. Add "github_packages" to steps, before
|
|
24
|
+
# "bundler". Credentials are written to .bundle/config — keep .bundle
|
|
25
|
+
# gitignored.
|
|
26
|
+
# github_packages:
|
|
27
|
+
# source: "https://rubygems.pkg.github.com/your-org"
|
|
28
|
+
|
|
21
29
|
# Pre-Rails steps (run BEFORE Rails loads)
|
|
22
30
|
# These are for system dependencies and environment setup that must happen
|
|
23
31
|
# before bundler/Rails can initialize. Use sparingly.
|
|
@@ -36,11 +44,13 @@ pre_steps: []
|
|
|
36
44
|
# - postgresql_tools # Optional: for apps that use structure.sql or pg_dump/psql directly
|
|
37
45
|
|
|
38
46
|
# Built-in commands to run (empty array runs all available commands)
|
|
39
|
-
# Available commands: brew, asdf, git, bundler, yarn, config, docker, pg_tools, env, database
|
|
47
|
+
# Available commands: brew, asdf, git, github_packages, bundler, yarn, config, docker, pg_tools, env, database
|
|
40
48
|
steps:
|
|
41
49
|
- brew
|
|
42
50
|
- asdf
|
|
43
51
|
- git
|
|
52
|
+
# Uncomment with the github_packages block above. Must precede bundler.
|
|
53
|
+
# - github_packages
|
|
44
54
|
- bundler
|
|
45
55
|
- yarn
|
|
46
56
|
- config
|
|
@@ -52,6 +62,11 @@ steps:
|
|
|
52
62
|
# - pg_tools
|
|
53
63
|
|
|
54
64
|
# Custom commands for application-specific setup (run AFTER Rails loads)
|
|
65
|
+
# Steps run last by default. Give a step a name and list it in steps: above
|
|
66
|
+
# to run it earlier, e.g.:
|
|
67
|
+
# - name: import_certs
|
|
68
|
+
# description: "Import development certificates"
|
|
69
|
+
# command: "bin/import-certs"
|
|
55
70
|
custom_steps:
|
|
56
71
|
- description: "Seed application data"
|
|
57
72
|
command: "bin/rails db:seed"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: discharger
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jim Gay
|
|
@@ -122,6 +122,7 @@ files:
|
|
|
122
122
|
- lib/discharger/setup_runner/commands/docker_command.rb
|
|
123
123
|
- lib/discharger/setup_runner/commands/env_command.rb
|
|
124
124
|
- lib/discharger/setup_runner/commands/git_command.rb
|
|
125
|
+
- lib/discharger/setup_runner/commands/github_packages_command.rb
|
|
125
126
|
- lib/discharger/setup_runner/commands/pg_tools_command.rb
|
|
126
127
|
- lib/discharger/setup_runner/commands/yarn_command.rb
|
|
127
128
|
- lib/discharger/setup_runner/condition_evaluator.rb
|