inquirex-tools 0.8.0 → 0.9.5
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/.env.encrypted +11 -0
- data/.env.example +5 -0
- data/.envrc +1 -0
- data/.relaxed_rubocop.yml +153 -0
- data/.sops.yaml +6 -0
- data/.sopsy.sha +1 -0
- data/.sopsy.yml +11 -0
- data/docs/badges/coverage_badge.svg +21 -0
- data/docs/monorepo-migration.md +163 -0
- data/exe/inquirex +3 -2
- data/exe/inquirexor +1 -0
- data/justfile +158 -0
- data/lib/inquirex/tools/changelogs.rb +21 -3
- data/lib/inquirex/tools/commands/changelogs.rb +5 -3
- data/lib/inquirex/tools/commands/create_release.rb +56 -0
- data/lib/inquirex/tools/commands/publish.rb +50 -0
- data/lib/inquirex/tools/commands/versions_bump.rb +34 -0
- data/lib/inquirex/tools/commands/versions_check.rb +36 -0
- data/lib/inquirex/tools/commands.rb +11 -3
- data/lib/inquirex/tools/publisher.rb +229 -0
- data/lib/inquirex/tools/releaser.rb +163 -0
- data/lib/inquirex/tools/version.rb +1 -1
- data/lib/inquirex/tools/version_bumper.rb +230 -0
- data/lib/inquirex/tools/version_checker.rb +113 -0
- data/lib/inquirex/tools/workspace.rb +30 -27
- data/lib/inquirex/tools.rb +12 -5
- metadata +35 -3
- data/lib/inquirex/tools/commands/versions.rb +0 -41
- data/lib/inquirex/tools/versions.rb +0 -125
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "colored2"
|
|
4
|
+
require "open3"
|
|
5
|
+
require "rubygems/version"
|
|
6
|
+
require_relative "workspace"
|
|
7
|
+
|
|
8
|
+
module Inquirex
|
|
9
|
+
module Tools
|
|
10
|
+
# Moves every lockstep package to one version, then proves each one still
|
|
11
|
+
# builds — the write half of the pair whose read half is {VersionChecker}.
|
|
12
|
+
#
|
|
13
|
+
# The family shares a serialization format, so a version that identifies
|
|
14
|
+
# one package identifies all of them. Bumping is therefore all-or-nothing:
|
|
15
|
+
# there is no supported way to move a single package.
|
|
16
|
+
#
|
|
17
|
+
# Writing a version and verifying it are one command on purpose. A version
|
|
18
|
+
# written but never checked is the failure this is meant to prevent: the
|
|
19
|
+
# number says the family agrees while one package no longer passes its own
|
|
20
|
+
# suite.
|
|
21
|
+
#
|
|
22
|
+
# @example Move the family to 0.9.2 and check every package
|
|
23
|
+
# Inquirex::Tools::VersionBumper.new.call("0.9.2") # => false if any check fails
|
|
24
|
+
class VersionBumper
|
|
25
|
+
# A version must be exactly MAJOR.MINOR.PATCH — no prereleases, because
|
|
26
|
+
# the npm packages and the gems would spell them differently.
|
|
27
|
+
SEMVER_RE = /\A\d+\.\d+\.\d+\z/
|
|
28
|
+
|
|
29
|
+
# Prefix for the refusal messages. This command's only feedback is what
|
|
30
|
+
# it prints, so the word carries weight.
|
|
31
|
+
ERROR = "ERROR"
|
|
32
|
+
|
|
33
|
+
# What each package is verified with. One recipe for gems and npm
|
|
34
|
+
# packages alike — every repo's justfile defines it, which is the point
|
|
35
|
+
# of having a justfile in each.
|
|
36
|
+
CHECK = %w[just check-all].freeze
|
|
37
|
+
|
|
38
|
+
# Rule across the report, sized to the rows.
|
|
39
|
+
RULE = ("—" * 52).freeze
|
|
40
|
+
|
|
41
|
+
# Bringing a checkout level with origin/main before its suite runs, so a
|
|
42
|
+
# bump is verified against what is actually on main rather than whatever
|
|
43
|
+
# the local clone last saw.
|
|
44
|
+
SYNC_STEPS = [
|
|
45
|
+
%w[fetch --tags origin],
|
|
46
|
+
%w[rebase origin/main]
|
|
47
|
+
].freeze
|
|
48
|
+
|
|
49
|
+
# Runs one git command, capturing stdout *and* stderr.
|
|
50
|
+
#
|
|
51
|
+
# Capturing is the point. Left attached, a `git` run against a directory
|
|
52
|
+
# that is not a checkout writes "fatal: not a git repository" straight to
|
|
53
|
+
# the terminal — thirty such lines during the specs, which is noise the
|
|
54
|
+
# operator has to learn to ignore. The caller decides what is worth
|
|
55
|
+
# printing.
|
|
56
|
+
#
|
|
57
|
+
# @return [Array(Boolean, String)] success, and the combined output
|
|
58
|
+
DEFAULT_GIT = lambda { |dir, argv|
|
|
59
|
+
output, status = Open3.capture2e("git", "-C", dir, *argv)
|
|
60
|
+
[status.success?, output]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# Runs a package's own checks in its own directory.
|
|
64
|
+
DEFAULT_RUNNER = ->(dir, argv) { Dir.chdir(dir) { system(*argv) } }
|
|
65
|
+
|
|
66
|
+
# @param workspace [Workspace] the ecosystem checkout to rewrite
|
|
67
|
+
# @param out [IO] where report output goes
|
|
68
|
+
# @param git [#call] invoked with (dir, argv); returns [ok, output].
|
|
69
|
+
# Injected so specs can assert what would run without touching a repo.
|
|
70
|
+
# @param runner [#call] invoked with (dir, argv) to run a package's
|
|
71
|
+
# checks. Injected for the same reason: a unit test must not shell out
|
|
72
|
+
# to six real suites.
|
|
73
|
+
def initialize(workspace: Workspace.new, out: $stdout, git: DEFAULT_GIT, runner: DEFAULT_RUNNER)
|
|
74
|
+
@workspace = workspace
|
|
75
|
+
@out = out
|
|
76
|
+
@git = git
|
|
77
|
+
@runner = runner
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Rewrites every lockstep package to the target version, then runs each
|
|
81
|
+
# package's own `just check-all`.
|
|
82
|
+
#
|
|
83
|
+
# Refuses to move backwards: a version already published cannot be
|
|
84
|
+
# reissued, so the target must be at least the highest current value.
|
|
85
|
+
#
|
|
86
|
+
# @param version [String] target, MAJOR.MINOR.PATCH
|
|
87
|
+
# @param verify [Boolean] run `just check-all` per package afterwards
|
|
88
|
+
# @return [Boolean] true when every package was written and checked
|
|
89
|
+
def call(version, verify: true)
|
|
90
|
+
return false unless valid?(version)
|
|
91
|
+
|
|
92
|
+
out.puts "Setting all lockstep packages to #{version} 🚨:"
|
|
93
|
+
each_package { |name, kind| write(name, kind, version) }
|
|
94
|
+
return true unless verify
|
|
95
|
+
|
|
96
|
+
out.puts
|
|
97
|
+
out.puts "Verifying each package with `#{CHECK.join(" ")}`:"
|
|
98
|
+
results = each_package { |name, _kind| check(name) }
|
|
99
|
+
report(results)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
# @return [Workspace] the ecosystem checkout being rewritten
|
|
105
|
+
attr_reader :workspace
|
|
106
|
+
|
|
107
|
+
# @return [IO] where report output goes
|
|
108
|
+
attr_reader :out
|
|
109
|
+
|
|
110
|
+
# @return [#call] runs one git command, returning [ok, output]
|
|
111
|
+
attr_reader :git
|
|
112
|
+
|
|
113
|
+
# @return [#call] runs a package's checks, returning truthy on success
|
|
114
|
+
attr_reader :runner
|
|
115
|
+
|
|
116
|
+
# The one place the package list is walked, so adding a package changes
|
|
117
|
+
# nothing here.
|
|
118
|
+
#
|
|
119
|
+
# @yieldparam name [String] package name
|
|
120
|
+
# @yieldparam kind [Symbol] :gem or :npm
|
|
121
|
+
# @return [Hash{String => Object}] name => whatever the block returned
|
|
122
|
+
def each_package
|
|
123
|
+
Workspace::LOCKSTEP.to_h { |name, kind| [name, yield(name, kind)] }
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# @return [void]
|
|
127
|
+
def write(name, kind, version)
|
|
128
|
+
workspace.write_version(name, version, kind)
|
|
129
|
+
row(name, kind.to_s.cyan, version.green)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Runs one package's checks in its own directory.
|
|
133
|
+
#
|
|
134
|
+
# A package with no justfile is reported and skipped rather than failed:
|
|
135
|
+
# the fixture used by the specs has none, and a repo that has not grown
|
|
136
|
+
# one yet should not make a legitimate bump look broken.
|
|
137
|
+
#
|
|
138
|
+
# @param name [String] package name
|
|
139
|
+
# @return [Boolean, nil] pass/fail, or nil when skipped
|
|
140
|
+
def check(name)
|
|
141
|
+
dir = File.join(workspace.root, name)
|
|
142
|
+
unless File.exist?(File.join(dir, "justfile"))
|
|
143
|
+
row(name, "skip".yellow, "no justfile".yellow)
|
|
144
|
+
return nil
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
sync(name, dir)
|
|
148
|
+
passed = runner.call(dir, CHECK)
|
|
149
|
+
row(name, passed ? "ok".green : "FAIL".red, dir)
|
|
150
|
+
passed
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Brings one package level with origin/main before its suite runs.
|
|
154
|
+
#
|
|
155
|
+
# Two states are reported and left alone rather than acted on:
|
|
156
|
+
#
|
|
157
|
+
# - **not a git checkout** — nothing to sync, and running git here is what
|
|
158
|
+
# printed "fatal: not a git repository" once per command per package.
|
|
159
|
+
# - **uncommitted changes** — the tree is *not* stashed. A version bump
|
|
160
|
+
# that silently runs `git stash -u` can bury work the operator never
|
|
161
|
+
# agreed to move, and the stash stack is shared across worktrees, so it
|
|
162
|
+
# is not even reliably theirs to pop back. Rebasing on top of local
|
|
163
|
+
# edits is equally unwelcome. Skipping is the only non-destructive
|
|
164
|
+
# answer; the checks still run against what is on disk.
|
|
165
|
+
#
|
|
166
|
+
# @param name [String] package name
|
|
167
|
+
# @param dir [String] absolute path to the package
|
|
168
|
+
# @return [void]
|
|
169
|
+
def sync(name, dir)
|
|
170
|
+
state = workspace.git_state(name)
|
|
171
|
+
return row(name, "sync".yellow, "not a git checkout".yellow) if state[:clean].nil?
|
|
172
|
+
return row(name, "sync".yellow, "uncommitted changes — left alone".yellow) unless state[:clean]
|
|
173
|
+
|
|
174
|
+
SYNC_STEPS.each do |argv|
|
|
175
|
+
ok, output = git.call(dir, argv)
|
|
176
|
+
next if ok
|
|
177
|
+
|
|
178
|
+
row(name, "sync".red, "git #{argv.first} failed: #{output.to_s.lines.first&.strip}".red)
|
|
179
|
+
break
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# The single formatting site for every line of the report.
|
|
184
|
+
#
|
|
185
|
+
# `format`, never `printf`: printf writes straight to $stdout and returns
|
|
186
|
+
# nil, so an injected out would receive one blank line per package while
|
|
187
|
+
# the real output went elsewhere.
|
|
188
|
+
#
|
|
189
|
+
# @return [void]
|
|
190
|
+
def row(name, state, detail)
|
|
191
|
+
out.puts format(" → %<name>-24s %<state>-6s %<detail>s", name: name.yellow, state:, detail:)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# @param results [Hash{String => Boolean, nil}] per-package check outcome
|
|
195
|
+
# @return [Boolean] true when nothing failed
|
|
196
|
+
def report(results)
|
|
197
|
+
failed = results.select { |_name, passed| passed == false }.keys
|
|
198
|
+
out.puts RULE
|
|
199
|
+
if failed.empty?
|
|
200
|
+
out.puts "OK: every package checked out".green
|
|
201
|
+
true
|
|
202
|
+
else
|
|
203
|
+
out.puts "#{ERROR.bold.red}: #{failed.join(", ")} failed `#{CHECK.join(" ")}`"
|
|
204
|
+
false
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Whether the target is well-formed and moves the family forward.
|
|
209
|
+
# Reports the reason it does not, since this is the CLI's only feedback.
|
|
210
|
+
#
|
|
211
|
+
# @param version [String] proposed target
|
|
212
|
+
# @return [Boolean]
|
|
213
|
+
def valid?(version)
|
|
214
|
+
unless SEMVER_RE.match?(version.to_s)
|
|
215
|
+
out.puts "#{ERROR.bold.red}: #{version.inspect} is not MAJOR.MINOR.PATCH"
|
|
216
|
+
return false
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
highest = workspace.versions.values.compact.max_by { Gem::Version.new(it) }
|
|
220
|
+
if highest && Gem::Version.new(highest) > Gem::Version.new(version)
|
|
221
|
+
out.puts "#{ERROR.bold.red}: #{version} is below the highest existing " \
|
|
222
|
+
"version (#{highest}) — versions only go forward"
|
|
223
|
+
return false
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
true
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "colored2"
|
|
4
|
+
require_relative "workspace"
|
|
5
|
+
|
|
6
|
+
module Inquirex
|
|
7
|
+
module Tools
|
|
8
|
+
# Reports version parity across the lockstep packages.
|
|
9
|
+
#
|
|
10
|
+
# Read-only by construction: nothing here writes a version file. Bumping is
|
|
11
|
+
# {VersionBumper}'s job. The split means a check can be run anywhere — CI, a
|
|
12
|
+
# dirty tree, a colleague's checkout — with no chance of editing something,
|
|
13
|
+
# and the two behaviours no longer share a flag that decides which happens.
|
|
14
|
+
#
|
|
15
|
+
# @example Check for drift
|
|
16
|
+
# Inquirex::Tools::VersionChecker.new.call # => false when versions differ
|
|
17
|
+
class VersionChecker
|
|
18
|
+
# @param workspace [Workspace] the ecosystem checkout to inspect
|
|
19
|
+
# @param out [IO] where report output goes
|
|
20
|
+
def initialize(workspace: Workspace.new, out: $stdout)
|
|
21
|
+
@workspace = workspace
|
|
22
|
+
@out = out
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Prints the current state of every lockstep package.
|
|
26
|
+
#
|
|
27
|
+
# @return [Boolean] true when all packages agree on one version
|
|
28
|
+
def call
|
|
29
|
+
versions = workspace.versions
|
|
30
|
+
print_table(versions)
|
|
31
|
+
verdict(versions)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Parity plus git readiness: clean tree, on main, tagged at the version.
|
|
35
|
+
#
|
|
36
|
+
# @return [Boolean] true when every package could be published now
|
|
37
|
+
def preflight
|
|
38
|
+
ok = call
|
|
39
|
+
target = workspace.versions.values.compact.uniq.first
|
|
40
|
+
out.puts
|
|
41
|
+
out.puts "Release readiness:"
|
|
42
|
+
|
|
43
|
+
Workspace::LOCKSTEP.each_key do |name|
|
|
44
|
+
problems = readiness_problems(name, target)
|
|
45
|
+
# `.green`, not `.bright_green`: colored2 defines no such method, so
|
|
46
|
+
# the ready branch raised NoMethodError — and only the ready branch,
|
|
47
|
+
# which is why a preflight that found problems looked fine.
|
|
48
|
+
out.puts format(" %<name>-16s %<state>s", name: name, state: problems.empty? ? "ready".green : problems.join(", ").red)
|
|
49
|
+
ok &&= problems.empty?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
out.puts
|
|
53
|
+
out.puts ok ? "ALL READY — publish with `gem push` / `npm publish`" : "NOT READY — resolve the above"
|
|
54
|
+
ok
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
# @return [Workspace] the ecosystem checkout being inspected
|
|
60
|
+
attr_reader :workspace
|
|
61
|
+
|
|
62
|
+
# @return [IO] where report output goes
|
|
63
|
+
attr_reader :out
|
|
64
|
+
|
|
65
|
+
# @param versions [Hash{String => String, nil}]
|
|
66
|
+
# @return [void]
|
|
67
|
+
def print_table(versions)
|
|
68
|
+
versions.each do |name, version|
|
|
69
|
+
# format, not printf: printf writes straight to $stdout and returns
|
|
70
|
+
# nil, so the injected out saw one blank line per package and the
|
|
71
|
+
# real table bypassed it entirely.
|
|
72
|
+
out.puts format(" → %<name>-30s %<kind>5s %<version>6s",
|
|
73
|
+
name: name.to_s.yellow,
|
|
74
|
+
kind: Workspace::LOCKSTEP[name].to_s.cyan,
|
|
75
|
+
version: (version || "MISSING").to_s.green)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @param versions [Hash{String => String, nil}]
|
|
80
|
+
# @return [Boolean] true when every package agrees on one version
|
|
81
|
+
def verdict(versions)
|
|
82
|
+
out.puts "——————————————————————————————————"
|
|
83
|
+
distinct = versions.values.compact.uniq
|
|
84
|
+
if versions.value?(nil)
|
|
85
|
+
out.puts "DRIFT: a version could not be read (MISSING above)".red
|
|
86
|
+
false
|
|
87
|
+
elsif distinct.one?
|
|
88
|
+
out.puts "OK: all #{versions.size} at #{distinct.first}".green
|
|
89
|
+
true
|
|
90
|
+
else
|
|
91
|
+
out.puts "DRIFT: #{distinct.sort.join(", ")} — run `inquirex versions-bump <version>`".yellow
|
|
92
|
+
false
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Why a package is not publishable right now.
|
|
97
|
+
#
|
|
98
|
+
# @param name [String] package name
|
|
99
|
+
# @param target [String, nil] the version its tag must match
|
|
100
|
+
# @return [Array<String>] empty when ready
|
|
101
|
+
def readiness_problems(name, target)
|
|
102
|
+
state = workspace.git_state(name)
|
|
103
|
+
return ["no git checkout"] if state[:clean].nil?
|
|
104
|
+
|
|
105
|
+
problems = []
|
|
106
|
+
problems << "DIRTY" unless state[:clean]
|
|
107
|
+
problems << "on #{state[:branch]}" unless state[:branch] == "main"
|
|
108
|
+
problems << "untagged" unless state[:tags].any? { |tag| tag.delete_prefix("v") == target }
|
|
109
|
+
problems
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -11,34 +11,34 @@ module Inquirex
|
|
|
11
11
|
# walking up from the working directory looking for the sibling checkouts,
|
|
12
12
|
# or taken from INQUIREX_ROOT.
|
|
13
13
|
class Workspace
|
|
14
|
-
#
|
|
15
|
-
# serialization format — the gem defines the DSL and emits step JSON,
|
|
16
|
-
# inquirex-
|
|
17
|
-
# inquirex-
|
|
18
|
-
#
|
|
19
|
-
#
|
|
14
|
+
# Every package in the family, all carrying the same version. They share
|
|
15
|
+
# one serialization format — the gem defines the DSL and emits step JSON,
|
|
16
|
+
# inquirex-widget (npm; formerly @kigster/inquirex-js) renders it,
|
|
17
|
+
# inquirex-webui prints it back to DSL, inquirex-llm extends the
|
|
18
|
+
# vocabulary, inquirex-tty walks a flow in the terminal, and
|
|
19
|
+
# inquirex-tools is the tooling that ships alongside them. A verb present
|
|
20
|
+
# in one and absent in another does not fail loudly; it drops data.
|
|
20
21
|
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
22
|
+
# Nothing is excluded. One version number across the whole family means
|
|
23
|
+
# "which combination has this bug?" is a lookup rather than an
|
|
24
|
+
# investigation.
|
|
23
25
|
LOCKSTEP = {
|
|
24
|
-
"inquirex"
|
|
25
|
-
"inquirex-llm"
|
|
26
|
-
"inquirex-tools"
|
|
27
|
-
"inquirex-
|
|
28
|
-
"inquirex-
|
|
26
|
+
"inquirex" => :gem,
|
|
27
|
+
"inquirex-llm" => :gem,
|
|
28
|
+
"inquirex-tools" => :gem,
|
|
29
|
+
"inquirex-tty" => :gem,
|
|
30
|
+
"inquirex-widget" => :npm,
|
|
31
|
+
"inquirex-webui" => :npm
|
|
29
32
|
}.freeze
|
|
30
33
|
|
|
31
|
-
# Excluded from lockstep, documented so the omission reads as deliberate.
|
|
32
|
-
EXCLUDED = %w[inquirex-tty].freeze
|
|
33
|
-
|
|
34
34
|
# Repositories that carry a generated CHANGELOG.md but are not versioned
|
|
35
35
|
# in lockstep.
|
|
36
|
-
CHANGELOG_ONLY = %w[
|
|
36
|
+
CHANGELOG_ONLY = %w[qualified-at].freeze
|
|
37
37
|
|
|
38
38
|
GEM_VERSION_RE = /VERSION\s*=\s*"([^"]+)"/
|
|
39
39
|
|
|
40
40
|
# Directories whose presence identifies the ecosystem root.
|
|
41
|
-
MARKERS = %w[inquirex inquirex-
|
|
41
|
+
MARKERS = %w[inquirex inquirex-widget].freeze
|
|
42
42
|
|
|
43
43
|
# @return [String] absolute path to the ecosystem root
|
|
44
44
|
attr_reader :root
|
|
@@ -92,18 +92,21 @@ module Inquirex
|
|
|
92
92
|
#
|
|
93
93
|
# @param name [String] package name
|
|
94
94
|
# @param version [String] new version
|
|
95
|
+
# @param kind [Symbol] :gem or :npm — which version line to rewrite.
|
|
96
|
+
# Passed by the caller rather than looked up here so a caller that
|
|
97
|
+
# already knows the kind does not pay for a second lookup.
|
|
95
98
|
# @return [void]
|
|
96
|
-
def write_version(name, version)
|
|
99
|
+
def write_version(name, version, kind)
|
|
97
100
|
file = version_file(name)
|
|
98
101
|
raise Error, "no version file for #{name}" unless file
|
|
99
102
|
|
|
100
103
|
src = File.read(file)
|
|
101
|
-
updated =
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
updated = case kind
|
|
105
|
+
when :gem
|
|
106
|
+
src.sub(GEM_VERSION_RE, %(VERSION = "#{version}"))
|
|
107
|
+
when :npm
|
|
108
|
+
src.sub(/"version":\s*"[^"]*"/, %("version": "#{version}"))
|
|
109
|
+
end
|
|
107
110
|
File.write(file, updated)
|
|
108
111
|
end
|
|
109
112
|
|
|
@@ -114,9 +117,9 @@ module Inquirex
|
|
|
114
117
|
return { clean: nil, branch: nil, tags: [] } unless File.exist?(File.join(dir, ".git"))
|
|
115
118
|
|
|
116
119
|
{
|
|
117
|
-
clean:
|
|
120
|
+
clean: `git -C #{dir} status --porcelain 2>/dev/null`.strip.empty?,
|
|
118
121
|
branch: `git -C #{dir} rev-parse --abbrev-ref HEAD 2>/dev/null`.strip,
|
|
119
|
-
tags:
|
|
122
|
+
tags: `git -C #{dir} tag --points-at HEAD 2>/dev/null`.split("\n").map(&:strip).reject(&:empty?)
|
|
120
123
|
}
|
|
121
124
|
end
|
|
122
125
|
|
data/lib/inquirex/tools.rb
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "tools/version"
|
|
4
4
|
require_relative "tools/workspace"
|
|
5
|
-
require_relative "tools/
|
|
5
|
+
require_relative "tools/version_checker"
|
|
6
|
+
require_relative "tools/version_bumper"
|
|
7
|
+
require_relative "tools/publisher"
|
|
8
|
+
require_relative "tools/releaser"
|
|
6
9
|
require_relative "tools/changelogs"
|
|
7
10
|
|
|
8
11
|
module Inquirex
|
|
@@ -17,13 +20,17 @@ module Inquirex
|
|
|
17
20
|
# required to have zero runtime dependencies, and nothing added here changes
|
|
18
21
|
# that, because nothing here is loaded by the runtime gems.
|
|
19
22
|
#
|
|
20
|
-
#
|
|
23
|
+
# It ships one executable, `inquirex`, with these subcommands:
|
|
21
24
|
#
|
|
22
|
-
# - `
|
|
23
|
-
# - `
|
|
25
|
+
# - `versions-check` — version parity across the lockstep packages
|
|
26
|
+
# - `versions-bump` — move every lockstep package to one version
|
|
27
|
+
# - `publish` — publish every package to RubyGems and npm
|
|
28
|
+
# - `create-release` — tag and create a GitHub release for every package
|
|
29
|
+
# - `changelogs` — regenerate CHANGELOG.md from merged PRs
|
|
30
|
+
# - `version` — this gem's own version
|
|
24
31
|
#
|
|
25
32
|
# @example Check whether the family is releasable
|
|
26
|
-
# Inquirex::Tools::
|
|
33
|
+
# Inquirex::Tools::VersionChecker.new.preflight # => true when every package is ready
|
|
27
34
|
module Tools
|
|
28
35
|
class Error < StandardError; end
|
|
29
36
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: inquirex-tools
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Konstantin Gredeskoul
|
|
@@ -23,6 +23,20 @@ dependencies:
|
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '1.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: colored2
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
26
40
|
description: |
|
|
27
41
|
Ships the `inquirex` command: lockstep version management across the
|
|
28
42
|
Inquirex gems and npm packages, changelog regeneration, and release
|
|
@@ -32,24 +46,42 @@ email:
|
|
|
32
46
|
- kigster@gmail.com
|
|
33
47
|
executables:
|
|
34
48
|
- inquirex
|
|
49
|
+
- inquirexor
|
|
35
50
|
extensions: []
|
|
36
51
|
extra_rdoc_files: []
|
|
37
52
|
files:
|
|
38
53
|
- ".DS_Store"
|
|
54
|
+
- ".env.encrypted"
|
|
55
|
+
- ".env.example"
|
|
56
|
+
- ".envrc"
|
|
39
57
|
- ".github_changelog_generator"
|
|
58
|
+
- ".relaxed_rubocop.yml"
|
|
59
|
+
- ".sops.yaml"
|
|
60
|
+
- ".sopsy.sha"
|
|
61
|
+
- ".sopsy.yml"
|
|
40
62
|
- CHANGELOG.md
|
|
41
63
|
- LICENSE.txt
|
|
42
64
|
- README.md
|
|
43
65
|
- Rakefile
|
|
66
|
+
- docs/badges/coverage_badge.svg
|
|
67
|
+
- docs/monorepo-migration.md
|
|
44
68
|
- exe/inquirex
|
|
69
|
+
- exe/inquirexor
|
|
70
|
+
- justfile
|
|
45
71
|
- lib/inquirex/tools.rb
|
|
46
72
|
- lib/inquirex/tools/changelogs.rb
|
|
47
73
|
- lib/inquirex/tools/commands.rb
|
|
48
74
|
- lib/inquirex/tools/commands/changelogs.rb
|
|
75
|
+
- lib/inquirex/tools/commands/create_release.rb
|
|
76
|
+
- lib/inquirex/tools/commands/publish.rb
|
|
49
77
|
- lib/inquirex/tools/commands/version.rb
|
|
50
|
-
- lib/inquirex/tools/commands/
|
|
78
|
+
- lib/inquirex/tools/commands/versions_bump.rb
|
|
79
|
+
- lib/inquirex/tools/commands/versions_check.rb
|
|
80
|
+
- lib/inquirex/tools/publisher.rb
|
|
81
|
+
- lib/inquirex/tools/releaser.rb
|
|
51
82
|
- lib/inquirex/tools/version.rb
|
|
52
|
-
- lib/inquirex/tools/
|
|
83
|
+
- lib/inquirex/tools/version_bumper.rb
|
|
84
|
+
- lib/inquirex/tools/version_checker.rb
|
|
53
85
|
- lib/inquirex/tools/workspace.rb
|
|
54
86
|
- sig/inquirex/tools.rbs
|
|
55
87
|
homepage: https://github.com/kigster/inquirex-tools
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Inquirex
|
|
4
|
-
module Tools
|
|
5
|
-
module Commands
|
|
6
|
-
# Reports and enforces version parity across the lockstep packages.
|
|
7
|
-
#
|
|
8
|
-
# Plural on purpose: `inquirex version` prints this tool's own version,
|
|
9
|
-
# `inquirex versions` manages the family's.
|
|
10
|
-
class Versions < Dry::CLI::Command
|
|
11
|
-
desc "Check, set, or preflight the family's lockstep version"
|
|
12
|
-
|
|
13
|
-
option :set, type: :string, desc: "Move every lockstep package to this version"
|
|
14
|
-
option :preflight, type: :boolean, default: false,
|
|
15
|
-
desc: "Also check clean trees, main branch, and tags"
|
|
16
|
-
|
|
17
|
-
example [
|
|
18
|
-
" # report parity, exit 1 on drift",
|
|
19
|
-
"--set 0.8.0 # move all five packages",
|
|
20
|
-
"--preflight # release readiness"
|
|
21
|
-
]
|
|
22
|
-
|
|
23
|
-
# @param set [String, nil] target version
|
|
24
|
-
# @param preflight [Boolean]
|
|
25
|
-
# @return [void]
|
|
26
|
-
def call(set: nil, preflight: false, **)
|
|
27
|
-
versions = Inquirex::Tools::Versions.new
|
|
28
|
-
ok =
|
|
29
|
-
if set then versions.set(set)
|
|
30
|
-
elsif preflight then versions.preflight
|
|
31
|
-
else versions.check
|
|
32
|
-
end
|
|
33
|
-
exit(1) unless ok
|
|
34
|
-
rescue Inquirex::Tools::Error => e
|
|
35
|
-
warn "ERROR: #{e.message}"
|
|
36
|
-
exit 1
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|