captive-release 0.14.0 → 0.16.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 +4 -4
- data/lib/captive_release/arrow_select.rb +37 -0
- data/lib/captive_release/build_config_prompt.rb +3 -5
- data/lib/captive_release/build_dispatch_run.rb +7 -5
- data/lib/captive_release/build_tag.rb +35 -0
- data/lib/captive_release/git_commands.rb +16 -0
- data/lib/captive_release/key_reader.rb +35 -0
- data/lib/captive_release/locales/en.yml +3 -0
- data/lib/captive_release/locales/fr.yml +3 -0
- data/lib/captive_release/menu.rb +13 -10
- data/lib/captive_release/terminal.rb +14 -7
- data/lib/captive_release/update_check.rb +1 -1
- data/lib/captive_release/version.rb +1 -1
- data/lib/captive_release.rb +3 -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: 6aec56835e050f42afb89b87243974752868add0b16f258291845acce96f8e75
|
|
4
|
+
data.tar.gz: bdfc4aeba04e48e427938799f76aeb5e172bc4d7894955a9ad470030e12696a0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ec482bf2fa5e0fa0eb93f23229ab9c9b2c1881965911c8636aaf1a07532b998cd89c0c37c4bbea2f607ab65e16a55123560b318cec9dcf379274b18665ae3f10
|
|
7
|
+
data.tar.gz: 712f8c60ac915352808867261630b3014b2aa42e9e7cb0f28a3421d6471ea06886faf396fb6215c372f9eb3d766f943626f15456b065836992a69c5ad34b0d2d
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CaptiveRelease
|
|
4
|
+
# Le choix au clavier : ↑/↓ déplacent le curseur, Entrée valide, et rend le rang choisi.
|
|
5
|
+
#
|
|
6
|
+
# Cette unité change quand le geste au clavier ou son rendu change, Menu quand la façon de
|
|
7
|
+
# choisir entre ce geste et la liste numérotée change.
|
|
8
|
+
class ArrowSelect
|
|
9
|
+
MOUVEMENTS = { up: -1, down: 1 }.freeze
|
|
10
|
+
|
|
11
|
+
def initialize(terminal:, keys:)
|
|
12
|
+
@terminal = terminal
|
|
13
|
+
@keys = keys
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call(libelles)
|
|
17
|
+
rang = 0
|
|
18
|
+
loop do
|
|
19
|
+
draw(libelles, rang)
|
|
20
|
+
touche = @keys.read
|
|
21
|
+
return rang if touche == :enter
|
|
22
|
+
|
|
23
|
+
rang = (rang + MOUVEMENTS.fetch(touche, 0)) % libelles.size
|
|
24
|
+
# Remonte d'autant de lignes que le menu en occupe, pour le redessiner sur place.
|
|
25
|
+
@terminal.prompt "\e[#{libelles.size}A\e[J"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def draw(libelles, choisi)
|
|
32
|
+
libelles.each_with_index do |libelle, rang|
|
|
33
|
+
@terminal.say rang == choisi ? @terminal.ok(" › #{libelle}") : " #{@terminal.dim(libelle)}"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -11,11 +11,9 @@ module CaptiveRelease
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def call(framework, workflow_par_defaut)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@terminal.say " #{@terminal.dim("2)")} #{Messages.t(:no)}"
|
|
18
|
-
return nil unless @terminal.ask_choice == 1
|
|
14
|
+
titre = "\n#{@terminal.bold(Messages.t(:framework_detected, framework: framework))} " \
|
|
15
|
+
"#{Messages.t(:configure_build)}"
|
|
16
|
+
return nil unless Menu.new(terminal: @terminal).choose(titre, Messages.t(:yes) => true, Messages.t(:no) => false)
|
|
19
17
|
|
|
20
18
|
BuildConfig.new(build_workflow: workflow(workflow_par_defaut))
|
|
21
19
|
end
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module CaptiveRelease
|
|
4
|
-
#
|
|
5
|
-
#
|
|
4
|
+
# Tague le commit du build, puis déclenche le workflow sur la branche courante après avoir dit
|
|
5
|
+
# ce qu'il va faire : profil, workflow, branche et tag sont ce qu'on vérifie avant de lancer.
|
|
6
6
|
#
|
|
7
7
|
# Cette unité change quand ce qu'on déclenche change, BuildCommand quand la façon de
|
|
8
8
|
# constituer la demande change.
|
|
@@ -13,7 +13,8 @@ module CaptiveRelease
|
|
|
13
13
|
|
|
14
14
|
def call(profil:, workflow:, jeton:)
|
|
15
15
|
branche = GitCommands.current_branch
|
|
16
|
-
|
|
16
|
+
tag = BuildTag.pose!
|
|
17
|
+
annoncer(profil, workflow, branche, tag)
|
|
17
18
|
|
|
18
19
|
url = GithubDispatch.dispatch(workflow_file: workflow, ref: branche,
|
|
19
20
|
inputs: { "profile" => profil },
|
|
@@ -21,17 +22,18 @@ module CaptiveRelease
|
|
|
21
22
|
|
|
22
23
|
@terminal.done Messages.t(:build_dispatched)
|
|
23
24
|
@terminal.say " #{url}\n"
|
|
24
|
-
rescue WorkflowIntrouvable => e
|
|
25
|
+
rescue WorkflowIntrouvable, BuildTagImpossible => e
|
|
25
26
|
@terminal.say @terminal.error(e.message)
|
|
26
27
|
exit 1
|
|
27
28
|
end
|
|
28
29
|
|
|
29
30
|
private
|
|
30
31
|
|
|
31
|
-
def annoncer(profil, workflow, branche)
|
|
32
|
+
def annoncer(profil, workflow, branche, tag)
|
|
32
33
|
@terminal.say Messages.t(:dispatch_profile, value: @terminal.bold(profil))
|
|
33
34
|
@terminal.say Messages.t(:dispatch_workflow, value: @terminal.bold(workflow))
|
|
34
35
|
@terminal.say Messages.t(:dispatch_branch, value: @terminal.bold(branche))
|
|
36
|
+
@terminal.say Messages.t(:dispatch_tag, value: @terminal.bold(tag))
|
|
35
37
|
@terminal.say "\n#{Messages.t(:dispatching)}\n"
|
|
36
38
|
end
|
|
37
39
|
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CaptiveRelease
|
|
4
|
+
# Le build ne peut pas être tagué : HEAD absent du remote, ou aucun tag de release.
|
|
5
|
+
class BuildTagImpossible < StandardError; end
|
|
6
|
+
|
|
7
|
+
# Le tag qui marque le commit d'un build : build/<prochaine version>-build.<N>+g<sha>.
|
|
8
|
+
#
|
|
9
|
+
# Il ne commence jamais par v, préfixe qui déclenche les déploiements. Sans build/, il reste
|
|
10
|
+
# un semver qui se trie entre la release qu'il suit et la suivante.
|
|
11
|
+
module BuildTag
|
|
12
|
+
DESCRIBE = /\Av(?<version>.+)-(?<distance>\d+)-(?<sha>g\h+)\z/
|
|
13
|
+
|
|
14
|
+
def self.pose!
|
|
15
|
+
raise BuildTagImpossible, Messages.t(:build_head_not_pushed) unless GitCommands.head_pushed?
|
|
16
|
+
|
|
17
|
+
tag = nom(GitCommands.describe_release)
|
|
18
|
+
GitCommands.tag_and_push(tag)
|
|
19
|
+
tag
|
|
20
|
+
rescue RuntimeError => e # tag ou push refusé par git : un message, pas une trace
|
|
21
|
+
raise BuildTagImpossible, e.message
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Depuis une pré-release, la prochaine version est son cœur : on prolonge la pré-release
|
|
25
|
+
# plutôt que de viser un patch qui sortirait après elle.
|
|
26
|
+
def self.nom(describe)
|
|
27
|
+
morceaux = describe.match(DESCRIBE)
|
|
28
|
+
raise BuildTagImpossible, Messages.t(:build_no_release_tag) unless morceaux && Semver.valid?(morceaux[:version])
|
|
29
|
+
|
|
30
|
+
version, distance, sha = morceaux.values_at(:version, :distance, :sha)
|
|
31
|
+
base = Semver.prerelease?(version) ? "#{version}." : "#{VersionBumper.bump(version, "patch")}-"
|
|
32
|
+
"build/#{base}build.#{distance}+#{sha}"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -49,6 +49,22 @@ module CaptiveRelease
|
|
|
49
49
|
Shell.run? "git push origin HEAD"
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
+
# --match ignore les tags build/ : seul un tag de release sert de point de départ.
|
|
53
|
+
def self.describe_release
|
|
54
|
+
Shell.capture("git describe --tags --long --match 'v[0-9]*' 2>/dev/null")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Vu depuis les branches de suivi locales : un commit qu'aucune ne contient n'existe pas
|
|
58
|
+
# pour le workflow qui le buildera.
|
|
59
|
+
def self.head_pushed? = !Shell.capture("git branch -r --contains HEAD 2>/dev/null").empty?
|
|
60
|
+
|
|
61
|
+
# Relancer un build sur le même commit retombe sur le même tag : on le repousse sans
|
|
62
|
+
# le recréer.
|
|
63
|
+
def self.tag_and_push(nom)
|
|
64
|
+
Shell.run! "git tag #{Shell.escape(nom)}" unless tag?(Shell.escape(nom))
|
|
65
|
+
Shell.run! "git push origin refs/tags/#{Shell.escape(nom)}"
|
|
66
|
+
end
|
|
67
|
+
|
|
52
68
|
# Pousse HEAD sous un autre nom sans quitter la branche courante.
|
|
53
69
|
def self.push_branch(nom)
|
|
54
70
|
Shell.run! "git push origin HEAD:refs/heads/#{Shell.escape(nom)}"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "io/console"
|
|
4
|
+
|
|
5
|
+
module CaptiveRelease
|
|
6
|
+
# Le clavier touche par touche, en mode raw, pour le sélecteur à flèches.
|
|
7
|
+
#
|
|
8
|
+
# Cette unité change quand la lecture des touches change, Terminal quand l'habillage ou la
|
|
9
|
+
# saisie ligne à ligne change.
|
|
10
|
+
class KeyReader
|
|
11
|
+
KEYS = { "\e[A" => :up, "\e[B" => :down, "\r" => :enter, "\n" => :enter, "k" => :up, "j" => :down }.freeze
|
|
12
|
+
|
|
13
|
+
# Une flèche arrive d'un bloc en trois octets (`\e[A`) : les lire en une fois évite
|
|
14
|
+
# d'attendre une suite qui ne viendrait pas après un Échap seul.
|
|
15
|
+
RAFALE = 3
|
|
16
|
+
|
|
17
|
+
def initialize(input: $stdin, output: $stdout)
|
|
18
|
+
@input = input
|
|
19
|
+
@output = output
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Le mode raw exige un vrai terminal des deux côtés. Ailleurs (CI, pipe, specs), le menu
|
|
23
|
+
# numéroté prend le relais.
|
|
24
|
+
def interactive? = @input.tty? && @output.tty?
|
|
25
|
+
|
|
26
|
+
# :up, :down, :enter, ou le caractère lu. Ctrl-C reste une interruption, le mode raw
|
|
27
|
+
# l'ayant sinon avalé.
|
|
28
|
+
def read
|
|
29
|
+
touche = @input.raw { |io| io.readpartial(RAFALE) }
|
|
30
|
+
raise Interrupt if touche == "\u0003"
|
|
31
|
+
|
|
32
|
+
KEYS.fetch(touche, touche)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -71,6 +71,9 @@ en:
|
|
|
71
71
|
dispatch_profile: 'Profile: %{value}'
|
|
72
72
|
dispatch_workflow: 'Workflow: %{value}'
|
|
73
73
|
dispatch_branch: 'Branch: %{value}'
|
|
74
|
+
build_head_not_pushed: HEAD is not pushed to the remote. Push the branch before building.
|
|
75
|
+
build_no_release_tag: No v* release tag found. Make a first release with captive-release new.
|
|
76
|
+
dispatch_tag: 'Tag: %{value}'
|
|
74
77
|
dispatching: Dispatching...
|
|
75
78
|
build_dispatched: Build dispatched!
|
|
76
79
|
workflow_not_found: |-
|
|
@@ -71,6 +71,9 @@ fr:
|
|
|
71
71
|
dispatch_profile: 'Profil : %{value}'
|
|
72
72
|
dispatch_workflow: 'Workflow : %{value}'
|
|
73
73
|
dispatch_branch: 'Branche : %{value}'
|
|
74
|
+
build_head_not_pushed: HEAD n'est pas poussé sur le remote. Poussez la branche avant de lancer le build.
|
|
75
|
+
build_no_release_tag: Aucun tag de release v* trouvé. Faites une première release avec captive-release new.
|
|
76
|
+
dispatch_tag: 'Tag : %{value}'
|
|
74
77
|
dispatching: Déclenchement...
|
|
75
78
|
build_dispatched: Build déclenché !
|
|
76
79
|
workflow_not_found: |-
|
data/lib/captive_release/menu.rb
CHANGED
|
@@ -3,28 +3,31 @@
|
|
|
3
3
|
module CaptiveRelease
|
|
4
4
|
# Une liste numérotée, un choix, et le refus de ce qui n'est pas dans la liste.
|
|
5
5
|
#
|
|
6
|
-
# Un rang
|
|
6
|
+
# Un rang hors liste est refusé, négatif compris : sans cette garde, une saisie non
|
|
7
7
|
# numérique vaut 0, donc l'indice -1, et Ruby rendait silencieusement la dernière entrée.
|
|
8
|
+
# Dans un terminal, le choix se fait aux flèches (ArrowSelect) et ne peut sortir de la liste.
|
|
8
9
|
class Menu
|
|
9
|
-
def initialize(terminal:)
|
|
10
|
+
def initialize(terminal:, keys: KeyReader.new)
|
|
10
11
|
@terminal = terminal
|
|
12
|
+
@keys = keys
|
|
11
13
|
end
|
|
12
14
|
|
|
13
15
|
# entries : un Hash {libellé affiché => valeur rendue}, dans l'ordre d'affichage.
|
|
14
16
|
def choose(title, entries)
|
|
15
17
|
@terminal.say "#{title}\n"
|
|
16
|
-
entries.
|
|
17
|
-
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
rang = @terminal.ask_choice - 1
|
|
21
|
-
refuse if rang.negative? || rang >= entries.size
|
|
22
|
-
|
|
23
|
-
entries.values[rang]
|
|
18
|
+
libelles = entries.keys
|
|
19
|
+
entries.values[@keys.interactive? ? ArrowSelect.new(terminal: @terminal, keys: @keys).call(libelles) : numbered(libelles)]
|
|
24
20
|
end
|
|
25
21
|
|
|
26
22
|
private
|
|
27
23
|
|
|
24
|
+
def numbered(libelles)
|
|
25
|
+
libelles.each_with_index { |libelle, rang| @terminal.say " #{@terminal.dim("#{rang + 1})")} #{libelle}" }
|
|
26
|
+
rang = @terminal.ask_choice - 1
|
|
27
|
+
refuse unless rang.between?(0, libelles.size - 1)
|
|
28
|
+
rang
|
|
29
|
+
end
|
|
30
|
+
|
|
28
31
|
def refuse
|
|
29
32
|
@terminal.say @terminal.error(Messages.t(:invalid_choice))
|
|
30
33
|
exit 1
|
|
@@ -6,9 +6,12 @@ module CaptiveRelease
|
|
|
6
6
|
# Cette unité change quand l'habillage change, les commandes quand leur geste change. Les
|
|
7
7
|
# deux flux sont injectés, ce qui rend chaque commande testable sans toucher à $stdin.
|
|
8
8
|
class Terminal
|
|
9
|
-
|
|
9
|
+
# https://no-color.org : la couleur s'éteint avec NO_COLOR, ou quand la sortie n'est pas un
|
|
10
|
+
# terminal (pipe, fichier de log) où les séquences ANSI deviendraient du bruit.
|
|
11
|
+
def initialize(input: $stdin, output: $stdout, color: output.tty? && ENV.fetch("NO_COLOR", "").empty?)
|
|
10
12
|
@input = input
|
|
11
13
|
@output = output
|
|
14
|
+
@color = color
|
|
12
15
|
end
|
|
13
16
|
|
|
14
17
|
def say(message = "") = @output.puts(message)
|
|
@@ -33,11 +36,15 @@ module CaptiveRelease
|
|
|
33
36
|
# pas eux-mêmes pour les liens OSC 8.
|
|
34
37
|
def link(url, texte = url) = "\e]8;;#{url}\e\\#{underline(texte)}\e]8;;\e\\"
|
|
35
38
|
|
|
36
|
-
def bold(str) =
|
|
37
|
-
def underline(str) =
|
|
38
|
-
def dim(str) =
|
|
39
|
-
def ok(str) =
|
|
40
|
-
def warn(str) =
|
|
41
|
-
def error(str) =
|
|
39
|
+
def bold(str) = paint(1, str)
|
|
40
|
+
def underline(str) = paint(4, str)
|
|
41
|
+
def dim(str) = paint(2, str)
|
|
42
|
+
def ok(str) = paint(32, str)
|
|
43
|
+
def warn(str) = paint(33, str)
|
|
44
|
+
def error(str) = paint(31, str)
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def paint(code, str) = @color ? "\e[#{code}m#{str}\e[0m" : str.to_s
|
|
42
49
|
end
|
|
43
50
|
end
|
|
@@ -29,7 +29,7 @@ module CaptiveRelease
|
|
|
29
29
|
derniere = @thread&.join(ATTENTE)&.value
|
|
30
30
|
return unless derniere && Gem::Version.new(derniere) > Gem::Version.new(courante)
|
|
31
31
|
|
|
32
|
-
@output.puts
|
|
32
|
+
@output.puts Terminal.new(output: @output).warn(Messages.t(:update_available, current: courante, latest: derniere))
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
private
|
data/lib/captive_release.rb
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
require_relative "captive_release/version"
|
|
4
4
|
require_relative "captive_release/messages"
|
|
5
5
|
require_relative "captive_release/semver"
|
|
6
|
+
require_relative "captive_release/arrow_select"
|
|
7
|
+
require_relative "captive_release/key_reader"
|
|
6
8
|
require_relative "captive_release/prerelease_iteration"
|
|
7
9
|
require_relative "captive_release/version_bumper"
|
|
8
10
|
require_relative "captive_release/ruby_gems_version"
|
|
@@ -37,6 +39,7 @@ require_relative "captive_release/config_writer"
|
|
|
37
39
|
require_relative "captive_release/version_files_argument"
|
|
38
40
|
require_relative "captive_release/version_file_bootstrap"
|
|
39
41
|
require_relative "captive_release/init_command"
|
|
42
|
+
require_relative "captive_release/build_tag"
|
|
40
43
|
require_relative "captive_release/build_dispatch_run"
|
|
41
44
|
require_relative "captive_release/build_command"
|
|
42
45
|
require_relative "captive_release/menu"
|
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.
|
|
4
|
+
version: 0.16.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Captive Studio
|
|
@@ -20,10 +20,12 @@ extra_rdoc_files: []
|
|
|
20
20
|
files:
|
|
21
21
|
- bin/captive-release
|
|
22
22
|
- lib/captive_release.rb
|
|
23
|
+
- lib/captive_release/arrow_select.rb
|
|
23
24
|
- lib/captive_release/build_command.rb
|
|
24
25
|
- lib/captive_release/build_config.rb
|
|
25
26
|
- lib/captive_release/build_config_prompt.rb
|
|
26
27
|
- lib/captive_release/build_dispatch_run.rb
|
|
28
|
+
- lib/captive_release/build_tag.rb
|
|
27
29
|
- lib/captive_release/bump_safety_net.rb
|
|
28
30
|
- lib/captive_release/bump_severity.rb
|
|
29
31
|
- lib/captive_release/bump_suggestion.rb
|
|
@@ -49,6 +51,7 @@ files:
|
|
|
49
51
|
- lib/captive_release/gitmoji_severity.rb
|
|
50
52
|
- lib/captive_release/help.rb
|
|
51
53
|
- lib/captive_release/init_command.rb
|
|
54
|
+
- lib/captive_release/key_reader.rb
|
|
52
55
|
- lib/captive_release/locale.rb
|
|
53
56
|
- lib/captive_release/locales/en.yml
|
|
54
57
|
- lib/captive_release/locales/fr.yml
|