discharger 0.4.1 → 0.5.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: 3212b79aa38f448d44523f2f83b622c7dfec1cff7bff08ed6033bbe1fea79761
4
- data.tar.gz: 6d9d0c008a85560a666522c58a97ee08feae68ef1ea4e8f7cda86e29b0e8bd75
3
+ metadata.gz: 8e3fabd68a173b372966725b98da677f18061b6377d47838e10305cb0baaa820
4
+ data.tar.gz: 9c29847b75d29cc764f6cd1eb621c7afdf5d98323877a106322a481ade425c3e
5
5
  SHA512:
6
- metadata.gz: a54728bf513a01a7f4f91507d0951f4075ab8d2fbd7a80cb01223bbd967f4f5bcc0dfba8a8842a3d3e6ab30c94995180bef978089c19a519ac07de608234f787
7
- data.tar.gz: bf5d95387d7f861cfef6041871c39a946f986225a8c20aa3597b5dc3d5840e5f3ac16ebe5babc354da4b292e84820bcf8cc5a75b56d519414c12614e325a350b
6
+ metadata.gz: 06d72d2a6be99df84988f1222311a6436d01f59d74d17fd971d4df25c0057f78e5b41435861556dffeae48e8bd21238ea6dc46c47b0a90b34df09460944be19d
7
+ data.tar.gz: 80711c35ce3061f8543658160fb7784d0b3ca5cdb465d36f11b375d6c961a0385a30204229084852923463c456d8a0f53327e794ae87ddf95b44099b5f3d1701
data/CHANGELOG.md CHANGED
@@ -5,39 +5,38 @@ 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.4.1] - 2026-09-29
8
+ ## [0.5.0] - 2026-10-02
9
9
 
10
10
  ### Added
11
11
 
12
- - Setup prints each step's elapsed time and the total (063521c)
13
- - bin/setup opens with a do-not-edit notice, and `discharger:install --setup-only` regenerates only the script (71ee122)
12
+ - discharger:setup:check fails when bin/setup is missing or differs from the installed gem's template, naming the regenerate command and the first differing line (512544a)
14
13
 
15
14
  ### Changed
16
15
 
17
- - Gem author listed as Savannah Albanez (a48c8d8)
16
+ - Setup creates missing config/**/*.example and .env.example counterparts before pre_steps and Rails, so apps no longer copy them in pre_steps (e7a8ce9)
17
+ - rake release refuses to reset the working branch over unpushed local commits (6d87ce7)
18
+ - rake release tags the release commit on the working branch and pushes the tag; nothing merges into a production branch (af4484d)
19
+ - working_branch defaults to main (af4484d)
20
+ - rake release checks the tree, unpushed commits, the release commit and the tag before asking to confirm (af4484d)
21
+ - rake release stops with a clear message when the version's tag already exists on origin (af4484d)
18
22
 
19
23
  ### Removed
20
24
 
21
- - Stale Qualify setup script from the repo root, never shipped in the gem (8744c43)
25
+ - staging_branch, production_branch, auto_deploy_staging and description settings; their writers warn and do nothing until 0.6 (af4484d)
26
+ - release:stage, release:build and DISCHARGER_BUILD_BRANCH (af4484d)
27
+ - the runbook preview on the stage build (af4484d)
22
28
 
23
29
  ### Fixed
24
30
 
25
- - Releases finish when the Slack token is missing or the post fails (3cf9b6d)
26
- - bin/setup bootstraps a fresh clone: GitHub Packages credentials, bundle install, then re-exec under bundle exec (7196fe4)
27
- - github_packages stores bundler credentials only after the source accepts them (41fab0f)
31
+ - bin/setup stores a gh token only when the GitHub Packages source answers 2xx (0ed1239)
32
+ - The yarn step installs the packageManager yarn with corepack install instead of corepack use, so setup no longer rewrites package.json (91967a1)
28
33
 
29
- ## [0.4.0] - 2026-09-24
34
+ ## [0.4.2] - 2026-09-30
30
35
 
31
36
  ### Added
32
37
 
33
- - pr_label option opening the finalize and version-bump PRs via gh with that label (08e1d10)
34
-
35
- ### Changed
36
-
37
- - 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)
38
- - Auto-deploy releases merge the release tag into the production branch (a7a1b49)
38
+ - database.db_name in setup.yml sets the exported DB_NAME, or false leaves it to database.yml (836e811)
39
39
 
40
40
  ### Fixed
41
41
 
42
- - retain_changelogs reaches Reissue, so finalize writes the archived changelog file (2a90edf)
43
- - release:prepare branches from origin, so a stale local branch no longer yields an empty changelog (8eb5fd3)
42
+ - The generated bin/setup stores a gh token only after the GitHub Packages source accepts it (1ef5a44)
data/README.md CHANGED
@@ -113,9 +113,7 @@ Runbook: Run \`rake data:cleanup\` after deploy"
113
113
 
114
114
  `rake release:prepare` finalizes the runbook alongside the changelog, so the release tag
115
115
  records exactly which steps that version needs. Discharger posts the checklist to Slack as
116
- a reply in the deploy thread — both on the stage build (`rake release:build` /
117
- `rake release:stage`, threaded under "Building…") and after the production release
118
- (threaded under the release announcement, right after the changelog):
116
+ a reply in the release thread, right after the changelog:
119
117
 
120
118
  ```
121
119
  *Post-release runbook for 1.2.3* — 2 steps
@@ -124,9 +122,6 @@ a reply in the deploy thread — both on the stage build (`rake release:build` /
124
122
  • Re-index search documents (def5678)
125
123
  ```
126
124
 
127
- Staging shows the steps as a preview so the team sees what production will need before it
128
- happens; the production thread repeats them when the release actually lands.
129
-
130
125
  When a release needs no follow-up, the thread says so explicitly rather than staying
131
126
  silent, so nobody has to wonder whether the check was skipped:
132
127
 
@@ -139,63 +134,49 @@ Leave `runbook_file` unset and Discharger posts nothing extra. See the
139
134
  [Reissue runbook documentation](https://github.com/SOFware/reissue#post-release-runbook)
140
135
  for how items are collected and merged.
141
136
 
142
- ## Available Tasks
137
+ ## Release Flow
143
138
 
144
- The gem creates several Rake tasks for managing your deployment workflow:
139
+ Discharger assumes one long-lived branch, `main` by default. CI deploys every push to that
140
+ branch to staging, and production deploys from `v*` tags. There is no staging branch and no
141
+ production branch to keep in sync.
145
142
 
146
143
  ```bash
147
144
  $ rake -T release
148
- rake release # ---------- STEP 3 ----------
149
- rake release:build # Release the current version to stage
145
+ rake release # ---------- STEP 2 ----------
150
146
  rake release:config # Echo the configuration settings
151
147
  rake release:prepare # ---------- STEP 1 ----------
152
148
  rake release:slack[text,channel,emoji] # Send a message to Slack
153
- rake release:stage # ---------- STEP 2 ----------
154
- ```
155
-
156
- ### Workflow Steps
157
-
158
- 1. **Prepare** (`rake release:prepare`): Create a new branch to prepare the release, update the changelog, and bump the version
159
- 2. **Stage** (`rake release:stage`): Update the staging branch and create a PR to production
160
- 3. **Release** (`rake release`): Release the current version to production by tagging and pushing to the production branch
161
-
162
- ### Building with a Working Branch
163
-
164
- To release a specific working branch to stage instead of the default branch, use the `DISCHARGER_BUILD_BRANCH` environment variable:
165
-
166
- ```bash
167
- DISCHARGER_BUILD_BRANCH=your-feature-branch rake build
168
149
  ```
169
150
 
170
- This will deploy your working branch to the staging environment.
151
+ 1. **Prepare** (`rake release:prepare`): Cut a finish branch from `main`, finalize the
152
+ changelog and runbook, and open a PR back to `main`. Merging that PR puts the finalized
153
+ release on staging.
154
+ 2. **Release** (`rake release`): Tag the release commit on `main`, push the tag, announce
155
+ the release in Slack, then open the PR that bumps `main` to the next version.
171
156
 
172
- #### Configuring Branch Names
157
+ `rake release` tags the newest commit that changed the current version's dated changelog
158
+ section, so work merged after finalizing (or re-finalizing for a hotfix) is left out.
159
+ Production ships the tagged commit, which can be older than what staging last ran.
173
160
 
174
- You can configure the branch names in your Rakefile when setting up the discharger task:
161
+ ### Configuring the Branch
175
162
 
176
163
  ```ruby
177
- require "discharger"
178
-
179
- Discharger::Task.new do |task|
164
+ Discharger::Task.create do |task|
180
165
  task.app_name = "MyApp"
181
- task.working_branch = ENV.fetch("WORKING_BRANCH", "develop")
182
- task.staging_branch = ENV.fetch("STAGING_BRANCH", "stage")
183
- task.production_branch = ENV.fetch("PRODUCTION_BRANCH", "main")
166
+ task.working_branch = ENV.fetch("WORKING_BRANCH", "main")
184
167
  # ... other configuration
185
168
  end
186
169
  ```
187
170
 
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
-
190
- ### Auto-deploy Staging
171
+ ### Upgrading to 0.5.0
191
172
 
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`:
173
+ Before bumping the gem, CI must deploy every push to the working branch to staging and
174
+ every `v*` tag to production. Then:
193
175
 
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.
176
+ - Remove `staging_branch`, `production_branch`, `auto_deploy_staging` and `description`
177
+ from the Rakefile. In 0.5.0 they warn and do nothing; 0.6 removes them.
178
+ - `working_branch` now defaults to `main`. Set it if the app releases from another branch.
179
+ - `release:stage` and `release:build` are gone; nothing replaces them.
199
180
 
200
181
  ## Development Setup Automation
201
182
 
@@ -209,18 +190,19 @@ After installing Discharger, run the setup script to configure your development
209
190
  $ bin/setup
210
191
  ```
211
192
 
212
- This script is idempotent - you can run it multiple times safely, and it will ensure your environment is properly configured.
193
+ You can rerun it whenever your environment drifts. Every run drops and rebuilds the development and test databases, so local data is lost.
213
194
 
214
195
  It works on a fresh clone with no gems installed. The generated script first runs a
215
196
  standard-library-only pass that stores bundler credentials for a configured
216
- `github_packages` source (from your GitHub CLI login, when its token has the
217
- `read:packages` scope), installs the bundle, and then
197
+ `github_packages` source (from your GitHub CLI login, only once the source
198
+ accepts that token), installs the bundle, and then
218
199
  re-execs itself under `bundle exec` so default gems such as psych never clash with
219
200
  `Gemfile.lock`. The `DISCHARGER_SETUP_BUNDLED` environment variable marks the second
220
201
  pass; the `github_packages` step later verifies the stored credentials and warns when
221
- the token lacks the `read:packages` scope. Rails still has to boot before the steps
222
- run, so an app whose boot needs `.env` or `config/database.yml` copies them in
223
- `pre_steps` (see below).
202
+ the token lacks the `read:packages` scope. Before `pre_steps` or Rails run, setup
203
+ creates every missing counterpart of `config/**/*.example` and `.env.example`, so an
204
+ app whose boot needs `.env` or `config/database.yml` gets them without copying
205
+ them in `pre_steps`.
224
206
 
225
207
  Each step prints its elapsed time when it finishes, and the closing line reports the
226
208
  total, so a slow setup shows which step to look at.
@@ -238,6 +220,16 @@ release that changed the template, regenerate the script without touching
238
220
  $ bin/rails generate discharger:install --setup-only --force
239
221
  ```
240
222
 
223
+ To catch drift in CI, run the check. It exits non-zero when `bin/setup` is missing
224
+ or differs from the installed gem's template, and names the command above:
225
+
226
+ ```bash
227
+ $ bin/rails discharger:setup:check
228
+ ```
229
+
230
+ A script generated elsewhere takes its path as the task argument:
231
+ `discharger:setup:check[scripts/setup]`.
232
+
241
233
  ### Configuration
242
234
 
243
235
  The setup process is configured through `config/setup.yml`. Here's an example configuration:
@@ -250,6 +242,9 @@ database:
250
242
  name: "db-your-app"
251
243
  version: "14"
252
244
  password: "postgres"
245
+ # Optional. DB_NAME exported before Rails boots; defaults to the container
246
+ # name without "db-". false leaves it to database.yml.
247
+ # db_name: "your-app"
253
248
  # Optional. Controls how the docker step handles a native PostgreSQL
254
249
  # already listening on the configured port:
255
250
  # omitted/nil/false - silently skip Docker and use the native instance (legacy default)
@@ -294,14 +289,15 @@ pre_steps:
294
289
  # - postgresql_tools
295
290
  ```
296
291
 
297
- You can also define custom pre_steps with shell commands:
292
+ You can also define custom pre_steps with shell commands. `config/**/*.example` and
293
+ `.env.example` counterparts are created before `pre_steps` run, so a step can rely
294
+ on them, as Qualify's does here:
298
295
 
299
296
  ```yaml
300
297
  pre_steps:
301
298
  - homebrew
302
- - description: "Set up environment variables"
303
- command: "cp .env.example .env"
304
- condition: "!File.exist?('.env')"
299
+ - description: "Check .env against .env.example"
300
+ command: "bin/check-env"
305
301
  ```
306
302
 
307
303
  ### Using Default Steps
@@ -314,11 +310,11 @@ The `steps` array specifies which built-in setup commands to run. Available comm
314
310
  - `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)
315
311
  - `bundler` - Install Ruby gems
316
312
  - `yarn` - Install JavaScript packages
317
- - `config` - Copy configuration files
313
+ - `config` - Copy Procfile.dev to Procfile and any example config file still missing
318
314
  - `docker` - Setup Docker containers
319
315
  - `pg_tools` - Create Docker-aware `pg_dump` and `psql` wrappers for apps that use `structure.sql` or call those tools directly
320
- - `env` - Configure environment variables
321
- - `database` - Setup and migrate database
316
+ - `env` - Create .env from .env.example if still missing
317
+ - `database` - Drop and recreate the development and test databases, load the schema, migrate, and seed. Every run resets local data.
322
318
 
323
319
  ### Selecting Specific Steps
324
320
 
@@ -9,6 +9,10 @@ module Discharger
9
9
  end
10
10
  end
11
11
 
12
+ rake_tasks do
13
+ load File.expand_path("../tasks/discharger.rake", __dir__)
14
+ end
15
+
12
16
  config.after_initialize do |app|
13
17
  if Rails.env.development? && Discharger.slack_token.nil?
14
18
  warn "Your application Discharger.slack_token must be set."
@@ -12,9 +12,10 @@ require_relative "timing"
12
12
  #
13
13
  # This will automatically:
14
14
  # 1. Load bundler/setup (activates correct gem versions)
15
- # 2. Run pre_steps (before Rails loads) - env vars, homebrew, etc.
16
- # 3. Initialize Rails
17
- # 4. Run regular setup steps and custom_steps
15
+ # 2. Create missing config/**/*.example and .env.example counterparts
16
+ # 3. Run pre_steps (before Rails loads) - env vars, homebrew, etc.
17
+ # 4. Initialize Rails
18
+ # 5. Run regular setup steps and custom_steps
18
19
  #
19
20
  module Discharger
20
21
  class Setup
@@ -39,14 +40,17 @@ module Discharger
39
40
  # This must happen before we parse YAML to avoid psych version conflicts
40
41
  load_bundler
41
42
 
42
- # Phase 2: Pre-Rails setup (env vars, system dependencies)
43
+ # Phase 2: Config files Rails needs to boot
44
+ create_example_files
45
+
46
+ # Phase 3: Pre-Rails setup (env vars, system dependencies)
43
47
  # These run AFTER bundler but BEFORE Rails loads
44
48
  run_prerequisites
45
49
 
46
- # Phase 3: Load Rails (uses env vars from phase 2)
50
+ # Phase 4: Load Rails (uses env vars from phase 3)
47
51
  load_rails
48
52
 
49
- # Phase 4: Run Discharger commands (after Rails loads)
53
+ # Phase 5: Run Discharger commands (after Rails loads)
50
54
  run_setup_commands
51
55
  end
52
56
 
@@ -81,6 +85,15 @@ module Discharger
81
85
  require "bundler/setup"
82
86
  end
83
87
 
88
+ def create_example_files
89
+ require_relative "setup_runner/example_files"
90
+ created = SetupRunner::ExampleFiles.create_missing(app_root)
91
+ return if created.empty?
92
+
93
+ puts "\n== Created config files from their examples =="
94
+ created.each { |path| puts " #{path}" }
95
+ end
96
+
84
97
  def run_prerequisites
85
98
  puts "\n== Setting up prerequisites =="
86
99
  require_relative "prerequisites"
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "version"
4
+
5
+ module Discharger
6
+ class SetupCheck
7
+ TEMPLATE = File.expand_path("../generators/discharger/install/templates/setup", __dir__)
8
+ GENERATE = "bin/rails generate discharger:install --setup-only --force"
9
+
10
+ def self.run(setup_path = "bin/setup", out: $stdout)
11
+ new(setup_path).run(out: out)
12
+ end
13
+
14
+ def initialize(setup_path = "bin/setup")
15
+ @setup_path = setup_path
16
+ end
17
+
18
+ def run(out: $stdout)
19
+ unless File.exist?(@setup_path)
20
+ out.puts "#{@setup_path} not found. Generate it: #{generate_command}"
21
+ return false
22
+ end
23
+
24
+ if (line = first_differing_line)
25
+ out.puts "#{@setup_path} differs from the discharger #{VERSION} template at line #{line}. " \
26
+ "Regenerate it: #{generate_command}"
27
+ return false
28
+ end
29
+
30
+ out.puts "#{@setup_path} matches the discharger #{VERSION} template"
31
+ true
32
+ end
33
+
34
+ private
35
+
36
+ def first_differing_line
37
+ actual = File.readlines(@setup_path)
38
+ expected = File.readlines(TEMPLATE)
39
+ return if actual == expected
40
+
41
+ (actual.zip(expected).index { |a, e| a != e } || actual.size) + 1
42
+ end
43
+
44
+ def generate_command
45
+ return GENERATE if @setup_path == "bin/setup"
46
+
47
+ "#{GENERATE} --setup_path=#{@setup_path}"
48
+ end
49
+ end
50
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "base_command"
4
+ require_relative "../example_files"
4
5
  require "fileutils"
5
6
 
6
7
  module Discharger
@@ -10,15 +11,6 @@ module Discharger
10
11
  def execute
11
12
  log "Ensuring configuration files are present"
12
13
 
13
- # Copy database.yml if needed
14
- database_yml = File.join(app_root, "config/database.yml")
15
- database_yml_example = File.join(app_root, "config/database.yml.example")
16
-
17
- if !File.exist?(database_yml) && File.exist?(database_yml_example)
18
- FileUtils.cp(database_yml_example, database_yml)
19
- log "Copied config/database.yml.example to config/database.yml"
20
- end
21
-
22
14
  # Copy Procfile.dev to Procfile if needed
23
15
  procfile = File.join(app_root, "Procfile")
24
16
  procfile_dev = File.join(app_root, "Procfile.dev")
@@ -28,13 +20,9 @@ module Discharger
28
20
  log "Copied Procfile.dev to Procfile"
29
21
  end
30
22
 
31
- # Copy any other example config files
32
- Dir.glob(File.join(app_root, "config/**/*.example")).each do |example_file|
33
- config_file = example_file.sub(/\.example$/, "")
34
- unless File.exist?(config_file)
35
- FileUtils.cp(example_file, config_file)
36
- log "Copied #{example_file.sub(app_root + "/", "")} to #{config_file.sub(app_root + "/", "")}"
37
- end
23
+ # No-op after Discharger::Setup; covers a Runner used without it.
24
+ ExampleFiles.create_missing(app_root, [ExampleFiles::CONFIG]).each do |path|
25
+ log "Copied #{path}.example to #{path}"
38
26
  end
39
27
  end
40
28
 
@@ -61,8 +61,8 @@ module Discharger
61
61
  return
62
62
  end
63
63
 
64
- unless credentials_valid?(username, token)
65
- log "#{source} rejected the gh token, so bundler credentials were left alone. The token " \
64
+ unless source_accepts_token?(username, token)
65
+ log "Leaving bundler credentials for #{source} alone: it did not accept the gh token. The token " \
66
66
  "may lack the read:packages scope — run `gh auth refresh -s read:packages` and rerun setup."
67
67
  return
68
68
  end
@@ -116,10 +116,7 @@ module Discharger
116
116
  raise "bundle config set --local #{source} failed: #{stderr}" unless status.success?
117
117
  end
118
118
 
119
- # Probes the source's compact index with the stored credentials.
120
- # Only a definite 401/403 counts as invalid — network trouble must
121
- # not fail setup over an unverifiable token.
122
- def credentials_valid?(username, token)
119
+ def source_accepts_token?(username, token)
123
120
  require "net/http"
124
121
  require "uri"
125
122
  uri = URI.join("#{source.chomp("/")}/", "versions")
@@ -129,10 +126,10 @@ module Discharger
129
126
  request.basic_auth(username, token)
130
127
  http.request(request)
131
128
  end
132
- !%w[401 403].include?(response.code)
129
+ response.is_a?(Net::HTTPSuccess)
133
130
  rescue => e
134
131
  logger&.debug("Could not verify GitHub Packages credentials: #{e.message}")
135
- true
132
+ false
136
133
  end
137
134
  end
138
135
  end
@@ -14,25 +14,11 @@ module Discharger
14
14
  if system_quiet("which corepack")
15
15
  system! "corepack enable"
16
16
 
17
- package_json_path = File.join(app_root, "package.json")
18
- if File.exist?(package_json_path)
19
- begin
20
- require "json"
21
- package_json = JSON.parse(File.read(package_json_path))
22
-
23
- if package_json["packageManager"]&.start_with?("yarn@")
24
- yarn_spec = package_json["packageManager"].split("+").first
25
- log "Using #{yarn_spec} from package.json"
26
- system! "corepack use #{yarn_spec}"
27
- else
28
- system! "corepack use yarn@stable"
29
- end
30
- rescue JSON::ParserError => e
31
- log "Warning: Could not parse package.json: #{e.message}"
32
- system! "corepack use yarn@stable"
33
- end
17
+ if (yarn_spec = package_manager_yarn_spec)
18
+ log "Using #{yarn_spec} from package.json"
19
+ system! "corepack install"
34
20
  else
35
- system! "corepack use yarn@stable"
21
+ system! "corepack install -g yarn@stable"
36
22
  end
37
23
  end
38
24
 
@@ -59,6 +45,17 @@ module Discharger
59
45
  def description
60
46
  "Install JavaScript dependencies"
61
47
  end
48
+
49
+ private
50
+
51
+ def package_manager_yarn_spec
52
+ require "json"
53
+ package_manager = JSON.parse(File.read(File.join(app_root, "package.json")))["packageManager"]
54
+ package_manager.split("+").first if package_manager&.start_with?("yarn@")
55
+ rescue JSON::ParserError => e
56
+ log "Warning: Could not parse package.json: #{e.message}"
57
+ nil
58
+ end
62
59
  end
63
60
  end
64
61
  end
@@ -39,7 +39,7 @@ module Discharger
39
39
  end
40
40
 
41
41
  class DatabaseConfig
42
- attr_accessor :port, :name, :version, :password, :prefer_docker
42
+ attr_accessor :port, :name, :version, :password, :prefer_docker, :db_name
43
43
 
44
44
  def initialize
45
45
  @port = 5432
@@ -47,6 +47,7 @@ module Discharger
47
47
  @version = "14"
48
48
  @password = "postgres"
49
49
  @prefer_docker = nil
50
+ @db_name = nil
50
51
  end
51
52
 
52
53
  def from_hash(hash)
@@ -55,6 +56,7 @@ module Discharger
55
56
  @version = hash["version"] if hash["version"]
56
57
  @password = hash["password"] if hash["password"]
57
58
  @prefer_docker = hash["prefer_docker"] if hash.key?("prefer_docker")
59
+ @db_name = hash["db_name"] if hash.key?("db_name")
58
60
  end
59
61
  end
60
62
 
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module Discharger
6
+ module SetupRunner
7
+ # Creates the counterpart of each example file that is missing, so Rails
8
+ # can boot on a fresh clone. Returns the created paths relative to app_root.
9
+ module ExampleFiles
10
+ CONFIG = "config/**/*.example"
11
+ ENV_FILE = ".env.example"
12
+
13
+ def self.create_missing(app_root, patterns = [CONFIG, ENV_FILE])
14
+ patterns.flat_map { |pattern| Dir.glob(pattern, base: app_root) }.sort.filter_map do |example|
15
+ target = example.delete_suffix(".example")
16
+ next if File.exist?(File.join(app_root, target))
17
+
18
+ FileUtils.cp(File.join(app_root, example), File.join(app_root, target))
19
+ target
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -86,15 +86,17 @@ module Discharger
86
86
  end
87
87
 
88
88
  def set_db_name(db_config)
89
- if db_config["name"] && !ENV["DB_NAME"]
90
- container_name = db_config["name"].to_s
91
- db_name = container_name.sub(/^db-/, "")
89
+ return if db_config["db_name"] == false
90
+
91
+ container_name = db_config["name"].to_s
92
+ db_name = (db_config["db_name"] || container_name.sub(/^db-/, "")).to_s
93
+ return if db_name.empty?
94
+
95
+ if ENV["DB_NAME"]
96
+ warn_if_mismatch("DB_NAME", ENV["DB_NAME"], db_name)
97
+ else
92
98
  ENV["DB_NAME"] = db_name
93
99
  puts " Setting DB_NAME=#{db_name} from config/setup.yml (container: #{container_name})"
94
- elsif ENV["DB_NAME"]
95
- container_name = db_config["name"].to_s
96
- expected_db_name = container_name.sub(/^db-/, "")
97
- warn_if_mismatch("DB_NAME", ENV["DB_NAME"], expected_db_name)
98
100
  end
99
101
  end
100
102
 
@@ -33,12 +33,7 @@ module Discharger
33
33
 
34
34
  attr_accessor :name
35
35
 
36
- attr_accessor :description
37
-
38
36
  attr_accessor :working_branch
39
- attr_accessor :staging_branch
40
- attr_accessor :production_branch
41
- attr_accessor :auto_deploy_staging
42
37
 
43
38
  attr_accessor :release_message_channel
44
39
  attr_accessor :version_constant
@@ -61,15 +56,19 @@ module Discharger
61
56
  }
62
57
  )
63
58
 
59
+ REMOVED_SETTINGS = %i[staging_branch production_branch auto_deploy_staging description].freeze
60
+
61
+ REMOVED_SETTINGS.each do |setting|
62
+ define_method(:"#{setting}=") do |_value|
63
+ warn "#{setting} was removed in discharger 0.5.0 and does nothing. Delete it from the Rakefile."
64
+ end
65
+ end
66
+
64
67
  def initialize(name = :release, tasker: Rake::Task)
65
68
  @name = name
66
69
  @tasker = tasker
67
- @working_branch = "develop"
68
- @staging_branch = "stage"
69
- @production_branch = "main"
70
- @description = "Release the current version to #{staging_branch}"
70
+ @working_branch = "main"
71
71
  @clear_fragments = true
72
- @auto_deploy_staging = false
73
72
  end
74
73
  private attr_reader :tasker
75
74
 
@@ -107,10 +106,6 @@ module Discharger
107
106
  end
108
107
  if block_given?
109
108
  success = !!yield(stdout, stderr, status)
110
- # If the error reports that a rule was bypassed, consider the command successful
111
- # because we are bypassing the rule intentionally when merging the release branch
112
- # to the production branch.
113
- success = true if stderr.match?(/bypassed rule violations/i)
114
109
  abort(stderr) unless success
115
110
  end
116
111
  success
@@ -121,21 +116,6 @@ module Discharger
121
116
  true
122
117
  end
123
118
 
124
- # Abort if staging branch has different VERSION than working branch
125
- def validate_version_match!(staging, working, output: $stdout)
126
- staging_v = git_show_version(staging)
127
- working_v = git_show_version(working)
128
-
129
- return sysecho("✓ Versions match (#{working_v})".bg(:green).black, output:) if staging_v == working_v
130
-
131
- abort <<~ERROR.bg(:red).white
132
- VERSION mismatch: #{staging}=#{staging_v || "not found"}, #{working}=#{working_v || "not found"}
133
-
134
- Run: rake #{name}:stage
135
- Then retry: rake #{name}
136
- ERROR
137
- end
138
-
139
119
  # The newest commit that changed the current version's dated changelog section
140
120
  def find_release_commit!(branch, output: $stdout)
141
121
  version = Object.const_get(version_constant)
@@ -156,29 +136,6 @@ module Discharger
156
136
  sha
157
137
  end
158
138
 
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?
163
-
164
- sysecho("Could not merge #{tag} into #{production_branch}: #{stderr.strip}".bg(:yellow).black, output:)
165
- nil
166
- end
167
-
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}")
172
-
173
- sysecho("✓ Merged #{tag} into #{production_branch}".bg(:green).black, output:)
174
- end
175
-
176
- def git_show_version(branch)
177
- content, _, status = Open3.capture3("git", "show", "origin/#{branch}:#{version_file}")
178
- return nil unless status.success?
179
- content[/VERSION\s*=\s*["']([^"']+)["']/, 1]
180
- end
181
-
182
139
  def git_file_commits(branch, path)
183
140
  stdout, _, status = Open3.capture3("git", "log", "--no-merges", branch, "--format=%H", "--", path)
184
141
  return [] unless status.success?
@@ -222,6 +179,8 @@ module Discharger
222
179
  end
223
180
 
224
181
  def ensure_branch_not_ahead!(branch)
182
+ return true unless local_branch?(branch)
183
+
225
184
  stdout, _, status = Open3.capture3("git", "rev-list", "--count", "origin/#{branch}..#{branch}")
226
185
  return true if status.success? && stdout.strip == "0"
227
186
 
@@ -232,6 +191,44 @@ module Discharger
232
191
  ERROR
233
192
  end
234
193
 
194
+ def ensure_tag_absent!(tag)
195
+ stdout, stderr, status = Open3.capture3("git", "ls-remote", "--tags", "origin", "refs/tags/#{tag}")
196
+ abort "Could not list tags on origin: #{stderr}" unless status.success?
197
+ return true if stdout.strip.empty?
198
+
199
+ abort <<~ERROR.bg(:red).white
200
+ Tag #{tag} already exists on origin.
201
+
202
+ Bump the version, or delete the tag if it was pushed by mistake, before releasing.
203
+ ERROR
204
+ end
205
+
206
+ def local_branch?(branch)
207
+ _, _, status = Open3.capture3("git", "rev-parse", "--verify", "--quiet", "refs/heads/#{branch}")
208
+ status.success?
209
+ end
210
+
211
+ def fetch_working_branch!
212
+ syscall(["git fetch origin #{working_branch}"])
213
+ end
214
+
215
+ def checkout_working_branch!
216
+ syscall(
217
+ ["git checkout #{working_branch}"],
218
+ ["git reset --hard origin/#{working_branch}"]
219
+ )
220
+ end
221
+
222
+ def confirm_or_exit!
223
+ sysecho "Are you ready to continue? (Press Enter to continue, Type 'x' and Enter to exit)".bg(:yellow).black
224
+ exit if $stdin.gets.chomp.match?(/^x/i)
225
+ end
226
+
227
+ def post_to_slack(text, emoji = nil, thread_ts = nil)
228
+ tasker["#{name}:slack"].reenable
229
+ tasker["#{name}:slack"].invoke(text, release_message_channel, emoji, thread_ts)
230
+ end
231
+
235
232
  # The post-release steps collected from "Runbook:" commit trailers.
236
233
  # Empty when no runbook is configured or the file has yet to be written.
237
234
  def runbook_items
@@ -254,15 +251,13 @@ module Discharger
254
251
  end
255
252
 
256
253
  # Post the runbook as a reply in a Slack thread, when a runbook is
257
- # configured. Reuses the same message on the stage build and the
258
- # production release. Returns the message posted, or nil when there is
259
- # no thread to reply to or no runbook to announce.
254
+ # configured. Returns the message posted, or nil when there is no thread
255
+ # to reply to or no runbook to announce.
260
256
  def post_runbook_to_thread(version, thread_ts)
261
257
  return unless thread_ts.present?
262
258
  return unless (message = runbook_announcement(version))
263
259
 
264
- tasker["#{name}:slack"].reenable
265
- tasker["#{name}:slack"].invoke(message, release_message_channel, ":clipboard:", thread_ts)
260
+ post_to_slack(message, ":clipboard:", thread_ts)
266
261
  message
267
262
  end
268
263
 
@@ -284,16 +279,6 @@ module Discharger
284
279
  pr.empty? ? nil : pr
285
280
  end
286
281
 
287
- def pr_already_merged?(pr_ref)
288
- stdout, _, status = Open3.capture3(
289
- "gh", "pr", "view", pr_ref.to_s,
290
- "--json", "state",
291
- "--jq", ".state"
292
- )
293
- return false unless status.success?
294
- stdout.strip == "MERGED"
295
- end
296
-
297
282
  def define
298
283
  require "slack-ruby-client"
299
284
  Slack.configure do |config|
@@ -301,11 +286,11 @@ module Discharger
301
286
  end
302
287
 
303
288
  desc <<~DESC
304
- ---------- STEP 3 ----------
289
+ ---------- STEP 2 ----------
305
290
  Release the current version to production
306
291
 
307
- This task merges the release branch into production via a GitHub pull
308
- request and tags the current version.
292
+ This task tags the release commit on #{working_branch} and pushes the
293
+ tag. Production deploys from the tag.
309
294
 
310
295
  After the release is complete, a new branch will be created to bump the
311
296
  version for the next release.
@@ -315,82 +300,38 @@ module Discharger
315
300
  abort "Error: GitHub CLI (gh) is required for the release process but was not found. Install it: https://cli.github.com"
316
301
  end
317
302
  validate_pr_label!
303
+ ensure_clean_worktree!
318
304
 
319
305
  current_version = Object.const_get(version_constant)
306
+ tag = "v#{current_version}"
320
307
 
321
- # When auto_deploy_staging is enabled, release directly from working_branch
322
- # instead of staging_branch (for CI/CD pipelines that auto-deploy staging)
323
- release_source = auto_deploy_staging ? working_branch : staging_branch
324
-
325
- release_action = if auto_deploy_staging
326
- "This will tag the release commit on #{working_branch}, push the tag, and merge it into #{production_branch}."
327
- else
328
- "This will tag the current version and push it to the production branch."
329
- end
308
+ fetch_working_branch!
309
+ ensure_branch_not_ahead!(working_branch)
310
+ ensure_tag_absent!(tag)
311
+ tag_ref = find_release_commit!("origin/#{working_branch}")
330
312
 
331
313
  sysecho <<~MSG
332
314
  Releasing version #{current_version} to production.
333
315
 
334
- #{release_action}
335
- Release source: #{release_source}
316
+ This will tag #{tag_ref[0, 8]} on #{working_branch} as #{tag} and push the tag.
336
317
  MSG
337
- sysecho "Are you ready to continue? (Press Enter to continue, Type 'x' and Enter to exit)".bg(:yellow).black
338
- input = $stdin.gets
339
- exit if input.chomp.match?(/^x/i)
318
+ confirm_or_exit!
319
+
320
+ checkout_working_branch!
340
321
 
341
322
  syscall(
342
- ["git checkout #{working_branch}"],
343
- ["git branch -D #{staging_branch} 2>/dev/null || true"],
344
- ["git branch -D #{production_branch} 2>/dev/null || true"]
323
+ ["git tag -a #{tag} -m 'Release #{current_version}' #{tag_ref}"],
324
+ ["git push origin #{tag}"]
345
325
  )
346
326
 
347
- if auto_deploy_staging
348
- syscall(
349
- ["git fetch origin #{working_branch}"],
350
- ["git reset --hard origin/#{working_branch}"]
351
- )
352
- tag_ref = find_release_commit!(release_source)
353
- else
354
- syscall(
355
- ["git fetch origin #{release_source}:#{release_source} #{production_branch}:#{production_branch}"]
356
- )
357
- validate_version_match!(staging_branch, working_branch)
358
-
359
- pr_ref = release_source
360
- if pr_already_merged?(pr_ref)
361
- sysecho "PR #{pr_ref} is already merged. Continuing..."
362
- else
363
- syscall(
364
- ["gh pr merge #{pr_ref} --merge"]
365
- )
366
- end
367
-
368
- syscall(["git fetch origin #{production_branch}:#{production_branch}"])
369
- tag_ref = production_branch
370
- end
371
-
372
- slack_sha = auto_deploy_staging ? tag_ref[0, 8] : commit_identifier.call
373
- continue = syscall(
374
- ["git tag -a v#{current_version} -m 'Release #{current_version}' #{tag_ref}"],
375
- ["git push origin v#{current_version}"]
376
- ) do
377
- tasker["#{name}:slack"].invoke("Released #{app_name} #{current_version} (#{slack_sha}) to production.", release_message_channel, ":chipmunk:")
378
- # Capture the root before replying; each post overwrites last_message_ts.
379
- thread_ts = last_message_ts
380
- if thread_ts.present?
381
- text = git_show_at_commit(tag_ref, changelog_file) || File.read(Rails.root.join(changelog_file))
382
- tasker["#{name}:slack"].reenable
383
- tasker["#{name}:slack"].invoke(text, release_message_channel, ":log:", thread_ts)
384
-
385
- post_runbook_to_thread(current_version, thread_ts)
386
- end
387
- # Signal success — no branch switch needed since we stay on working_branch throughout
388
- true
327
+ post_to_slack("Released #{app_name} #{current_version} (#{tag_ref[0, 8]}) to production.", ":chipmunk:")
328
+ # Capture the root before replying; each post overwrites last_message_ts.
329
+ if (thread_ts = last_message_ts).present?
330
+ changelog = git_show_at_commit(tag_ref, changelog_file) || File.read(Rails.root.join(changelog_file))
331
+ post_to_slack(changelog, ":log:", thread_ts)
332
+ post_runbook_to_thread(current_version, thread_ts)
389
333
  end
390
334
 
391
- abort "Release failed." unless continue
392
- merge_tag_into_production_branch("v#{current_version}") if auto_deploy_staging
393
-
394
335
  sysecho <<~MSG
395
336
  Version #{current_version} released to production.
396
337
 
@@ -437,30 +378,6 @@ module Discharger
437
378
  sysecho "----------------------------------".bg(:green).black
438
379
  end
439
380
 
440
- desc description
441
- task build: :environment do
442
- if auto_deploy_staging
443
- sysecho "Note: auto_deploy_staging is enabled. Staging deploys automatically from #{working_branch}.".bg(:yellow).black
444
- end
445
-
446
- # Allow overriding the working branch via environment variable
447
- build_branch = ENV["DISCHARGER_BUILD_BRANCH"] || working_branch
448
-
449
- syscall(
450
- ["git fetch origin #{build_branch}"],
451
- ["git checkout #{build_branch}"],
452
- ["git reset --hard origin/#{build_branch}"],
453
- ["git branch -D #{staging_branch} 2>/dev/null || true"],
454
- ["git checkout -b #{staging_branch}"],
455
- ["git push origin #{staging_branch} --force"]
456
- ) do
457
- current_version = Object.const_get(version_constant)
458
- tasker["#{name}:slack"].invoke("Building #{app_name} #{current_version} (#{commit_identifier.call}) on #{staging_branch}.", release_message_channel)
459
- post_runbook_to_thread(current_version, last_message_ts)
460
- syscall ["git checkout #{build_branch}"]
461
- end
462
- end
463
-
464
381
  desc "Send a message to Slack."
465
382
  task :slack, [:text, :channel, :emoji, :ts] => :environment do |_, args|
466
383
  instance_variable_set(:@last_message_ts, nil)
@@ -496,7 +413,7 @@ module Discharger
496
413
 
497
414
  desc <<~DESC
498
415
  ---------- STEP 1 ----------
499
- Prepare the current version for release to production (#{production_branch})
416
+ Prepare the current version for release to production
500
417
 
501
418
  This task will create a new branch to prepare the release. The CHANGELOG
502
419
  will be updated and the version will be bumped. The branch will be pushed
@@ -512,15 +429,10 @@ module Discharger
512
429
  current_version = Object.const_get(version_constant)
513
430
  finish_branch = "bump/finish-#{current_version.tr(".", "-")}"
514
431
 
515
- syscall(
516
- ["git fetch origin #{working_branch}"],
517
- ["git checkout #{working_branch}"]
518
- )
432
+ fetch_working_branch!
519
433
  ensure_branch_not_ahead!(working_branch)
520
- syscall(
521
- ["git reset --hard origin/#{working_branch}"],
522
- ["git checkout -b #{finish_branch}"]
523
- )
434
+ checkout_working_branch!
435
+ syscall(["git checkout -b #{finish_branch}"])
524
436
  sysecho <<~MSG
525
437
  Branch #{finish_branch} created.
526
438
 
@@ -529,18 +441,14 @@ module Discharger
529
441
  If you need to make changes, edit the CHANGELOG and save the file.
530
442
  Then return here to continue with this commit.
531
443
  MSG
532
- sysecho "Are you ready to continue? (Press Enter to continue, Type 'x' and Enter to exit)".bg(:yellow).black
533
- input = $stdin.gets
534
- exit if input.chomp.match?(/^x/i)
444
+ confirm_or_exit!
535
445
 
536
446
  tasker["reissue:finalize"].invoke
537
447
 
538
- next_step = auto_deploy_staging ? "rake #{name}" : "rake #{name}:stage"
539
- next_step_desc = auto_deploy_staging ? "release to production" : "stage the release branch"
540
448
  after_merge = <<~MSG.chomp
541
449
  Once the PR is merged, pull down #{working_branch} and run
542
- '#{next_step}'
543
- to #{next_step_desc}.
450
+ 'rake #{name}'
451
+ to release to production.
544
452
  MSG
545
453
 
546
454
  if pr_label
@@ -583,50 +491,6 @@ module Discharger
583
491
  end
584
492
  end
585
493
  end
586
-
587
- desc <<~DESC
588
- ---------- STEP 2 ----------
589
- Stage the release branch
590
-
591
- This task will update Stage, open a PR, and instruct you on the next steps.
592
-
593
- NOTE: If you just want to update the stage environment but aren't ready to release, run:
594
-
595
- bin/rails #{name}:build
596
- DESC
597
- task stage: [:environment] do
598
- if auto_deploy_staging
599
- sysecho <<~MSG.bg(:yellow).black
600
- Note: auto_deploy_staging is enabled.
601
- Staging is handled automatically when code is pushed to #{working_branch}.
602
- To release to production, run: 'rake #{name}'
603
- MSG
604
- next
605
- end
606
-
607
- tasker["build"].invoke
608
- current_version = Object.const_get(version_constant)
609
-
610
- params = {
611
- expand: 1,
612
- title: "Stage to Main",
613
- body: <<~BODY
614
- Deploy #{current_version} to production.
615
- BODY
616
- }
617
-
618
- pr_url = "#{pull_request_url}/compare/#{production_branch}...#{staging_branch}?#{params.to_query}"
619
-
620
- sysecho <<~MSG
621
- Branch #{staging_branch} updated.
622
- Open a PR to #{production_branch} to release the version.
623
-
624
- Opening PR: #{pr_url}
625
-
626
- Once the PR is **approved**, run 'rake release' to release the version.
627
- MSG
628
- syscall ["open", pr_url]
629
- end
630
494
  end
631
495
  end
632
496
  end
@@ -1,3 +1,3 @@
1
1
  module Discharger
2
- VERSION = "0.4.1"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -22,18 +22,31 @@ unless ENV["DISCHARGER_SETUP_BUNDLED"]
22
22
  nil # reported by Discharger::Setup on the second pass
23
23
  end
24
24
  return if source.to_s.empty?
25
- unless `gh auth status 2>&1`.include?("read:packages")
26
- warn "Skipping #{source} credentials: the gh token lacks read:packages (gh auth refresh -s read:packages)"
27
- return
28
- end
29
25
 
30
26
  username = `gh api user --jq .login 2>/dev/null`.strip
31
27
  token = `gh auth token 2>/dev/null`.strip
32
28
  return if username.empty? || token.empty?
29
+ unless source_accepts_token?(source, username, token)
30
+ warn "Leaving bundler credentials for #{source} alone: it did not accept the gh token (gh auth refresh -s read:packages)"
31
+ return
32
+ end
33
33
 
34
34
  system("bundle", "config", "set", "--local", source, "#{username}:#{token}", out: File::NULL, err: File::NULL)
35
35
  end
36
36
 
37
+ def source_accepts_token?(source, username, token)
38
+ require "net/http"
39
+ uri = URI.join("#{source.chomp("/")}/", "versions")
40
+ response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https", open_timeout: 5, read_timeout: 10) do |http|
41
+ request = Net::HTTP::Get.new(uri)
42
+ request.basic_auth(username, token)
43
+ http.request(request)
44
+ end
45
+ response.is_a?(Net::HTTPSuccess)
46
+ rescue
47
+ false
48
+ end
49
+
37
50
  unless system("bundle", "check", out: File::NULL, err: File::NULL)
38
51
  store_github_packages_credentials
39
52
  system("bundle", "install") || abort(<<~MSG)
@@ -0,0 +1,9 @@
1
+ namespace :discharger do
2
+ namespace :setup do
3
+ desc "Fail when bin/setup differs from the installed discharger template"
4
+ task :check, [:setup_path] do |_task, args|
5
+ require "discharger/setup_check"
6
+ abort unless Discharger::SetupCheck.run(args[:setup_path] || "bin/setup")
7
+ end
8
+ end
9
+ end
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.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jim Gay
@@ -109,6 +109,7 @@ files:
109
109
  - lib/discharger/prerequisites.rb
110
110
  - lib/discharger/railtie.rb
111
111
  - lib/discharger/setup.rb
112
+ - lib/discharger/setup_check.rb
112
113
  - lib/discharger/setup_runner.rb
113
114
  - lib/discharger/setup_runner/command_factory.rb
114
115
  - lib/discharger/setup_runner/command_registry.rb
@@ -127,6 +128,7 @@ files:
127
128
  - lib/discharger/setup_runner/commands/yarn_command.rb
128
129
  - lib/discharger/setup_runner/condition_evaluator.rb
129
130
  - lib/discharger/setup_runner/configuration.rb
131
+ - lib/discharger/setup_runner/example_files.rb
130
132
  - lib/discharger/setup_runner/pre_commands/base_pre_command.rb
131
133
  - lib/discharger/setup_runner/pre_commands/homebrew_pre_command.rb
132
134
  - lib/discharger/setup_runner/pre_commands/postgresql_tools_pre_command.rb
@@ -141,6 +143,7 @@ files:
141
143
  - lib/generators/discharger/install/templates/discharger_initializer.rb
142
144
  - lib/generators/discharger/install/templates/setup
143
145
  - lib/generators/discharger/install/templates/setup.yml
146
+ - lib/tasks/discharger.rake
144
147
  homepage: https://github.com/SOFware/discharger
145
148
  licenses: []
146
149
  metadata: