rakit 0.1.0 → 0.1.1

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: f6c23b7ddfc9bc502482e836f739cf44d6c5259d5f4b33f38bde0583572bc870
4
- data.tar.gz: 9c5266364d1f0e1a5e6c0674c1d33f24e2848698d095e7dc30274b0a25fcd91c
3
+ metadata.gz: ee82c70e32276dc9332649c9932aa9212e358a3dcefacec587070521982d5cae
4
+ data.tar.gz: d22e913a279a3ea5f4b51adc280aab7c1d303e4fd921a9c44ade41b4993471f3
5
5
  SHA512:
6
- metadata.gz: 86944bd681c3fdcde105dca0b09be9b0bce798af6ebd2fcd965be12951c024c882899d220f5c7fec3fa343363237c570e52a6be2325e5726f0f057d96f615e0d
7
- data.tar.gz: 300ffd474942c4f366c48532a11414230ed86c91c825473b89c8385695100c5132807508ef60111d78bc5796c3207b511f2cff22299fbc7ebfd1e1f2bd1a1e34
6
+ metadata.gz: e428b21575d78b7205df71fead16f344ab8c3316b6d6a83a2ec238d367940bfc854be84fa14400759c9c235bbdb061f3ed9e820def62be1515db909484882c79
7
+ data.tar.gz: 600283a6b6f4269af6e0b93a6ccd521786f65bd5c7347c17080fe1aafa8c92e86387362e9ee209995be762343fc23efc35e254a4827a1043cfd7b89cd380af3c
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Azure DevOps Pipelines API: pipeline status.
4
+ # Requires: AZURE_DEVOPS_ORG, AZURE_DEVOPS_PROJECT, AZURE_DEVOPS_PIPELINE_ID, AZURE_DEVOPS_TOKEN (PAT with Build Read).
5
+ require "json"
6
+ require "net/http"
7
+ require "uri"
8
+
9
+ module Rakit
10
+ module Azure
11
+ module DevOps
12
+ PipelineStatus = Struct.new(:successful, :errors, :warnings, keyword_init: true) do
13
+ def self.empty_failure(errors_message)
14
+ new(successful: false, errors: errors_message.to_s, warnings: "")
15
+ end
16
+ end
17
+
18
+ API_VERSION = "7.1"
19
+ BASE_URL = "https://dev.azure.com"
20
+
21
+ class << self
22
+ # Returns the latest pipeline status.
23
+ # Uses ENV: AZURE_DEVOPS_ORG, AZURE_DEVOPS_PROJECT, AZURE_DEVOPS_PIPELINE_ID, AZURE_DEVOPS_TOKEN.
24
+ def get_pipeline_status
25
+ org = ENV["AZURE_DEVOPS_ORG"]
26
+ project = ENV["AZURE_DEVOPS_PROJECT"]
27
+ pipeline_id = ENV["AZURE_DEVOPS_PIPELINE_ID"]
28
+ token = ENV["AZURE_DEVOPS_TOKEN"]
29
+
30
+ if [org, project, pipeline_id, token].any?(&:nil?) || [org, project, pipeline_id, token].any?(&:empty?)
31
+ return PipelineStatus.empty_failure(
32
+ "Missing config: set AZURE_DEVOPS_ORG, AZURE_DEVOPS_PROJECT, AZURE_DEVOPS_PIPELINE_ID, AZURE_DEVOPS_TOKEN"
33
+ )
34
+ end
35
+
36
+ run = _list_runs(org, project, pipeline_id.to_s, token).first
37
+ return PipelineStatus.empty_failure("No pipeline run found") unless run
38
+
39
+ timeline = _get_timeline(org, project, pipeline_id.to_s, token, run["id"])
40
+ successful = _run_passed?(run)
41
+ errors = _format_failed_steps(_failed_steps_details(timeline))
42
+ warnings = _format_warnings(_warning_details(timeline))
43
+
44
+ PipelineStatus.new(
45
+ successful: successful,
46
+ errors: errors.to_s,
47
+ warnings: warnings.to_s
48
+ )
49
+ rescue StandardError => e
50
+ PipelineStatus.empty_failure("Pipeline status error: #{e.message}")
51
+ end
52
+
53
+ private
54
+
55
+ def _base_uri(org, project, path)
56
+ URI("#{BASE_URL}/#{URI.encode_www_form_component(org)}/#{URI.encode_www_form_component(project)}/_apis/#{path}?api-version=#{API_VERSION}")
57
+ end
58
+
59
+ def _get(org, project, token, path)
60
+ uri = _base_uri(org, project, path)
61
+ req = Net::HTTP::Get.new(uri)
62
+ req.basic_auth("", token)
63
+ req["Accept"] = "application/json"
64
+ resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") { |http| http.request(req) }
65
+ raise "HTTP #{resp.code}: #{resp.body}" unless resp.is_a?(Net::HTTPSuccess)
66
+
67
+ body = resp.body.to_s.strip
68
+ body.empty? ? {} : JSON.parse(body)
69
+ end
70
+
71
+ def _list_runs(org, project, pipeline_id, token, top: 10)
72
+ data = _get(org, project, token, "pipelines/#{pipeline_id}/runs")
73
+ (data["value"] || []).first(top)
74
+ end
75
+
76
+ def _get_timeline(org, project, pipeline_id, token, build_id)
77
+ _get(org, project, token, "build/builds/#{build_id}/timeline")
78
+ rescue StandardError => e
79
+ return nil if e.message.include?("404")
80
+
81
+ raise
82
+ end
83
+
84
+ def _run_passed?(run)
85
+ run && %w[succeeded succeededwithissues].include?(run["result"]&.downcase)
86
+ end
87
+
88
+ def _failed_steps_details(timeline)
89
+ return [] unless timeline && timeline["records"].is_a?(Array)
90
+
91
+ timeline["records"]
92
+ .select { |r| r["result"]&.downcase == "failed" }
93
+ .map do |r|
94
+ {
95
+ "name" => r["name"] || r["identifier"] || "Unknown",
96
+ "type" => r["type"],
97
+ "result" => r["result"],
98
+ "issues" => (r["issues"] || []).map { |i| { "type" => i["type"], "message" => i["message"] } }
99
+ }
100
+ end
101
+ end
102
+
103
+ def _format_failed_steps(failures)
104
+ return "" if failures.nil? || failures.empty?
105
+
106
+ failures.map do |f|
107
+ lines = ["--- #{f['name']} (#{f['type']}) ---", " Result: #{f['result']}"]
108
+ (f["issues"] || []).each { |iss| lines << " [#{iss['type']}] #{iss['message']}" }
109
+ lines.join("\n")
110
+ end.join("\n\n")
111
+ end
112
+
113
+ def _warning_details(timeline)
114
+ return [] unless timeline && timeline["records"].is_a?(Array)
115
+
116
+ timeline["records"].each_with_object([]) do |r, out|
117
+ next unless r["issues"].is_a?(Array)
118
+
119
+ warn_issues = r["issues"].select { |i| i["type"]&.downcase&.include?("warning") }
120
+ next if warn_issues.empty?
121
+
122
+ out << {
123
+ "name" => r["name"] || r["identifier"] || "Unknown",
124
+ "issues" => warn_issues.map { |i| { "type" => i["type"], "message" => i["message"] } }
125
+ }
126
+ end
127
+ end
128
+
129
+ def _format_warnings(warnings)
130
+ return "" if warnings.nil? || warnings.empty?
131
+
132
+ warnings.flat_map do |w|
133
+ (w["issues"] || []).map { |iss| " [#{w['name']}] #{iss['message']}" }
134
+ end.join("\n")
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rakit
4
+ module RubyGems
5
+ # Bump the last digit of the version in the gemspec file (e.g. "0.1.0" -> "0.1.1").
6
+ # Writes the file in place. Returns the new version string.
7
+ def self.bump(gemspec_path)
8
+ content = File.read(gemspec_path)
9
+ content.sub!(/^(\s*s\.version\s*=\s*["'])([\d.]+)(["'])/) do
10
+ segs = Regexp.last_match(2).split(".")
11
+ segs[-1] = (segs[-1].to_i + 1).to_s
12
+ "#{Regexp.last_match(1)}#{segs.join('.')}#{Regexp.last_match(3)}"
13
+ end or raise "No s.version line found in #{gemspec_path}"
14
+ File.write(gemspec_path, content)
15
+ content[/s\.version\s*=\s*["']([^"']+)["']/, 1]
16
+ end
17
+
18
+ def self.version_published?(name, version)
19
+ require "net/http"
20
+ require "uri"
21
+ uri = URI("https://rubygems.org/api/v2/rubygems/#{URI::DEFAULT_PARSER.escape(name)}/versions/#{URI::DEFAULT_PARSER.escape(version)}.json")
22
+ response = Net::HTTP.get_response(uri)
23
+ response.is_a?(Net::HTTPSuccess)
24
+ rescue StandardError
25
+ false
26
+ end
27
+
28
+ # Publish the .gem at gem_path to rubygems.org. If that version is already
29
+ # published, warns and returns without pushing. Raises if the file is missing
30
+ # or if gem push fails.
31
+ def self.publish(gem_path)
32
+ raise "Gem not found: #{gem_path}. Run rake package first." unless File.file?(gem_path)
33
+
34
+ base = File.basename(gem_path, ".gem")
35
+ parts = base.split("-")
36
+ version = parts.pop
37
+ name = parts.join("-")
38
+
39
+ if version_published?(name, version)
40
+ warn "publish: Version #{version} of #{name} is already published on rubygems.org. Skipping push. Bump the version in the gemspec to publish again."
41
+ return
42
+ end
43
+
44
+ success = system("gem", "push", gem_path)
45
+ raise "gem push failed" unless success
46
+ end
47
+ end
48
+ end
data/lib/rakit.rb CHANGED
@@ -6,3 +6,6 @@ module Rakit
6
6
  "Rakit hello"
7
7
  end
8
8
  end
9
+
10
+ require_relative "rakit/ruby_gems"
11
+ require_relative "rakit/azure/dev_ops"
metadata CHANGED
@@ -1,20 +1,65 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rakit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - rakit
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: google-protobuf
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '3.25'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '3.25'
26
+ - !ruby/object:Gem::Dependency
27
+ name: grpc-tools
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.72'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.72'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rake
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '13'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '13'
12
54
  executables: []
13
55
  extensions: []
14
56
  extra_rdoc_files: []
15
57
  files:
16
58
  - lib/rakit.rb
17
- licenses: []
59
+ - lib/rakit/azure/dev_ops.rb
60
+ - lib/rakit/ruby_gems.rb
61
+ licenses:
62
+ - MIT
18
63
  metadata: {}
19
64
  rdoc_options: []
20
65
  require_paths: