captive-release 0.5.0 → 0.7.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: 03403604d3d533136b2f1901345f1a1192d698ec43ac8e54589e966e33d8a328
4
- data.tar.gz: f822d4f46347d765a3c1beb5415b5e19d223f35f55a94592555d38fe4633ac4c
3
+ metadata.gz: 51e98c43b123462f3166f453ca8623c9b748fff59c11259840ea5c5e13ead96b
4
+ data.tar.gz: db1e3f7f2e542786774b8eb72f4501196cccfe5b971a4d4c29a9f3ce6cf9f243
5
5
  SHA512:
6
- metadata.gz: c7db3c9d1f2289de5eaf34d7057dd8abdc2e1d39c3d52eedcca661f3f8d494810b36e130320137bb9c58993e33ecef257407ee6cfd8ff5caceb036975c1a4a83
7
- data.tar.gz: d4f457a4cc32c465ca677def528172deb6d0efb2e48bc95ae5911c9ca742a34c0d1a79abfa2c31551383c04531ec16569745e84e15786e97cd462fe4bb5b48ea
6
+ metadata.gz: 396e2ba7f46def9bde2aeba4afdcfefe91e9a0ba4d028e29f5f0881e5c3c4f32dfe3c753a3cd493b78fa80c1e81283ceea9ec2b593a2ffe8ecce7b593a08195f
7
+ data.tar.gz: da82b40b4abb5f32649022dcf1f54b13c769a5ae046f097768b54b422588fae14154ddbe1b260f676a65c2d8e438efd73cc13e1c4dcbd0def640503c6abdff29
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CaptiveRelease
4
+ # Le garde-fou : si la version choisie est d'une sévérité inférieure à ce que les commits
5
+ # suggèrent, impose une confirmation explicite avant de continuer. Contrairement à
6
+ # confirmer!, une entrée vide refuse : c'est un avertissement, pas la confirmation normale.
7
+ class BumpSafetyNet
8
+ def initialize(terminal:)
9
+ @terminal = terminal
10
+ end
11
+
12
+ def verifier!(suggestion, courante, prochaine)
13
+ return unless suggestion
14
+
15
+ choisie = VersionSeverity.classify(from: courante, to: prochaine)
16
+ return if BumpSeverity::RANK.fetch(choisie, -1) >= BumpSeverity::RANK.fetch(suggestion)
17
+
18
+ avertir_et_confirmer!(suggestion, choisie)
19
+ end
20
+
21
+ private
22
+
23
+ def avertir_et_confirmer!(suggestion, choisie)
24
+ @terminal.say @terminal.warn(
25
+ "\n⚠️ Les commits suggèrent un bump #{suggestion}, mais la version choisie n'est qu'un #{choisie}.",
26
+ )
27
+ reponse = @terminal.ask("Continuer quand même ? [y/N] ").downcase
28
+ exit 1 unless reponse == "y"
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CaptiveRelease
4
+ # L'ordre de gravité partagé entre la suggestion (gitmoji) et le garde-fou (semver choisi) :
5
+ # dit ce qui prime quand les signaux sont mêlés, patch < minor < major.
6
+ module BumpSeverity
7
+ RANK = { "patch" => 0, "minor" => 1, "major" => 2 }.freeze
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CaptiveRelease
4
+ # La sévérité de bump suggérée par les commits depuis le dernier tag - nil tant que ce tag
5
+ # n'existe pas, comme ChangesSince pour le lien de comparaison.
6
+ class BumpSuggestion
7
+ def self.for(version)
8
+ return nil unless GitCommands.tag?("v#{version}")
9
+
10
+ GitmojiSeverity.for(GitCommands.messages_since("v#{version}"))
11
+ rescue StandardError
12
+ nil
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CaptiveRelease
4
+ # Remet Gemfile.lock d'aplomb après un bump : un .gemspec à la racine porte la version
5
+ # bumpée, donc Gemfile.lock devient périmé et --deployment le refuse en CI si personne ne
6
+ # le relance.
7
+ #
8
+ # Cette unité change quand la façon de détecter ou de relancer le lock change,
9
+ # ReleaseCommand quand la suite des étapes change.
10
+ class GemfileLockRefresh
11
+ LOCKFILE = "Gemfile.lock"
12
+
13
+ def initialize(files: RepoFiles.new("."))
14
+ @files = files
15
+ end
16
+
17
+ # rescue : une arborescence hostile ne doit jamais faire échouer la release, la détection
18
+ # n'est qu'une commodité. Une fois vraie, l'exécution du bundle lock, elle, échoue haut
19
+ # la main si elle rate.
20
+ def needed?
21
+ File.exist?(@files.path(LOCKFILE)) && @files.glob("*.gemspec").any?
22
+ rescue StandardError
23
+ false
24
+ end
25
+
26
+ def call
27
+ Shell.run! "bundle lock"
28
+ end
29
+ end
30
+ end
@@ -12,6 +12,12 @@ module CaptiveRelease
12
12
 
13
13
  def self.tag?(nom) = !Shell.capture("git tag -l #{nom} 2>/dev/null").empty?
14
14
 
15
+ # Les sujets de commit depuis le tag donné jusqu'à HEAD - la matière première de la
16
+ # suggestion de bump, un message par ligne, propre de toute ligne vide.
17
+ def self.messages_since(tag)
18
+ Shell.capture("git log --pretty=%s #{Shell.escape(tag)}..HEAD").lines.map(&:strip).reject(&:empty?)
19
+ end
20
+
15
21
  def self.current_branch
16
22
  branche = Shell.capture("git rev-parse --abbrev-ref HEAD 2>/dev/null")
17
23
  branche.empty? ? "main" : branche
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CaptiveRelease
4
+ # Traduit une liste de messages de commit en sévérité de bump suggérée : 💥 casse, ✨
5
+ # ajoute, tout le reste est un patch par défaut. Signaux mêlés : le plus sévère gagne.
6
+ class GitmojiSeverity
7
+ MAJOR = "💥"
8
+ MINOR = "✨"
9
+
10
+ def self.for(messages)
11
+ messages.reduce("patch") { |pire, message| plus_severe(pire, severity_of(message)) }
12
+ end
13
+
14
+ def self.severity_of(message)
15
+ return "major" if message.start_with?(MAJOR)
16
+ return "minor" if message.start_with?(MINOR)
17
+
18
+ "patch"
19
+ end
20
+ private_class_method :severity_of
21
+
22
+ def self.plus_severe(a, b) = BumpSeverity::RANK[a] >= BumpSeverity::RANK[b] ? a : b
23
+ private_class_method :plus_severe
24
+ end
25
+ end
@@ -18,23 +18,46 @@ module CaptiveRelease
18
18
  @terminal.say "\n#{@terminal.bold("captive-release")}\n"
19
19
  @terminal.say "Current version: #{@terminal.bold(courante)}\n"
20
20
  afficher_changements(courante)
21
+ suggestion = BumpSuggestion.for(courante)
22
+ afficher_suggestion(suggestion)
21
23
 
22
24
  prochaine = VersionChoice.new(terminal: @terminal).call(courante, bump_arg)
23
- confirmer!(prochaine) if confirmation
25
+ if confirmation
26
+ BumpSafetyNet.new(terminal: @terminal).verifier!(suggestion, courante, prochaine)
27
+ confirmer!(prochaine)
28
+ end
24
29
 
25
30
  VersionFilesWriting.new(terminal: @terminal).call(prochaine, @config.version_files)
26
- GitRelease.new(terminal: @terminal).call(prochaine, @config.version_files.map(&:path))
31
+ GitRelease.new(terminal: @terminal).call(prochaine, chemins_a_commiter)
27
32
  publication.call(prochaine)
28
33
  @terminal.say "\n🎉 #{@terminal.bold("Version v#{prochaine} released successfully!")}\n"
29
34
  end
30
35
 
31
36
  private
32
37
 
38
+ def chemins_a_commiter
39
+ chemins = @config.version_files.map(&:path)
40
+ return chemins unless (refresh = GemfileLockRefresh.new).needed?
41
+
42
+ @terminal.say "\n#{@terminal.bold("Refreshing Gemfile.lock...")}"
43
+ refresh.call
44
+ @terminal.done GemfileLockRefresh::LOCKFILE
45
+
46
+ chemins + [ GemfileLockRefresh::LOCKFILE ]
47
+ end
48
+
33
49
  def afficher_changements(courante)
34
50
  lien = ChangesSince.url(courante)
35
51
  return unless lien
36
52
 
37
- @terminal.say "Changement : #{@terminal.link(lien)}\n"
53
+ @terminal.say "Changement : #{@terminal.link(lien)}\n\n"
54
+ end
55
+
56
+ def afficher_suggestion(suggestion)
57
+ return unless suggestion
58
+
59
+ detail = { "major" => "💥 détecté", "minor" => "✨ détecté", "patch" => "aucun commit notable détecté" }
60
+ @terminal.say "Suggestion : #{suggestion} (#{detail.fetch(suggestion)})\n"
38
61
  end
39
62
 
40
63
  def confirmer!(version)
@@ -29,12 +29,15 @@ module CaptiveRelease
29
29
  def done(etape) = say(" #{ok("✅")} #{etape}")
30
30
 
31
31
  # OSC 8 : le terminal affiche `texte` mais ouvre `url` au clic, sans exposer l'URL brute.
32
- def link(url, texte = url) = "\e]8;;#{url}\e\\#{texte}\e]8;;\e\\"
33
-
34
- def bold(str) = "\e[1m#{str}\e[0m"
35
- def dim(str) = "\e[2m#{str}\e[0m"
36
- def ok(str) = "\e[32m#{str}\e[0m"
37
- def warn(str) = "\e[33m#{str}\e[0m"
38
- def error(str) = "\e[31m#{str}\e[0m"
32
+ # Le texte est souligné pour signaler qu'il est cliquable, certains terminaux ne le faisant
33
+ # pas eux-mêmes pour les liens OSC 8.
34
+ def link(url, texte = url) = "\e]8;;#{url}\e\\#{underline(texte)}\e]8;;\e\\"
35
+
36
+ def bold(str) = "\e[1m#{str}\e[0m"
37
+ def underline(str) = "\e[4m#{str}\e[0m"
38
+ def dim(str) = "\e[2m#{str}\e[0m"
39
+ def ok(str) = "\e[32m#{str}\e[0m"
40
+ def warn(str) = "\e[33m#{str}\e[0m"
41
+ def error(str) = "\e[31m#{str}\e[0m"
39
42
  end
40
43
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CaptiveRelease
4
- VERSION = "0.5.0"
4
+ VERSION = "0.7.0"
5
5
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CaptiveRelease
4
+ # Classe la taille réelle d'un bump en comparant les composants major.minor.patch entre
5
+ # deux versions - y compris un semver tapé à la main, qui ne porte plus de "type" une fois
6
+ # sorti de VersionChoice.
7
+ class VersionSeverity
8
+ def self.classify(from:, to:)
9
+ depart = from.split(".").map(&:to_i)
10
+ arrivee = to.split(".").map(&:to_i)
11
+
12
+ return "major" if depart[0] != arrivee[0]
13
+ return "minor" if depart[1] != arrivee[1]
14
+ return "patch" if depart[2] != arrivee[2]
15
+
16
+ "other"
17
+ end
18
+ end
19
+ end
@@ -12,6 +12,11 @@ require_relative "captive_release/framework_detector"
12
12
  require_relative "captive_release/version_detector"
13
13
  require_relative "captive_release/shell"
14
14
  require_relative "captive_release/git_commands"
15
+ require_relative "captive_release/bump_severity"
16
+ require_relative "captive_release/gitmoji_severity"
17
+ require_relative "captive_release/bump_suggestion"
18
+ require_relative "captive_release/version_severity"
19
+ require_relative "captive_release/bump_safety_net"
15
20
  require_relative "captive_release/github_repo"
16
21
  require_relative "captive_release/github_http"
17
22
  require_relative "captive_release/github_api"
@@ -31,6 +36,7 @@ require_relative "captive_release/build_dispatch_run"
31
36
  require_relative "captive_release/build_command"
32
37
  require_relative "captive_release/menu"
33
38
  require_relative "captive_release/version_choice"
39
+ require_relative "captive_release/gemfile_lock_refresh"
34
40
  require_relative "captive_release/git_release"
35
41
  require_relative "captive_release/changes_since"
36
42
  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.5.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Captive Studio
@@ -24,6 +24,9 @@ files:
24
24
  - lib/captive_release/build_config.rb
25
25
  - lib/captive_release/build_config_prompt.rb
26
26
  - lib/captive_release/build_dispatch_run.rb
27
+ - lib/captive_release/bump_safety_net.rb
28
+ - lib/captive_release/bump_severity.rb
29
+ - lib/captive_release/bump_suggestion.rb
27
30
  - lib/captive_release/changes_since.rb
28
31
  - lib/captive_release/cli.rb
29
32
  - lib/captive_release/config.rb
@@ -31,6 +34,7 @@ files:
31
34
  - lib/captive_release/config_loader.rb
32
35
  - lib/captive_release/config_writer.rb
33
36
  - lib/captive_release/framework_detector.rb
37
+ - lib/captive_release/gemfile_lock_refresh.rb
34
38
  - lib/captive_release/git_commands.rb
35
39
  - lib/captive_release/git_release.rb
36
40
  - lib/captive_release/github_api.rb
@@ -39,6 +43,7 @@ files:
39
43
  - lib/captive_release/github_release.rb
40
44
  - lib/captive_release/github_repo.rb
41
45
  - lib/captive_release/github_token.rb
46
+ - lib/captive_release/gitmoji_severity.rb
42
47
  - lib/captive_release/help.rb
43
48
  - lib/captive_release/init_command.rb
44
49
  - lib/captive_release/menu.rb
@@ -59,6 +64,7 @@ files:
59
64
  - lib/captive_release/version_files_argument.rb
60
65
  - lib/captive_release/version_files_writing.rb
61
66
  - lib/captive_release/version_format.rb
67
+ - lib/captive_release/version_severity.rb
62
68
  homepage: https://github.com/captive-studio/captive-release
63
69
  licenses:
64
70
  - MIT