captive-release 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6a8fd7809824a87b08805de31f9ddc40af157b6ba41e0b76e19f90edd4c9ade2
4
- data.tar.gz: 33dff63edd19f1889f5fa014271eff8640b6cba8effc7937ab9332fe2bf14316
3
+ metadata.gz: f164e66eb747a40700e3fb75708097cc3284dbaf52cd40719bcf72689d6c11df
4
+ data.tar.gz: 464925ca718160f9968ee03d1e4611c7a5ccc3fa5f3b076a7f29de96507696fd
5
5
  SHA512:
6
- metadata.gz: 9cfbc9751d8b2607e05512a7120f3773d3553526786e57d47a45d557e97bda793e26f09be493f015c3caaf982329f5363576a61e14399ecef1fa4157d913b352
7
- data.tar.gz: 6a114be8ba3248b560619b8e345e1ec8e9a9d9b009b73d7723be59ba743bae25ba519f1f9baf533e772e16608741d0a77fdc1ab42007a359a6a38b49b266ffee
6
+ metadata.gz: 7ebed4eca5ded03e043ec51adc60da07141435c8aa368ae2caea3185605b9a5f86becb1e3bf1ccec66a6df42505da10d20f89d029a85217d60526b41fbc543cd
7
+ data.tar.gz: 788821f98efa0e1a3afbcf97a8307370fda1b6131c8769250bd5a157b8b929bb4cec13d3b6b3c19a8820209ff719ffcd0a080befb0820df0d0ac22b47c95aae2
@@ -21,6 +21,9 @@ module CaptiveRelease
21
21
 
22
22
  @terminal.done Messages.t(:build_dispatched)
23
23
  @terminal.say " #{url}\n"
24
+ rescue WorkflowIntrouvable => e
25
+ @terminal.say @terminal.error(e.message)
26
+ exit 1
24
27
  end
25
28
 
26
29
  private
@@ -41,9 +41,17 @@ module CaptiveRelease
41
41
  # Le tag part avant HEAD : au moment où le webhook du push sur main arrive, le tag existe
42
42
  # déjà. Dans l'autre sens, demander « ce sha porte-t-il un tag v* ? » répond non presque
43
43
  # toujours, et la même release se construit deux fois.
44
+ #
45
+ # Le refus de HEAD se rend plutôt qu'il ne lève : une branche protégée refuse le push
46
+ # direct, et la release se replie alors sur une pull request.
44
47
  def self.push(version)
45
48
  Shell.run! "git push origin v#{version}"
46
- Shell.run! "git push origin HEAD"
49
+ Shell.run? "git push origin HEAD"
50
+ end
51
+
52
+ # Pousse HEAD sous un autre nom sans quitter la branche courante.
53
+ def self.push_branch(nom)
54
+ Shell.run! "git push origin HEAD:refs/heads/#{Shell.escape(nom)}"
47
55
  end
48
56
  end
49
57
  end
@@ -7,8 +7,9 @@ module CaptiveRelease
7
7
  # change. Chaque étape est annoncée au fur et à mesure : une release qui s'arrête doit
8
8
  # laisser voir jusqu'où elle est allée.
9
9
  class GitRelease
10
- def initialize(terminal:)
11
- @terminal = terminal
10
+ def initialize(terminal:, token_provider:)
11
+ @terminal = terminal
12
+ @token_provider = token_provider
12
13
  end
13
14
 
14
15
  def call(version, paths)
@@ -17,8 +18,27 @@ module CaptiveRelease
17
18
  @terminal.done %(git commit "🔖 Set version v#{version}")
18
19
  GitCommands.tag(version)
19
20
  @terminal.done "git tag v#{version}"
20
- GitCommands.push(version)
21
- @terminal.done "git push + tag"
21
+ return @terminal.done("git push + tag") if GitCommands.push(version)
22
+
23
+ pull_request(version)
24
+ end
25
+
26
+ private
27
+
28
+ # Le tag est déjà poussé et reste sur le commit de version : la release GitHub peut
29
+ # suivre, et un merge commit le ramène sur la branche de base.
30
+ def pull_request(version)
31
+ @terminal.say " #{@terminal.warn(Messages.t(:push_refused))}"
32
+ base = GitCommands.current_branch
33
+ branche = "release/v#{version}"
34
+ GitCommands.push_branch(branche)
35
+ @terminal.done "git push origin #{branche}"
36
+
37
+ jeton = @token_provider.call
38
+ raise Messages.t(:pull_request_needs_token, branch: branche) unless jeton
39
+
40
+ @terminal.done ReleasePullRequest.create(version: version, base: base, head: branche,
41
+ remote_url: GitCommands.remote_url, token: jeton)
22
42
  end
23
43
  end
24
44
  end
@@ -25,6 +25,15 @@ module CaptiveRelease
25
25
 
26
26
  def get(chemin) = analyser(@http.get(adresse(chemin)), "200")
27
27
 
28
+ # Un 404 dit l'absence ; tout autre échec reste une erreur plutôt qu'une absence supposée.
29
+ def existe?(chemin)
30
+ reponse = @http.get(adresse(chemin))
31
+ return false if reponse.code == "404"
32
+
33
+ analyser(reponse, "200")
34
+ true
35
+ end
36
+
28
37
  def post(chemin, corps, attendu:)
29
38
  analyser(@http.post(adresse(chemin), JSON.generate(corps)), attendu)
30
39
  end
@@ -3,6 +3,10 @@
3
3
  require_relative "github_api"
4
4
 
5
5
  module CaptiveRelease
6
+ # Le workflow demandé n'existe pas sur la branche par défaut du dépôt, seule que GitHub
7
+ # consulte pour accepter un dispatch.
8
+ class WorkflowIntrouvable < StandardError; end
9
+
6
10
  # Déclenche un workflow GitHub Actions. Elle compose ce qu'elle demande ; l'API, son
7
11
  # authentification et ses échecs vivent dans GithubApi.
8
12
  class GithubDispatch
@@ -16,10 +20,21 @@ module CaptiveRelease
16
20
  end
17
21
 
18
22
  def dispatch(workflow_file:, ref:, inputs:)
23
+ exiger(workflow_file)
19
24
  @api.post("actions/workflows/#{workflow_file}/dispatches",
20
25
  { "ref" => ref, "inputs" => inputs }, attendu: "204")
21
26
 
22
27
  @api.depot.actions_url
23
28
  end
29
+
30
+ private
31
+
32
+ def exiger(workflow_file)
33
+ return if @api.existe?("actions/workflows/#{workflow_file}")
34
+
35
+ depot = @api.depot
36
+ raise WorkflowIntrouvable, Messages.t(:workflow_not_found, workflow: workflow_file,
37
+ repo: "#{depot.owner}/#{depot.repo}")
38
+ end
24
39
  end
25
40
  end
@@ -73,6 +73,9 @@ en:
73
73
  dispatch_branch: 'Branch: %{value}'
74
74
  dispatching: Dispatching...
75
75
  build_dispatched: Build dispatched!
76
+ workflow_not_found: |-
77
+ Workflow %{workflow} not found on %{repo}.
78
+ Add .github/workflows/%{workflow} to the default branch, or fix build_workflow in the configuration.
76
79
  dirty_tree: The working tree is not clean.
77
80
  dirty_tree_hint: Commit or stash your changes before releasing.
78
81
  current_version: 'Current version: %{version}'
@@ -102,6 +105,9 @@ en:
102
105
  updating_version_files: Updating version files...
103
106
  refreshing_lockfile: Refreshing Gemfile.lock...
104
107
  running_git: Running git commands...
108
+ push_refused: "⚠️ Direct push refused (protected branch?) - falling back to a pull request."
109
+ pull_request_needs_token: 'GITHUB_TOKEN required to open the pull request: branch %{branch} is pushed, open it by hand.'
110
+ pull_request_body: "Direct push to %{base} was refused. Merge with a **merge commit**: a squash or a rebase would change the sha, and tag v%{version} would no longer be on %{base}."
105
111
  creating_github_release: Creating GitHub release...
106
112
  no_token_skip_release: "⚠️ GITHUB_TOKEN not set - skipping GitHub release creation."
107
113
  released: Version v%{version} released successfully!
@@ -73,6 +73,9 @@ fr:
73
73
  dispatch_branch: 'Branche : %{value}'
74
74
  dispatching: Déclenchement...
75
75
  build_dispatched: Build déclenché !
76
+ workflow_not_found: |-
77
+ Workflow %{workflow} introuvable sur %{repo}.
78
+ Ajoutez .github/workflows/%{workflow} sur la branche par défaut, ou corrigez build_workflow dans la configuration.
76
79
  dirty_tree: L'arbre de travail n'est pas propre.
77
80
  dirty_tree_hint: Commitez ou remisez vos modifications avant de releaser.
78
81
  current_version: 'Version actuelle : %{version}'
@@ -102,6 +105,9 @@ fr:
102
105
  updating_version_files: Mise à jour des fichiers de version...
103
106
  refreshing_lockfile: Rafraîchissement du Gemfile.lock...
104
107
  running_git: Exécution des commandes git...
108
+ push_refused: "⚠️ Push direct refusé (branche protégée ?) - repli sur une pull request."
109
+ pull_request_needs_token: 'GITHUB_TOKEN requis pour ouvrir la pull request : la branche %{branch} est poussée, ouvrez-la à la main.'
110
+ pull_request_body: "Le push direct sur %{base} a été refusé. Mergez avec un **merge commit** : un squash ou un rebase changerait le sha, et le tag v%{version} ne serait plus sur %{base}."
105
111
  creating_github_release: Création de la release GitHub...
106
112
  no_token_skip_release: "⚠️ GITHUB_TOKEN absent - la release GitHub n'est pas créée."
107
113
  released: Version v%{version} publiée avec succès !
@@ -22,7 +22,7 @@ module CaptiveRelease
22
22
  ReleaseConfirmation.new(terminal: @terminal).verifier!(suggestion, courante, prochaine) if confirmation
23
23
 
24
24
  VersionFilesWriting.new(terminal: @terminal).call(prochaine, @config.version_files)
25
- GitRelease.new(terminal: @terminal).call(prochaine, chemins_a_commiter)
25
+ GitRelease.new(terminal: @terminal, token_provider: @token_provider).call(prochaine, chemins_a_commiter)
26
26
  publication.call(prochaine)
27
27
  @terminal.say "\n🎉 #{@terminal.bold(Messages.t(:released, version: prochaine))}\n"
28
28
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "github_api"
4
+
5
+ module CaptiveRelease
6
+ # Ouvre la pull request d'une release que la branche protégée a refusée en push direct.
7
+ # Elle compose ce qu'elle demande ; l'API, son authentification et ses échecs vivent dans
8
+ # GithubApi.
9
+ class ReleasePullRequest
10
+ def self.create(version:, base:, head:, remote_url:, token:, api: nil)
11
+ api ||= GithubApi.new(remote_url: remote_url, token: token)
12
+ new(api).create(version, base: base, head: head)
13
+ end
14
+
15
+ def initialize(api)
16
+ @api = api
17
+ end
18
+
19
+ def create(version, base:, head:)
20
+ @api.post("pulls",
21
+ { "title" => "🔖 Set version v#{version}", "head" => head, "base" => base,
22
+ "body" => Messages.t(:pull_request_body, base: base, version: version), },
23
+ attendu: "201")["html_url"]
24
+ end
25
+ end
26
+ end
@@ -11,6 +11,9 @@ module CaptiveRelease
11
11
  system(commande) || raise(Messages.t(:command_failed, command: commande))
12
12
  end
13
13
 
14
+ # Pour une commande dont l'échec est une issue attendue, que l'appelant traite.
15
+ def self.run?(commande) = system(commande)
16
+
14
17
  def self.capture(commande) = `#{commande}`.strip
15
18
 
16
19
  def self.capture_lines(commande) = capture(commande).lines.map(&:strip).reject(&:empty?)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CaptiveRelease
4
- VERSION = "0.12.0"
4
+ VERSION = "0.13.0"
5
5
  end
@@ -43,6 +43,7 @@ require_relative "captive_release/menu"
43
43
  require_relative "captive_release/version_choice"
44
44
  require_relative "captive_release/gemfile_lock_refresh"
45
45
  require_relative "captive_release/exclusion_input"
46
+ require_relative "captive_release/release_pull_request"
46
47
  require_relative "captive_release/git_release"
47
48
  require_relative "captive_release/changes_since"
48
49
  require_relative "captive_release/release_publication"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: captive-release
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Captive Studio
@@ -63,6 +63,7 @@ files:
63
63
  - lib/captive_release/release_overview.rb
64
64
  - lib/captive_release/release_preconditions.rb
65
65
  - lib/captive_release/release_publication.rb
66
+ - lib/captive_release/release_pull_request.rb
66
67
  - lib/captive_release/repo_files.rb
67
68
  - lib/captive_release/ruby_gems_version.rb
68
69
  - lib/captive_release/semver.rb