toys-release 0.2.2 → 0.3.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.
@@ -512,7 +512,7 @@ module Toys
512
512
  @components = {}
513
513
  @utils.accumulate_errors("Errors while validating components") do
514
514
  settings.all_component_names.each do |name|
515
- releasable = Component.build(settings, name, @utils)
515
+ releasable = Component.new(settings, name, @utils)
516
516
  releasable.validate
517
517
  @components[releasable.name] = releasable
518
518
  end
@@ -594,7 +594,7 @@ module Toys
594
594
  results << "No GitHub checks found for #{ref}" if checks.empty?
595
595
  checks.each do |check|
596
596
  name = check["name"]
597
- next if @settings.release_jobs_regexp.match(name)
597
+ next if name.start_with?("release-")
598
598
  next unless @settings.required_checks_regexp.match(name)
599
599
  if check["status"] != "completed"
600
600
  results << "GitHub check #{name.inspect} is not complete"
@@ -612,7 +612,7 @@ module Toys
612
612
  def single_released_component_and_version(pull_request)
613
613
  component_name =
614
614
  if @settings.all_component_names.size == 1
615
- @settings.default_component_name
615
+ @settings.all_component_names.first
616
616
  else
617
617
  component_name_from_release_branch(pull_request.head_ref)
618
618
  end
@@ -63,10 +63,16 @@ module Toys
63
63
  # @private
64
64
  def run(step_context)
65
65
  tool = Array(step_context.option("tool", required: true))
66
+ chdir = step_context.option("chdir", default: ".")
66
67
  step_context.log("Running tool #{tool.inspect}...")
67
- result = step_context.utils.exec_separate_tool(tool, out: [:child, :err])
68
+ result = step_context.utils.exec_separate_tool(tool, chdir: chdir, out: [:child, :err])
68
69
  unless result.success?
69
- step_context.abort_pipeline("Tool failed: #{tool.inspect}. Check the logs for details.")
70
+ if step_context.option("continue_on_error")
71
+ step_context.warning("Tool failed: #{tool.inspect}.")
72
+ step_context.exit_step
73
+ else
74
+ step_context.abort_pipeline("Tool failed: #{tool.inspect}. Check the logs for details.")
75
+ end
70
76
  end
71
77
  step_context.log("Completed tool")
72
78
  end
@@ -82,10 +88,16 @@ module Toys
82
88
  # @private
83
89
  def run(step_context)
84
90
  command = Array(step_context.option("command", required: true))
91
+ chdir = step_context.option("chdir", default: ".")
85
92
  step_context.log("Running command #{command.inspect}...")
86
- result = step_context.utils.exec(command, out: [:child, :err])
93
+ result = step_context.utils.exec(command, chdir: chdir, out: [:child, :err])
87
94
  unless result.success?
88
- step_context.abort_pipeline("Command failed: #{command.inspect}. Check the logs for details.")
95
+ if step_context.option("continue_on_error")
96
+ step_context.warning("Command failed: #{command.inspect}.")
97
+ step_context.exit_step
98
+ else
99
+ step_context.abort_pipeline("Command failed: #{command.inspect}. Check the logs for details.")
100
+ end
89
101
  end
90
102
  step_context.log("Completed command")
91
103
  end
@@ -98,18 +110,23 @@ module Toys
98
110
  class << BUNDLE
99
111
  # @private
100
112
  def run(step_context)
113
+ chdir = step_context.option("chdir", default: ".")
101
114
  component = step_context.component
102
115
  step_context.log("Running bundler for #{component.name} ...")
103
- component.bundle
116
+ ::Dir.chdir(chdir) do
117
+ result = component.bundle
118
+ unless result.success?
119
+ step_context.abort_pipeline("Bundle failed for #{component.name}. Check the logs for details.")
120
+ end
121
+ end
104
122
  step_context.log("Completed bundler for #{component.name}")
105
123
  step_context.copy_to_output(source_path: "Gemfile.lock")
106
124
  end
107
125
  end
108
126
 
109
127
  ##
110
- # A step that builds the gem, and leaves the built gem file in the step's
111
- # artifact directory. This step can also run a pre_command and/or a
112
- # pre_tool.
128
+ # A step that builds the gem, and leaves the built gem package file in
129
+ # the step's output directory.
113
130
  #
114
131
  BUILD_GEM = ::Object.new
115
132
  class << BUILD_GEM
@@ -133,9 +150,8 @@ module Toys
133
150
  end
134
151
 
135
152
  ##
136
- # A step that builds yardocs, and leaves the built documentation file in
137
- # the step's artifact directory. This step can also run a pre_command
138
- # and/or a pre_tool.
153
+ # A step that builds yardocs, and leaves the built documentation in the
154
+ # step's output directory.
139
155
  #
140
156
  BUILD_YARD = ::Object.new
141
157
  class << BUILD_YARD
@@ -144,7 +160,7 @@ module Toys
144
160
  if step_context.option("uses_gems")
145
161
  []
146
162
  else
147
- ["bundle"]
163
+ [step_context.option("bundle_step", default: "bundle")]
148
164
  end
149
165
  end
150
166
 
@@ -169,10 +185,10 @@ module Toys
169
185
 
170
186
  def setup_gems(step_context)
171
187
  if (uses_gems = step_context.option("uses_gems"))
172
- ::Toys::Utils::Gems.activate("yard")
173
188
  Array(uses_gems).each do |gem_info|
174
189
  ::Toys::Utils::Gems.activate(*Array(gem_info))
175
190
  end
191
+ ::Toys::Utils::Gems.activate("yard")
176
192
  [
177
193
  "gem 'yard'",
178
194
  "puts 'Loading gems explicitly: #{uses_gems.inspect}'",
@@ -189,15 +205,14 @@ module Toys
189
205
  end
190
206
 
191
207
  ##
192
- # A step that releases a gem built by a previous run of BuildGem. The
193
- # `"input"` option provides the name of the artifact directory containing
194
- # the built gem.
208
+ # A step that releases a gem built by a previous step, normally a
209
+ # run of BUILD_GEM.
195
210
  #
196
211
  RELEASE_GEM = ::Object.new
197
212
  class << RELEASE_GEM
198
213
  # @private
199
- def primary?(_step_context)
200
- true
214
+ def primary?(step_context)
215
+ ::File.file?("#{step_context.component.name}.gemspec")
201
216
  end
202
217
 
203
218
  # @private
@@ -224,7 +239,10 @@ module Toys
224
239
 
225
240
  def check_existence(step_context)
226
241
  step_context.log("Checking whether #{step_context.release_description} already exists...")
227
- if step_context.component.version_released?(step_context.release_version)
242
+ gem_name = step_context.component.name
243
+ gem_version = step_context.release_version.to_s
244
+ cmd = ["gem", "search", gem_name, "--exact", "--remote", "--version", gem_version]
245
+ if step_context.utils.capture(cmd).include?("#{gem_name} (#{gem_version})")
228
246
  step_context.warning("Gem already pushed for #{step_context.release_description}. Skipping.")
229
247
  step_context.add_success("Gem already pushed for #{step_context.release_description}")
230
248
  step_context.exit_step
@@ -260,9 +278,8 @@ module Toys
260
278
  end
261
279
 
262
280
  ##
263
- # A step that pushes to gh-pages documentation built by a previous run of
264
- # BuildYard. The `"input"` option provides the name of the artifact
265
- # directory containing the built documentation.
281
+ # A step that pushes to gh-pages documentation built by a previous step,
282
+ # normally an instance of BUILD_YARD.
266
283
  #
267
284
  PUSH_GH_PAGES = ::Object.new
268
285
  class << PUSH_GH_PAGES
data/toys/.toys.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- toys_version!("~> 0.17")
3
+ toys_version!("~> 0.18")
4
4
 
5
- desc "Release tools namespace"
5
+ desc "Namespace for toys-release tools"
data/toys/_onclosed.rb CHANGED
@@ -83,6 +83,7 @@ end
83
83
 
84
84
  def handle_release_merged
85
85
  setup_git
86
+ report_release_starting
86
87
  github_check_errors = @repository.wait_github_checks
87
88
  unless github_check_errors.empty?
88
89
  @utils.error("GitHub checks failed", *github_check_errors)
@@ -103,6 +104,13 @@ def setup_git
103
104
  exec(["git", "switch", "release/current"], e: true)
104
105
  end
105
106
 
107
+ def report_release_starting
108
+ reports = ["Starting release automation."]
109
+ url = @utils.current_workflow_run_url
110
+ reports << "See #{url} for execution logs." if url
111
+ @pull_request.add_comment(reports.join(" "))
112
+ end
113
+
106
114
  def create_performer
107
115
  require "toys/release/performer"
108
116
  dry_run = /^t/i.match?(::ENV["TOYS_RELEASE_DRY_RUN"].to_s)
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc "Generate an initial config file"
4
+
5
+ long_desc \
6
+ "This tool generates an initial config file for this repo." \
7
+ " You will generally need to make additional edits to this file after" \
8
+ " initial generation."
9
+
10
+ flag :repo, "--repo=REPO" do
11
+ desc "GitHub repo owner and name (e.g. dazuma/toys)"
12
+ end
13
+ flag :git_user, "--git-user=NAME" do
14
+ default ""
15
+ desc "User name for git commits (defaults to the git user.name config)"
16
+ end
17
+ flag :git_email, "--git-email=EMAIL" do
18
+ default ""
19
+ desc "User email for git commits (defaults to the git user.email config)"
20
+ end
21
+ flag :file_path, "-o PATH", "--output=PATH" do
22
+ desc "Output file path (defaults to .toys/.data/releases.yml)"
23
+ end
24
+ flag :yes, "--yes", "-y" do
25
+ desc "Automatically answer yes to all confirmations"
26
+ end
27
+
28
+ include :exec, e: true
29
+ include :terminal, styled: true
30
+ include :fileutils
31
+
32
+ def run
33
+ setup
34
+ interpret_github_repo
35
+ interpret_git_user
36
+ check_file_path
37
+ gems_and_dirs = find_gems
38
+ confirm_with_user
39
+ mkdir_p(::File.dirname(file_path))
40
+ ::File.open(file_path, "w") do |file|
41
+ write_settings(file, gems_and_dirs)
42
+ end
43
+ puts("Wrote initial config file to #{file_path}.", :green, :bold)
44
+ end
45
+
46
+ def setup
47
+ require "toys/release/environment_utils"
48
+ @utils = Toys::Release::EnvironmentUtils.new(self)
49
+ cd(@utils.repo_root_directory)
50
+ end
51
+
52
+ def interpret_github_repo
53
+ return if repo
54
+ current_guess = nil
55
+ capture(["git", "remote", "-v"]).split("\n").each do |line|
56
+ match = %r{^(\S+)\s+git@github\.com:([^/.\s]+/[^/.\s]+)(?:\.git)?}.match(line)
57
+ current_guess = match[2] if match && (match[1] == "origin" || current_guess.nil?)
58
+ match = %r{^(\S+)\s+https://github\.com/([^/.\s]+/[^/.\s]+)(?:\.git)?}.match(line)
59
+ current_guess = match[2] if match && (match[1] == "origin" || current_guess.nil?)
60
+ end
61
+ if current_guess.nil?
62
+ puts "Unable to determine the GitHub repo associated with this repository.", :red, :bold
63
+ exit(1)
64
+ end
65
+ puts "GitHub repository inferred to be #{current_guess}."
66
+ puts "If this is incorrect, specify the correct repo using the --repo= flag."
67
+ set(:repo, current_guess)
68
+ end
69
+
70
+ def interpret_git_user
71
+ if git_user.empty?
72
+ set(:git_user, capture(["git", "config", "get", "user.name"]).strip)
73
+ if git_user.empty?
74
+ puts "Unable to determine git user.name. Using a hard-coded fallback", :yellow
75
+ set(:git_user, "Example User")
76
+ else
77
+ puts "Using the current git user.name of #{git_user}"
78
+ end
79
+ end
80
+ if git_email.empty?
81
+ set(:git_email, capture(["git", "config", "get", "user.email"]).strip)
82
+ if git_email.empty?
83
+ puts "Unable to determine git user.email. Using a hard-coded fallback", :yellow
84
+ set(:git_email, "hello@example.com")
85
+ else
86
+ puts "Using the current git user.email of #{git_email}"
87
+ end
88
+ end
89
+ end
90
+
91
+ def check_file_path
92
+ set(:file_path, ::File.join(".toys", ".data", "releases.yml")) unless file_path
93
+ if ::File.readable?(file_path)
94
+ puts "Cannot overwrite existing file: #{file_path}", :red, :bold
95
+ exit(1)
96
+ end
97
+ end
98
+
99
+ def confirm_with_user
100
+ exit unless yes || confirm("Create config file #{file_path}? ", default: true)
101
+ end
102
+
103
+ def find_gems
104
+ toplevel = ::Dir.glob("*.gemspec")
105
+ subdirs = ::Dir.glob("*/*.gemspec")
106
+ if toplevel.size > 1
107
+ puts "Unexpected: Found multiple gemspecs at the top level.", :red, :bold
108
+ exit(1)
109
+ end
110
+ if toplevel.size == 1 && subdirs.empty?
111
+ path = toplevel.first
112
+ puts "Found #{path} at the toplevel of the repo."
113
+ [[::File.basename(path, ".gemspec"), "."]]
114
+ elsif toplevel.empty? && !subdirs.empty?
115
+ subdirs.map do |path|
116
+ puts "Found #{path} in the repo."
117
+ [::File.basename(path, ".gemspec"), ::File.dirname(path)]
118
+ end
119
+ else
120
+ puts "Unexpected: Found gemspecs at the toplevel and in subdirectories.", :red, :bold
121
+ exit(1)
122
+ end
123
+ end
124
+
125
+ def write_settings(file, gems_and_dirs)
126
+ file.puts("repo: #{repo}")
127
+ file.puts("git_user_name: #{git_user}")
128
+ file.puts("git_user_email: #{git_email}")
129
+ file.puts("# Insert additional repo-level settings here.")
130
+ file.puts
131
+ file.puts("gems:")
132
+ gems_and_dirs.sort_by(&:first).each do |(name, dir)|
133
+ file.puts(" - name: #{name}")
134
+ file.puts(" directory: #{dir}")
135
+ file.puts(" # Insert additional gem-level settings here.")
136
+ end
137
+ end
@@ -5,12 +5,16 @@ desc "Generate GitHub Actions workflow files"
5
5
  long_desc \
6
6
  "This tool generates workflow files for GitHub Actions."
7
7
 
8
+ flag :workflows_dir, "-o PATH", "--output=PATH" do
9
+ desc "Output directory (defaults to .github/workflows)"
10
+ end
8
11
  flag :yes, "--yes", "-y" do
9
12
  desc "Automatically answer yes to all confirmations"
10
13
  end
11
14
 
12
- include :exec
15
+ include :exec, e: true
13
16
  include :terminal, styled: true
17
+ include :fileutils
14
18
 
15
19
  # Context for ERB templates
16
20
  class ErbContext
@@ -24,21 +28,35 @@ class ErbContext
24
28
  end
25
29
 
26
30
  def run
31
+ setup
32
+ user_confirmation
33
+ generate_all_files
34
+ end
35
+
36
+ def setup
27
37
  require "erb"
38
+ require "fileutils"
28
39
  require "toys/release/environment_utils"
29
40
  require "toys/release/repo_settings"
30
41
 
31
42
  @utils = Toys::Release::EnvironmentUtils.new(self)
43
+ cd(@utils.repo_root_directory)
32
44
  @settings = Toys::Release::RepoSettings.load_from_environment(@utils)
33
45
 
46
+ set(:workflows_dir, ::File.join(".github", "workflows")) if workflows_dir.to_s.empty?
47
+ end
48
+
49
+ def user_confirmation
34
50
  unless @settings.enable_release_automation?
35
51
  puts "Release automation disabled in settings."
36
52
  unless yes || confirm("Create workflow files anyway? ", default: false)
37
53
  @utils.error("Aborted.")
38
54
  end
39
55
  end
56
+ end
40
57
 
41
- @workflows_dir = ::File.join(context_directory, ".github", "workflows")
58
+ def generate_all_files
59
+ mkdir_p(workflows_dir)
42
60
  files = [
43
61
  "release-hook-on-closed.yml",
44
62
  "release-hook-on-push.yml",
@@ -46,12 +64,11 @@ def run
46
64
  "release-request.yml",
47
65
  "release-retry.yml",
48
66
  ]
49
-
50
- files.each { |name| generate(name) }
67
+ files.each { |name| generate_file(name) }
51
68
  end
52
69
 
53
- def generate(name)
54
- destination = ::File.join(@workflows_dir, name)
70
+ def generate_file(name)
71
+ destination = ::File.join(workflows_dir, name)
55
72
  if ::File.readable?(destination)
56
73
  puts "Destination file #{destination} exists.", :yellow, :bold
57
74
  return unless yes || confirm("Overwrite? ", default: true)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: toys-release
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Azuma
@@ -71,8 +71,8 @@ files:
71
71
  - toys/_onclosed.rb
72
72
  - toys/_onpush.rb
73
73
  - toys/create-labels.rb
74
+ - toys/gen-config.rb
74
75
  - toys/gen-gh-pages.rb
75
- - toys/gen-settings.rb
76
76
  - toys/gen-workflows.rb
77
77
  - toys/perform.rb
78
78
  - toys/request.rb
@@ -81,10 +81,10 @@ homepage: https://github.com/dazuma/toys
81
81
  licenses:
82
82
  - MIT
83
83
  metadata:
84
- changelog_uri: https://dazuma.github.io/toys/gems/toys-release/v0.2.2/file.CHANGELOG.html
84
+ changelog_uri: https://dazuma.github.io/toys/gems/toys-release/v0.3.0/file.CHANGELOG.html
85
85
  source_code_uri: https://github.com/dazuma/toys/tree/main/toys-release
86
86
  bug_tracker_uri: https://github.com/dazuma/toys/issues
87
- documentation_uri: https://dazuma.github.io/toys/gems/toys-release/v0.2.2
87
+ documentation_uri: https://dazuma.github.io/toys/gems/toys-release/v0.3.0
88
88
  rdoc_options: []
89
89
  require_paths:
90
90
  - lib
data/toys/gen-settings.rb DELETED
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- desc "Generate initial settings file"
4
-
5
- long_desc \
6
- "This tool generates an initial settings file for this repo." \
7
- " You will generally need to make additional edits to this file after" \
8
- " initial generation."
9
-
10
- required_arg :repo do
11
- desc "GitHub repo owner and name (e.g. dazuma/toys)"
12
- end
13
-
14
- flag :yes, "--yes", "-y" do
15
- desc "Automatically answer yes to all confirmations"
16
- end
17
-
18
- include :exec
19
- include :terminal, styled: true
20
-
21
- def run
22
- file_path = ::File.join(context_directory, ".toys", ".data", "releases.yml")
23
- if ::File.readable?(file_path)
24
- puts "Cannot overwrite existing file: #{file_path}", :red, :bold
25
- exit(1)
26
- end
27
- return unless yes || confirm("Create file #{file_path}? ", default: true)
28
- ::File.open(file_path, "w") do |file|
29
- write_settings(file)
30
- end
31
- puts("Wrote initial settings file: #{file_path}.", :green, :bold)
32
- end
33
-
34
- def write_settings(file)
35
- file.puts("repo: #{repo}")
36
- file.puts("# Insert additional repo-level settings here.")
37
- file.puts
38
- file.puts("gems:")
39
- ::Dir.glob("**/*.gemspec").each do |gemspec|
40
- gem_name = ::File.basename(gemspec, ".gemspec")
41
- file.puts(" - name: #{gem_name}")
42
- dir = ::File.dirname(gemspec)
43
- file.puts(" directory: #{dir}") if dir != gem_name
44
- file.puts(" # Insert additional gem-level settings here.")
45
- end
46
- end