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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b3ec93e3425d765de198975bb390031d33f4a408c31994a18999e3178aec2cee
4
- data.tar.gz: c22c0cec6c2dc9a387281adb456d7303bc838e687be27b0c69ecc59b7f8038ae
3
+ metadata.gz: e59cb730ec2b1f48e8cf47c533677f9c20bb4490004416558015375ddb88c932
4
+ data.tar.gz: 3450aa12f83fda8e385d9c7e02b6c3c7a735fcaa3ca512690aa9969fd34c8221
5
5
  SHA512:
6
- metadata.gz: 7520e01212b4faebaed857c69a7bd0c068be891e8fa79c4ccb7485f9cd8fdd4daa824c5943cc030da765992422a1bf252fe76fde2e68b3b4f0feeaf179beb30a
7
- data.tar.gz: '0683aae4dfdbbde5b004719856f32b65f7810acd81446145456a7810e4625f85a516f1d53d08d0c1622f65d7d8a2902c0dfb2ff57b35950f19bc76355cdded56'
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.4...HEAD
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.131-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
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
@@ -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 %w[metadata release-state].include?(command)
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
 
@@ -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
@@ -3,7 +3,7 @@
3
3
  module Kettle
4
4
  module Family
5
5
  module Version
6
- VERSION = "0.1.4"
6
+ VERSION = "0.1.5"
7
7
  end
8
8
  VERSION = Version::VERSION # Traditional Constant Location
9
9
  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
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.9
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.9
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.4
312
- changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v0.1.4/CHANGELOG.md
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.4
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