captive-release 0.7.1 → 0.7.2
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/build_command.rb +4 -4
- data/lib/captive_release/build_config_prompt.rb +6 -5
- data/lib/captive_release/build_dispatch_run.rb +5 -5
- data/lib/captive_release/bump_safety_net.rb +2 -5
- data/lib/captive_release/cli.rb +1 -1
- data/lib/captive_release/config.rb +1 -1
- data/lib/captive_release/config_file.rb +1 -1
- data/lib/captive_release/config_loader.rb +3 -5
- data/lib/captive_release/config_writer.rb +4 -5
- data/lib/captive_release/git_release.rb +1 -1
- data/lib/captive_release/github_api.rb +4 -10
- data/lib/captive_release/github_error_message.rb +35 -0
- data/lib/captive_release/github_http.rb +2 -2
- data/lib/captive_release/github_release.rb +29 -2
- data/lib/captive_release/github_repo.rb +1 -1
- data/lib/captive_release/help.rb +6 -36
- data/lib/captive_release/init_command.rb +3 -3
- data/lib/captive_release/locale.rb +30 -0
- data/lib/captive_release/locales/en.yml +106 -0
- data/lib/captive_release/locales/fr.yml +106 -0
- data/lib/captive_release/menu.rb +1 -1
- data/lib/captive_release/messages.rb +31 -0
- data/lib/captive_release/release_command.rb +4 -31
- data/lib/captive_release/release_confirmation.rb +21 -0
- data/lib/captive_release/release_overview.rb +40 -0
- data/lib/captive_release/release_preconditions.rb +2 -2
- data/lib/captive_release/release_publication.rb +2 -2
- data/lib/captive_release/shell.rb +1 -1
- data/lib/captive_release/version.rb +1 -1
- data/lib/captive_release/version_bumper.rb +2 -2
- data/lib/captive_release/version_choice.rb +5 -5
- data/lib/captive_release/version_file.rb +3 -3
- data/lib/captive_release/version_file_bootstrap.rb +3 -3
- data/lib/captive_release/version_file_selection.rb +3 -3
- data/lib/captive_release/version_files_writing.rb +1 -1
- data/lib/captive_release.rb +3 -0
- metadata +23 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e40b1f1d86f5fd4ac74c2567a5c08ca52038229e77fdd2e9eedfb16d20d0edd7
|
|
4
|
+
data.tar.gz: 388299f2cc9531dd30dc47fcd83f49ebe841929a5f3fb379928e18d83f65f7a8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dca417210074ab658e94f172ecbd2ac5f19568ce9dd99416a7bc99d7ae5cab57e0ea9c6c7bc19bff36bdfc78fff61184a0159cb370caacc20994da6d793af96e
|
|
7
|
+
data.tar.gz: 2e30da7a1d7db50c419f66cef9f479b5384590253802553bfd9a425d00443fb9da212240cbcd4ec69e92100478d1f843b48549d076e771883bdc8b1b8452a292
|
|
@@ -26,8 +26,8 @@ module CaptiveRelease
|
|
|
26
26
|
def refuser_sans_configuration
|
|
27
27
|
return if @config.expo? || @config.capacitor?
|
|
28
28
|
|
|
29
|
-
@terminal.say @terminal.error(
|
|
30
|
-
@terminal.say "
|
|
29
|
+
@terminal.say @terminal.error(Messages.t(:no_build_config, file: CONFIG_FILE))
|
|
30
|
+
@terminal.say "#{Messages.t(:run_init_for_build, command: @terminal.bold("captive-release init"))}\n\n"
|
|
31
31
|
exit 1
|
|
32
32
|
end
|
|
33
33
|
|
|
@@ -35,12 +35,12 @@ module CaptiveRelease
|
|
|
35
35
|
jeton = @token_provider.call
|
|
36
36
|
return jeton if jeton
|
|
37
37
|
|
|
38
|
-
@terminal.say @terminal.error(
|
|
38
|
+
@terminal.say @terminal.error(Messages.t(:build_token_required))
|
|
39
39
|
exit 1
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
def demander_le_profil
|
|
43
|
-
Menu.new(terminal: @terminal).choose(
|
|
43
|
+
Menu.new(terminal: @terminal).choose(Messages.t(:choose_profile),
|
|
44
44
|
{ "staging" => "staging",
|
|
45
45
|
"production" => "production", })
|
|
46
46
|
end
|
|
@@ -11,10 +11,10 @@ module CaptiveRelease
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def call(framework, workflow_par_defaut)
|
|
14
|
-
@terminal.say "\n#{@terminal.bold(
|
|
15
|
-
"
|
|
16
|
-
@terminal.say " #{@terminal.dim("1)")}
|
|
17
|
-
@terminal.say " #{@terminal.dim("2)")}
|
|
14
|
+
@terminal.say "\n#{@terminal.bold(Messages.t(:framework_detected, framework: framework))} " \
|
|
15
|
+
"#{Messages.t(:configure_build)}\n"
|
|
16
|
+
@terminal.say " #{@terminal.dim("1)")} #{Messages.t(:yes)}"
|
|
17
|
+
@terminal.say " #{@terminal.dim("2)")} #{Messages.t(:no)}"
|
|
18
18
|
return nil unless @terminal.ask_choice == 1
|
|
19
19
|
|
|
20
20
|
BuildConfig.new(build_workflow: workflow(workflow_par_defaut))
|
|
@@ -23,7 +23,8 @@ module CaptiveRelease
|
|
|
23
23
|
private
|
|
24
24
|
|
|
25
25
|
def workflow(workflow_par_defaut)
|
|
26
|
-
|
|
26
|
+
par_defaut = @terminal.dim(Messages.t(:workflow_default, workflow: workflow_par_defaut))
|
|
27
|
+
@terminal.say "\n#{Messages.t(:workflow_file_name, hint: par_defaut)}"
|
|
27
28
|
saisie = @terminal.ask
|
|
28
29
|
saisie.empty? ? workflow_par_defaut : saisie
|
|
29
30
|
end
|
|
@@ -19,17 +19,17 @@ module CaptiveRelease
|
|
|
19
19
|
inputs: { "profile" => profil },
|
|
20
20
|
remote_url: GitCommands.remote_url, token: jeton)
|
|
21
21
|
|
|
22
|
-
@terminal.done
|
|
22
|
+
@terminal.done Messages.t(:build_dispatched)
|
|
23
23
|
@terminal.say " #{url}\n"
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
private
|
|
27
27
|
|
|
28
28
|
def annoncer(profil, workflow, branche)
|
|
29
|
-
@terminal.say
|
|
30
|
-
@terminal.say
|
|
31
|
-
@terminal.say
|
|
32
|
-
@terminal.say "\
|
|
29
|
+
@terminal.say Messages.t(:dispatch_profile, value: @terminal.bold(profil))
|
|
30
|
+
@terminal.say Messages.t(:dispatch_workflow, value: @terminal.bold(workflow))
|
|
31
|
+
@terminal.say Messages.t(:dispatch_branch, value: @terminal.bold(branche))
|
|
32
|
+
@terminal.say "\n#{Messages.t(:dispatching)}\n"
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
end
|
|
@@ -21,11 +21,8 @@ module CaptiveRelease
|
|
|
21
21
|
private
|
|
22
22
|
|
|
23
23
|
def avertir_et_confirmer!(suggestion, choisie)
|
|
24
|
-
@terminal.say @terminal.warn(
|
|
25
|
-
|
|
26
|
-
)
|
|
27
|
-
reponse = @terminal.ask("Continuer quand même ? [y/N] ").downcase
|
|
28
|
-
exit 1 unless reponse == "y"
|
|
24
|
+
@terminal.say @terminal.warn("\n#{Messages.t(:bump_warning, suggestion: suggestion, chosen: choisie)}")
|
|
25
|
+
exit 1 unless Messages.yes?(@terminal.ask(Messages.t(:continue_anyway)))
|
|
29
26
|
end
|
|
30
27
|
end
|
|
31
28
|
end
|
data/lib/captive_release/cli.rb
CHANGED
|
@@ -9,7 +9,7 @@ module CaptiveRelease
|
|
|
9
9
|
attr_reader :version_files, :expo, :capacitor
|
|
10
10
|
|
|
11
11
|
def initialize(version_files, expo = nil, capacitor = nil)
|
|
12
|
-
raise ArgumentError,
|
|
12
|
+
raise ArgumentError, Messages.t(:version_files_empty) if version_files.empty?
|
|
13
13
|
|
|
14
14
|
@version_files = version_files
|
|
15
15
|
@expo = expo
|
|
@@ -10,7 +10,7 @@ module CaptiveRelease
|
|
|
10
10
|
# configuration change.
|
|
11
11
|
module ConfigFile
|
|
12
12
|
def self.load(path = CONFIG_FILE)
|
|
13
|
-
raise
|
|
13
|
+
raise Messages.t(:config_not_found, file: CONFIG_FILE) unless File.exist?(path)
|
|
14
14
|
|
|
15
15
|
data = JSON.parse(File.read(path))
|
|
16
16
|
Config.new(
|
|
@@ -23,11 +23,9 @@ module CaptiveRelease
|
|
|
23
23
|
private
|
|
24
24
|
|
|
25
25
|
def refuser
|
|
26
|
-
@terminal.say @terminal.error(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
@terminal.say "Créez un fichier #{@terminal.bold("VERSION")} " \
|
|
30
|
-
"ou lancez #{@terminal.bold("captive-release init")}.\n"
|
|
26
|
+
@terminal.say @terminal.error(Messages.t(:no_version_file))
|
|
27
|
+
@terminal.say "#{Messages.t(:create_version_or_init, file: @terminal.bold("VERSION"),
|
|
28
|
+
command: @terminal.bold("captive-release init"))}\n"
|
|
31
29
|
exit 1
|
|
32
30
|
end
|
|
33
31
|
end
|
|
@@ -25,17 +25,16 @@ module CaptiveRelease
|
|
|
25
25
|
private
|
|
26
26
|
|
|
27
27
|
def refuser_le_vide
|
|
28
|
-
@terminal.say @terminal.error(
|
|
28
|
+
@terminal.say @terminal.error(Messages.t(:no_files_selected))
|
|
29
29
|
exit 1
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def annoncer(fichiers, expo, capacitor)
|
|
33
|
-
@terminal.say "\n#{@terminal.ok(
|
|
34
|
-
|
|
35
|
-
@terminal.say " #{@terminal.dim("Expo build workflow: #{expo.build_workflow}")}" if expo
|
|
33
|
+
@terminal.say "\n#{@terminal.ok(Messages.t(:config_written, count: fichiers.size))}"
|
|
34
|
+
@terminal.say " #{@terminal.dim(Messages.t(:expo_workflow, workflow: expo.build_workflow))}" if expo
|
|
36
35
|
return unless capacitor
|
|
37
36
|
|
|
38
|
-
@terminal.say " #{@terminal.dim(
|
|
37
|
+
@terminal.say " #{@terminal.dim(Messages.t(:capacitor_workflow, workflow: capacitor.build_workflow))}"
|
|
39
38
|
end
|
|
40
39
|
|
|
41
40
|
def demander(framework, workflow_par_defaut)
|
|
@@ -12,7 +12,7 @@ module CaptiveRelease
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def call(version, paths)
|
|
15
|
-
@terminal.say "\n#{@terminal.bold(
|
|
15
|
+
@terminal.say "\n#{@terminal.bold(Messages.t(:running_git))}"
|
|
16
16
|
GitCommands.add_and_commit(version, paths)
|
|
17
17
|
@terminal.done %(git commit "🔖 Set version v#{version}")
|
|
18
18
|
GitCommands.tag(version)
|
|
@@ -4,14 +4,15 @@ require "json"
|
|
|
4
4
|
require "uri"
|
|
5
5
|
require_relative "github_http"
|
|
6
6
|
require_relative "github_repo"
|
|
7
|
+
require_relative "github_error_message"
|
|
7
8
|
|
|
8
9
|
module CaptiveRelease
|
|
9
10
|
# L'API GitHub d'un dépôt : à quelle adresse poster, et ce qui compte comme un échec. Les
|
|
10
11
|
# commandes lui parlent par délégation plutôt que d'en hériter le transport, ce qui leur
|
|
11
12
|
# laisse la seule composition de leur payload.
|
|
12
13
|
#
|
|
13
|
-
# Cette unité change quand l'adressage ou
|
|
14
|
-
# le transport change.
|
|
14
|
+
# Cette unité change quand l'adressage ou ce qui compte comme un échec changent, GithubHttp
|
|
15
|
+
# quand le transport change, GithubErrorMessage quand la forme des erreurs change.
|
|
15
16
|
class GithubApi
|
|
16
17
|
HOTE = "https://api.github.com"
|
|
17
18
|
|
|
@@ -31,15 +32,8 @@ module CaptiveRelease
|
|
|
31
32
|
|
|
32
33
|
private
|
|
33
34
|
|
|
34
|
-
# Le corps d'une erreur GitHub est du JSON qui nomme la cause, sauf quand il n'en est pas :
|
|
35
|
-
# un 500 rend parfois du texte brut, qu'il vaut mieux montrer que d'avaler.
|
|
36
35
|
def refuser(reponse)
|
|
37
|
-
|
|
38
|
-
JSON.parse(reponse.body)
|
|
39
|
-
rescue StandardError
|
|
40
|
-
{}
|
|
41
|
-
end
|
|
42
|
-
raise "GitHub API error (#{reponse.code}): #{data["message"] || reponse.body}"
|
|
36
|
+
raise Messages.t(:github_api_error, code: reponse.code, message: GithubErrorMessage.from(reponse.body.to_s))
|
|
43
37
|
end
|
|
44
38
|
end
|
|
45
39
|
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module CaptiveRelease
|
|
6
|
+
# Ce qu'un corps d'erreur GitHub dit de la cause. Le corps est du JSON qui la nomme, sauf
|
|
7
|
+
# quand il n'en est pas : un 500 rend parfois du texte brut, qu'il vaut mieux montrer que
|
|
8
|
+
# d'avaler. Et un 422 ne dit que « Validation Failed » : la cause réelle est dans `errors`.
|
|
9
|
+
#
|
|
10
|
+
# Cette unité change quand GitHub change la forme de ses erreurs, GithubApi quand ce qui
|
|
11
|
+
# compte comme un échec change.
|
|
12
|
+
module GithubErrorMessage
|
|
13
|
+
def self.from(brut)
|
|
14
|
+
data = analyser(brut)
|
|
15
|
+
details = Array(data["errors"]).map { |erreur| detail(erreur) }.reject(&:empty?)
|
|
16
|
+
principal = data["message"] || brut
|
|
17
|
+
details.empty? ? principal : "#{principal} — #{details.join("; ")}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.analyser(brut)
|
|
21
|
+
data = JSON.parse(brut)
|
|
22
|
+
data.is_a?(Hash) ? data : {}
|
|
23
|
+
rescue StandardError
|
|
24
|
+
{}
|
|
25
|
+
end
|
|
26
|
+
private_class_method :analyser
|
|
27
|
+
|
|
28
|
+
def self.detail(erreur)
|
|
29
|
+
return erreur.to_s unless erreur.is_a?(Hash)
|
|
30
|
+
|
|
31
|
+
erreur["message"] || [ erreur["field"], erreur["code"] ].compact.join(" ")
|
|
32
|
+
end
|
|
33
|
+
private_class_method :detail
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -28,7 +28,7 @@ module CaptiveRelease
|
|
|
28
28
|
uri = URI(ailleurs!(reponse))
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
raise
|
|
31
|
+
raise Messages.t(:too_many_redirects)
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
private
|
|
@@ -45,7 +45,7 @@ module CaptiveRelease
|
|
|
45
45
|
|
|
46
46
|
def ailleurs!(reponse)
|
|
47
47
|
reponse["location"] || reponse["Location"] ||
|
|
48
|
-
raise(
|
|
48
|
+
raise(Messages.t(:redirect_without_location))
|
|
49
49
|
end
|
|
50
50
|
end
|
|
51
51
|
end
|
|
@@ -6,6 +6,11 @@ module CaptiveRelease
|
|
|
6
6
|
# Crée la release GitHub d'une version. Elle compose ce qu'elle demande ; l'API, son
|
|
7
7
|
# authentification et ses échecs vivent dans GithubApi.
|
|
8
8
|
class GithubRelease
|
|
9
|
+
# GitHub refuse (422) un corps de release plus long. Sans tag précédent, les notes
|
|
10
|
+
# générées couvrent tout l'historique et dépassent vite cette limite.
|
|
11
|
+
CORPS_MAX = 125_000
|
|
12
|
+
COUPURE = "\n\n…"
|
|
13
|
+
|
|
9
14
|
def self.create(version:, remote_url:, token:, api: nil)
|
|
10
15
|
api ||= GithubApi.new(remote_url: remote_url, token: token)
|
|
11
16
|
new(api).create(version)
|
|
@@ -19,10 +24,32 @@ module CaptiveRelease
|
|
|
19
24
|
# composer ici obligeait à lister les commits, et sans tag précédent cette liste était
|
|
20
25
|
# l'historique entier du dépôt.
|
|
21
26
|
def create(version)
|
|
27
|
+
tag = "v#{version}"
|
|
22
28
|
@api.post("releases",
|
|
23
|
-
{ "tag_name" =>
|
|
24
|
-
"
|
|
29
|
+
{ "tag_name" => tag, "name" => tag, "body" => notes(tag),
|
|
30
|
+
"draft" => false, "prerelease" => false, },
|
|
25
31
|
attendu: "201")["html_url"]
|
|
26
32
|
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def notes(tag)
|
|
37
|
+
corps = @api.post("releases/generate-notes", { "tag_name" => tag }, attendu: "200")["body"].to_s
|
|
38
|
+
corps.length > CORPS_MAX ? tronquer(corps) : corps
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Coupe à une fin de ligne et garde le lien Full Changelog final, qui mène à la liste complète.
|
|
42
|
+
def tronquer(corps)
|
|
43
|
+
tete, pied = separer_pied(corps)
|
|
44
|
+
place = CORPS_MAX - COUPURE.length - pied.length
|
|
45
|
+
coupe = tete[0, place]
|
|
46
|
+
coupe = coupe[0, coupe.rindex("\n")] if coupe.include?("\n")
|
|
47
|
+
"#{coupe}#{COUPURE}#{pied}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def separer_pied(corps)
|
|
51
|
+
index = corps.rindex("\n**Full Changelog**")
|
|
52
|
+
index ? [ corps[0, index], "\n#{corps[index..]}" ] : [ corps, "" ]
|
|
53
|
+
end
|
|
27
54
|
end
|
|
28
55
|
end
|
data/lib/captive_release/help.rb
CHANGED
|
@@ -3,48 +3,18 @@
|
|
|
3
3
|
module CaptiveRelease
|
|
4
4
|
# Ce que la CLI documente d'elle-même. Cette unité change quand la documentation de
|
|
5
5
|
# l'interface change, la CLI quand son aiguillage change.
|
|
6
|
+
#
|
|
7
|
+
# L'aide est un seul texte par langue, que le traducteur lit d'un bloc : les titres s'y
|
|
8
|
+
# reconnaissent à ce qu'ils ne sont pas indentés et finissent par deux-points.
|
|
6
9
|
class Help
|
|
10
|
+
TITRES = /\Acaptive-release|^\S.*:$/
|
|
11
|
+
|
|
7
12
|
def initialize(terminal:)
|
|
8
13
|
@terminal = terminal
|
|
9
14
|
end
|
|
10
15
|
|
|
11
16
|
def show
|
|
12
|
-
@terminal.say
|
|
13
|
-
|
|
14
|
-
#{@terminal.bold("captive-release")} : Semver release tool
|
|
15
|
-
|
|
16
|
-
#{@terminal.bold("Usage:")}
|
|
17
|
-
captive-release init [--files path[:type],...]
|
|
18
|
-
captive-release new [patch|minor|major|x.y.z] [--yes]
|
|
19
|
-
captive-release version
|
|
20
|
-
|
|
21
|
-
#{@terminal.bold("Commands:")}
|
|
22
|
-
init Detect version files and write captive-release.json
|
|
23
|
-
new Bump version, commit, tag, push, create GitHub release
|
|
24
|
-
Refuse de tourner sur un arbre de travail sale.
|
|
25
|
-
Demande toujours confirmation, sauf avec --yes.
|
|
26
|
-
build Dispatch an EAS build workflow (staging/production) via GitHub Actions
|
|
27
|
-
|
|
28
|
-
#{@terminal.bold("Options for init:")}
|
|
29
|
-
--files Non-interactive: comma-separated list of files with optional type
|
|
30
|
-
Type is auto-detected (package_json or plain) when omitted
|
|
31
|
-
Example: --files=package.json,ios/VERSION:plain
|
|
32
|
-
|
|
33
|
-
#{@terminal.bold("Examples:")}
|
|
34
|
-
captive-release init
|
|
35
|
-
captive-release init --files=package.json,apps/mobile/package.json
|
|
36
|
-
captive-release new
|
|
37
|
-
captive-release new patch
|
|
38
|
-
captive-release new 2.0.0
|
|
39
|
-
captive-release new patch --yes
|
|
40
|
-
captive-release build
|
|
41
|
-
captive-release build --profile staging
|
|
42
|
-
captive-release build --profile production
|
|
43
|
-
|
|
44
|
-
#{@terminal.bold("GitHub release:")}
|
|
45
|
-
Set GITHUB_TOKEN env var or authenticate with `gh auth login`
|
|
46
|
-
|
|
47
|
-
HELP
|
|
17
|
+
@terminal.say "\n#{Messages.t(:help).gsub(TITRES) { |titre| @terminal.bold(titre) }}\n"
|
|
48
18
|
end
|
|
49
19
|
end
|
|
50
20
|
end
|
|
@@ -22,13 +22,13 @@ module CaptiveRelease
|
|
|
22
22
|
|
|
23
23
|
def avec_interaction
|
|
24
24
|
@terminal.say "\n#{@terminal.bold("captive-release init")}\n"
|
|
25
|
-
@terminal.say "
|
|
25
|
+
@terminal.say "#{Messages.t(:scanning)}\n"
|
|
26
26
|
|
|
27
27
|
trouves = VersionDetector.scan
|
|
28
|
-
return creer_un_version(
|
|
28
|
+
return creer_un_version(Messages.t(:no_version_files_found)) if trouves.empty?
|
|
29
29
|
|
|
30
30
|
retenus = selection.call(trouves)
|
|
31
|
-
return creer_un_version(
|
|
31
|
+
return creer_un_version(Messages.t(:no_files_selected)) if retenus.empty?
|
|
32
32
|
|
|
33
33
|
writer.call(retenus)
|
|
34
34
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CaptiveRelease
|
|
4
|
+
# Dans quelle langue parler au développeur : celle qu'il force, sinon celle de son système
|
|
5
|
+
# sous macOS, sinon celle que son environnement déclare, sinon l'anglais.
|
|
6
|
+
#
|
|
7
|
+
# Cette unité change quand les sources de la langue changent, Messages quand les textes
|
|
8
|
+
# changent. La langue de macOS passe devant LC_ALL et LANG : un shell les hérite souvent en
|
|
9
|
+
# anglais sans que personne ne l'ait choisi, alors que la langue du système, elle, l'a été.
|
|
10
|
+
# CAPTIVE_RELEASE_LANG reste le moyen de forcer une langue sans toucher au reste.
|
|
11
|
+
module Locale
|
|
12
|
+
SUPPORTED = %w[en fr].freeze
|
|
13
|
+
DEFAULT = "en"
|
|
14
|
+
FORCEE = "CAPTIVE_RELEASE_LANG"
|
|
15
|
+
POSIX = %w[LC_ALL LC_MESSAGES LANG].freeze
|
|
16
|
+
|
|
17
|
+
def self.detect(env: ENV, system: method(:systeme))
|
|
18
|
+
candidates = [ env[FORCEE], system.call, *env.values_at(*POSIX) ]
|
|
19
|
+
langue = candidates.compact.reject(&:empty?).first.to_s[0, 2].downcase
|
|
20
|
+
SUPPORTED.include?(langue) ? langue : DEFAULT
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Hors macOS, rien : les variables POSIX décident.
|
|
24
|
+
def self.systeme(plateforme = RUBY_PLATFORM)
|
|
25
|
+
return "" unless plateforme.include?("darwin")
|
|
26
|
+
|
|
27
|
+
`defaults read -g AppleLocale 2>/dev/null`.strip
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
en:
|
|
2
|
+
captive_release:
|
|
3
|
+
help: |
|
|
4
|
+
captive-release : Semver release tool
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
captive-release init [--files path[:type],...]
|
|
8
|
+
captive-release new [patch|minor|major|x.y.z] [--yes]
|
|
9
|
+
captive-release build [--profile staging|production]
|
|
10
|
+
captive-release version
|
|
11
|
+
|
|
12
|
+
Commands:
|
|
13
|
+
init Detect version files and write captive-release.json
|
|
14
|
+
new Bump version, commit, tag, push, create GitHub release
|
|
15
|
+
Refuses to run on a dirty working tree.
|
|
16
|
+
Always asks for confirmation, unless --yes.
|
|
17
|
+
build Dispatch a mobile build workflow (staging/production) via GitHub Actions
|
|
18
|
+
|
|
19
|
+
Options for init:
|
|
20
|
+
--files Non-interactive: comma-separated list of files with optional type
|
|
21
|
+
Type is auto-detected (package_json or plain) when omitted
|
|
22
|
+
Example: --files=package.json,ios/VERSION:plain
|
|
23
|
+
|
|
24
|
+
Examples:
|
|
25
|
+
captive-release init
|
|
26
|
+
captive-release init --files=package.json,apps/mobile/package.json
|
|
27
|
+
captive-release new
|
|
28
|
+
captive-release new patch
|
|
29
|
+
captive-release new 2.0.0
|
|
30
|
+
captive-release new patch --yes
|
|
31
|
+
captive-release build
|
|
32
|
+
captive-release build --profile staging
|
|
33
|
+
captive-release build --profile production
|
|
34
|
+
|
|
35
|
+
GitHub release:
|
|
36
|
+
Set GITHUB_TOKEN env var or authenticate with `gh auth login`
|
|
37
|
+
|
|
38
|
+
Language:
|
|
39
|
+
Detected from CAPTIVE_RELEASE_LANG, the macOS language, then LC_ALL, LC_MESSAGES or LANG (en, fr)
|
|
40
|
+
'yes': 'Yes'
|
|
41
|
+
'no': 'No'
|
|
42
|
+
invalid_choice: Invalid choice.
|
|
43
|
+
unknown_command: 'Unknown command: %{command}'
|
|
44
|
+
scanning: Scanning for version files...
|
|
45
|
+
no_version_files_found: No version files found (package.json, VERSION).
|
|
46
|
+
no_files_selected: No files selected.
|
|
47
|
+
single_file_selected: '1 file detected: selected automatically.'
|
|
48
|
+
select_prompt: 'Press %{enter} to select all, or enter the numbers to %{exclude} (e.g. 2,3):'
|
|
49
|
+
select_enter: Enter
|
|
50
|
+
select_exclude: exclude
|
|
51
|
+
create_version_file: 'Create a %{file} file? [y/N] '
|
|
52
|
+
version_file_created: "%{file} created with %{version}"
|
|
53
|
+
config_written:
|
|
54
|
+
one: "✅ captive-release.json written with %{count} version file."
|
|
55
|
+
other: "✅ captive-release.json written with %{count} version files."
|
|
56
|
+
expo_workflow: 'Expo build workflow: %{workflow}'
|
|
57
|
+
capacitor_workflow: 'Capacitor build workflow: %{workflow}'
|
|
58
|
+
framework_detected: "%{framework} project detected."
|
|
59
|
+
configure_build: Configure build dispatch?
|
|
60
|
+
workflow_file_name: 'Workflow file name %{hint}:'
|
|
61
|
+
workflow_default: "(default: %{workflow})"
|
|
62
|
+
no_version_file: No version file found (package.json, lib/**/version.rb, VERSION).
|
|
63
|
+
create_version_or_init: Create a %{file} file or run %{command}.
|
|
64
|
+
config_not_found: "%{file} not found. Run `captive-release init` first."
|
|
65
|
+
version_files_empty: version_files cannot be empty
|
|
66
|
+
no_build_config: No build configuration in %{file}.
|
|
67
|
+
run_init_for_build: Run %{command} to configure the build workflow.
|
|
68
|
+
build_token_required: GITHUB_TOKEN or `gh auth login` required to dispatch build workflows.
|
|
69
|
+
choose_profile: 'Choose profile:'
|
|
70
|
+
dispatch_profile: 'Profile: %{value}'
|
|
71
|
+
dispatch_workflow: 'Workflow: %{value}'
|
|
72
|
+
dispatch_branch: 'Branch: %{value}'
|
|
73
|
+
dispatching: Dispatching...
|
|
74
|
+
build_dispatched: Build dispatched!
|
|
75
|
+
dirty_tree: The working tree is not clean.
|
|
76
|
+
dirty_tree_hint: Commit or stash your changes before releasing.
|
|
77
|
+
current_version: 'Current version: %{version}'
|
|
78
|
+
changes: 'Changes: %{link}'
|
|
79
|
+
suggestion: 'Suggestion: %{severity} (%{detail})'
|
|
80
|
+
suggestion_major: "\U0001F4A5 detected"
|
|
81
|
+
suggestion_minor: "✨ detected"
|
|
82
|
+
suggestion_patch: no notable commit detected
|
|
83
|
+
choose_release_type: 'Choose release type:'
|
|
84
|
+
custom: Custom
|
|
85
|
+
enter_version: 'Enter version (x.y.z): '
|
|
86
|
+
invalid_bump_type: 'Invalid bump type: %{type}. Use patch|minor|major|x.y.z'
|
|
87
|
+
invalid_semver: 'Invalid semver: %{value}'
|
|
88
|
+
invalid_version: 'Invalid version: %{value}'
|
|
89
|
+
bump_warning: "⚠️ Commits suggest a %{suggestion} bump, but the chosen version is only a %{chosen}."
|
|
90
|
+
continue_anyway: 'Continue anyway? [y/N] '
|
|
91
|
+
new_version: 'New version: %{version}'
|
|
92
|
+
confirm: 'Confirm? [Y/n] '
|
|
93
|
+
updating_version_files: Updating version files...
|
|
94
|
+
refreshing_lockfile: Refreshing Gemfile.lock...
|
|
95
|
+
running_git: Running git commands...
|
|
96
|
+
creating_github_release: Creating GitHub release...
|
|
97
|
+
no_token_skip_release: "⚠️ GITHUB_TOKEN not set - skipping GitHub release creation."
|
|
98
|
+
released: Version v%{version} released successfully!
|
|
99
|
+
unknown_type: 'Unknown type %{type}. Must be one of: %{types}'
|
|
100
|
+
cannot_read_version: 'Cannot read version from %{path} : expected pattern: %{expected}'
|
|
101
|
+
file_not_found: 'File not found: %{path}'
|
|
102
|
+
command_failed: 'Command failed: %{command}'
|
|
103
|
+
github_api_error: 'GitHub API error (%{code}): %{message}'
|
|
104
|
+
too_many_redirects: Too many redirects from GitHub API
|
|
105
|
+
redirect_without_location: GitHub redirect without Location header
|
|
106
|
+
cannot_parse_remote: 'Cannot parse GitHub repo from remote URL: %{url}'
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
fr:
|
|
2
|
+
captive_release:
|
|
3
|
+
help: |
|
|
4
|
+
captive-release : Outil de release semver
|
|
5
|
+
|
|
6
|
+
Utilisation :
|
|
7
|
+
captive-release init [--files path[:type],...]
|
|
8
|
+
captive-release new [patch|minor|major|x.y.z] [--yes]
|
|
9
|
+
captive-release build [--profile staging|production]
|
|
10
|
+
captive-release version
|
|
11
|
+
|
|
12
|
+
Commandes :
|
|
13
|
+
init Détecte les fichiers de version et écrit captive-release.json
|
|
14
|
+
new Bumpe la version, commite, tague, pousse, crée la release GitHub
|
|
15
|
+
Refuse de tourner sur un arbre de travail sale.
|
|
16
|
+
Demande toujours confirmation, sauf avec --yes.
|
|
17
|
+
build Déclenche un workflow de build mobile (staging/production) via GitHub Actions
|
|
18
|
+
|
|
19
|
+
Options de init :
|
|
20
|
+
--files Non interactif : liste de fichiers séparés par des virgules, type optionnel
|
|
21
|
+
Le type est déduit (package_json ou plain) s'il est omis
|
|
22
|
+
Exemple : --files=package.json,ios/VERSION:plain
|
|
23
|
+
|
|
24
|
+
Exemples :
|
|
25
|
+
captive-release init
|
|
26
|
+
captive-release init --files=package.json,apps/mobile/package.json
|
|
27
|
+
captive-release new
|
|
28
|
+
captive-release new patch
|
|
29
|
+
captive-release new 2.0.0
|
|
30
|
+
captive-release new patch --yes
|
|
31
|
+
captive-release build
|
|
32
|
+
captive-release build --profile staging
|
|
33
|
+
captive-release build --profile production
|
|
34
|
+
|
|
35
|
+
Release GitHub :
|
|
36
|
+
Définissez la variable GITHUB_TOKEN ou authentifiez-vous avec `gh auth login`
|
|
37
|
+
|
|
38
|
+
Langue :
|
|
39
|
+
Déduite de CAPTIVE_RELEASE_LANG, de la langue de macOS, puis de LC_ALL, LC_MESSAGES ou LANG (en, fr)
|
|
40
|
+
'yes': Oui
|
|
41
|
+
'no': Non
|
|
42
|
+
invalid_choice: Choix invalide.
|
|
43
|
+
unknown_command: 'Commande inconnue : %{command}'
|
|
44
|
+
scanning: Recherche des fichiers de version...
|
|
45
|
+
no_version_files_found: Aucun fichier de version trouvé (package.json, VERSION).
|
|
46
|
+
no_files_selected: Aucun fichier sélectionné.
|
|
47
|
+
single_file_selected: '1 fichier détecté : sélectionné automatiquement.'
|
|
48
|
+
select_prompt: 'Appuyez sur %{enter} pour tout sélectionner, ou entrez les numéros à %{exclude} (ex: 2,3) :'
|
|
49
|
+
select_enter: Entrée
|
|
50
|
+
select_exclude: exclure
|
|
51
|
+
create_version_file: 'Créer un fichier %{file} ? [o/N] '
|
|
52
|
+
version_file_created: "%{file} créé avec %{version}"
|
|
53
|
+
config_written:
|
|
54
|
+
one: "✅ captive-release.json écrit avec %{count} fichier de version."
|
|
55
|
+
other: "✅ captive-release.json écrit avec %{count} fichiers de version."
|
|
56
|
+
expo_workflow: 'Workflow de build Expo : %{workflow}'
|
|
57
|
+
capacitor_workflow: 'Workflow de build Capacitor : %{workflow}'
|
|
58
|
+
framework_detected: Projet %{framework} détecté.
|
|
59
|
+
configure_build: Configurer le déclenchement du build ?
|
|
60
|
+
workflow_file_name: 'Nom du fichier de workflow %{hint} :'
|
|
61
|
+
workflow_default: "(par défaut : %{workflow})"
|
|
62
|
+
no_version_file: Aucun fichier de version trouvé (package.json, lib/**/version.rb, VERSION).
|
|
63
|
+
create_version_or_init: Créez un fichier %{file} ou lancez %{command}.
|
|
64
|
+
config_not_found: "%{file} introuvable. Lancez d'abord `captive-release init`."
|
|
65
|
+
version_files_empty: version_files ne peut pas être vide
|
|
66
|
+
no_build_config: Aucune configuration de build dans %{file}.
|
|
67
|
+
run_init_for_build: Lancez %{command} pour configurer le workflow de build.
|
|
68
|
+
build_token_required: GITHUB_TOKEN ou `gh auth login` requis pour déclencher les workflows de build.
|
|
69
|
+
choose_profile: 'Choisissez le profil :'
|
|
70
|
+
dispatch_profile: 'Profil : %{value}'
|
|
71
|
+
dispatch_workflow: 'Workflow : %{value}'
|
|
72
|
+
dispatch_branch: 'Branche : %{value}'
|
|
73
|
+
dispatching: Déclenchement...
|
|
74
|
+
build_dispatched: Build déclenché !
|
|
75
|
+
dirty_tree: L'arbre de travail n'est pas propre.
|
|
76
|
+
dirty_tree_hint: Commitez ou remisez vos modifications avant de releaser.
|
|
77
|
+
current_version: 'Version actuelle : %{version}'
|
|
78
|
+
changes: 'Changements : %{link}'
|
|
79
|
+
suggestion: 'Suggestion : %{severity} (%{detail})'
|
|
80
|
+
suggestion_major: "\U0001F4A5 détecté"
|
|
81
|
+
suggestion_minor: "✨ détecté"
|
|
82
|
+
suggestion_patch: aucun commit notable détecté
|
|
83
|
+
choose_release_type: 'Choisissez le type de release :'
|
|
84
|
+
custom: Personnalisée
|
|
85
|
+
enter_version: 'Saisissez la version (x.y.z) : '
|
|
86
|
+
invalid_bump_type: 'Type de bump invalide : %{type}. Utilisez patch|minor|major|x.y.z'
|
|
87
|
+
invalid_semver: 'Semver invalide : %{value}'
|
|
88
|
+
invalid_version: 'Version invalide : %{value}'
|
|
89
|
+
bump_warning: "⚠️ Les commits suggèrent un bump %{suggestion}, mais la version choisie n'est qu'un %{chosen}."
|
|
90
|
+
continue_anyway: 'Continuer quand même ? [o/N] '
|
|
91
|
+
new_version: 'Nouvelle version : %{version}'
|
|
92
|
+
confirm: 'Confirmer ? [O/n] '
|
|
93
|
+
updating_version_files: Mise à jour des fichiers de version...
|
|
94
|
+
refreshing_lockfile: Rafraîchissement du Gemfile.lock...
|
|
95
|
+
running_git: Exécution des commandes git...
|
|
96
|
+
creating_github_release: Création de la release GitHub...
|
|
97
|
+
no_token_skip_release: "⚠️ GITHUB_TOKEN absent - la release GitHub n'est pas créée."
|
|
98
|
+
released: Version v%{version} publiée avec succès !
|
|
99
|
+
unknown_type: 'Type inconnu %{type}. Valeurs possibles : %{types}'
|
|
100
|
+
cannot_read_version: 'Impossible de lire la version dans %{path} : motif attendu : %{expected}'
|
|
101
|
+
file_not_found: 'Fichier introuvable : %{path}'
|
|
102
|
+
command_failed: 'Échec de la commande : %{command}'
|
|
103
|
+
github_api_error: 'Erreur de l''API GitHub (%{code}) : %{message}'
|
|
104
|
+
too_many_redirects: Trop de redirections de l'API GitHub
|
|
105
|
+
redirect_without_location: Redirection GitHub sans en-tête Location
|
|
106
|
+
cannot_parse_remote: 'Impossible de déduire le dépôt GitHub du remote : %{url}'
|
data/lib/captive_release/menu.rb
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "i18n"
|
|
4
|
+
require_relative "locale"
|
|
5
|
+
|
|
6
|
+
module CaptiveRelease
|
|
7
|
+
# Les textes de la CLI dans la langue du développeur, lus par I18n depuis locales/*.yml.
|
|
8
|
+
#
|
|
9
|
+
# Cette unité change quand la façon de retrouver un texte change, les fichiers YAML quand un
|
|
10
|
+
# texte change, Locale quand la détection de la langue change. Tout passe sous la portée
|
|
11
|
+
# captive_release, pour ne pas marcher sur les traductions d'un hôte qui chargerait la gem.
|
|
12
|
+
module Messages
|
|
13
|
+
OUI = %w[y yes o oui].freeze
|
|
14
|
+
|
|
15
|
+
I18n.load_path |= Dir["#{__dir__}/locales/*.yml"]
|
|
16
|
+
|
|
17
|
+
def self.locale = @locale ||= Locale.detect
|
|
18
|
+
|
|
19
|
+
def self.locale=(langue)
|
|
20
|
+
@locale = langue
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# raise : un texte manquant doit casser la suite de tests, pas s'afficher en
|
|
24
|
+
# « Translation missing » devant le développeur.
|
|
25
|
+
def self.t(cle, **valeurs) = I18n.t(cle, scope: :captive_release, locale: locale, raise: true, **valeurs)
|
|
26
|
+
|
|
27
|
+
# « o » comme « y » : l'invite française propose [o/N], mais un réflexe anglais ne doit
|
|
28
|
+
# pas se transformer en refus.
|
|
29
|
+
def self.yes?(reponse) = OUI.include?(reponse.strip.downcase)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -15,22 +15,15 @@ module CaptiveRelease
|
|
|
15
15
|
def run(bump_arg, confirmation:)
|
|
16
16
|
ReleasePreconditions.new(terminal: @terminal).verifier!
|
|
17
17
|
courante = @config.current_version
|
|
18
|
-
|
|
19
|
-
@terminal.say "Current version: #{@terminal.bold(courante)}\n"
|
|
20
|
-
afficher_changements(courante)
|
|
21
|
-
suggestion = BumpSuggestion.for(courante)
|
|
22
|
-
afficher_suggestion(suggestion)
|
|
18
|
+
suggestion = ReleaseOverview.new(terminal: @terminal).call(courante)
|
|
23
19
|
|
|
24
20
|
prochaine = VersionChoice.new(terminal: @terminal).call(courante, bump_arg)
|
|
25
|
-
if confirmation
|
|
26
|
-
BumpSafetyNet.new(terminal: @terminal).verifier!(suggestion, courante, prochaine)
|
|
27
|
-
confirmer!(prochaine)
|
|
28
|
-
end
|
|
21
|
+
ReleaseConfirmation.new(terminal: @terminal).verifier!(suggestion, courante, prochaine) if confirmation
|
|
29
22
|
|
|
30
23
|
VersionFilesWriting.new(terminal: @terminal).call(prochaine, @config.version_files)
|
|
31
24
|
GitRelease.new(terminal: @terminal).call(prochaine, chemins_a_commiter)
|
|
32
25
|
publication.call(prochaine)
|
|
33
|
-
@terminal.say "\n🎉 #{@terminal.bold(
|
|
26
|
+
@terminal.say "\n🎉 #{@terminal.bold(Messages.t(:released, version: prochaine))}\n"
|
|
34
27
|
end
|
|
35
28
|
|
|
36
29
|
private
|
|
@@ -39,33 +32,13 @@ module CaptiveRelease
|
|
|
39
32
|
chemins = @config.version_files.map(&:path)
|
|
40
33
|
return chemins unless (refresh = GemfileLockRefresh.new).needed?
|
|
41
34
|
|
|
42
|
-
@terminal.say "\n#{@terminal.bold(
|
|
35
|
+
@terminal.say "\n#{@terminal.bold(Messages.t(:refreshing_lockfile))}"
|
|
43
36
|
refresh.call
|
|
44
37
|
@terminal.done GemfileLockRefresh::LOCKFILE
|
|
45
38
|
|
|
46
39
|
chemins + [ GemfileLockRefresh::LOCKFILE ]
|
|
47
40
|
end
|
|
48
41
|
|
|
49
|
-
def afficher_changements(courante)
|
|
50
|
-
lien = ChangesSince.url(courante)
|
|
51
|
-
return unless lien
|
|
52
|
-
|
|
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"
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def confirmer!(version)
|
|
64
|
-
@terminal.say "\nNew version: #{@terminal.bold(version)}"
|
|
65
|
-
reponse = @terminal.ask("Confirm? [Y/n] ").downcase
|
|
66
|
-
exit 1 unless reponse == "y" || reponse.empty?
|
|
67
|
-
end
|
|
68
|
-
|
|
69
42
|
def publication
|
|
70
43
|
ReleasePublication.new(terminal: @terminal, token_provider: @token_provider)
|
|
71
44
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CaptiveRelease
|
|
4
|
+
# Les points d'arrêt avant ce qui ne se défait pas : le garde-fou quand la version est en
|
|
5
|
+
# deçà de ce que les commits suggèrent, puis la confirmation de la nouvelle version.
|
|
6
|
+
#
|
|
7
|
+
# Cette unité change quand la politique de confirmation change, ReleaseCommand quand la
|
|
8
|
+
# suite des étapes change.
|
|
9
|
+
class ReleaseConfirmation
|
|
10
|
+
def initialize(terminal:)
|
|
11
|
+
@terminal = terminal
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def verifier!(suggestion, courante, prochaine)
|
|
15
|
+
BumpSafetyNet.new(terminal: @terminal).verifier!(suggestion, courante, prochaine)
|
|
16
|
+
@terminal.say "\n#{Messages.t(:new_version, version: @terminal.bold(prochaine))}"
|
|
17
|
+
reponse = @terminal.ask(Messages.t(:confirm))
|
|
18
|
+
exit 1 unless reponse.strip.empty? || Messages.yes?(reponse)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CaptiveRelease
|
|
4
|
+
# Ce qu'on montre avant de choisir la version : d'où l'on part, ce qui a changé depuis, et
|
|
5
|
+
# le bump que les commits suggèrent. La suggestion est rendue, le garde-fou en a besoin.
|
|
6
|
+
#
|
|
7
|
+
# Cette unité change quand ce qu'on montre avant le choix change, ReleaseCommand quand la
|
|
8
|
+
# suite des étapes change.
|
|
9
|
+
class ReleaseOverview
|
|
10
|
+
def initialize(terminal:)
|
|
11
|
+
@terminal = terminal
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call(courante)
|
|
15
|
+
@terminal.say "\n#{@terminal.bold("captive-release")}\n"
|
|
16
|
+
@terminal.say Messages.t(:current_version, version: @terminal.bold(courante))
|
|
17
|
+
afficher_changements(courante)
|
|
18
|
+
BumpSuggestion.for(courante).tap do |suggestion|
|
|
19
|
+
afficher_suggestion(suggestion)
|
|
20
|
+
@terminal.say
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def afficher_changements(courante)
|
|
27
|
+
lien = ChangesSince.url(courante)
|
|
28
|
+
return unless lien
|
|
29
|
+
|
|
30
|
+
@terminal.say "\n#{Messages.t(:changes, link: @terminal.link(lien))}"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def afficher_suggestion(suggestion)
|
|
34
|
+
return unless suggestion
|
|
35
|
+
|
|
36
|
+
detail = Messages.t(:"suggestion_#{suggestion}")
|
|
37
|
+
@terminal.say Messages.t(:suggestion, severity: suggestion, detail: detail)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -16,8 +16,8 @@ module CaptiveRelease
|
|
|
16
16
|
def verifier!
|
|
17
17
|
return unless GitCommands.dirty?
|
|
18
18
|
|
|
19
|
-
@terminal.say @terminal.error(
|
|
20
|
-
@terminal.say "
|
|
19
|
+
@terminal.say @terminal.error(Messages.t(:dirty_tree))
|
|
20
|
+
@terminal.say "#{Messages.t(:dirty_tree_hint)}\n"
|
|
21
21
|
exit 1
|
|
22
22
|
end
|
|
23
23
|
end
|
|
@@ -13,7 +13,7 @@ module CaptiveRelease
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def call(version)
|
|
16
|
-
@terminal.say "\n#{@terminal.bold(
|
|
16
|
+
@terminal.say "\n#{@terminal.bold(Messages.t(:creating_github_release))}"
|
|
17
17
|
jeton = @token_provider.call
|
|
18
18
|
return sans_jeton unless jeton
|
|
19
19
|
|
|
@@ -24,7 +24,7 @@ module CaptiveRelease
|
|
|
24
24
|
private
|
|
25
25
|
|
|
26
26
|
def sans_jeton
|
|
27
|
-
@terminal.say " #{@terminal.warn(
|
|
27
|
+
@terminal.say " #{@terminal.warn(Messages.t(:no_token_skip_release))}"
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
end
|
|
@@ -8,7 +8,7 @@ module CaptiveRelease
|
|
|
8
8
|
# commandes d'une release changent.
|
|
9
9
|
module Shell
|
|
10
10
|
def self.run!(commande)
|
|
11
|
-
system(commande) || raise(
|
|
11
|
+
system(commande) || raise(Messages.t(:command_failed, command: commande))
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def self.capture(commande) = `#{commande}`.strip
|
|
@@ -9,7 +9,7 @@ module CaptiveRelease
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def initialize(current)
|
|
12
|
-
raise ArgumentError,
|
|
12
|
+
raise ArgumentError, Messages.t(:invalid_semver, value: current) unless current.match?(SEMVER_RE)
|
|
13
13
|
|
|
14
14
|
@major, @minor, @patch = current.split(".").map(&:to_i)
|
|
15
15
|
end
|
|
@@ -20,7 +20,7 @@ module CaptiveRelease
|
|
|
20
20
|
when "minor" then "#{@major}.#{@minor + 1}.0"
|
|
21
21
|
when "patch" then "#{@major}.#{@minor}.#{@patch + 1}"
|
|
22
22
|
else
|
|
23
|
-
raise ArgumentError,
|
|
23
|
+
raise ArgumentError, Messages.t(:invalid_version, value: type) unless type.to_s.match?(SEMVER_RE)
|
|
24
24
|
|
|
25
25
|
type.to_s
|
|
26
26
|
end
|
|
@@ -24,11 +24,11 @@ module CaptiveRelease
|
|
|
24
24
|
def resolve(current, type)
|
|
25
25
|
return VersionBumper.bump(current, type) if type.match?(SEMVER_RE) || BUMP_TYPES.include?(type)
|
|
26
26
|
|
|
27
|
-
refuse(
|
|
27
|
+
refuse(Messages.t(:invalid_bump_type, type: type))
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def ask(current)
|
|
31
|
-
choisi = Menu.new(terminal: @terminal).choose(
|
|
31
|
+
choisi = Menu.new(terminal: @terminal).choose(Messages.t(:choose_release_type), candidates_for(current))
|
|
32
32
|
choisi == CUSTOM ? type_it : choisi
|
|
33
33
|
end
|
|
34
34
|
|
|
@@ -38,14 +38,14 @@ module CaptiveRelease
|
|
|
38
38
|
version = VersionBumper.bump(current, type)
|
|
39
39
|
[ "#{type.capitalize} → #{@terminal.bold(version)}", version ]
|
|
40
40
|
end
|
|
41
|
-
candidats.merge("#{
|
|
41
|
+
candidats.merge("#{Messages.t(:custom)} → ?" => CUSTOM)
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def type_it
|
|
45
|
-
version = @terminal.ask(
|
|
45
|
+
version = @terminal.ask(Messages.t(:enter_version))
|
|
46
46
|
return version if version.match?(SEMVER_RE)
|
|
47
47
|
|
|
48
|
-
refuse(
|
|
48
|
+
refuse(Messages.t(:invalid_semver, value: version))
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def refuse(message)
|
|
@@ -10,7 +10,7 @@ module CaptiveRelease
|
|
|
10
10
|
attr_reader :path, :type
|
|
11
11
|
|
|
12
12
|
def initialize(path, type)
|
|
13
|
-
raise ArgumentError,
|
|
13
|
+
raise ArgumentError, Messages.t(:unknown_type, type: type, types: TYPES.join(", ")) unless TYPES.include?(type)
|
|
14
14
|
|
|
15
15
|
@path = path
|
|
16
16
|
@type = type
|
|
@@ -19,7 +19,7 @@ module CaptiveRelease
|
|
|
19
19
|
|
|
20
20
|
def read_version
|
|
21
21
|
@format.read(lire!) ||
|
|
22
|
-
raise(
|
|
22
|
+
raise(Messages.t(:cannot_read_version, path: path, expected: @format.expected))
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def write_version(new_version)
|
|
@@ -34,7 +34,7 @@ module CaptiveRelease
|
|
|
34
34
|
private
|
|
35
35
|
|
|
36
36
|
def lire!
|
|
37
|
-
raise
|
|
37
|
+
raise Messages.t(:file_not_found, path: path) unless File.exist?(path)
|
|
38
38
|
|
|
39
39
|
File.read(path)
|
|
40
40
|
end
|
|
@@ -14,11 +14,11 @@ module CaptiveRelease
|
|
|
14
14
|
|
|
15
15
|
def call(motif)
|
|
16
16
|
@terminal.say @terminal.warn(motif)
|
|
17
|
-
@terminal.say "\
|
|
18
|
-
exit 1 unless @terminal.ask
|
|
17
|
+
@terminal.say "\n#{Messages.t(:create_version_file, file: @terminal.bold("VERSION"))}"
|
|
18
|
+
exit 1 unless Messages.yes?(@terminal.ask)
|
|
19
19
|
|
|
20
20
|
File.write("VERSION", "#{VERSION_INITIALE}\n")
|
|
21
|
-
@terminal.done "VERSION
|
|
21
|
+
@terminal.done Messages.t(:version_file_created, file: "VERSION", version: @terminal.bold(VERSION_INITIALE))
|
|
22
22
|
[ VersionFile.new("VERSION", "plain") ]
|
|
23
23
|
end
|
|
24
24
|
end
|
|
@@ -26,14 +26,14 @@ module CaptiveRelease
|
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def demander
|
|
29
|
-
@terminal.say "\
|
|
30
|
-
|
|
29
|
+
@terminal.say "\n#{Messages.t(:select_prompt, enter: @terminal.bold(Messages.t(:select_enter)),
|
|
30
|
+
exclude: @terminal.bold(Messages.t(:select_exclude)))}"
|
|
31
31
|
@terminal.ask
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def seul(found)
|
|
35
35
|
@terminal.say ""
|
|
36
|
-
@terminal.done
|
|
36
|
+
@terminal.done Messages.t(:single_file_selected)
|
|
37
37
|
found
|
|
38
38
|
end
|
|
39
39
|
|
|
@@ -12,7 +12,7 @@ module CaptiveRelease
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def call(version, fichiers)
|
|
15
|
-
@terminal.say "\n#{@terminal.bold(
|
|
15
|
+
@terminal.say "\n#{@terminal.bold(Messages.t(:updating_version_files))}"
|
|
16
16
|
fichiers.each do |fichier|
|
|
17
17
|
fichier.write_version(version)
|
|
18
18
|
@terminal.done fichier.path
|
data/lib/captive_release.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "captive_release/version"
|
|
4
|
+
require_relative "captive_release/messages"
|
|
4
5
|
require_relative "captive_release/version_bumper"
|
|
5
6
|
require_relative "captive_release/version_format"
|
|
6
7
|
require_relative "captive_release/version_file"
|
|
@@ -42,6 +43,8 @@ require_relative "captive_release/changes_since"
|
|
|
42
43
|
require_relative "captive_release/release_publication"
|
|
43
44
|
require_relative "captive_release/version_files_writing"
|
|
44
45
|
require_relative "captive_release/release_preconditions"
|
|
46
|
+
require_relative "captive_release/release_overview"
|
|
47
|
+
require_relative "captive_release/release_confirmation"
|
|
45
48
|
require_relative "captive_release/release_command"
|
|
46
49
|
require_relative "captive_release/help"
|
|
47
50
|
require_relative "captive_release/cli"
|
metadata
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: captive-release
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.7.
|
|
4
|
+
version: 0.7.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Captive Studio
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
-
dependencies:
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: i18n
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - '='
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 1.15.2
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - '='
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 1.15.2
|
|
12
26
|
description: Gère les bumps de version (patch/minor/major/custom), met à jour package.json
|
|
13
27
|
et fichiers VERSION, crée le commit, le tag et la release GitHub.
|
|
14
28
|
email:
|
|
@@ -39,6 +53,7 @@ files:
|
|
|
39
53
|
- lib/captive_release/git_release.rb
|
|
40
54
|
- lib/captive_release/github_api.rb
|
|
41
55
|
- lib/captive_release/github_dispatch.rb
|
|
56
|
+
- lib/captive_release/github_error_message.rb
|
|
42
57
|
- lib/captive_release/github_http.rb
|
|
43
58
|
- lib/captive_release/github_release.rb
|
|
44
59
|
- lib/captive_release/github_repo.rb
|
|
@@ -46,9 +61,15 @@ files:
|
|
|
46
61
|
- lib/captive_release/gitmoji_severity.rb
|
|
47
62
|
- lib/captive_release/help.rb
|
|
48
63
|
- lib/captive_release/init_command.rb
|
|
64
|
+
- lib/captive_release/locale.rb
|
|
65
|
+
- lib/captive_release/locales/en.yml
|
|
66
|
+
- lib/captive_release/locales/fr.yml
|
|
49
67
|
- lib/captive_release/menu.rb
|
|
68
|
+
- lib/captive_release/messages.rb
|
|
50
69
|
- lib/captive_release/options.rb
|
|
51
70
|
- lib/captive_release/release_command.rb
|
|
71
|
+
- lib/captive_release/release_confirmation.rb
|
|
72
|
+
- lib/captive_release/release_overview.rb
|
|
52
73
|
- lib/captive_release/release_preconditions.rb
|
|
53
74
|
- lib/captive_release/release_publication.rb
|
|
54
75
|
- lib/captive_release/repo_files.rb
|