hiiro 0.1.313 → 0.1.314
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/CHANGELOG.md +6 -3
- data/exe/h +4 -4
- data/lib/hiiro/version.rb +1 -1
- data/script/publish +194 -158
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6fd4ee6b28c10a3203f70a472a4e65bae62c1636f24dceb4a1b21fe48c96c3ff
|
|
4
|
+
data.tar.gz: 1ddaded4e27043098e685c7af4445e042119ff2bdee80a47ac25a29d4f05ba2d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7ff30eb7284958c439bc087bad2e1a74a805c6955a9482d8f4a0b6198f0fc6e35181742537102b7f8d0abeb5fc1ea8bce38a595042a5df2cf1470470a239ee52
|
|
7
|
+
data.tar.gz: 68d4ab840a4535a86f99faf26d910cc2fee354b35fd5a2fdcdec22e8fa4bc4b962a9cca0a18c56250076dd28db28102fe76b798621bd5b7084d516314a54182a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
```markdown
|
|
2
1
|
# Changelog
|
|
3
2
|
|
|
3
|
+
## [0.1.314] - 2026-04-01
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- Rename `Gem` class to `RubyGem` in publish script to avoid conflict with Ruby stdlib
|
|
7
|
+
|
|
4
8
|
## [0.1.313] - 2026-04-01
|
|
5
9
|
|
|
6
10
|
### Fixed
|
|
@@ -194,5 +198,4 @@
|
|
|
194
198
|
## [0.1.295]
|
|
195
199
|
|
|
196
200
|
### Changed
|
|
197
|
-
- Filter logic changes for PR management
|
|
198
|
-
```
|
|
201
|
+
- Filter logic changes for PR management
|
data/exe/h
CHANGED
|
@@ -225,7 +225,7 @@ Hiiro.run(*ARGV, cwd: Dir.pwd, tasks: true) do
|
|
|
225
225
|
end
|
|
226
226
|
|
|
227
227
|
unless found
|
|
228
|
-
system('
|
|
228
|
+
system('terminal-notifier', '-title', 'hiiro publish', '-message', 'Timed out waiting for RubyGems')
|
|
229
229
|
exit 1
|
|
230
230
|
end
|
|
231
231
|
|
|
@@ -239,11 +239,11 @@ Hiiro.run(*ARGV, cwd: Dir.pwd, tasks: true) do
|
|
|
239
239
|
output.match?(/hiiro \\([^)]*#{Regexp.escape(expected)}/)
|
|
240
240
|
end
|
|
241
241
|
if mismatched.empty?
|
|
242
|
-
system('
|
|
242
|
+
system('terminal-notifier', '-title', 'hiiro updated', '-message', "v\#{expected} installed across all rbenv versions")
|
|
243
243
|
elsif mismatched.size == versions.size
|
|
244
|
-
system('
|
|
244
|
+
system('terminal-notifier', '-title', 'hiiro update failed', '-message', "v\#{expected} not installed in any rbenv version")
|
|
245
245
|
else
|
|
246
|
-
system('
|
|
246
|
+
system('terminal-notifier', '-title', 'hiiro updated', '-message', "v\#{expected} installed — \#{mismatched.size} version\#{mismatched.size == 1 ? '' : 's'} skipped")
|
|
247
247
|
end
|
|
248
248
|
RUBY
|
|
249
249
|
|
data/lib/hiiro/version.rb
CHANGED
data/script/publish
CHANGED
|
@@ -3,202 +3,238 @@
|
|
|
3
3
|
require "net/http"
|
|
4
4
|
require "json"
|
|
5
5
|
|
|
6
|
-
#
|
|
7
|
-
pre_release = ARGV.include?('-t') || `git branch --show-current`.strip != 'main'
|
|
8
|
-
|
|
9
|
-
# Check for uncommitted changes before we modify anything
|
|
10
|
-
has_pending_changes = !`git status --porcelain`.strip.empty?
|
|
11
|
-
|
|
12
|
-
# Ensure local branch is up to date with remote
|
|
13
|
-
system('git', 'fetch', 'origin')
|
|
14
|
-
behind = `git rev-list HEAD..origin/main --count`.strip.to_i
|
|
15
|
-
if behind > 0
|
|
16
|
-
puts "ERROR: Local branch is #{behind} commit(s) behind origin/main. Pull before publishing."
|
|
17
|
-
exit 1
|
|
18
|
-
end
|
|
6
|
+
# ─── RubyGems API ────────────────────────────────────────────────────────────
|
|
19
7
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
require "hiiro"
|
|
30
|
-
[Hiiro::VERSION]
|
|
31
|
-
end
|
|
8
|
+
class RubyGems
|
|
9
|
+
def self.published_versions(gem_name)
|
|
10
|
+
uri = URI("https://rubygems.org/api/v1/versions/#{gem_name}.json")
|
|
11
|
+
JSON.parse(Net::HTTP.get(uri)).map { |v| v["number"] }
|
|
12
|
+
rescue => e
|
|
13
|
+
puts "Warning: could not fetch versions from RubyGems (#{e.message}), falling back to local"
|
|
14
|
+
require "hiiro"
|
|
15
|
+
[Hiiro::VERSION]
|
|
16
|
+
end
|
|
32
17
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
18
|
+
def self.wait_for(gem_name, version, max_attempts: 40)
|
|
19
|
+
attempts = 0
|
|
20
|
+
loop do
|
|
21
|
+
sleep(attempts == 0 ? 5 : 15)
|
|
22
|
+
attempts += 1
|
|
23
|
+
versions = published_versions(gem_name)
|
|
24
|
+
return true if versions.include?(version)
|
|
25
|
+
puts "Waiting for v#{version} on RubyGems (attempt #{attempts}/#{max_attempts})..."
|
|
26
|
+
return false if attempts >= max_attempts
|
|
27
|
+
end
|
|
28
|
+
rescue => e
|
|
29
|
+
puts "RubyGems poll error: #{e.message}"
|
|
30
|
+
false
|
|
41
31
|
end
|
|
42
32
|
end
|
|
43
33
|
|
|
44
|
-
#
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
34
|
+
# ─── Version bumping ─────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
class Version
|
|
37
|
+
attr_reader :major, :minor, :patch, :pre, :pre_num
|
|
38
|
+
|
|
39
|
+
def self.parse(str)
|
|
40
|
+
parts = str.split(".")
|
|
41
|
+
pre = parts.length >= 5 && parts[-2] == "pre"
|
|
42
|
+
new(parts[0].to_i, parts[1].to_i, parts[2].to_i, pre, pre ? parts[-1].to_i : 0)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.latest(versions)
|
|
46
|
+
versions.max_by { |v| parse(v).sort_key }
|
|
50
47
|
end
|
|
48
|
+
|
|
49
|
+
def self.bump(current_str, pre_release:)
|
|
50
|
+
v = parse(current_str)
|
|
51
|
+
if v.pre
|
|
52
|
+
pre_release ? "#{v.major}.#{v.minor}.#{v.patch}.pre.#{v.pre_num + 1}"
|
|
53
|
+
: "#{v.major}.#{v.minor}.#{v.patch}"
|
|
54
|
+
else
|
|
55
|
+
pre_release ? "#{v.major}.#{v.minor}.#{v.patch + 1}.pre.1"
|
|
56
|
+
: "#{v.major}.#{v.minor}.#{v.patch + 1}"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def initialize(major, minor, patch, pre, pre_num)
|
|
61
|
+
@major, @minor, @patch, @pre, @pre_num = major, minor, patch, pre, pre_num
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def sort_key = [major, minor, patch, pre ? 0 : 1, pre_num]
|
|
51
65
|
end
|
|
52
66
|
|
|
53
|
-
|
|
54
|
-
current = latest_version(versions)
|
|
55
|
-
puts "Latest published version: #{current}"
|
|
67
|
+
# ─── Git ─────────────────────────────────────────────────────────────────────
|
|
56
68
|
|
|
57
|
-
|
|
58
|
-
|
|
69
|
+
class Git
|
|
70
|
+
def self.current_branch = `git branch --show-current`.strip
|
|
71
|
+
def self.pending_changes? = !`git status --porcelain`.strip.empty?
|
|
72
|
+
def self.diff = `git diff HEAD`
|
|
73
|
+
def self.status = `git status --short`
|
|
59
74
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
# pre -> release: remove pre, keep same version number
|
|
68
|
-
new_version = [major, minor, patch].join(?.)
|
|
75
|
+
def self.ensure_up_to_date!
|
|
76
|
+
system("git", "fetch", "origin")
|
|
77
|
+
behind = `git rev-list HEAD..origin/main --count`.strip.to_i
|
|
78
|
+
if behind > 0
|
|
79
|
+
puts "ERROR: #{behind} commit(s) behind origin/main — pull before publishing."
|
|
80
|
+
exit 1
|
|
81
|
+
end
|
|
69
82
|
end
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
83
|
+
|
|
84
|
+
def self.commits_since_last_tag
|
|
85
|
+
last_tag = `git describe --tags --abbrev=0 2>/dev/null`.strip
|
|
86
|
+
last_tag.empty? ? `git log --oneline` : `git log #{last_tag}..HEAD --oneline`
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def self.stage_and_commit(files, message)
|
|
90
|
+
system("git", "reset", "HEAD")
|
|
91
|
+
files.each { |f| system("git", "add", "--", f) }
|
|
92
|
+
system("git", "commit", "-m", message)
|
|
93
|
+
puts " → #{message}"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def self.commit_all(message)
|
|
97
|
+
system("git", "add", "--all")
|
|
98
|
+
system("git", "commit", "-m", message)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def self.tag(version)
|
|
102
|
+
system("git", "tag", "-a", "v#{version}", "-m", "v#{version}")
|
|
103
|
+
puts "Tagged v#{version}"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def self.push
|
|
107
|
+
if system("git", "push", "origin", "main", "--follow-tags")
|
|
108
|
+
puts "Pushed to origin/main"
|
|
109
|
+
else
|
|
110
|
+
branch = "publish-v#{version}"
|
|
111
|
+
system("git", "checkout", "-b", branch)
|
|
112
|
+
system("git", "push", "origin", branch, "--follow-tags") \
|
|
113
|
+
? puts("Push to main failed — pushed to origin/#{branch} instead")
|
|
114
|
+
: puts("ERROR: unable to push to origin")
|
|
115
|
+
system("git", "checkout", "main")
|
|
116
|
+
end
|
|
78
117
|
end
|
|
79
118
|
end
|
|
80
119
|
|
|
81
|
-
|
|
120
|
+
# ─── Claude AI calls ─────────────────────────────────────────────────────────
|
|
82
121
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
puts "Generating commit plan for pending changes..."
|
|
122
|
+
class Claude
|
|
123
|
+
MODEL = "claude-haiku-4-5-20251001"
|
|
86
124
|
|
|
87
|
-
|
|
88
|
-
status = `git status --short`
|
|
125
|
+
Result = Struct.new(:commits, :changelog)
|
|
89
126
|
|
|
90
|
-
|
|
91
|
-
|
|
127
|
+
# Single call: returns a Result with commit plan + updated changelog
|
|
128
|
+
def self.prepare_release(version:, diff:, status:, git_log:, existing_changelog:)
|
|
129
|
+
prompt = <<~PROMPT
|
|
130
|
+
You are preparing a release for the Ruby gem "hiiro" version #{version}.
|
|
131
|
+
Do two things and return them as a single JSON object — no explanation, no markdown fences.
|
|
92
132
|
|
|
93
|
-
|
|
94
|
-
|
|
133
|
+
## 1. Commit plan
|
|
134
|
+
Analyze the pending git changes below and group them into logical commits.
|
|
135
|
+
Use conventional commit format (feat:, fix:, chore:, refactor:, docs:, etc.).
|
|
136
|
+
Only include files that appear in the git status output.
|
|
95
137
|
|
|
96
|
-
|
|
97
|
-
|
|
138
|
+
git status:
|
|
139
|
+
#{status.strip}
|
|
98
140
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
{"message": "commit message", "files": ["path/to/file"]},
|
|
102
|
-
...
|
|
103
|
-
]
|
|
141
|
+
git diff:
|
|
142
|
+
#{diff.strip}
|
|
104
143
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
144
|
+
## 2. Changelog
|
|
145
|
+
Write a complete updated CHANGELOG.md. Add a new section for v#{version} at the top
|
|
146
|
+
with today's date. Group changes into Added, Changed, Fixed, Removed where appropriate.
|
|
147
|
+
Be concise.
|
|
108
148
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
io.close_write
|
|
112
|
-
io.read
|
|
113
|
-
}
|
|
149
|
+
Recent commits (for changelog context):
|
|
150
|
+
#{git_log.strip}
|
|
114
151
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
152
|
+
Existing CHANGELOG.md:
|
|
153
|
+
#{existing_changelog.empty? ? "(none)" : existing_changelog}
|
|
154
|
+
|
|
155
|
+
## Output format (JSON only, no extra text)
|
|
156
|
+
{
|
|
157
|
+
"commits": [
|
|
158
|
+
{"message": "commit message", "files": ["path/to/file"]},
|
|
159
|
+
...
|
|
160
|
+
],
|
|
161
|
+
"changelog": "full CHANGELOG.md content here"
|
|
162
|
+
}
|
|
163
|
+
PROMPT
|
|
164
|
+
|
|
165
|
+
raw = IO.popen(["claude", "-p", "--model", MODEL], "r+") { |io|
|
|
166
|
+
io.write(prompt)
|
|
167
|
+
io.close_write
|
|
168
|
+
io.read
|
|
169
|
+
}.strip.gsub(/\A```(?:json)?\n?/, "").gsub(/\n?```\z/, "")
|
|
170
|
+
|
|
171
|
+
parsed = JSON.parse(raw)
|
|
172
|
+
Result.new(parsed["commits"], parsed["changelog"])
|
|
130
173
|
end
|
|
131
174
|
end
|
|
132
175
|
|
|
133
|
-
|
|
134
|
-
puts "Generating CHANGELOG.md..."
|
|
135
|
-
last_tag = `git describe --tags --abbrev=0 2>/dev/null`.strip
|
|
136
|
-
commit_log = last_tag.empty? ? `git log --oneline` : `git log #{last_tag}..HEAD --oneline`
|
|
137
|
-
existing_changelog = File.exist?('CHANGELOG.md') ? File.read('CHANGELOG.md') : ''
|
|
176
|
+
# ─── Gem publishing ──────────────────────────────────────────────────────────
|
|
138
177
|
|
|
139
|
-
|
|
140
|
-
|
|
178
|
+
class RubyGem
|
|
179
|
+
def self.build(version)
|
|
180
|
+
system("gem", "build", "hiiro.gemspec") || abort("ERROR: gem build failed")
|
|
181
|
+
end
|
|
141
182
|
|
|
142
|
-
|
|
143
|
-
|
|
183
|
+
def self.push(version)
|
|
184
|
+
system("gem", "push", "hiiro-#{version}.gem")
|
|
185
|
+
end
|
|
186
|
+
end
|
|
144
187
|
|
|
145
|
-
|
|
146
|
-
#{existing_changelog.empty? ? '(none)' : existing_changelog}
|
|
188
|
+
# ─── Main ─────────────────────────────────────────────────────────────────────
|
|
147
189
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
Be concise. Output only the file content, no explanation.
|
|
151
|
-
PROMPT
|
|
190
|
+
pre_release = ARGV.include?("-t") || Git.current_branch != "main"
|
|
191
|
+
pending_before = Git.pending_changes?
|
|
152
192
|
|
|
153
|
-
|
|
154
|
-
io.write(changelog_prompt)
|
|
155
|
-
io.close_write
|
|
156
|
-
io.read
|
|
157
|
-
}
|
|
193
|
+
Git.ensure_up_to_date!
|
|
158
194
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
195
|
+
current_version = Version.latest(RubyGems.published_versions("hiiro"))
|
|
196
|
+
new_version = Version.bump(current_version, pre_release: pre_release)
|
|
197
|
+
puts "Latest: #{current_version} → New: #{new_version}"
|
|
198
|
+
|
|
199
|
+
puts "\nAsking Claude to plan commits and generate changelog..."
|
|
200
|
+
release = Claude.prepare_release(
|
|
201
|
+
version: new_version,
|
|
202
|
+
diff: pending_before ? Git.diff : "",
|
|
203
|
+
status: pending_before ? Git.status : "",
|
|
204
|
+
git_log: Git.commits_since_last_tag,
|
|
205
|
+
existing_changelog: File.exist?("CHANGELOG.md") ? File.read("CHANGELOG.md") : ""
|
|
206
|
+
)
|
|
165
207
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
208
|
+
if pending_before
|
|
209
|
+
begin
|
|
210
|
+
system("git", "add", "--all")
|
|
211
|
+
release.commits.each { |c| Git.stage_and_commit(c["files"], c["message"]) }
|
|
212
|
+
rescue => e
|
|
213
|
+
puts "Warning: could not apply Claude commit plan (#{e.message}) — committing all as one"
|
|
214
|
+
Git.commit_all("chore: pre-publish changes")
|
|
215
|
+
end
|
|
171
216
|
end
|
|
172
217
|
|
|
173
|
-
|
|
174
|
-
puts "\nERROR: unable to build gem\n" unless built
|
|
218
|
+
File.write("CHANGELOG.md", release.changelog) if release.changelog && !release.changelog.strip.empty?
|
|
175
219
|
|
|
176
|
-
|
|
177
|
-
pushed = system('gem', 'push', "hiiro-#{new_version}.gem")
|
|
178
|
-
puts "\nERROR: unable to push\n" unless pushed
|
|
220
|
+
File.write("lib/hiiro/version.rb", "class Hiiro\n VERSION = #{new_version.inspect}\nend\n")
|
|
179
221
|
|
|
180
|
-
|
|
181
|
-
system 'git', 'add', '--all'
|
|
182
|
-
system 'git', 'commit', '-m', "publishing v#{new_version}"
|
|
222
|
+
RubyGem.build(new_version)
|
|
183
223
|
|
|
184
|
-
|
|
185
|
-
|
|
224
|
+
unless RubyGem.push(new_version)
|
|
225
|
+
abort("ERROR: gem push failed — skipping git commit, tag, and push")
|
|
226
|
+
end
|
|
186
227
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
228
|
+
Git.commit_all("publishing v#{new_version}")
|
|
229
|
+
Git.tag(new_version)
|
|
230
|
+
Git.push
|
|
231
|
+
|
|
232
|
+
puts "\nWaiting for v#{new_version} to appear on RubyGems..."
|
|
233
|
+
if RubyGems.wait_for("hiiro", new_version)
|
|
234
|
+
puts "v#{new_version} available — running h update -a"
|
|
235
|
+
update_args = ["update", "-a"]
|
|
236
|
+
update_args << "--pre" if pre_release
|
|
237
|
+
system("h", *update_args)
|
|
191
238
|
else
|
|
192
|
-
|
|
193
|
-
system 'git', 'checkout', '-b', branch_name
|
|
194
|
-
if system('git', 'push', 'origin', branch_name, '--follow-tags')
|
|
195
|
-
puts "Push to main failed. Pushed to origin/#{branch_name} instead"
|
|
196
|
-
else
|
|
197
|
-
puts "\nERROR: unable to push to origin\n"
|
|
198
|
-
end
|
|
199
|
-
system 'git', 'checkout', 'main'
|
|
239
|
+
puts "WARNING: timed out waiting for v#{new_version} on RubyGems — run `h update -a` manually"
|
|
200
240
|
end
|
|
201
|
-
|
|
202
|
-
puts ""
|
|
203
|
-
puts "Running: h delayed_update #{new_version}"
|
|
204
|
-
system('h', 'delayed_update', new_version)
|