discharger 0.5.0 → 0.5.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: 8e3fabd68a173b372966725b98da677f18061b6377d47838e10305cb0baaa820
4
- data.tar.gz: 9c29847b75d29cc764f6cd1eb621c7afdf5d98323877a106322a481ade425c3e
3
+ metadata.gz: e92d9aa7bcc8894b1db11f44195b1fc5ac26d7995debf672867d4739b8bf3002
4
+ data.tar.gz: a813680ef1fdecbaf8ae436b47d8f11e2d2d2a306dea0f6de03fbbe085035b9f
5
5
  SHA512:
6
- metadata.gz: 06d72d2a6be99df84988f1222311a6436d01f59d74d17fd971d4df25c0057f78e5b41435861556dffeae48e8bd21238ea6dc46c47b0a90b34df09460944be19d
7
- data.tar.gz: 80711c35ce3061f8543658160fb7784d0b3ca5cdb465d36f11b375d6c961a0385a30204229084852923463c456d8a0f53327e794ae87ddf95b44099b5f3d1701
6
+ metadata.gz: 957623082c1bd56b60223cf6c940bbed32c46612daad4baaf2fd2ad542f297637ecd0aee033ce50c75ba42cefc43299a35e48d603b373b723c688a91340bf01f
7
+ data.tar.gz: b28f208e2e4057f8d795d01600d840e3634bf75f88968852278bfe624b6a570135a6dda25aa0f95a3b9fb0e489e2950d4c4dcc459639da365e30f84e0073b8bf
data/CHANGELOG.md CHANGED
@@ -5,38 +5,23 @@ 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.5.0] - 2026-10-02
8
+ ## [0.5.2] - 2026-10-05
9
9
 
10
10
  ### Added
11
11
 
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)
12
+ - github_packages.gh_timeout in setup.yml sets the gh deadline (e7d7620)
13
+ - pre_steps print elapsed time (9bdb05f)
13
14
 
14
15
  ### Changed
15
16
 
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)
17
+ - github_packages step only checks the gh token; bin/setup stores credentials (e7d7620)
22
18
 
23
19
  ### Removed
24
20
 
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)
21
+ - the unreachable seed_env option (8b7c654)
28
22
 
29
23
  ### Fixed
30
24
 
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)
25
+ - gh calls in setup time out after 15s instead of hanging (e7d7620)
33
26
 
34
- ## [0.4.2] - 2026-09-30
35
-
36
- ### Added
37
-
38
- - database.db_name in setup.yml sets the exported DB_NAME, or false leaves it to database.yml (836e811)
39
-
40
- ### Fixed
41
-
42
- - The generated bin/setup stores a gh token only after the GitHub Packages source accepts it (1ef5a44)
27
+ ## [0.5.1] - 2026-10-02
data/README.md CHANGED
@@ -195,7 +195,8 @@ You can rerun it whenever your environment drifts. Every run drops and rebuilds
195
195
  It works on a fresh clone with no gems installed. The generated script first runs a
196
196
  standard-library-only pass that stores bundler credentials for a configured
197
197
  `github_packages` source (from your GitHub CLI login, only once the source
198
- accepts that token), installs the bundle, and then
198
+ accepts that token; gh calls time out after `github_packages.gh_timeout` seconds,
199
+ 15 by default, or `DISCHARGER_GH_TIMEOUT`), installs the bundle, and then
199
200
  re-execs itself under `bundle exec` so default gems such as psych never clash with
200
201
  `Gemfile.lock`. The `DISCHARGER_SETUP_BUNDLED` environment variable marks the second
201
202
  pass; the `github_packages` step later verifies the stored credentials and warns when
@@ -204,8 +205,8 @@ creates every missing counterpart of `config/**/*.example` and `.env.example`, s
204
205
  app whose boot needs `.env` or `config/database.yml` gets them without copying
205
206
  them in `pre_steps`.
206
207
 
207
- Each step prints its elapsed time when it finishes, and the closing line reports the
208
- total, so a slow setup shows which step to look at.
208
+ Each pre_step and step prints its elapsed time when it finishes, and the closing line
209
+ reports the total, so a slow setup shows which step to look at.
209
210
 
210
211
  ### Keeping bin/setup Generated
211
212
 
@@ -307,8 +308,8 @@ The `steps` array specifies which built-in setup commands to run. Available comm
307
308
  - `brew` - Install Homebrew dependencies
308
309
  - `asdf` - Setup version management with asdf
309
310
  - `git` - Configure git settings
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)
311
- - `bundler` - Install Ruby gems
311
+ - `github_packages` - Check the GitHub CLI token against a private GitHub Packages gem source; the generated `bin/setup` stores the credentials (requires a `github_packages.source` config entry)
312
+ - `bundler` - Install Ruby gems; a check under the generated `bin/setup`, whose first pass already installed the bundle
312
313
  - `yarn` - Install JavaScript packages
313
314
  - `config` - Copy Procfile.dev to Procfile and any example config file still missing
314
315
  - `docker` - Setup Docker containers
@@ -5,6 +5,7 @@ require_relative "base_command"
5
5
  module Discharger
6
6
  module SetupRunner
7
7
  module Commands
8
+ # bin/setup's first pass mirrors this; change them together.
8
9
  class BundlerCommand < BaseCommand
9
10
  def execute
10
11
  log "Installing dependencies"
@@ -31,9 +31,8 @@ module Discharger
31
31
  end
32
32
 
33
33
  # Seed the database
34
- seed_env = db_env.merge((config.respond_to?(:seed_env) && config.seed_env) ? {"SEED_DEV_ENV" => "true"} : {})
35
34
  with_spinner("Seeding the database") do
36
- _stdout, stderr, status = Open3.capture3(seed_env, "bin/rails", "db:seed")
35
+ _stdout, stderr, status = Open3.capture3(db_env, "bin/rails", "db:seed")
37
36
  if status.success?
38
37
  {success: true}
39
38
  else
@@ -5,43 +5,10 @@ require_relative "base_command"
5
5
  module Discharger
6
6
  module SetupRunner
7
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. 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.
8
+ # Checks the gh token against the GitHub Packages source; bin/setup
9
+ # stored the credentials. The template's first pass mirrors this.
14
10
  class GithubPackagesCommand < BaseCommand
15
- # A configured source with nothing scheduled to store its credentials
16
- # is inert, and the only symptom is bundler failing against the
17
- # private source. The Runner surfaces this before executing commands.
18
- # Guarded for duck-typed configs passed to the public Runner API, and
19
- # quiet when a custom step's command references the source (assumed
20
- # to handle credentials itself).
21
- def self.unscheduled_warning(config)
22
- return unless config.respond_to?(:github_packages)
23
-
24
- source = config.github_packages&.source
25
- return if source.to_s.empty?
26
- if config.respond_to?(:custom_steps) &&
27
- config.custom_steps.any? { |step| step_handles_source?(step, source) }
28
- return
29
- end
30
-
31
- "github_packages is configured but missing from steps; " \
32
- "credentials will not be stored and bundler may fail for #{source}"
33
- end
34
-
35
- # A referencing step only counts as handling credentials when it will
36
- # actually run: CustomCommand skips itself when its condition is
37
- # false, storing nothing on this run.
38
- def self.step_handles_source?(step, source)
39
- return false unless step["command"].to_s.include?(source)
40
-
41
- require_relative "../condition_evaluator"
42
- ConditionEvaluator.evaluate(step["condition"])
43
- end
44
- private_class_method :step_handles_source?
11
+ GH_TIMEOUT_SECONDS = 15
45
12
 
46
13
  def execute
47
14
  unless gh_installed?
@@ -61,14 +28,12 @@ module Discharger
61
28
  return
62
29
  end
63
30
 
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
- "may lack the read:packages scope — run `gh auth refresh -s read:packages` and rerun setup."
67
- return
31
+ if source_accepts_token?(username, token)
32
+ log "#{source} accepts the gh token"
33
+ else
34
+ log "#{source} did not accept the gh token. It may lack the read:packages scope — " \
35
+ "run `gh auth refresh -s read:packages` and rerun setup."
68
36
  end
69
-
70
- store_bundler_credentials(username, token)
71
- log "Configured bundler credentials for #{source}"
72
37
  end
73
38
 
74
39
  def can_execute?
@@ -76,7 +41,7 @@ module Discharger
76
41
  end
77
42
 
78
43
  def description
79
- "Configure GitHub Packages credentials"
44
+ "Check GitHub Packages credentials"
80
45
  end
81
46
 
82
47
  protected
@@ -90,7 +55,7 @@ module Discharger
90
55
  end
91
56
 
92
57
  def authenticated?
93
- system_quiet("gh auth status")
58
+ !gh("auth", "status").nil?
94
59
  end
95
60
 
96
61
  def login
@@ -102,18 +67,24 @@ module Discharger
102
67
 
103
68
  def gh(*args)
104
69
  require "open3"
105
- stdout, _stderr, status = Open3.capture3("gh", *args)
106
- status.success? ? stdout.chomp : nil
70
+ Open3.popen2("gh", *args, err: File::NULL) do |stdin, stdout, wait|
71
+ stdin.close
72
+ out = Thread.new { stdout.read }
73
+ if wait.join(gh_timeout)
74
+ out.value.chomp if wait.value.success?
75
+ else
76
+ Process.kill("KILL", wait.pid)
77
+ out.kill
78
+ log "gh #{args.join(" ")} did not answer within #{gh_timeout.to_i}s."
79
+ nil
80
+ end
81
+ end
82
+ rescue Errno::ENOENT
83
+ nil
107
84
  end
108
85
 
109
- # Runs bundle config directly instead of through system! so the
110
- # token never reaches the spinner display or the debug log.
111
- def store_bundler_credentials(username, token)
112
- require "open3"
113
- _stdout, stderr, status = Open3.capture3(
114
- "bundle", "config", "set", "--local", source, "#{username}:#{token}"
115
- )
116
- raise "bundle config set --local #{source} failed: #{stderr}" unless status.success?
86
+ def gh_timeout
87
+ Float(ENV.fetch("DISCHARGER_GH_TIMEOUT") { config.github_packages&.gh_timeout || GH_TIMEOUT_SECONDS })
117
88
  end
118
89
 
119
90
  def source_accepts_token?(username, token)
@@ -61,14 +61,16 @@ module Discharger
61
61
  end
62
62
 
63
63
  class GithubPackagesConfig
64
- attr_accessor :source
64
+ attr_accessor :source, :gh_timeout
65
65
 
66
66
  def initialize
67
67
  @source = nil
68
+ @gh_timeout = nil
68
69
  end
69
70
 
70
71
  def from_hash(hash)
71
72
  @source = hash["source"] if hash["source"]
73
+ @gh_timeout = hash["gh_timeout"] if hash["gh_timeout"]
72
74
  end
73
75
  end
74
76
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "yaml"
4
+ require_relative "../timing"
4
5
  require_relative "pre_commands/pre_command_registry"
5
6
 
6
7
  module Discharger
@@ -130,8 +131,7 @@ module Discharger
130
131
  end
131
132
 
132
133
  command = command_class.new(config)
133
- puts " #{command.description}..."
134
- command.execute
134
+ timed(command.description) { command.execute }
135
135
  end
136
136
 
137
137
  def run_custom_pre_step(step)
@@ -144,8 +144,12 @@ module Discharger
144
144
  return
145
145
  end
146
146
 
147
- puts " Running: #{description}"
148
- system(command)
147
+ timed(description) { system(command) }
148
+ end
149
+
150
+ def timed(description)
151
+ elapsed = Timing.measure { yield }
152
+ puts format(" %s: %.2fs", description, elapsed)
149
153
  end
150
154
 
151
155
  def evaluate_condition(condition)
@@ -26,8 +26,6 @@ module Discharger
26
26
  puts Rainbow("=" * 50).blue
27
27
  end
28
28
 
29
- warn_unscheduled_commands
30
-
31
29
  elapsed = Timing.measure do
32
30
  FileUtils.chdir app_root do
33
31
  execute_commands
@@ -81,18 +79,6 @@ module Discharger
81
79
  @commands ||= command_factory.create_all_commands
82
80
  end
83
81
 
84
- # Checked at run time, after the customization block, so commands
85
- # scheduled through add_command/insert_command_* count as scheduled.
86
- def warn_unscheduled_commands
87
- CommandRegistry.all.each do |klass|
88
- next unless klass.respond_to?(:unscheduled_warning)
89
- next if commands.any? { |command| command.is_a?(klass) }
90
-
91
- message = klass.unscheduled_warning(config)
92
- logger.warn message if message
93
- end
94
- end
95
-
96
82
  def execute_commands
97
83
  commands.each do |command|
98
84
  execute_command(command)
@@ -152,7 +152,7 @@ module Discharger
152
152
  stdout, _, status = Open3.capture3("git", "status", "--porcelain")
153
153
  return true if status.success? && stdout.empty?
154
154
 
155
- abort "Working tree has uncommitted changes. Commit or stash them before running rake #{name}:prepare."
155
+ abort "Working tree has uncommitted changes. Commit or stash them before releasing."
156
156
  end
157
157
 
158
158
  def validate_pr_label!
@@ -1,3 +1,3 @@
1
1
  module Discharger
2
- VERSION = "0.5.0"
2
+ VERSION = "0.5.2"
3
3
  end
@@ -14,18 +14,21 @@ Dir.chdir(File.expand_path("..", __dir__))
14
14
  # under bundle exec before yaml loads, or a default psych clashes with Gemfile.lock.
15
15
  # Mirrors the github_packages and bundler steps, which re-run under bundle exec.
16
16
  unless ENV["DISCHARGER_SETUP_BUNDLED"]
17
+ # Mirrors GithubPackagesCommand and BundlerCommand in the gem; change them together.
17
18
  def store_github_packages_credentials
18
19
  require "yaml"
19
- source = begin
20
- YAML.safe_load_file("config/setup.yml")&.dig("github_packages", "source")
20
+ packages = begin
21
+ YAML.safe_load_file("config/setup.yml")&.dig("github_packages") || {}
21
22
  rescue SystemCallError, Psych::Exception
22
- nil # reported by Discharger::Setup on the second pass
23
+ {} # reported by Discharger::Setup on the second pass
23
24
  end
25
+ source = packages["source"]
24
26
  return if source.to_s.empty?
25
27
 
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?
28
+ timeout = Float(ENV.fetch("DISCHARGER_GH_TIMEOUT") { packages["gh_timeout"] || 15 })
29
+ username = gh("api", "user", "--jq", ".login", timeout: timeout)
30
+ token = gh("auth", "token", timeout: timeout)
31
+ return if username.to_s.empty? || token.to_s.empty?
29
32
  unless source_accepts_token?(source, username, token)
30
33
  warn "Leaving bundler credentials for #{source} alone: it did not accept the gh token (gh auth refresh -s read:packages)"
31
34
  return
@@ -34,6 +37,24 @@ unless ENV["DISCHARGER_SETUP_BUNDLED"]
34
37
  system("bundle", "config", "set", "--local", source, "#{username}:#{token}", out: File::NULL, err: File::NULL)
35
38
  end
36
39
 
40
+ def gh(*args, timeout:)
41
+ require "open3"
42
+ Open3.popen2("gh", *args, err: File::NULL) do |stdin, stdout, wait|
43
+ stdin.close
44
+ out = Thread.new { stdout.read }
45
+ if wait.join(timeout)
46
+ out.value.strip if wait.value.success?
47
+ else
48
+ Process.kill("KILL", wait.pid)
49
+ out.kill
50
+ warn "gh #{args.join(" ")} did not answer within #{timeout.to_i}s; continuing without credentials"
51
+ nil
52
+ end
53
+ end
54
+ rescue Errno::ENOENT
55
+ nil
56
+ end
57
+
37
58
  def source_accepts_token?(source, username, token)
38
59
  require "net/http"
39
60
  uri = URI.join("#{source.chomp("/")}/", "versions")
@@ -19,12 +19,13 @@ redis:
19
19
  version: "latest"
20
20
 
21
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.
22
+ # bin/setup stores bundler credentials from the GitHub CLI (gh) before
23
+ # installing the bundle, so the Gemfile needs no embedded token. Add
24
+ # "github_packages" to steps to check the token on every run. Credentials
25
+ # are written to .bundle/config — keep .bundle gitignored.
26
26
  # github_packages:
27
27
  # source: "https://rubygems.pkg.github.com/your-org"
28
+ # gh_timeout: 15 # seconds to wait for gh; DISCHARGER_GH_TIMEOUT overrides
28
29
 
29
30
  # Pre-Rails steps (run BEFORE Rails loads)
30
31
  # These are for system dependencies and environment setup that must happen
@@ -49,9 +50,9 @@ steps:
49
50
  - brew
50
51
  - asdf
51
52
  - git
52
- # Uncomment with the github_packages block above. Must precede bundler.
53
+ # Uncomment with the github_packages block above to check the token.
53
54
  # - github_packages
54
- - bundler
55
+ - bundler # a check; bin/setup already installed the bundle
55
56
  - yarn
56
57
  - config
57
58
  - docker
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.5.0
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jim Gay