captive-release 0.13.2 → 0.15.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: 423b36a49572d388825d68a5128239d40977d95642dbb88e9e430c12a2c828e1
4
- data.tar.gz: 743a871e6213ac1c663ecc75bfeff8cecfeba16925bf95fa3b743d6186c0826c
3
+ metadata.gz: 615663c58dbf71da94afba56edf11a7c269c288e419458ab147109174cb32b70
4
+ data.tar.gz: c00fa194187b633dd2b2560cb14f5fce0b9de2247734c3f5cb1caf8ba23cbade
5
5
  SHA512:
6
- metadata.gz: ab946e3effe06248e0633c40936cc572a9cf2b875e61b4dda50f26d915698cc8f5bceab19a5b1f39f7649d024a3b3dd46080a4046a7f12185a54d8e77f5474de
7
- data.tar.gz: 5ce64635bc166ef4aacdb3cc468e07fda810941a8a0c73a3b085f466529aaf579e15240baf8e2e7ad37a18edd0a2a096a50183143c55d3784d127ebba7fcef6e
6
+ metadata.gz: 98ec33d80bc912c27c262225173c783aa94d34013219377dab148ce633f975f14cd8214e17d7e25608d03ea79c8b2bcde1b3549840ada1665881a2448bd7170e
7
+ data.tar.gz: 516607116274624df99f5a98ce15cf13881e1fb75d697fc7e451eb8258f1b14fcede77c5855fb3045bcb78f51755c484fd7f9c7b25876bebd16a6a264d7cefa2
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CaptiveRelease
4
- # Déclenche le workflow de build sur la branche courante, après avoir dit ce qu'il va faire :
5
- # le profil, le workflow et la branche sont les trois choses qu'on vérifie avant de lancer.
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
- annoncer(profil, workflow, branche)
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)}"
@@ -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: |-
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CaptiveRelease
4
- VERSION = "0.13.2"
4
+ VERSION = "0.15.0"
5
5
  end
@@ -3,9 +3,23 @@
3
3
  module CaptiveRelease
4
4
  # Quels fichiers d'un dépôt portent une version, et sous quel format.
5
5
  class VersionDetector
6
- RUBY_VERSION_PATTERN = /VERSION\s*=\s*["'].+?["']/
7
- EXPO_APP_JSON_PATTERN = /"expo"\s*:.*"version"\s*:/m
8
- GRADLE_PATH = %w[android app build.gradle].freeze
6
+ GRADLE_PATH = %w[android app build.gradle].freeze
7
+ CLAUDE_PLUGIN_PATH = %w[.claude-plugin plugin.json].freeze
8
+
9
+ # Où chercher, quel format y lire, et ce que le fichier doit contenir pour en être un.
10
+ RULES = [
11
+ [ %w[** package.json], "package_json" ],
12
+ [ %w[** app.config.js], "app_config_js" ],
13
+ [ %w[** app.json], "app_json", /"expo"\s*:.*"version"\s*:/m ],
14
+ [ %w[lib ** version.rb], "ruby_version", /VERSION\s*=\s*["'].+?["']/ ],
15
+ [ %w[** VERSION], "plain" ],
16
+ # Chemin fixe : un glob remonterait les build.gradle des modules Android embarqués, qui ne
17
+ # portent pas la version de l'app.
18
+ [ GRADLE_PATH, "android_gradle", /versionName\s+['"]/ ],
19
+ # Un dépôt publie un seul plugin : son manifeste est à la racine.
20
+ [ CLAUDE_PLUGIN_PATH, "claude_plugin", /"version"/ ],
21
+ [ %w[ios ** Info.plist], "ios_plist", /CFBundleShortVersionString/ ],
22
+ ].freeze
9
23
 
10
24
  def self.scan(root = ".")
11
25
  new(root).scan
@@ -16,33 +30,15 @@ module CaptiveRelease
16
30
  end
17
31
 
18
32
  def scan
19
- by_name("package.json", "package_json") +
20
- by_name("app.config.js", "app_config_js") +
21
- by_content(%w[** app.json], "app_json", EXPO_APP_JSON_PATTERN) +
22
- by_content(%w[lib ** version.rb], "ruby_version", RUBY_VERSION_PATTERN) +
23
- by_name("VERSION", "plain") +
24
- android_gradle +
25
- by_content(%w[ios ** Info.plist], "ios_plist", /CFBundleShortVersionString/)
33
+ RULES.flat_map { |segments, type, pattern| detect(segments, type, pattern) }
26
34
  end
27
35
 
28
36
  private
29
37
 
30
- def by_name(filename, type)
31
- @files.glob("**", filename).map { |chemin| version_file(chemin, type) }
32
- end
33
-
34
- def by_content(segments, type, pattern)
38
+ def detect(segments, type, pattern)
35
39
  @files.glob(*segments)
36
- .select { |chemin| contient?(chemin, pattern) }
37
- .map { |chemin| version_file(chemin, type) }
38
- end
39
-
40
- # Le build.gradle est à un chemin fixe : le chercher par glob remonterait ceux des
41
- # modules Android embarqués, qui ne portent pas la version de l'app.
42
- def android_gradle
43
- return [] unless contient?(@files.path(*GRADLE_PATH), /versionName\s+['"]/)
44
-
45
- [ VersionFile.new(GRADLE_PATH.join("/"), "android_gradle") ]
40
+ .select { |chemin| pattern.nil? || contient?(chemin, pattern) }
41
+ .map { |chemin| VersionFile.new(@files.relative(chemin), type) }
46
42
  end
47
43
 
48
44
  def contient?(chemin, pattern)
@@ -50,7 +46,5 @@ module CaptiveRelease
50
46
  rescue StandardError
51
47
  false
52
48
  end
53
-
54
- def version_file(chemin, type) = VersionFile.new(@files.relative(chemin), type)
55
49
  end
56
50
  end
@@ -15,6 +15,8 @@ module CaptiveRelease
15
15
  end
16
16
 
17
17
  def self.type_deduit(chemin)
18
+ return "claude_plugin" if chemin == VersionDetector::CLAUDE_PLUGIN_PATH.join("/")
19
+
18
20
  File.basename(chemin) == "package.json" ? "package_json" : "plain"
19
21
  end
20
22
  end
@@ -52,16 +52,19 @@ module CaptiveRelease
52
52
 
53
53
  PLIST_VALUE = %r{(?<=<string>).+?(?=</string>)}
54
54
 
55
+ PACKAGE_JSON = new(read: ->(content) { JSON.parse(content)["version"] },
56
+ write: lambda { |content, old, nouvelle|
57
+ content.gsub(/"version": "#{Regexp.escape(old)}"/, %("version": "#{nouvelle}"))
58
+ },
59
+ expected: %("version": "x.y.z"))
60
+
55
61
  FORMATS = {
56
62
  "plain" => new(read: ->(content) { content.strip },
57
63
  write: ->(_content, _old, nouvelle) { "#{nouvelle}\n" },
58
64
  expected: "x.y.z"),
59
- "package_json" => new(read: ->(content) { JSON.parse(content)["version"] },
60
- write: lambda { |content, old, nouvelle|
61
- content.gsub(/"version": "#{Regexp.escape(old)}"/,
62
- %("version": "#{nouvelle}"))
63
- },
64
- expected: %("version": "x.y.z")),
65
+ "package_json" => PACKAGE_JSON,
66
+ # Le manifeste d'un plugin Claude porte sa version comme un package.json.
67
+ "claude_plugin" => PACKAGE_JSON,
65
68
 
66
69
  "ruby_version" => RubyGemsVersion.new(
67
70
  by_pattern(/VERSION\s*=\s*["'](.+?)["']/, %(VERSION = "x.y.z")) { |occurrence, nouvelle|
@@ -37,6 +37,7 @@ require_relative "captive_release/config_writer"
37
37
  require_relative "captive_release/version_files_argument"
38
38
  require_relative "captive_release/version_file_bootstrap"
39
39
  require_relative "captive_release/init_command"
40
+ require_relative "captive_release/build_tag"
40
41
  require_relative "captive_release/build_dispatch_run"
41
42
  require_relative "captive_release/build_command"
42
43
  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.13.2
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Captive Studio
@@ -24,6 +24,7 @@ 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/build_tag.rb
27
28
  - lib/captive_release/bump_safety_net.rb
28
29
  - lib/captive_release/bump_severity.rb
29
30
  - lib/captive_release/bump_suggestion.rb