discharger 0.4.2 → 0.5.1
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 -17
- data/README.md +45 -52
- data/lib/discharger/railtie.rb +4 -0
- data/lib/discharger/setup.rb +19 -6
- data/lib/discharger/setup_check.rb +50 -0
- data/lib/discharger/setup_runner/commands/config_command.rb +4 -16
- data/lib/discharger/setup_runner/commands/github_packages_command.rb +5 -8
- data/lib/discharger/setup_runner/commands/yarn_command.rb +15 -18
- data/lib/discharger/setup_runner/example_files.rb +24 -0
- data/lib/discharger/task.rb +81 -217
- data/lib/discharger/version.rb +1 -1
- data/lib/generators/discharger/install/templates/setup +3 -3
- data/lib/tasks/discharger.rake +9 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 115ce3036bb2da29f6afcde6015de3d019cb8bd228e8a8b655fce03dce0bfab6
|
|
4
|
+
data.tar.gz: 056c7cdb11e704cbb0a20425fef6edb21d80f8f33cb1f6a36930e2ea48e9bb19
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0e26e1ca08b82035ff400055d8ce554f14c54a5894e3dae192cb3711b02018f54871157d3ca84c0cecfecd737a9038ed64dae5f093fe7d3853bb6c1a3828a6f4
|
|
7
|
+
data.tar.gz: f31eb92df8f4396bde953c27a5ff5544657124f0b8077a7b941566c433e7bc437bca657bf896e02e95b824591f63e4c94c4392c87cc271f64517f431b19c7bf7
|
data/CHANGELOG.md
CHANGED
|
@@ -5,33 +5,30 @@ 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.5.1] - 2026-10-02
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
- database.db_name in setup.yml sets the exported DB_NAME, or false leaves it to database.yml (836e811)
|
|
13
|
-
|
|
14
|
-
### Fixed
|
|
15
|
-
|
|
16
|
-
- The generated bin/setup stores a gh token only after the GitHub Packages source accepts it (1ef5a44)
|
|
17
|
-
|
|
18
|
-
## [0.4.1] - 2026-09-29
|
|
10
|
+
## [0.5.0] - 2026-10-02
|
|
19
11
|
|
|
20
12
|
### Added
|
|
21
13
|
|
|
22
|
-
-
|
|
23
|
-
- bin/setup opens with a do-not-edit notice, and `discharger:install --setup-only` regenerates only the script (71ee122)
|
|
14
|
+
- 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)
|
|
24
15
|
|
|
25
16
|
### Changed
|
|
26
17
|
|
|
27
|
-
-
|
|
18
|
+
- Setup creates missing config/**/*.example and .env.example counterparts before pre_steps and Rails, so apps no longer copy them in pre_steps (e7a8ce9)
|
|
19
|
+
- rake release refuses to reset the working branch over unpushed local commits (6d87ce7)
|
|
20
|
+
- rake release tags the release commit on the working branch and pushes the tag; nothing merges into a production branch (af4484d)
|
|
21
|
+
- working_branch defaults to main (af4484d)
|
|
22
|
+
- rake release checks the tree, unpushed commits, the release commit and the tag before asking to confirm (af4484d)
|
|
23
|
+
- rake release stops with a clear message when the version's tag already exists on origin (af4484d)
|
|
28
24
|
|
|
29
25
|
### Removed
|
|
30
26
|
|
|
31
|
-
-
|
|
27
|
+
- staging_branch, production_branch, auto_deploy_staging and description settings; their writers warn and do nothing until 0.6 (af4484d)
|
|
28
|
+
- release:stage, release:build and DISCHARGER_BUILD_BRANCH (af4484d)
|
|
29
|
+
- the runbook preview on the stage build (af4484d)
|
|
32
30
|
|
|
33
31
|
### Fixed
|
|
34
32
|
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
- github_packages stores bundler credentials only after the source accepts them (41fab0f)
|
|
33
|
+
- bin/setup stores a gh token only when the GitHub Packages source answers 2xx (0ed1239)
|
|
34
|
+
- The yarn step installs the packageManager yarn with corepack install instead of corepack use, so setup no longer rewrites package.json (91967a1)
|
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
|
|
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
|
-
##
|
|
137
|
+
## Release Flow
|
|
143
138
|
|
|
144
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
161
|
+
### Configuring the Branch
|
|
175
162
|
|
|
176
163
|
```ruby
|
|
177
|
-
|
|
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", "
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
### Auto-deploy Staging
|
|
171
|
+
### Upgrading to 0.5.0
|
|
191
172
|
|
|
192
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
|
|
@@ -218,9 +199,10 @@ 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.
|
|
222
|
-
|
|
223
|
-
`
|
|
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:
|
|
@@ -297,14 +289,15 @@ pre_steps:
|
|
|
297
289
|
# - postgresql_tools
|
|
298
290
|
```
|
|
299
291
|
|
|
300
|
-
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:
|
|
301
295
|
|
|
302
296
|
```yaml
|
|
303
297
|
pre_steps:
|
|
304
298
|
- homebrew
|
|
305
|
-
- description: "
|
|
306
|
-
command: "
|
|
307
|
-
condition: "!File.exist?('.env')"
|
|
299
|
+
- description: "Check .env against .env.example"
|
|
300
|
+
command: "bin/check-env"
|
|
308
301
|
```
|
|
309
302
|
|
|
310
303
|
### Using Default Steps
|
|
@@ -317,10 +310,10 @@ The `steps` array specifies which built-in setup commands to run. Available comm
|
|
|
317
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)
|
|
318
311
|
- `bundler` - Install Ruby gems
|
|
319
312
|
- `yarn` - Install JavaScript packages
|
|
320
|
-
- `config` - Copy
|
|
313
|
+
- `config` - Copy Procfile.dev to Procfile and any example config file still missing
|
|
321
314
|
- `docker` - Setup Docker containers
|
|
322
315
|
- `pg_tools` - Create Docker-aware `pg_dump` and `psql` wrappers for apps that use `structure.sql` or call those tools directly
|
|
323
|
-
- `env` -
|
|
316
|
+
- `env` - Create .env from .env.example if still missing
|
|
324
317
|
- `database` - Drop and recreate the development and test databases, load the schema, migrate, and seed. Every run resets local data.
|
|
325
318
|
|
|
326
319
|
### Selecting Specific Steps
|
data/lib/discharger/railtie.rb
CHANGED
|
@@ -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."
|
data/lib/discharger/setup.rb
CHANGED
|
@@ -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.
|
|
16
|
-
# 3.
|
|
17
|
-
# 4.
|
|
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:
|
|
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
|
|
50
|
+
# Phase 4: Load Rails (uses env vars from phase 3)
|
|
47
51
|
load_rails
|
|
48
52
|
|
|
49
|
-
# Phase
|
|
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
|
-
#
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
65
|
-
log "#{source}
|
|
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
|
-
|
|
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
|
-
|
|
129
|
+
response.is_a?(Net::HTTPSuccess)
|
|
133
130
|
rescue => e
|
|
134
131
|
logger&.debug("Could not verify GitHub Packages credentials: #{e.message}")
|
|
135
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
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
|
|
@@ -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
|
data/lib/discharger/task.rb
CHANGED
|
@@ -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 = "
|
|
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?
|
|
@@ -195,7 +152,7 @@ module Discharger
|
|
|
195
152
|
stdout, _, status = Open3.capture3("git", "status", "--porcelain")
|
|
196
153
|
return true if status.success? && stdout.empty?
|
|
197
154
|
|
|
198
|
-
abort "Working tree has uncommitted changes. Commit or stash them before
|
|
155
|
+
abort "Working tree has uncommitted changes. Commit or stash them before releasing."
|
|
199
156
|
end
|
|
200
157
|
|
|
201
158
|
def validate_pr_label!
|
|
@@ -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.
|
|
258
|
-
#
|
|
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
|
-
|
|
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
|
|
289
|
+
---------- STEP 2 ----------
|
|
305
290
|
Release the current version to production
|
|
306
291
|
|
|
307
|
-
This task
|
|
308
|
-
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
-
#{
|
|
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
|
-
|
|
338
|
-
|
|
339
|
-
|
|
318
|
+
confirm_or_exit!
|
|
319
|
+
|
|
320
|
+
checkout_working_branch!
|
|
340
321
|
|
|
341
322
|
syscall(
|
|
342
|
-
["git
|
|
343
|
-
["git
|
|
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
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
)
|
|
352
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
521
|
-
|
|
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
|
-
|
|
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
|
-
'#{
|
|
543
|
-
to
|
|
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
|
data/lib/discharger/version.rb
CHANGED
|
@@ -26,7 +26,7 @@ unless ENV["DISCHARGER_SETUP_BUNDLED"]
|
|
|
26
26
|
username = `gh api user --jq .login 2>/dev/null`.strip
|
|
27
27
|
token = `gh auth token 2>/dev/null`.strip
|
|
28
28
|
return if username.empty? || token.empty?
|
|
29
|
-
unless
|
|
29
|
+
unless source_accepts_token?(source, username, token)
|
|
30
30
|
warn "Leaving bundler credentials for #{source} alone: it did not accept the gh token (gh auth refresh -s read:packages)"
|
|
31
31
|
return
|
|
32
32
|
end
|
|
@@ -34,7 +34,7 @@ unless ENV["DISCHARGER_SETUP_BUNDLED"]
|
|
|
34
34
|
system("bundle", "config", "set", "--local", source, "#{username}:#{token}", out: File::NULL, err: File::NULL)
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
def
|
|
37
|
+
def source_accepts_token?(source, username, token)
|
|
38
38
|
require "net/http"
|
|
39
39
|
uri = URI.join("#{source.chomp("/")}/", "versions")
|
|
40
40
|
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https", open_timeout: 5, read_timeout: 10) do |http|
|
|
@@ -42,7 +42,7 @@ unless ENV["DISCHARGER_SETUP_BUNDLED"]
|
|
|
42
42
|
request.basic_auth(username, token)
|
|
43
43
|
http.request(request)
|
|
44
44
|
end
|
|
45
|
-
|
|
45
|
+
response.is_a?(Net::HTTPSuccess)
|
|
46
46
|
rescue
|
|
47
47
|
false
|
|
48
48
|
end
|
|
@@ -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
|
+
version: 0.5.1
|
|
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:
|