kettle-family 0.1.2 → 0.1.3

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: 3a7c89b89e309502af9e996022c4c93d5b02b0118279464d197537e76bd2c65a
4
- data.tar.gz: f80c4406bda1cd8e3a0d42c47da1e4fa645303b71f1cd68a5381053c041e0351
3
+ metadata.gz: 2db11fe0f0487d5a8d9b7413b680997f63b65ba2e025e5b6dbeef9e2a4f0b6c8
4
+ data.tar.gz: fcd754360608f5791e7c3372f37cd9e8a073ce1f9a8b1c883e457740d2bf12d5
5
5
  SHA512:
6
- metadata.gz: 5160695320d1ce2a37cbb6e4cfa8dd45026a6784d660a932678aa601b5c4b5a22903530109537cf9e9d58f6658f20d0e2ea8d36ead281f0258a1743da7c1e565
7
- data.tar.gz: 324f032033f94e3d7891d9935efd8ca1583c081eac9e14d265c2dcb3d258ffdbb310e8fb0e89587ccb55db73d265ced7cd10e681954a778cbcd981668f375a19
6
+ metadata.gz: 8a92079c7ba5f0b0ce6d6cae83ee1625c592270260079643e6f988b3a8e95f839173e6b70c21326eae93a3e1888c49b59f402fdbae12a295a7e903f0e1f2c790
7
+ data.tar.gz: 56112e0a206756aa3c9b885029d5ed5c1a555cde829ea56cfbc335df405c3c9bfa54badfc29eebf76b9be339ef55b5999c0250e4db14aa755157de3ff0de2195
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,37 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [0.1.3] - 2026-06-14
34
+
35
+ - TAG: [v0.1.3][0.1.3t]
36
+ - COVERAGE: 94.34% -- 917/972 lines in 19 files
37
+ - BRANCH COVERAGE: 78.36% -- 268/342 branches in 19 files
38
+ - 44.00% documented
39
+
40
+ ### Changed
41
+
42
+ - Runtime dependency `kettle-dev` now requires 2.2.8 or newer.
43
+ - `kettle-family release-state` now expands configured
44
+ `release.target_branches` and reports release state for each branch
45
+ independently.
46
+
47
+ - Project licensing changed from MIT to AGPL-3.0-only.
48
+ - `kettle-family release-state` now invokes `kettle-changelog` from the active
49
+ toolchain instead of depending on `kettle-dev` as a published runtime
50
+ dependency.
51
+
52
+ ### Fixed
53
+
54
+ - Fixed release-state checks to use the active `kettle-dev` API instead of each
55
+ member's potentially stale bundle.
56
+ - Fixed default discovery excludes so top-level `vendor/`, `tmp/`, `spec/`, and
57
+ `test/` directories are ignored.
58
+
59
+ ### Added
60
+
61
+ - Added `kettle-family metadata` to report each family member's version, Ruby
62
+ requirement, licenses, and authors.
63
+
33
64
  ## [0.1.2] - 2026-06-13
34
65
 
35
66
  - TAG: [v0.1.2][0.1.2t]
@@ -91,7 +122,9 @@ Please file a bug if you notice a violation of semantic versioning.
91
122
  - Fixed CI load failures on engines without compatible `pty` support by falling back to Open3 for interactive release commands.
92
123
  - Fixed Ruby 3.2 version-bump support by loading Prism lazily and wiring the Prism gem only for MRI versions that need it.
93
124
 
94
- [Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v0.1.2...HEAD
125
+ [Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v0.1.3...HEAD
126
+ [0.1.3]: https://github.com/kettle-dev/kettle-family/compare/v0.1.2...v0.1.3
127
+ [0.1.3t]: https://github.com/kettle-dev/kettle-family/releases/tag/v0.1.3
95
128
  [0.1.2]: https://github.com/kettle-dev/kettle-family/compare/v0.1.1...v0.1.2
96
129
  [0.1.2t]: https://github.com/kettle-dev/kettle-family/releases/tag/v0.1.2
97
130
  [0.1.1]: https://github.com/kettle-dev/kettle-family/compare/v0.1.0...v0.1.1
data/LICENSE.md CHANGED
@@ -3,7 +3,9 @@
3
3
  This project is made available under the following license.
4
4
  Choose the option that best fits your use case:
5
5
 
6
- - [MIT](MIT.md)
6
+ - [AGPL-3.0-only](AGPL-3.0-only.md)
7
+
8
+ If none of the above licenses fit your use case, please [contact us](mailto:floss@galtzo.com) to discuss a custom commercial license.
7
9
 
8
10
  ## Copyright Notice
9
11
 
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # 👩‍👩‍👧‍👧 Kettle::Family
4
4
 
5
- [![Version][👽versioni]][👽version] [![GitHub tag (latest SemVer)][⛳️tag-img]][⛳️tag] [![License: MIT][📄license-img]][📄license] [![Downloads Rank][👽dl-ranki]][👽dl-rank] [![CodeCov Test Coverage][🏀codecovi]][🏀codecov] [![Coveralls Test Coverage][🏀coveralls-img]][🏀coveralls] [![QLTY Test Coverage][🏀qlty-covi]][🏀qlty-cov] [![QLTY Maintainability][🏀qlty-mnti]][🏀qlty-mnt] [![CI Heads][🚎3-hd-wfi]][🚎3-hd-wf] [![CI Runtime Dependencies @ HEAD][🚎12-crh-wfi]][🚎12-crh-wf] [![CI Current][🚎11-c-wfi]][🚎11-c-wf] [![CI Truffle Ruby][🚎9-t-wfi]][🚎9-t-wf] [![CI JRuby][🚎10-j-wfi]][🚎10-j-wf] [![Deps Locked][🚎13-🔒️-wfi]][🚎13-🔒️-wf] [![Deps Unlocked][🚎14-🔓️-wfi]][🚎14-🔓️-wf] [![CI Test Coverage][🚎2-cov-wfi]][🚎2-cov-wf] [![CI Style][🚎5-st-wfi]][🚎5-st-wf] [![Apache SkyWalking Eyes License Compatibility Check][🚎15-🪪-wfi]][🚎15-🪪-wf]
5
+ [![Version][👽versioni]][👽version] [![GitHub tag (latest SemVer)][⛳️tag-img]][⛳️tag] [![License: AGPL-3.0-only][📄license-img]][📄license] [![Downloads Rank][👽dl-ranki]][👽dl-rank] [![CodeCov Test Coverage][🏀codecovi]][🏀codecov] [![Coveralls Test Coverage][🏀coveralls-img]][🏀coveralls] [![QLTY Test Coverage][🏀qlty-covi]][🏀qlty-cov] [![QLTY Maintainability][🏀qlty-mnti]][🏀qlty-mnt] [![CI Heads][🚎3-hd-wfi]][🚎3-hd-wf] [![CI Runtime Dependencies @ HEAD][🚎12-crh-wfi]][🚎12-crh-wf] [![CI Current][🚎11-c-wfi]][🚎11-c-wf] [![CI Truffle Ruby][🚎9-t-wfi]][🚎9-t-wf] [![CI JRuby][🚎10-j-wfi]][🚎10-j-wf] [![Deps Locked][🚎13-🔒️-wfi]][🚎13-🔒️-wf] [![Deps Unlocked][🚎14-🔓️-wfi]][🚎14-🔓️-wf] [![CI Test Coverage][🚎2-cov-wfi]][🚎2-cov-wf] [![CI Style][🚎5-st-wfi]][🚎5-st-wf]
6
6
 
7
7
  `if ci_badges.map(&:color).detect { it != "green"}` ☝️ [let me know][✉️discord-invite], as I may have missed the [discord notification][✉️discord-invite].
8
8
 
@@ -51,7 +51,7 @@ while RubyGems MFA prompts remain interactive.
51
51
  | Support & Community | [![Join Me on Daily.dev's RubyFriends][✉️ruby-friends-img]][✉️ruby-friends] [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite] [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork] [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor] |
52
52
  | Source | [![Source on GitLab.com][📜src-gl-img]][📜src-gl] [![Source on CodeBerg.org][📜src-cb-img]][📜src-cb] [![Source on Github.com][📜src-gh-img]][📜src-gh] [![The best SHA: dQw4w9WgXcQ!][🧮kloc-img]][🧮kloc] |
53
53
  | Documentation | [![Current release on RubyDoc.info][📜docs-cr-rd-img]][🚎yard-current] [![YARD on Galtzo.com][📜docs-head-rd-img]][🚎yard-head] [![Maintainer Blog][🚂maint-blog-img]][🚂maint-blog] [![GitLab Wiki][📜gl-wiki-img]][📜gl-wiki] [![GitHub Wiki][📜gh-wiki-img]][📜gh-wiki] |
54
- | Compliance | [![License: MIT][📄license-img]][📄license] [![Apache license compatibility: Category A][📄license-compat-img]][📄license-compat] [![📄ilo-declaration-img]][📄ilo-declaration] [![Security Policy][🔐security-img]][🔐security] [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct] [![SemVer 2.0.0][📌semver-img]][📌semver] |
54
+ | Compliance | [![License: AGPL-3.0-only][📄license-img]][📄license] [![Apache license compatibility: Category X][📄license-compat-img]][📄license-compat] [![📄ilo-declaration-img]][📄ilo-declaration] [![Security Policy][🔐security-img]][🔐security] [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct] [![SemVer 2.0.0][📌semver-img]][📌semver] |
55
55
  | Style | [![Enforced Code Style Linter][💎rlts-img]][💎rlts] [![Keep-A-Changelog 1.0.0][📗keep-changelog-img]][📗keep-changelog] [![Gitmoji Commits][📌gitmoji-img]][📌gitmoji] [![Compatibility appraised by: appraisal2][💎appraisal2-img]][💎appraisal2] |
56
56
  | Maintainer 🎖️ | [![Follow Me on LinkedIn][💖🖇linkedin-img]][💖🖇linkedin] [![Follow Me on Ruby.Social][💖🐘ruby-mast-img]][💖🐘ruby-mast] [![Follow Me on Bluesky][💖🦋bluesky-img]][💖🦋bluesky] [![Contact Maintainer][🚂maint-contact-img]][🚂maint-contact] [![My technical writing][💖💁🏼‍♂️devto-img]][💖💁🏼‍♂️devto] |
57
57
  | `...` 💖 | [![Find Me on WellFound:][💖✌️wellfound-img]][💖✌️wellfound] [![Find Me on CrunchBase][💖💲crunchbase-img]][💖💲crunchbase] [![My LinkTree][💖🌳linktree-img]][💖🌳linktree] [![More About Me][💖💁🏼‍♂️aboutme-img]][💖💁🏼‍♂️aboutme] [🧊][💖🧊berg] [🐙][💖🐙hub] [🛖][💖🛖hut] [🧪][💖🧪lab] |
@@ -232,63 +232,6 @@ If you intentionally need to continue after CI failures, opt in explicitly:
232
232
  kettle-family release --publish --execute --continue-ci-failures
233
233
  ```
234
234
 
235
- ## 🦷 FLOSS Funding
236
-
237
- While kettle-dev tools are free software and will always be, the project would benefit immensely from some funding.
238
- Raising a monthly budget of... "dollars" would make the project more sustainable.
239
-
240
- We welcome both individual and corporate sponsors! We also offer a
241
- wide array of funding channels to account for your preferences.
242
- Currently, [Open Collective][🖇osc] is our preferred funding platform.
243
-
244
- **If you're working in a company that's making significant use of kettle-dev tools we'd
245
- appreciate it if you suggest to your company to become a kettle-dev sponsor.**
246
-
247
- You can support the development of kettle-dev tools via
248
- [GitHub Sponsors][🖇sponsor],
249
- [Liberapay][⛳liberapay],
250
- [PayPal][🖇paypal],
251
- [Open Collective][🖇osc]
252
- and [Tidelift][🏙️entsup-tidelift].
253
-
254
- | 📍 NOTE |
255
- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
256
- | If doing a sponsorship in the form of donation is problematic for your company <br/> from an accounting standpoint, we'd recommend the use of Tidelift, <br/> where you can get a support-like subscription instead. |
257
-
258
- ### Open Collective for Individuals
259
-
260
- Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/kettle-dev#backer)]
261
-
262
- NOTE: [kettle-readme-backers][kettle-readme-backers] updates this list every day, automatically.
263
-
264
- <!-- OPENCOLLECTIVE-INDIVIDUALS:START -->
265
- No backers yet. Be the first!
266
- <!-- OPENCOLLECTIVE-INDIVIDUALS:END -->
267
-
268
- ### Open Collective for Organizations
269
-
270
- Become a sponsor and get your logo on our README on GitHub with a link to your site. [[Become a sponsor](https://opencollective.com/kettle-dev#sponsor)]
271
-
272
- NOTE: [kettle-readme-backers][kettle-readme-backers] updates this list every day, automatically.
273
-
274
- <!-- OPENCOLLECTIVE-ORGANIZATIONS:START -->
275
- No sponsors yet. Be the first!
276
- <!-- OPENCOLLECTIVE-ORGANIZATIONS:END -->
277
-
278
- [kettle-readme-backers]: https://github.com/kettle-dev/kettle-family/blob/main/exe/kettle-readme-backers
279
-
280
- ### Another way to support open-source
281
-
282
- I’m driven by a passion to foster a thriving open-source community – a space where people can tackle complex problems, no matter how small. Revitalizing libraries that have fallen into disrepair, and building new libraries focused on solving real-world challenges, are my passions. I was recently affected by layoffs, and the tech jobs market is unwelcoming. I’m reaching out here because your support would significantly aid my efforts to provide for my family, and my farm (11 🐔 chickens, 2 🐶 dogs, 3 🐰 rabbits, 8 🐈‍ cats).
283
-
284
- If you work at a company that uses my work, please encourage them to support me as a corporate sponsor. My work on gems you use might show up in `bundle fund`.
285
-
286
- I’m developing a new library, [floss_funding][🖇floss-funding-gem], designed to empower open-source developers like myself to get paid for the work we do, in a sustainable way. Please give it a look.
287
-
288
- **[Floss-Funding.dev][🖇floss-funding.dev]: 👉️ No network calls. 👉️ No tracking. 👉️ No oversight. 👉️ Minimal crypto hashing. 💡 Easily disabled nags**
289
-
290
- [![OpenCollective Backers][🖇osc-backers-i]][🖇osc-backers] [![OpenCollective Sponsors][🖇osc-sponsors-i]][🖇osc-sponsors] [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Liberapay Goal Progress][⛳liberapay-img]][⛳liberapay] [![Donate on PayPal][🖇paypal-img]][🖇paypal] [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate to my FLOSS efforts at ko-fi.com][🖇kofi-img]][🖇kofi] [![Donate to my FLOSS efforts using Patreon][🖇patreon-img]][🖇patreon]
291
-
292
235
  ## 🔐 Security
293
236
 
294
237
  See [SECURITY.md][🔐security].
@@ -374,8 +317,10 @@ See [CHANGELOG.md][📌changelog] for a list of releases.
374
317
 
375
318
  ## 📄 License
376
319
 
377
- The gem is available as open source under the terms of
378
- the [MIT](MIT.md) [![License: MIT][📄license-img]][📄license-ref].
320
+ The gem is available under the following license: [AGPL-3.0-only](AGPL-3.0-only.md).
321
+ See [LICENSE.md][📄license] for details.
322
+
323
+ If none of the available licenses suit your use case, please [contact us](mailto:floss@galtzo.com) to discuss a custom commercial license.
379
324
 
380
325
  ### © Copyright
381
326
 
@@ -534,8 +479,6 @@ Thanks for RTFM. ☺️
534
479
  [🚎13-🔒️-wfi]: https://github.com/kettle-dev/kettle-family/actions/workflows/locked_deps.yml/badge.svg
535
480
  [🚎14-🔓️-wf]: https://github.com/kettle-dev/kettle-family/actions/workflows/unlocked_deps.yml
536
481
  [🚎14-🔓️-wfi]: https://github.com/kettle-dev/kettle-family/actions/workflows/unlocked_deps.yml/badge.svg
537
- [🚎15-🪪-wf]: https://github.com/kettle-dev/kettle-family/actions/workflows/license-eye.yml
538
- [🚎15-🪪-wfi]: https://github.com/kettle-dev/kettle-family/actions/workflows/license-eye.yml/badge.svg
539
482
  [💎ruby-3.2i]: https://img.shields.io/badge/Ruby-3.2-CC342D?style=for-the-badge&logo=ruby&logoColor=white
540
483
  [💎ruby-3.3i]: https://img.shields.io/badge/Ruby-3.3-CC342D?style=for-the-badge&logo=ruby&logoColor=white
541
484
  [💎ruby-3.4i]: https://img.shields.io/badge/Ruby-3.4-CC342D?style=for-the-badge&logo=ruby&logoColor=white
@@ -573,15 +516,15 @@ Thanks for RTFM. ☺️
573
516
  [📌gitmoji]: https://gitmoji.dev
574
517
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
575
518
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
576
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.892-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
519
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.972-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
577
520
  [🔐security]: https://github.com/kettle-dev/kettle-family/blob/main/SECURITY.md
578
521
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
579
522
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
580
523
  [📄license]: LICENSE.md
581
- [📄license-ref]: MIT.md
582
- [📄license-img]: https://img.shields.io/badge/License-MIT-259D6C.svg
583
- [📄license-compat]: https://www.apache.org/legal/resolved.html#category-a
584
- [📄license-compat-img]: https://img.shields.io/badge/Apache_Compatible:_Category_A-%E2%9C%93-259D6C.svg?style=flat&logo=Apache
524
+ [📄license-ref]: AGPL-3.0-only.md
525
+ [📄license-img]: https://img.shields.io/badge/License-AGPL--3.0--only-259D6C.svg
526
+ [📄license-compat]: https://www.apache.org/legal/resolved.html#category-x
527
+ [📄license-compat-img]: https://img.shields.io/badge/Apache_Incompatible:_Category_X-%E2%9C%97-C0392B.svg?style=flat&logo=Apache
585
528
 
586
529
  [📄ilo-declaration]: https://www.ilo.org/declaration/lang--en/index.htm
587
530
  [📄ilo-declaration-img]: https://img.shields.io/badge/ILO_Fundamental_Principles-✓-259D6C.svg?style=flat
@@ -601,7 +544,7 @@ Thanks for RTFM. ☺️
601
544
  | Package | kettle-family |
602
545
  | Description | 👩‍👩‍👧‍👧 Kettle::Family provides scripts and conventions for coordinating related Ruby gems as one family. |
603
546
  | Homepage | https://github.com/kettle-dev/kettle-family |
604
- | Source | https://github.com/kettle-dev/kettle-family/tree/v0.1.0 |
605
- | License | `MIT` |
547
+ | Source | https://github.com/kettle-dev/kettle-family/tree/v0.1.3 |
548
+ | License | `AGPL-3.0-only` |
606
549
  | Funding | https://github.com/sponsors/pboling, https://issuehunt.io/u/pboling, https://ko-fi.com/pboling, https://liberapay.com/pboling/donate, https://opencollective.com/kettle-dev, https://opencollective.com/kettle-rb, https://patreon.com/galtzo, https://polar.sh/pboling, https://thanks.dev/u/gh/pboling, https://tidelift.com/funding/github/rubygems/kettle-family, https://www.buymeacoffee.com/pboling |
607
550
  <!-- kettle-jem:metadata:end -->
@@ -6,7 +6,7 @@ require "optparse"
6
6
  module Kettle
7
7
  module Family
8
8
  class CLI
9
- COMMANDS = %w[discover plan report 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 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)
@@ -53,6 +53,7 @@ module Kettle
53
53
  discover Discover family members and print selected order
54
54
  plan Alias for discover while execution workflows are built
55
55
  report Print family discovery and configuration report
56
+ metadata Print version, Ruby floor, license, and author metadata
56
57
  check Run internal read-only readiness checks
57
58
  test Plan or execute configured test command per member
58
59
  lint Plan or execute configured lint command per member
@@ -139,7 +140,7 @@ module Kettle
139
140
  def build_report(command, options)
140
141
  config = Config.load(root: options[:root], path: options[:config])
141
142
  members = Discovery.new(config: config).members
142
- ordered = if command == "release-state"
143
+ ordered = if %w[metadata release-state].include?(command)
143
144
  members.sort_by(&:name)
144
145
  else
145
146
  Orderer.new(members: members, mode: config.order_mode, hints: config.order_hints).ordered
@@ -168,7 +169,7 @@ module Kettle
168
169
  def command_results(command:, config:, members:, options:)
169
170
  return bump_version_results(members: members, options: options) if command == "bump-version"
170
171
  return branch_lane_results(config: config, members: members) if command == "branch-lanes"
171
- return release_state_results(members: members) if command == "release-state"
172
+ return release_state_results(config: config, members: members) if command == "release-state"
172
173
  return [] unless WORKFLOW_COMMANDS.include?(command)
173
174
 
174
175
  Workflow.new(
@@ -207,8 +208,8 @@ module Kettle
207
208
  BranchLaneAudit.new(config: config, members: members).results
208
209
  end
209
210
 
210
- def release_state_results(members:)
211
- ReleaseStateCheck.new(members: members).results
211
+ def release_state_results(config:, members:)
212
+ ReleaseStateCheck.new(config: config, members: members).results
212
213
  end
213
214
 
214
215
  def write_report(report, options)
@@ -6,7 +6,16 @@ module Kettle
6
6
  module Family
7
7
  class Config
8
8
  DEFAULT_PATHS = [".kettle-family.yml", ".structuredmerge/kettle-family.yml"].freeze
9
- DEFAULT_MEMBER_EXCLUDES = ["**/vendor/**", "**/tmp/**", "**/spec/**", "**/test/**"].freeze
9
+ DEFAULT_MEMBER_EXCLUDES = [
10
+ "vendor/**",
11
+ "**/vendor/**",
12
+ "tmp/**",
13
+ "**/tmp/**",
14
+ "spec/**",
15
+ "**/spec/**",
16
+ "test/**",
17
+ "**/test/**"
18
+ ].freeze
10
19
 
11
20
  attr_reader :data, :path, :root
12
21
 
@@ -63,17 +63,39 @@ module Kettle
63
63
  gemspec_path: path,
64
64
  version_file: version_file(File.dirname(path)),
65
65
  version: spec.version.to_s,
66
- dependencies: spec.dependencies.map(&:name).sort
66
+ dependencies: spec.dependencies.map(&:name).sort,
67
+ required_ruby_version: required_ruby_version(spec),
68
+ licenses: licenses(spec),
69
+ authors: authors(spec)
67
70
  )
68
71
  end
69
72
 
73
+ def required_ruby_version(spec)
74
+ value = spec.required_ruby_version&.to_s&.strip
75
+ value.empty? ? nil : value
76
+ end
77
+
78
+ def licenses(spec)
79
+ values = Array(spec.licenses).compact.map(&:to_s).map(&:strip).reject(&:empty?)
80
+ values = [spec.license.to_s.strip] if values.empty? && spec.respond_to?(:license) && !spec.license.to_s.strip.empty?
81
+ values
82
+ end
83
+
84
+ def authors(spec)
85
+ Array(spec.authors).compact.map(&:to_s).map(&:strip).reject(&:empty?)
86
+ end
87
+
70
88
  def version_file(root)
71
89
  candidates = Dir.glob(File.join(root, "lib", "**", "version.rb"))
72
90
  candidates.min
73
91
  end
74
92
 
75
93
  def load_gemspec(path)
76
- spec = Gem::Specification.load(path)
94
+ # Some legacy gemspecs use root-relative Kernel.load calls, and RubyGems
95
+ # evaluates gemspecs relative to the current process directory.
96
+ # rubocop:disable ThreadSafety/DirChdir
97
+ spec = Dir.chdir(File.dirname(path)) { Gem::Specification.load(path) }
98
+ # rubocop:enable ThreadSafety/DirChdir
77
99
  raise Error, "could not load gemspec #{path}" unless spec
78
100
 
79
101
  spec
@@ -8,7 +8,10 @@ module Kettle
8
8
  :gemspec_path,
9
9
  :version_file,
10
10
  :version,
11
- :dependencies
11
+ :dependencies,
12
+ :required_ruby_version,
13
+ :licenses,
14
+ :authors
12
15
  ) do
13
16
  def to_h
14
17
  {
@@ -17,7 +20,10 @@ module Kettle
17
20
  "gemspec_path" => gemspec_path,
18
21
  "version_file" => version_file,
19
22
  "version" => version,
20
- "dependencies" => dependencies
23
+ "dependencies" => dependencies,
24
+ "required_ruby_version" => required_ruby_version,
25
+ "licenses" => Array(licenses),
26
+ "authors" => Array(authors)
21
27
  }
22
28
  end
23
29
  end
@@ -1,40 +1,62 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "json"
4
+ require "fileutils"
4
5
  require "open3"
6
+ require "rbconfig"
7
+ require "securerandom"
5
8
 
6
9
  module Kettle
7
10
  module Family
8
11
  class ReleaseStateCheck
9
- COMMAND = ["bundle", "exec", "kettle-changelog", "--release-state", "--json"].freeze
10
-
11
- def initialize(members:)
12
+ def initialize(members:, config: nil)
12
13
  @members = members
14
+ @config = config
13
15
  end
14
16
 
15
17
  def results
18
+ return branch_results unless release_target_branches.empty?
19
+
16
20
  members.map { |member| check_member(member) }
17
21
  end
18
22
 
19
23
  private
20
24
 
21
- attr_reader :members
25
+ attr_reader :members, :config
26
+
27
+ def branch_results
28
+ root = git_root
29
+ selected_names = members.map(&:name)
30
+ release_target_branches.each_with_object([]) do |branch, memo|
31
+ with_branch_worktree(root: root, branch: branch) do |worktree_root|
32
+ branch_members = discover_branch_members(worktree_root: worktree_root, selected_names: selected_names)
33
+ memo.concat(branch_members.map { |member| check_member(member, branch: branch) })
34
+ end
35
+ rescue Error => error
36
+ memo << error_result(branch: branch, error: error)
37
+ end
38
+ end
22
39
 
23
- def check_member(member)
40
+ def check_member(member, branch: nil)
24
41
  started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
25
- stdout, stderr, status = Open3.capture3(*COMMAND, chdir: member.root)
42
+ command = release_state_command
43
+ stdout, stderr, status = Open3.capture3(*command, chdir: member.root)
26
44
  elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started
27
45
  success = status.success?
28
46
  state = success ? JSON.parse(stdout) : {}
29
- result(member: member, stdout: stdout, stderr: stderr, status: status.exitstatus, elapsed: elapsed, success: success, state: state)
47
+ result(member: member, command: command, stdout: stdout, stderr: stderr, status: status.exitstatus, elapsed: elapsed, success: success, state: state, branch: branch)
30
48
  rescue JSON::ParserError => error
31
- result(member: member, stdout: stdout, stderr: stderr, status: 1, elapsed: elapsed || 0.0, success: false, state: {}, reason: "invalid release-state JSON: #{error.message}")
49
+ result(member: member, command: command || release_state_command, stdout: stdout, stderr: stderr, status: 1, elapsed: elapsed || 0.0, success: false, state: {}, reason: "invalid release-state JSON: #{error.message}", branch: branch)
32
50
  end
33
51
 
34
- def result(member:, stdout:, stderr:, status:, elapsed:, success:, state:, reason: nil)
52
+ def release_state_command
53
+ [RbConfig.ruby, "-S", "kettle-changelog", "--release-state", "--json"]
54
+ end
55
+
56
+ def result(member:, command:, stdout:, stderr:, status:, elapsed:, success:, state:, reason: nil, branch: nil)
35
57
  ReleaseStateResult.new(
36
58
  member_name: member.name,
37
- command: COMMAND,
59
+ command: command,
38
60
  workdir: member.root,
39
61
  status: status,
40
62
  success: success,
@@ -42,9 +64,85 @@ module Kettle
42
64
  stderr: stderr,
43
65
  elapsed_seconds: elapsed,
44
66
  state: state,
45
- reason: reason || (success ? nil : "release state check failed")
67
+ reason: reason || (success ? nil : "release state check failed"),
68
+ branch: branch
46
69
  )
47
70
  end
71
+
72
+ def error_result(branch:, error:)
73
+ ReleaseStateResult.new(
74
+ member_name: branch,
75
+ command: ["internal", "release-state", branch],
76
+ workdir: config.root,
77
+ status: 1,
78
+ success: false,
79
+ stdout: "",
80
+ stderr: error.message,
81
+ elapsed_seconds: 0.0,
82
+ state: {},
83
+ reason: "branch release state failed",
84
+ branch: branch
85
+ )
86
+ end
87
+
88
+ def release_target_branches
89
+ return [] unless config
90
+
91
+ config.release_target_branches
92
+ end
93
+
94
+ def git_root
95
+ stdout, stderr, status = Open3.capture3("git", "rev-parse", "--show-toplevel", chdir: config.root)
96
+ raise Error, "could not determine git root for #{config.root}: #{stderr}" unless status.success?
97
+
98
+ File.realpath(stdout.strip)
99
+ end
100
+
101
+ def with_branch_worktree(root:, branch:)
102
+ base = File.join(root, "tmp", "kettle-family-release-state")
103
+ FileUtils.mkdir_p(base)
104
+ worktree_root = File.join(base, "worktree-#{Process.pid}-#{SecureRandom.hex(8)}")
105
+ add_branch_worktree(root: root, branch: branch, worktree_root: worktree_root)
106
+ yield worktree_root
107
+ ensure
108
+ remove_branch_worktree(root: root, worktree_root: worktree_root)
109
+ end
110
+
111
+ def add_branch_worktree(root:, branch:, worktree_root:)
112
+ _stdout, stderr, status = Open3.capture3("git", "worktree", "add", "--detach", worktree_root, branch, chdir: root)
113
+ raise Error, "could not add worktree for #{branch}: #{stderr}" unless status.success?
114
+ end
115
+
116
+ def remove_branch_worktree(root:, worktree_root:)
117
+ return unless worktree_root && Dir.exist?(worktree_root)
118
+
119
+ Open3.capture3("git", "worktree", "remove", "--force", worktree_root, chdir: root)
120
+ end
121
+
122
+ def discover_branch_members(worktree_root:, selected_names:)
123
+ branch_config = Config.load(root: branch_config_root(worktree_root))
124
+ Discovery.new(config: branch_config).members
125
+ .sort_by(&:name)
126
+ .select { |member| selected_names.include?(member.name) }
127
+ end
128
+
129
+ def branch_config_root(worktree_root)
130
+ File.join(worktree_root, relative_config_root)
131
+ end
132
+
133
+ def relative_config_root
134
+ @relative_config_root ||= begin
135
+ root = git_root
136
+ config_root = File.realpath(config.root)
137
+ if config_root == root
138
+ "."
139
+ elsif config_root.start_with?("#{root}/")
140
+ config_root.delete_prefix("#{root}/")
141
+ else
142
+ raise Error, "configured root #{config.root} is outside git root #{root}"
143
+ end
144
+ end
145
+ end
48
146
  end
49
147
  end
50
148
  end
@@ -3,9 +3,9 @@
3
3
  module Kettle
4
4
  module Family
5
5
  class ReleaseStateResult
6
- attr_reader :member_name, :phase, :command, :workdir, :status, :success, :stdout, :stderr, :elapsed_seconds, :skipped, :reason, :state
6
+ attr_reader :member_name, :phase, :command, :workdir, :status, :success, :stdout, :stderr, :elapsed_seconds, :skipped, :reason, :state, :branch
7
7
 
8
- def initialize(member_name:, command:, workdir:, status:, success:, stdout:, stderr:, elapsed_seconds:, state:, reason: nil)
8
+ def initialize(member_name:, command:, workdir:, status:, success:, stdout:, stderr:, elapsed_seconds:, state:, reason: nil, branch: nil)
9
9
  @member_name = member_name
10
10
  @phase = "release_state"
11
11
  @command = command
@@ -18,6 +18,7 @@ module Kettle
18
18
  @skipped = false
19
19
  @reason = reason
20
20
  @state = state
21
+ @branch = branch
21
22
  end
22
23
 
23
24
  def ok?
@@ -37,6 +38,7 @@ module Kettle
37
38
  "elapsed_seconds" => elapsed_seconds,
38
39
  "skipped" => skipped,
39
40
  "reason" => reason,
41
+ "branch" => branch,
40
42
  "release_state" => state
41
43
  }
42
44
  end
@@ -64,6 +64,7 @@ module Kettle
64
64
  private
65
65
 
66
66
  def append_results(lines)
67
+ return append_metadata_results(lines) if command == "metadata"
67
68
  return if results.empty?
68
69
  return append_release_state_results(lines) if command == "release-state"
69
70
 
@@ -82,9 +83,24 @@ module Kettle
82
83
  "failed"
83
84
  end
84
85
 
86
+ def append_metadata_results(lines)
87
+ lines << "metadata:"
88
+ rows = [["gem", "version", "ruby", "licenses", "authors"]]
89
+ selected_members.each do |member|
90
+ rows << [
91
+ member.name.to_s,
92
+ member.version.to_s,
93
+ blank_as_none(member.required_ruby_version),
94
+ blank_as_none(Array(member.licenses).join(", ")),
95
+ blank_as_none(Array(member.authors).join(", "))
96
+ ]
97
+ end
98
+ lines.concat(format_table(rows).map { |line| " #{line}" })
99
+ end
100
+
85
101
  def append_release_state_results(lines)
86
102
  lines << "release state:"
87
- rows = [["gem", "version.rb", "latest released", "latest changelog", "unreleased", "prepared", "pending"]]
103
+ rows = release_state_header
88
104
  results.each do |result|
89
105
  rows << release_state_row(result)
90
106
  end
@@ -101,7 +117,7 @@ module Kettle
101
117
 
102
118
  def release_state_row(result)
103
119
  state = result.state || {}
104
- [
120
+ row = [
105
121
  state.fetch("gem_name", result.member_name).to_s,
106
122
  state.fetch("version", "unknown").to_s,
107
123
  state.fetch("latest_released", nil).to_s.empty? ? "unknown" : state.fetch("latest_released").to_s,
@@ -110,6 +126,20 @@ module Kettle
110
126
  yes_no(state.fetch("prepared_release_pending", nil)),
111
127
  yes_no(state.fetch("pending_release", nil))
112
128
  ]
129
+ return row unless release_state_has_branches?
130
+
131
+ [result.branch.to_s.empty? ? "current" : result.branch.to_s, *row]
132
+ end
133
+
134
+ def release_state_header
135
+ header = [["gem", "version.rb", "latest released", "latest changelog", "unreleased", "prepared", "pending"]]
136
+ return header unless release_state_has_branches?
137
+
138
+ [["branch", *header.first]]
139
+ end
140
+
141
+ def release_state_has_branches?
142
+ results.any? { |result| !result.branch.to_s.empty? }
113
143
  end
114
144
 
115
145
  def format_table(rows)
@@ -131,6 +161,11 @@ module Kettle
131
161
  end
132
162
  end
133
163
 
164
+ def blank_as_none(value)
165
+ text = value.to_s.strip
166
+ text.empty? ? "(none)" : text
167
+ end
168
+
134
169
  def resume_hint
135
170
  failed = results.find { |result| !result.ok? }
136
171
  resume_hint_for(failed) if failed
@@ -3,7 +3,7 @@
3
3
  module Kettle
4
4
  module Family
5
5
  module Version
6
- VERSION = "0.1.2"
6
+ VERSION = "0.1.3"
7
7
  end
8
8
  VERSION = Version::VERSION # Traditional Constant Location
9
9
  end
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.2
4
+ version: 0.1.3
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.7
83
+ version: 2.2.8
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.7
93
+ version: 2.2.8
94
94
  - !ruby/object:Gem::Dependency
95
95
  name: bundler-audit
96
96
  requirement: !ruby/object:Gem::Requirement
@@ -305,13 +305,13 @@ files:
305
305
  - sig/kettle/family/version.rbs
306
306
  homepage: https://github.com/kettle-dev/kettle-family
307
307
  licenses:
308
- - MIT
308
+ - AGPL-3.0-only
309
309
  metadata:
310
310
  homepage_uri: https://kettle-family.galtzo.com
311
- source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v0.1.2
312
- changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v0.1.2/CHANGELOG.md
311
+ source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v0.1.3
312
+ changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v0.1.3/CHANGELOG.md
313
313
  bug_tracker_uri: https://github.com/kettle-dev/kettle-family/issues
314
- documentation_uri: https://www.rubydoc.info/gems/kettle-family/0.1.2
314
+ documentation_uri: https://www.rubydoc.info/gems/kettle-family/0.1.3
315
315
  funding_uri: https://github.com/sponsors/pboling
316
316
  wiki_uri: https://github.com/kettle-dev/kettle-family/wiki
317
317
  news_uri: https://www.railsbling.com/tags/kettle-family
metadata.gz.sig CHANGED
Binary file