discharger 0.4.0 → 0.4.2

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: 3b0eda964739589ae49d73ea992ff80d8e782b3de287ca19fcb1a82c605d1638
4
- data.tar.gz: 5d86440df15d87cb9a66b5133dc1d70d8f736bd0644a7b9f57b5a4d2a3428e89
3
+ metadata.gz: '0438319d582d725e71df1e9fdaf270f8036bc78f1f3c8c01b0bd4038d251e981'
4
+ data.tar.gz: 862ac9091b25ac1a179768f251db61a8aa2b9bcd9b6b1be6848aeb41e8687d66
5
5
  SHA512:
6
- metadata.gz: 82c6912aee9e99edeb92304f26a0443983495b4aa8b3152cea7497da0da895ad892b8d392bdd25d7a1d559176c6a412589fd9b2dab0bc8fae9fb1c28019602ad
7
- data.tar.gz: 48a5949f5177ace0de43d0788ce3f45a6dd24b558478704014b5234c6625e23737c209f831550ed224c1ca6b6869f08d0fdb8c30515f1af6efebafeb129c80db
6
+ metadata.gz: c237780b6041dd817e2d3c8e48ee0eebd98e8af7103c91c52d4725c0436c37c3312b7acdb60c70fcd86b660a18086cebd4ff24746921d6eb75a010f896e307b3
7
+ data.tar.gz: 89dd92c5c832f9cee93ae0866711ad0b8989c0c6eade619e14eccff38db6e9740bdad00a97779809b5695c4f5d55763e504422858f8f0f5c7bc1d0279712448d
data/CHANGELOG.md CHANGED
@@ -5,24 +5,33 @@ 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.0] - 2026-09-24
8
+ ## [0.4.2] - 2026-09-30
9
9
 
10
10
  ### Added
11
11
 
12
- - pr_label option opening the finalize and version-bump PRs via gh with that label (08e1d10)
13
-
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)
12
+ - database.db_name in setup.yml sets the exported DB_NAME, or false leaves it to database.yml (836e811)
18
13
 
19
14
  ### Fixed
20
15
 
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)
16
+ - The generated bin/setup stores a gh token only after the GitHub Packages source accepts it (1ef5a44)
23
17
 
24
- ## [0.3.5] - 2026-08-10
18
+ ## [0.4.1] - 2026-09-29
25
19
 
26
20
  ### Added
27
21
 
28
- - github_packages setup step storing bundler credentials for a private GitHub Packages gem source via the gh CLI (2cff4b6)
22
+ - Setup prints each step's elapsed time and the total (063521c)
23
+ - bin/setup opens with a do-not-edit notice, and `discharger:install --setup-only` regenerates only the script (71ee122)
24
+
25
+ ### Changed
26
+
27
+ - Gem author listed as Savannah Albanez (a48c8d8)
28
+
29
+ ### Removed
30
+
31
+ - Stale Qualify setup script from the repo root, never shipped in the gem (8744c43)
32
+
33
+ ### Fixed
34
+
35
+ - Releases finish when the Slack token is missing or the post fails (3cf9b6d)
36
+ - bin/setup bootstraps a fresh clone: GitHub Packages credentials, bundle install, then re-exec under bundle exec (7196fe4)
37
+ - github_packages stores bundler credentials only after the source accepts them (41fab0f)
data/README.md CHANGED
@@ -209,7 +209,34 @@ After installing Discharger, run the setup script to configure your development
209
209
  $ bin/setup
210
210
  ```
211
211
 
212
- This script is idempotent - you can run it multiple times safely, and it will ensure your environment is properly configured.
212
+ You can rerun it whenever your environment drifts. Every run drops and rebuilds the development and test databases, so local data is lost.
213
+
214
+ It works on a fresh clone with no gems installed. The generated script first runs a
215
+ standard-library-only pass that stores bundler credentials for a configured
216
+ `github_packages` source (from your GitHub CLI login, only once the source
217
+ accepts that token), installs the bundle, and then
218
+ re-execs itself under `bundle exec` so default gems such as psych never clash with
219
+ `Gemfile.lock`. The `DISCHARGER_SETUP_BUNDLED` environment variable marks the second
220
+ 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).
224
+
225
+ Each step prints its elapsed time when it finishes, and the closing line reports the
226
+ total, so a slow setup shows which step to look at.
227
+
228
+ ### Keeping bin/setup Generated
229
+
230
+ `bin/setup` is a verbatim copy of the gem's template and opens with a notice saying
231
+ so. Do not hand-edit it in an app: app-specific work belongs in `config/setup.yml`
232
+ (`pre_steps`, `steps`, `custom_steps`), and changes to the script itself belong in
233
+ this gem's template so every app picks them up. After bumping to a discharger
234
+ release that changed the template, regenerate the script without touching
235
+ `config/setup.yml` or the initializer:
236
+
237
+ ```bash
238
+ $ bin/rails generate discharger:install --setup-only --force
239
+ ```
213
240
 
214
241
  ### Configuration
215
242
 
@@ -223,6 +250,9 @@ database:
223
250
  name: "db-your-app"
224
251
  version: "14"
225
252
  password: "postgres"
253
+ # Optional. DB_NAME exported before Rails boots; defaults to the container
254
+ # name without "db-". false leaves it to database.yml.
255
+ # db_name: "your-app"
226
256
  # Optional. Controls how the docker step handles a native PostgreSQL
227
257
  # already listening on the configured port:
228
258
  # omitted/nil/false - silently skip Docker and use the native instance (legacy default)
@@ -291,7 +321,7 @@ The `steps` array specifies which built-in setup commands to run. Available comm
291
321
  - `docker` - Setup Docker containers
292
322
  - `pg_tools` - Create Docker-aware `pg_dump` and `psql` wrappers for apps that use `structure.sql` or call those tools directly
293
323
  - `env` - Configure environment variables
294
- - `database` - Setup and migrate database
324
+ - `database` - Drop and recreate the development and test databases, load the schema, migrate, and seed. Every run resets local data.
295
325
 
296
326
  ### Selecting Specific Steps
297
327
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "fileutils"
4
- require "pathname"
4
+ require_relative "timing"
5
5
 
6
6
  # Unified entry point for Discharger setup.
7
7
  # Handles both pre-Rails prerequisites and post-Rails setup commands.
@@ -34,21 +34,23 @@ module Discharger
34
34
  validate_environment
35
35
  print_header
36
36
 
37
- # Phase 1: Load bundler first (activates correct gem versions)
38
- # This must happen before we parse YAML to avoid psych version conflicts
39
- load_bundler
37
+ elapsed = Timing.measure do
38
+ # Phase 1: Load bundler first (activates correct gem versions)
39
+ # This must happen before we parse YAML to avoid psych version conflicts
40
+ load_bundler
40
41
 
41
- # Phase 2: Pre-Rails setup (env vars, system dependencies)
42
- # These run AFTER bundler but BEFORE Rails loads
43
- run_prerequisites
42
+ # Phase 2: Pre-Rails setup (env vars, system dependencies)
43
+ # These run AFTER bundler but BEFORE Rails loads
44
+ run_prerequisites
44
45
 
45
- # Phase 3: Load Rails (uses env vars from phase 2)
46
- load_rails
46
+ # Phase 3: Load Rails (uses env vars from phase 2)
47
+ load_rails
47
48
 
48
- # Phase 4: Run Discharger commands (after Rails loads)
49
- run_setup_commands
49
+ # Phase 4: Run Discharger commands (after Rails loads)
50
+ run_setup_commands
51
+ end
50
52
 
51
- print_footer
53
+ print_footer(elapsed)
52
54
  end
53
55
  end
54
56
 
@@ -71,8 +73,8 @@ module Discharger
71
73
  puts "Configuration loaded from: #{config_path}"
72
74
  end
73
75
 
74
- def print_footer
75
- puts "\n== Setup completed successfully! =="
76
+ def print_footer(elapsed)
77
+ puts format("\n== Setup completed successfully! (%.2fs) ==", elapsed)
76
78
  end
77
79
 
78
80
  def load_bundler
@@ -89,8 +91,11 @@ module Discharger
89
91
  # Load Rails from the standard location
90
92
  rails_config = File.join(app_root, "config", "application.rb")
91
93
  if File.exist?(rails_config)
92
- require rails_config
93
- Rails.application.initialize!
94
+ elapsed = Timing.measure do
95
+ require rails_config
96
+ Rails.application.initialize!
97
+ end
98
+ puts format("\n== Rails loaded (%.2fs) ==", elapsed)
94
99
  else
95
100
  puts "Warning: config/application.rb not found. Skipping Rails initialization."
96
101
  end
@@ -6,9 +6,11 @@ module Discharger
6
6
  module SetupRunner
7
7
  module Commands
8
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.
9
+ # using the GitHub CLI, so Gemfiles don't need embedded tokens. A token
10
+ # the source rejects is not stored, so it cannot shadow a working
11
+ # credential in the user's own bundler config. Credentials are written
12
+ # to the app's .bundle/config — keep .bundle gitignored. Run this step
13
+ # before "bundler" in setup.yml.
12
14
  class GithubPackagesCommand < BaseCommand
13
15
  # A configured source with nothing scheduled to store its credentials
14
16
  # is inert, and the only symptom is bundler failing against the
@@ -59,13 +61,14 @@ module Discharger
59
61
  return
60
62
  end
61
63
 
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."
64
+ unless credentials_valid?(username, token)
65
+ log "#{source} rejected the gh token, so bundler credentials were left alone. The token " \
66
+ "may lack the read:packages scope — run `gh auth refresh -s read:packages` and rerun setup."
67
+ return
68
68
  end
69
+
70
+ store_bundler_credentials(username, token)
71
+ log "Configured bundler credentials for #{source}"
69
72
  end
70
73
 
71
74
  def can_execute?
@@ -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
 
@@ -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
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "fileutils"
4
4
  require "logger"
5
+ require_relative "../timing"
5
6
  require_relative "command_factory"
6
7
 
7
8
  module Discharger
@@ -27,12 +28,14 @@ module Discharger
27
28
 
28
29
  warn_unscheduled_commands
29
30
 
30
- FileUtils.chdir app_root do
31
- execute_commands
31
+ elapsed = Timing.measure do
32
+ FileUtils.chdir app_root do
33
+ execute_commands
34
+ end
32
35
  end
33
36
 
34
37
  unless ENV["QUIET_SETUP"] || ENV["DISABLE_OUTPUT"]
35
- puts Rainbow("\n✅ Setup completed successfully!").bright.green
38
+ puts Rainbow(format("\n✅ Setup completed successfully! (%.2fs)", elapsed)).bright.green
36
39
  end
37
40
  rescue => e
38
41
  unless ENV["QUIET_SETUP"] || ENV["DISABLE_OUTPUT"]
@@ -108,7 +111,10 @@ module Discharger
108
111
  unless ENV["QUIET_SETUP"] || ENV["DISABLE_OUTPUT"]
109
112
  puts Rainbow("\n▶️ #{command.description}").bright
110
113
  end
111
- command.execute
114
+ elapsed = Timing.measure { command.execute }
115
+ unless ENV["QUIET_SETUP"] || ENV["DISABLE_OUTPUT"]
116
+ puts Rainbow(format(" %s: %.2fs", command.description, elapsed)).faint
117
+ end
112
118
  rescue => e
113
119
  unless ENV["QUIET_SETUP"] || ENV["DISABLE_OUTPUT"]
114
120
  require "rainbow"
@@ -266,6 +266,10 @@ module Discharger
266
266
  message
267
267
  end
268
268
 
269
+ def echo_unsent_slack_message(text, channel)
270
+ sysecho "Post this to #{channel} yourself:\n\n#{text}\n"
271
+ end
272
+
269
273
  def existing_pr_number(base, head)
270
274
  stdout, _, status = Open3.capture3(
271
275
  "gh", "pr", "list",
@@ -459,17 +463,33 @@ module Discharger
459
463
 
460
464
  desc "Send a message to Slack."
461
465
  task :slack, [:text, :channel, :emoji, :ts] => :environment do |_, args|
466
+ instance_variable_set(:@last_message_ts, nil)
462
467
  args.with_defaults(
463
468
  channel: release_message_channel,
464
469
  emoji: nil
465
470
  )
471
+ if chat_token.blank?
472
+ sysecho <<~MSG.bg(:yellow).black
473
+ Slack message not sent: chat_token is not set.
474
+ Set it with the Slack release token from your team's password manager before the next release.
475
+ MSG
476
+ echo_unsent_slack_message(args[:text], args[:channel])
477
+ next
478
+ end
479
+
466
480
  client = Slack::Web::Client.new
467
481
  options = args.to_h
468
482
  options[:icon_emoji] = options.delete(:emoji) if options[:emoji]
469
483
  options[:thread_ts] = options.delete(:ts) if options[:ts]
470
484
 
471
485
  sysecho "Sending message to Slack:".bg(:green).black + " #{args[:text]}"
472
- result = client.chat_postMessage(**options)
486
+ begin
487
+ result = client.chat_postMessage(**options)
488
+ rescue Faraday::Error => e
489
+ sysecho "Slack message not sent: #{e.message}.".bg(:yellow).black
490
+ echo_unsent_slack_message(args[:text], args[:channel])
491
+ next
492
+ end
473
493
  instance_variable_set(:@last_message_ts, result["ts"])
474
494
  sysecho %(Message sent: #{result["ts"]})
475
495
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discharger
4
+ module Timing
5
+ # Seconds the block took, from the monotonic clock.
6
+ def self.measure
7
+ started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
8
+ yield
9
+ Process.clock_gettime(Process::CLOCK_MONOTONIC) - started
10
+ end
11
+ end
12
+ end
@@ -1,3 +1,3 @@
1
1
  module Discharger
2
- VERSION = "0.4.0"
2
+ VERSION = "0.4.2"
3
3
  end
@@ -8,7 +8,14 @@ module Discharger
8
8
  default: "bin/setup",
9
9
  desc: "Path where the setup script should be created"
10
10
 
11
+ class_option :setup_only,
12
+ type: :boolean,
13
+ default: false,
14
+ desc: "Only (re)generate the setup script, leaving config/setup.yml and the initializer alone"
15
+
11
16
  def copy_initializer
17
+ return if options[:setup_only]
18
+
12
19
  template "discharger_initializer.rb", "config/initializers/discharger.rb"
13
20
  end
14
21
 
@@ -18,6 +25,8 @@ module Discharger
18
25
  end
19
26
 
20
27
  def create_sample_setup_yml
28
+ return if options[:setup_only]
29
+
21
30
  template "setup.yml", "config/setup.yml"
22
31
  end
23
32
  end
@@ -1,30 +1,64 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # Path to the application root.
4
- APP_ROOT = File.expand_path("..", __dir__)
5
-
6
- Dir.chdir(APP_ROOT) do
7
- # This script uses Discharger to set up your development environment automatically.
8
- # All setup steps are configured in config/setup.yml
9
- # This script is idempotent, so you can run it at any time and get an expectable outcome.
10
-
11
- # Find discharger gem path from Gemfile without loading bundler
12
- # (loading bundler early can cause gem version conflicts)
13
- gemfile = File.read("Gemfile")
14
- if (match = gemfile.match(/gem\s+["']discharger["'].*path:\s*["']([^"']+)["']/))
15
- discharger_path = File.expand_path(match[1], APP_ROOT)
16
- $LOAD_PATH.unshift(File.join(discharger_path, "lib"))
17
- else
18
- # Fall back to installed gem
19
- begin
20
- gem "discharger"
21
- rescue Gem::MissingSpecError
22
- puts "Could not find discharger gem."
23
- puts "If using a custom bundle path, try: bundle exec bin/setup"
24
- exit 1
3
+ # GENERATED BY DISCHARGER. DO NOT EDIT. Edits here are lost on regeneration.
4
+ #
5
+ # App setup belongs in config/setup.yml (pre_steps, steps, custom_steps).
6
+ # Script changes belong in the discharger gem's template,
7
+ # lib/generators/discharger/install/templates/setup; release it, bump it, then
8
+ # bin/rails generate discharger:install --setup-only --force
9
+
10
+ script = File.expand_path(__FILE__)
11
+ Dir.chdir(File.expand_path("..", __dir__))
12
+
13
+ # First pass, standard library only: the bundle may not exist yet. Re-exec
14
+ # under bundle exec before yaml loads, or a default psych clashes with Gemfile.lock.
15
+ # Mirrors the github_packages and bundler steps, which re-run under bundle exec.
16
+ unless ENV["DISCHARGER_SETUP_BUNDLED"]
17
+ def store_github_packages_credentials
18
+ require "yaml"
19
+ source = begin
20
+ YAML.safe_load_file("config/setup.yml")&.dig("github_packages", "source")
21
+ rescue SystemCallError, Psych::Exception
22
+ nil # reported by Discharger::Setup on the second pass
23
+ end
24
+ return if source.to_s.empty?
25
+
26
+ username = `gh api user --jq .login 2>/dev/null`.strip
27
+ token = `gh auth token 2>/dev/null`.strip
28
+ return if username.empty? || token.empty?
29
+ unless source_confirms_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
+
34
+ system("bundle", "config", "set", "--local", source, "#{username}:#{token}", out: File::NULL, err: File::NULL)
35
+ end
36
+
37
+ def source_confirms_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)
25
44
  end
45
+ !%w[401 403].include?(response.code)
46
+ rescue
47
+ false
48
+ end
49
+
50
+ unless system("bundle", "check", out: File::NULL, err: File::NULL)
51
+ store_github_packages_credentials
52
+ system("bundle", "install") || abort(<<~MSG)
53
+
54
+ == bundle install failed ==
55
+ If the Gemfile uses a private GitHub Packages source, run `gh auth login`
56
+ (or `gh auth refresh -s read:packages`) and rerun bin/setup.
57
+ MSG
26
58
  end
27
59
 
28
- require "discharger/setup"
29
- Discharger::Setup.run("config/setup.yml")
60
+ exec({"DISCHARGER_SETUP_BUNDLED" => "1"}, "bundle", "exec", RbConfig.ruby, script, *ARGV)
30
61
  end
62
+
63
+ require "discharger/setup"
64
+ Discharger::Setup.run("config/setup.yml")
metadata CHANGED
@@ -1,11 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: discharger
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jim Gay
8
- - Savannah Moore
8
+ - Savannah Albanez
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
  date: 1980-01-02 00:00:00.000000000 Z
@@ -135,6 +135,7 @@ files:
135
135
  - lib/discharger/setup_runner/runner.rb
136
136
  - lib/discharger/setup_runner/version.rb
137
137
  - lib/discharger/task.rb
138
+ - lib/discharger/timing.rb
138
139
  - lib/discharger/version.rb
139
140
  - lib/generators/discharger/install/install_generator.rb
140
141
  - lib/generators/discharger/install/templates/discharger_initializer.rb