kettle-family 0.1.4 → 0.1.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
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +20 -1
- data/README.md +1 -1
- data/lib/kettle/family/cli.rb +17 -2
- data/lib/kettle/family/config.rb +19 -0
- data/lib/kettle/family/local_install.rb +190 -0
- data/lib/kettle/family/version.rb +1 -1
- data/lib/kettle/family.rb +1 -0
- data.tar.gz.sig +0 -0
- metadata +7 -6
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e59cb730ec2b1f48e8cf47c533677f9c20bb4490004416558015375ddb88c932
|
|
4
|
+
data.tar.gz: 3450aa12f83fda8e385d9c7e02b6c3c7a735fcaa3ca512690aa9969fd34c8221
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 68f19eb798c28c122e79e4a74e754e703f456addcf23105eaa940c8b466f233963d10062a9c5c9776fb80d86dac2e19448c88f8d788a4c426d389c8e81b61124
|
|
7
|
+
data.tar.gz: 7edcb026f774f469066f91972fa60523b8df626d2e50241cf14a966d744dd7c214c1eee7e920b1c5bc2f5f7decf36bde05743cf5da040dc8cd2cbf9997369833
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,23 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
30
30
|
|
|
31
31
|
### Security
|
|
32
32
|
|
|
33
|
+
## [0.1.5] - 2026-06-17
|
|
34
|
+
|
|
35
|
+
- TAG: [v0.1.5][0.1.5t]
|
|
36
|
+
- COVERAGE: 94.24% -- 1162/1233 lines in 20 files
|
|
37
|
+
- BRANCH COVERAGE: 77.32% -- 358/463 branches in 20 files
|
|
38
|
+
- 39.73% documented
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- Added `kettle-family install` to build and install selected local family gems,
|
|
43
|
+
including config-defined `install.local_dependencies` resolved relative to the
|
|
44
|
+
`.kettle-family.yml` file.
|
|
45
|
+
|
|
46
|
+
### Changed
|
|
47
|
+
|
|
48
|
+
- Development dependency `kettle-dev` now requires 2.2.10 or newer.
|
|
49
|
+
|
|
33
50
|
## [0.1.4] - 2026-06-16
|
|
34
51
|
|
|
35
52
|
- TAG: [v0.1.4][0.1.4t]
|
|
@@ -140,7 +157,9 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
140
157
|
- Fixed CI load failures on engines without compatible `pty` support by falling back to Open3 for interactive release commands.
|
|
141
158
|
- Fixed Ruby 3.2 version-bump support by loading Prism lazily and wiring the Prism gem only for MRI versions that need it.
|
|
142
159
|
|
|
143
|
-
[Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v0.1.
|
|
160
|
+
[Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v0.1.5...HEAD
|
|
161
|
+
[0.1.5]: https://github.com/kettle-dev/kettle-family/compare/v0.1.4...v0.1.5
|
|
162
|
+
[0.1.5t]: https://github.com/kettle-dev/kettle-family/releases/tag/v0.1.5
|
|
144
163
|
[0.1.4]: https://github.com/kettle-dev/kettle-family/compare/v0.1.3...v0.1.4
|
|
145
164
|
[0.1.4t]: https://github.com/kettle-dev/kettle-family/releases/tag/v0.1.4
|
|
146
165
|
[0.1.3]: https://github.com/kettle-dev/kettle-family/compare/v0.1.2...v0.1.3
|
data/README.md
CHANGED
|
@@ -546,7 +546,7 @@ Thanks for RTFM. ☺️
|
|
|
546
546
|
[📌gitmoji]: https://gitmoji.dev
|
|
547
547
|
[📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
|
|
548
548
|
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
549
|
-
[🧮kloc-img]: https://img.shields.io/badge/KLOC-1.
|
|
549
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-1.233-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
|
550
550
|
[🔐security]: https://github.com/kettle-dev/kettle-family/blob/main/SECURITY.md
|
|
551
551
|
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
|
552
552
|
[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
|
data/lib/kettle/family/cli.rb
CHANGED
|
@@ -6,7 +6,7 @@ require "optparse"
|
|
|
6
6
|
module Kettle
|
|
7
7
|
module Family
|
|
8
8
|
class CLI
|
|
9
|
-
COMMANDS = %w[discover plan report metadata check test lint docs template bump-version release branch-lanes release-state].freeze
|
|
9
|
+
COMMANDS = %w[discover plan report metadata check test lint docs template install bump-version release branch-lanes release-state].freeze
|
|
10
10
|
WORKFLOW_COMMANDS = %w[check test lint docs template release].freeze
|
|
11
11
|
|
|
12
12
|
def self.call(argv, out: $stdout, err: $stderr)
|
|
@@ -59,6 +59,7 @@ module Kettle
|
|
|
59
59
|
lint Plan or execute configured lint command per member
|
|
60
60
|
docs Plan or execute configured docs command per member
|
|
61
61
|
template Plan or execute kettle-jem templating per member
|
|
62
|
+
install Build and install selected local family gems
|
|
62
63
|
bump-version Check, plan, or execute family version alignment
|
|
63
64
|
release Plan or execute release build/publish phases
|
|
64
65
|
branch-lanes Audit configured branch lane release mappings
|
|
@@ -140,7 +141,9 @@ module Kettle
|
|
|
140
141
|
def build_report(command, options)
|
|
141
142
|
config = Config.load(root: options[:root], path: options[:config])
|
|
142
143
|
members = Discovery.new(config: config).members
|
|
143
|
-
ordered = if
|
|
144
|
+
ordered = if command == "install"
|
|
145
|
+
install_order(members, config)
|
|
146
|
+
elsif %w[metadata release-state].include?(command)
|
|
144
147
|
members.sort_by(&:name)
|
|
145
148
|
else
|
|
146
149
|
Orderer.new(members: members, mode: config.order_mode, hints: config.order_hints).ordered
|
|
@@ -170,6 +173,7 @@ module Kettle
|
|
|
170
173
|
return bump_version_results(members: members, options: options) if command == "bump-version"
|
|
171
174
|
return branch_lane_results(config: config, members: members) if command == "branch-lanes"
|
|
172
175
|
return release_state_results(config: config, members: members) if command == "release-state"
|
|
176
|
+
return install_results(config: config, members: members, options: options) if command == "install"
|
|
173
177
|
return [] unless WORKFLOW_COMMANDS.include?(command)
|
|
174
178
|
|
|
175
179
|
Workflow.new(
|
|
@@ -208,10 +212,21 @@ module Kettle
|
|
|
208
212
|
BranchLaneAudit.new(config: config, members: members).results
|
|
209
213
|
end
|
|
210
214
|
|
|
215
|
+
def install_results(config:, members:, options:)
|
|
216
|
+
LocalInstall.new(config: config, members: members, execute: options[:execute]).results
|
|
217
|
+
end
|
|
218
|
+
|
|
211
219
|
def release_state_results(config:, members:)
|
|
212
220
|
ReleaseStateCheck.new(config: config, members: members).results
|
|
213
221
|
end
|
|
214
222
|
|
|
223
|
+
def install_order(members, config)
|
|
224
|
+
by_name = members.to_h { |member| [member.name, member] }
|
|
225
|
+
hinted = config.order_hints.filter_map { |name| by_name[name] }
|
|
226
|
+
hinted_names = hinted.map(&:name)
|
|
227
|
+
hinted + members.reject { |member| hinted_names.include?(member.name) }.sort_by(&:name)
|
|
228
|
+
end
|
|
229
|
+
|
|
215
230
|
def write_report(report, options)
|
|
216
231
|
return unless options[:report]
|
|
217
232
|
|
data/lib/kettle/family/config.rb
CHANGED
|
@@ -168,6 +168,14 @@ module Kettle
|
|
|
168
168
|
fetch_path("template", "normalize_lockfiles_command") || "bundle lock"
|
|
169
169
|
end
|
|
170
170
|
|
|
171
|
+
def install_local_dependencies
|
|
172
|
+
paths = fetch_path("install", "local_dependencies") || fetch_path("local_dependencies") || []
|
|
173
|
+
Array(paths).map do |entry|
|
|
174
|
+
value = entry.is_a?(Hash) ? stringify_keys(entry).fetch("path") : entry
|
|
175
|
+
expand_config_relative_path(value)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
171
179
|
def release_build_command
|
|
172
180
|
fetch_path("release", "build_command") || command_for("release_build") || "bundle exec rake build"
|
|
173
181
|
end
|
|
@@ -206,6 +214,17 @@ module Kettle
|
|
|
206
214
|
|
|
207
215
|
private
|
|
208
216
|
|
|
217
|
+
def expand_config_relative_path(value)
|
|
218
|
+
text = value.to_s
|
|
219
|
+
return text if text.start_with?("/")
|
|
220
|
+
|
|
221
|
+
File.expand_path(text, config_dir)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def config_dir
|
|
225
|
+
path ? File.dirname(path) : root
|
|
226
|
+
end
|
|
227
|
+
|
|
209
228
|
def sibling_member_roots
|
|
210
229
|
Dir.children(root)
|
|
211
230
|
.map { |entry| File.join(root, entry) }
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "json"
|
|
5
|
+
require "open3"
|
|
6
|
+
require "time"
|
|
7
|
+
|
|
8
|
+
module Kettle
|
|
9
|
+
module Family
|
|
10
|
+
class LocalInstall
|
|
11
|
+
def initialize(config:, members:, execute: false)
|
|
12
|
+
@config = config
|
|
13
|
+
@members = members
|
|
14
|
+
@execute = execute
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def results
|
|
18
|
+
results = install_members.each_with_object([]) do |member, memo|
|
|
19
|
+
memo << install_member(member)
|
|
20
|
+
break memo unless memo.last.ok?
|
|
21
|
+
end
|
|
22
|
+
write_local_install_marker if execute && results.all?(&:ok?)
|
|
23
|
+
results
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
attr_reader :config, :members, :execute
|
|
29
|
+
|
|
30
|
+
def install_members
|
|
31
|
+
seen = {}
|
|
32
|
+
(local_dependency_members + members).each_with_object([]) do |member, memo|
|
|
33
|
+
next if seen.key?(member.name)
|
|
34
|
+
|
|
35
|
+
seen[member.name] = true
|
|
36
|
+
memo << member
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def local_dependency_members
|
|
41
|
+
config.install_local_dependencies.map { |path| member_from_path(path) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def member_from_path(path)
|
|
45
|
+
gemspec = gemspec_path(path)
|
|
46
|
+
spec = load_gemspec(gemspec)
|
|
47
|
+
Member.new(
|
|
48
|
+
name: spec.name,
|
|
49
|
+
root: File.dirname(gemspec),
|
|
50
|
+
gemspec_path: gemspec,
|
|
51
|
+
version_file: version_file(File.dirname(gemspec)),
|
|
52
|
+
version: spec.version.to_s,
|
|
53
|
+
dependencies: spec.dependencies.map(&:name).sort,
|
|
54
|
+
required_ruby_version: required_ruby_version(spec),
|
|
55
|
+
licenses: Array(spec.licenses),
|
|
56
|
+
authors: Array(spec.authors)
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def gemspec_path(path)
|
|
61
|
+
expanded = File.expand_path(path)
|
|
62
|
+
return expanded if File.file?(expanded) && File.extname(expanded) == ".gemspec"
|
|
63
|
+
|
|
64
|
+
raise Error, "install local dependency does not exist: #{path}" unless Dir.exist?(expanded)
|
|
65
|
+
|
|
66
|
+
gemspecs = Dir.glob(File.join(expanded, "*.gemspec"))
|
|
67
|
+
raise Error, "no gemspec found for install local dependency: #{path}" if gemspecs.empty?
|
|
68
|
+
raise Error, "multiple gemspecs found for install local dependency: #{path}" if gemspecs.size > 1
|
|
69
|
+
|
|
70
|
+
gemspecs.first
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def load_gemspec(path)
|
|
74
|
+
# Some gemspecs use root-relative loads.
|
|
75
|
+
# rubocop:disable ThreadSafety/DirChdir
|
|
76
|
+
spec = Dir.chdir(File.dirname(path)) { Gem::Specification.load(path) }
|
|
77
|
+
# rubocop:enable ThreadSafety/DirChdir
|
|
78
|
+
raise Error, "could not load gemspec #{path}" unless spec
|
|
79
|
+
|
|
80
|
+
spec
|
|
81
|
+
rescue => error
|
|
82
|
+
raise Error, "could not load gemspec #{path}: #{error.message}"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def version_file(root)
|
|
86
|
+
Dir.glob(File.join(root, "lib", "**", "version.rb")).min
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def required_ruby_version(spec)
|
|
90
|
+
value = spec.required_ruby_version&.to_s&.strip
|
|
91
|
+
value.empty? ? nil : value
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def install_member(member)
|
|
95
|
+
gem_path = local_gem_path(member)
|
|
96
|
+
argv = install_argv(gem_path)
|
|
97
|
+
return skipped_result(member: member, argv: argv) unless execute
|
|
98
|
+
|
|
99
|
+
FileUtils.mkdir_p(File.dirname(gem_path))
|
|
100
|
+
FileUtils.rm_f(gem_path)
|
|
101
|
+
build_stdout, build_stderr, build_status = run(build_argv(member, gem_path), chdir: member.root)
|
|
102
|
+
return failed_result(member: member, argv: build_argv(member, gem_path), stdout: build_stdout, stderr: build_stderr, status: build_status) unless build_status.success?
|
|
103
|
+
|
|
104
|
+
stdout, stderr, status = run(argv)
|
|
105
|
+
CommandResult.new(
|
|
106
|
+
member_name: member.name,
|
|
107
|
+
phase: "install",
|
|
108
|
+
command: argv,
|
|
109
|
+
workdir: member.root,
|
|
110
|
+
status: status.exitstatus,
|
|
111
|
+
success: status.success?,
|
|
112
|
+
stdout: build_stdout + stdout,
|
|
113
|
+
stderr: build_stderr + stderr,
|
|
114
|
+
elapsed_seconds: 0.0,
|
|
115
|
+
skipped: false,
|
|
116
|
+
reason: status.success? ? nil : "command failed"
|
|
117
|
+
)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def run(argv, chdir: nil)
|
|
121
|
+
env = {"SKIP_GEM_SIGNING" => "true"}
|
|
122
|
+
chdir ? Open3.capture3(env, *argv, chdir: chdir) : Open3.capture3(env, *argv)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def local_gem_path(member)
|
|
126
|
+
File.join(member.root, "tmp", "local-gem-install", "#{member.name}-#{member.version}.gem")
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def build_argv(member, gem_path)
|
|
130
|
+
["gem", "build", member.gemspec_path, "--output", gem_path]
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def install_argv(gem_path)
|
|
134
|
+
["gem", "install", "--force", "--no-document", "--local", "--ignore-dependencies", gem_path]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def skipped_result(member:, argv:)
|
|
138
|
+
CommandResult.new(
|
|
139
|
+
member_name: member.name,
|
|
140
|
+
phase: "install",
|
|
141
|
+
command: argv,
|
|
142
|
+
workdir: member.root,
|
|
143
|
+
status: nil,
|
|
144
|
+
success: true,
|
|
145
|
+
stdout: "",
|
|
146
|
+
stderr: "",
|
|
147
|
+
elapsed_seconds: 0.0,
|
|
148
|
+
skipped: true,
|
|
149
|
+
reason: "dry-run; pass --execute to run"
|
|
150
|
+
)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def failed_result(member:, argv:, stdout:, stderr:, status:)
|
|
154
|
+
CommandResult.new(
|
|
155
|
+
member_name: member.name,
|
|
156
|
+
phase: "install",
|
|
157
|
+
command: argv,
|
|
158
|
+
workdir: member.root,
|
|
159
|
+
status: status.exitstatus,
|
|
160
|
+
success: false,
|
|
161
|
+
stdout: stdout,
|
|
162
|
+
stderr: stderr,
|
|
163
|
+
elapsed_seconds: 0.0,
|
|
164
|
+
skipped: false,
|
|
165
|
+
reason: "command failed"
|
|
166
|
+
)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def write_local_install_marker
|
|
170
|
+
FileUtils.mkdir_p(File.dirname(local_install_marker_path))
|
|
171
|
+
File.write(local_install_marker_path, JSON.pretty_generate(local_install_marker))
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def local_install_marker
|
|
175
|
+
{
|
|
176
|
+
"family" => config.family_name,
|
|
177
|
+
"root" => config.root,
|
|
178
|
+
"members_root" => config.members_root,
|
|
179
|
+
"local_dependencies" => config.install_local_dependencies,
|
|
180
|
+
"installed_members" => install_members.map(&:name),
|
|
181
|
+
"installed_at" => Time.now.utc.iso8601
|
|
182
|
+
}
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def local_install_marker_path
|
|
186
|
+
File.join(Dir.home, ".kettle-family", "local-install.json")
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
data/lib/kettle/family.rb
CHANGED
|
@@ -13,6 +13,7 @@ require_relative "family/git_status"
|
|
|
13
13
|
require_relative "family/version_bump"
|
|
14
14
|
require_relative "family/branch_lane_audit"
|
|
15
15
|
require_relative "family/release_state_check"
|
|
16
|
+
require_relative "family/local_install"
|
|
16
17
|
require_relative "family/workflow"
|
|
17
18
|
require_relative "family/config"
|
|
18
19
|
require_relative "family/discovery"
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kettle-family
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
@@ -80,7 +80,7 @@ dependencies:
|
|
|
80
80
|
version: '2.2'
|
|
81
81
|
- - ">="
|
|
82
82
|
- !ruby/object:Gem::Version
|
|
83
|
-
version: 2.2.
|
|
83
|
+
version: 2.2.10
|
|
84
84
|
type: :development
|
|
85
85
|
prerelease: false
|
|
86
86
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -90,7 +90,7 @@ dependencies:
|
|
|
90
90
|
version: '2.2'
|
|
91
91
|
- - ">="
|
|
92
92
|
- !ruby/object:Gem::Version
|
|
93
|
-
version: 2.2.
|
|
93
|
+
version: 2.2.10
|
|
94
94
|
- !ruby/object:Gem::Dependency
|
|
95
95
|
name: bundler-audit
|
|
96
96
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -291,6 +291,7 @@ files:
|
|
|
291
291
|
- lib/kettle/family/config.rb
|
|
292
292
|
- lib/kettle/family/discovery.rb
|
|
293
293
|
- lib/kettle/family/git_status.rb
|
|
294
|
+
- lib/kettle/family/local_install.rb
|
|
294
295
|
- lib/kettle/family/member.rb
|
|
295
296
|
- lib/kettle/family/orderer.rb
|
|
296
297
|
- lib/kettle/family/readiness_check.rb
|
|
@@ -308,10 +309,10 @@ licenses:
|
|
|
308
309
|
- AGPL-3.0-only
|
|
309
310
|
metadata:
|
|
310
311
|
homepage_uri: https://kettle-family.galtzo.com
|
|
311
|
-
source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v0.1.
|
|
312
|
-
changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v0.1.
|
|
312
|
+
source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v0.1.5
|
|
313
|
+
changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v0.1.5/CHANGELOG.md
|
|
313
314
|
bug_tracker_uri: https://github.com/kettle-dev/kettle-family/issues
|
|
314
|
-
documentation_uri: https://www.rubydoc.info/gems/kettle-family/0.1.
|
|
315
|
+
documentation_uri: https://www.rubydoc.info/gems/kettle-family/0.1.5
|
|
315
316
|
funding_uri: https://github.com/sponsors/pboling
|
|
316
317
|
wiki_uri: https://github.com/kettle-dev/kettle-family/wiki
|
|
317
318
|
news_uri: https://www.railsbling.com/tags/kettle-family
|
metadata.gz.sig
CHANGED
|
Binary file
|