kettle-dev 1.1.25 → 1.1.27
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/.github/workflows/current.yml +33 -7
- data/.github/workflows/current.yml.example +33 -7
- data/.github/workflows/dep-heads.yml +25 -11
- data/.github/workflows/heads.yml +26 -11
- data/.github/workflows/heads.yml.example +25 -11
- data/.github/workflows/truffle.yml +9 -3
- data/CHANGELOG.md +21 -1
- data/README.md +9 -5
- data/README.md.example +10 -4
- data/Rakefile.example +1 -1
- data/lib/kettle/dev/readme_backers.rb +350 -34
- data/lib/kettle/dev/release_cli.rb +20 -2
- data/lib/kettle/dev/tasks/ci_task.rb +1 -1
- data/lib/kettle/dev/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +4 -4
- 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: 48545f8f9c5f1038e2da7c0d42f8f3bc19527fc13a86e6f15dabefdfc9ad002f
|
4
|
+
data.tar.gz: 51c810bc680047555ed09eaeaed37da6eeed85fe5336038bc7e43602263da9a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: de5d434eca23c5ceca682a1961b7178b6dce3d6604844e0167b85f727b0ad2f259e51b3ffa461bfae7bc2590a1989ea350241c93d7c244b95dc4c390eb5a3f5d
|
7
|
+
data.tar.gz: eb0c76005b25f23357fbf410861a3fdb903dffe1ee8c32abad32052a3de28ec18643dac5599af7222ab7340b6f1dd9e0894622949729255d593e3f1ca83a8710
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
@@ -64,11 +64,11 @@ jobs:
|
|
64
64
|
|
65
65
|
steps:
|
66
66
|
- name: Checkout
|
67
|
-
if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
|
67
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
68
68
|
uses: actions/checkout@v5
|
69
69
|
|
70
70
|
- name: Setup Ruby & RubyGems
|
71
|
-
if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
|
71
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
72
72
|
uses: ruby/setup-ruby@v1
|
73
73
|
with:
|
74
74
|
ruby-version: ${{ matrix.ruby }}
|
@@ -80,11 +80,37 @@ jobs:
|
|
80
80
|
# We need to do this first to get appraisal installed.
|
81
81
|
# NOTE: This does not use the primary Gemfile at all.
|
82
82
|
- name: Install Root Appraisal
|
83
|
-
if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
|
83
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
84
84
|
run: bundle
|
85
|
-
|
86
|
-
|
85
|
+
|
86
|
+
- name: "[Attempt 1] Install Root Appraisal"
|
87
|
+
id: bundleAttempt1
|
88
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
89
|
+
run: bundle
|
90
|
+
# Continue to the next step on failure
|
91
|
+
continue-on-error: true
|
92
|
+
|
93
|
+
# Effectively an automatic retry of the previous step.
|
94
|
+
- name: "[Attempt 2] Install Root Appraisal"
|
95
|
+
id: bundleAttempt2
|
96
|
+
# If bundleAttempt1 failed, try again here; Otherwise skip.
|
97
|
+
if: ${{ steps.bundleAttempt1.outcome == 'failure' && !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
98
|
+
run: bundle
|
99
|
+
|
100
|
+
- name: "[Attempt 1] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}"
|
101
|
+
id: bundleAppraisalAttempt1
|
102
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
103
|
+
run: bundle exec appraisal ${{ matrix.appraisal }} bundle
|
104
|
+
# Continue to the next step on failure
|
105
|
+
continue-on-error: true
|
106
|
+
|
107
|
+
# Effectively an automatic retry of the previous step.
|
108
|
+
- name: "[Attempt 2] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}"
|
109
|
+
id: bundleAppraisalAttempt2
|
110
|
+
# If bundleAppraisalAttempt1 failed, try again here; Otherwise skip.
|
111
|
+
if: ${{ steps.bundleAppraisalAttempt1.outcome == 'failure' && !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
87
112
|
run: bundle exec appraisal ${{ matrix.appraisal }} bundle
|
88
|
-
|
89
|
-
|
113
|
+
|
114
|
+
- name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }}
|
115
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
90
116
|
run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }}
|
@@ -63,11 +63,11 @@ jobs:
|
|
63
63
|
|
64
64
|
steps:
|
65
65
|
- name: Checkout
|
66
|
-
if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
|
66
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
67
67
|
uses: actions/checkout@v5
|
68
68
|
|
69
69
|
- name: Setup Ruby & RubyGems
|
70
|
-
if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
|
70
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
71
71
|
uses: ruby/setup-ruby@v1
|
72
72
|
with:
|
73
73
|
ruby-version: ${{ matrix.ruby }}
|
@@ -79,11 +79,37 @@ jobs:
|
|
79
79
|
# We need to do this first to get appraisal installed.
|
80
80
|
# NOTE: This does not use the primary Gemfile at all.
|
81
81
|
- name: Install Root Appraisal
|
82
|
-
if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
|
82
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
83
83
|
run: bundle
|
84
|
-
|
85
|
-
|
84
|
+
|
85
|
+
- name: "[Attempt 1] Install Root Appraisal"
|
86
|
+
id: bundleAttempt1
|
87
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
88
|
+
run: bundle
|
89
|
+
# Continue to the next step on failure
|
90
|
+
continue-on-error: true
|
91
|
+
|
92
|
+
# Effectively an automatic retry of the previous step.
|
93
|
+
- name: "[Attempt 2] Install Root Appraisal"
|
94
|
+
id: bundleAttempt2
|
95
|
+
# If bundleAttempt1 failed, try again here; Otherwise skip.
|
96
|
+
if: ${{ steps.bundleAttempt1.outcome == 'failure' && !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
97
|
+
run: bundle
|
98
|
+
|
99
|
+
- name: "[Attempt 1] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}"
|
100
|
+
id: bundleAppraisalAttempt1
|
101
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
102
|
+
run: bundle exec appraisal ${{ matrix.appraisal }} bundle
|
103
|
+
# Continue to the next step on failure
|
104
|
+
continue-on-error: true
|
105
|
+
|
106
|
+
# Effectively an automatic retry of the previous step.
|
107
|
+
- name: "[Attempt 2] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}"
|
108
|
+
id: bundleAppraisalAttempt2
|
109
|
+
# If bundleAppraisalAttempt1 failed, try again here; Otherwise skip.
|
110
|
+
if: ${{ steps.bundleAppraisalAttempt1.outcome == 'failure' && !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
86
111
|
run: bundle exec appraisal ${{ matrix.appraisal }} bundle
|
87
|
-
|
88
|
-
|
112
|
+
|
113
|
+
- name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }}
|
114
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
89
115
|
run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }}
|
@@ -67,11 +67,11 @@ jobs:
|
|
67
67
|
|
68
68
|
steps:
|
69
69
|
- name: Checkout
|
70
|
-
if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
|
70
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
71
71
|
uses: actions/checkout@v5
|
72
72
|
|
73
73
|
- name: Setup Ruby & RubyGems
|
74
|
-
if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
|
74
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
75
75
|
uses: ruby/setup-ruby@v1
|
76
76
|
with:
|
77
77
|
ruby-version: ${{ matrix.ruby }}
|
@@ -82,24 +82,38 @@ jobs:
|
|
82
82
|
# Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root)
|
83
83
|
# We need to do this first to get appraisal installed.
|
84
84
|
# NOTE: This does not use the primary Gemfile at all.
|
85
|
-
- name:
|
86
|
-
if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
|
85
|
+
- name: Install Root Appraisal
|
86
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
87
87
|
run: bundle
|
88
88
|
|
89
|
-
- name: "[Attempt 1]
|
90
|
-
if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
|
89
|
+
- name: "[Attempt 1] Install Root Appraisal"
|
91
90
|
id: bundleAttempt1
|
91
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
92
|
+
run: bundle
|
93
|
+
# Continue to the next step on failure
|
94
|
+
continue-on-error: true
|
95
|
+
|
96
|
+
# Effectively an automatic retry of the previous step.
|
97
|
+
- name: "[Attempt 2] Install Root Appraisal"
|
98
|
+
id: bundleAttempt2
|
99
|
+
# If bundleAttempt1 failed, try again here; Otherwise skip.
|
100
|
+
if: ${{ steps.bundleAttempt1.outcome == 'failure' && !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
101
|
+
run: bundle
|
102
|
+
|
103
|
+
- name: "[Attempt 1] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}"
|
104
|
+
id: bundleAppraisalAttempt1
|
105
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
92
106
|
run: bundle exec appraisal ${{ matrix.appraisal }} bundle
|
93
107
|
# Continue to the next step on failure
|
94
108
|
continue-on-error: true
|
95
109
|
|
96
110
|
# Effectively an automatic retry of the previous step.
|
97
111
|
- name: "[Attempt 2] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}"
|
98
|
-
|
99
|
-
|
100
|
-
|
112
|
+
id: bundleAppraisalAttempt2
|
113
|
+
# If bundleAppraisalAttempt1 failed, try again here; Otherwise skip.
|
114
|
+
if: ${{ steps.bundleAppraisalAttempt1.outcome == 'failure' && !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
101
115
|
run: bundle exec appraisal ${{ matrix.appraisal }} bundle
|
102
116
|
|
103
|
-
- name: Tests for ${{ matrix.ruby }}
|
104
|
-
if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
|
117
|
+
- name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }}
|
118
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
105
119
|
run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }}
|
data/.github/workflows/heads.yml
CHANGED
@@ -46,6 +46,7 @@ jobs:
|
|
46
46
|
rubygems: default
|
47
47
|
bundler: default
|
48
48
|
|
49
|
+
# # Turn back on once allow-failures is a feature of GHA.
|
49
50
|
# # truffleruby-head
|
50
51
|
# - ruby: "truffleruby-head"
|
51
52
|
# appraisal: "head"
|
@@ -64,11 +65,11 @@ jobs:
|
|
64
65
|
|
65
66
|
steps:
|
66
67
|
- name: Checkout
|
67
|
-
if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
|
68
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
68
69
|
uses: actions/checkout@v5
|
69
70
|
|
70
71
|
- name: Setup Ruby & RubyGems
|
71
|
-
if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
|
72
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
72
73
|
uses: ruby/setup-ruby@v1
|
73
74
|
with:
|
74
75
|
ruby-version: ${{ matrix.ruby }}
|
@@ -79,24 +80,38 @@ jobs:
|
|
79
80
|
# Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root)
|
80
81
|
# We need to do this first to get appraisal installed.
|
81
82
|
# NOTE: This does not use the primary Gemfile at all.
|
82
|
-
- name:
|
83
|
-
if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
|
83
|
+
- name: Install Root Appraisal
|
84
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
84
85
|
run: bundle
|
85
86
|
|
86
|
-
- name: "[Attempt 1]
|
87
|
-
if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
|
87
|
+
- name: "[Attempt 1] Install Root Appraisal"
|
88
88
|
id: bundleAttempt1
|
89
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
90
|
+
run: bundle
|
91
|
+
# Continue to the next step on failure
|
92
|
+
continue-on-error: true
|
93
|
+
|
94
|
+
# Effectively an automatic retry of the previous step.
|
95
|
+
- name: "[Attempt 2] Install Root Appraisal"
|
96
|
+
id: bundleAttempt2
|
97
|
+
# If bundleAttempt1 failed, try again here; Otherwise skip.
|
98
|
+
if: ${{ steps.bundleAttempt1.outcome == 'failure' && !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
99
|
+
run: bundle
|
100
|
+
|
101
|
+
- name: "[Attempt 1] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}"
|
102
|
+
id: bundleAppraisalAttempt1
|
103
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
89
104
|
run: bundle exec appraisal ${{ matrix.appraisal }} bundle
|
90
105
|
# Continue to the next step on failure
|
91
106
|
continue-on-error: true
|
92
107
|
|
93
108
|
# Effectively an automatic retry of the previous step.
|
94
109
|
- name: "[Attempt 2] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}"
|
95
|
-
|
96
|
-
|
97
|
-
|
110
|
+
id: bundleAppraisalAttempt2
|
111
|
+
# If bundleAppraisalAttempt1 failed, try again here; Otherwise skip.
|
112
|
+
if: ${{ steps.bundleAppraisalAttempt1.outcome == 'failure' && !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
98
113
|
run: bundle exec appraisal ${{ matrix.appraisal }} bundle
|
99
114
|
|
100
|
-
- name: Tests for ${{ matrix.ruby }}
|
101
|
-
if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
|
115
|
+
- name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }}
|
116
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
102
117
|
run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }}
|
@@ -64,11 +64,11 @@ jobs:
|
|
64
64
|
|
65
65
|
steps:
|
66
66
|
- name: Checkout
|
67
|
-
if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
|
67
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
68
68
|
uses: actions/checkout@v5
|
69
69
|
|
70
70
|
- name: Setup Ruby & RubyGems
|
71
|
-
if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
|
71
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
72
72
|
uses: ruby/setup-ruby@v1
|
73
73
|
with:
|
74
74
|
ruby-version: ${{ matrix.ruby }}
|
@@ -79,24 +79,38 @@ jobs:
|
|
79
79
|
# Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root)
|
80
80
|
# We need to do this first to get appraisal installed.
|
81
81
|
# NOTE: This does not use the primary Gemfile at all.
|
82
|
-
- name:
|
83
|
-
if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
|
82
|
+
- name: Install Root Appraisal
|
83
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
84
84
|
run: bundle
|
85
85
|
|
86
|
-
- name: "[Attempt 1]
|
87
|
-
if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
|
86
|
+
- name: "[Attempt 1] Install Root Appraisal"
|
88
87
|
id: bundleAttempt1
|
88
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
89
|
+
run: bundle
|
90
|
+
# Continue to the next step on failure
|
91
|
+
continue-on-error: true
|
92
|
+
|
93
|
+
# Effectively an automatic retry of the previous step.
|
94
|
+
- name: "[Attempt 2] Install Root Appraisal"
|
95
|
+
id: bundleAttempt2
|
96
|
+
# If bundleAttempt1 failed, try again here; Otherwise skip.
|
97
|
+
if: ${{ steps.bundleAttempt1.outcome == 'failure' && !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
98
|
+
run: bundle
|
99
|
+
|
100
|
+
- name: "[Attempt 1] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}"
|
101
|
+
id: bundleAppraisalAttempt1
|
102
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
89
103
|
run: bundle exec appraisal ${{ matrix.appraisal }} bundle
|
90
104
|
# Continue to the next step on failure
|
91
105
|
continue-on-error: true
|
92
106
|
|
93
107
|
# Effectively an automatic retry of the previous step.
|
94
108
|
- name: "[Attempt 2] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}"
|
95
|
-
|
96
|
-
|
97
|
-
|
109
|
+
id: bundleAppraisalAttempt2
|
110
|
+
# If bundleAppraisalAttempt1 failed, try again here; Otherwise skip.
|
111
|
+
if: ${{ steps.bundleAppraisalAttempt1.outcome == 'failure' && !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
98
112
|
run: bundle exec appraisal ${{ matrix.appraisal }} bundle
|
99
113
|
|
100
|
-
- name: Tests for ${{ matrix.ruby }}
|
101
|
-
if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
|
114
|
+
- name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }}
|
115
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
102
116
|
run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }}
|
@@ -47,9 +47,11 @@ jobs:
|
|
47
47
|
|
48
48
|
steps:
|
49
49
|
- name: Checkout
|
50
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
50
51
|
uses: actions/checkout@v5
|
51
52
|
|
52
53
|
- name: Setup Ruby & RubyGems
|
54
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
53
55
|
uses: ruby/setup-ruby@v1
|
54
56
|
with:
|
55
57
|
ruby-version: ${{ matrix.ruby }}
|
@@ -61,10 +63,12 @@ jobs:
|
|
61
63
|
# We need to do this first to get appraisal installed.
|
62
64
|
# NOTE: This does not use the primary Gemfile at all.
|
63
65
|
- name: Install Root Appraisal
|
66
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
64
67
|
run: bundle
|
65
68
|
|
66
69
|
- name: "[Attempt 1] Install Root Appraisal"
|
67
70
|
id: bundleAttempt1
|
71
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
68
72
|
run: bundle
|
69
73
|
# Continue to the next step on failure
|
70
74
|
continue-on-error: true
|
@@ -73,11 +77,12 @@ jobs:
|
|
73
77
|
- name: "[Attempt 2] Install Root Appraisal"
|
74
78
|
id: bundleAttempt2
|
75
79
|
# If bundleAttempt1 failed, try again here; Otherwise skip.
|
76
|
-
if: steps.bundleAttempt1.outcome == 'failure'
|
80
|
+
if: ${{ steps.bundleAttempt1.outcome == 'failure' && !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
77
81
|
run: bundle
|
78
82
|
|
79
83
|
- name: "[Attempt 1] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}"
|
80
84
|
id: bundleAppraisalAttempt1
|
85
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
81
86
|
run: bundle exec appraisal ${{ matrix.appraisal }} bundle
|
82
87
|
# Continue to the next step on failure
|
83
88
|
continue-on-error: true
|
@@ -85,9 +90,10 @@ jobs:
|
|
85
90
|
# Effectively an automatic retry of the previous step.
|
86
91
|
- name: "[Attempt 2] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}"
|
87
92
|
id: bundleAppraisalAttempt2
|
88
|
-
# If
|
89
|
-
if: steps.bundleAppraisalAttempt1.outcome == 'failure'
|
93
|
+
# If bundleAppraisalAttempt1 failed, try again here; Otherwise skip.
|
94
|
+
if: ${{ steps.bundleAppraisalAttempt1.outcome == 'failure' && !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
90
95
|
run: bundle exec appraisal ${{ matrix.appraisal }} bundle
|
91
96
|
|
92
97
|
- name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }}
|
98
|
+
if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
|
93
99
|
run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }}
|
data/CHANGELOG.md
CHANGED
@@ -30,6 +30,22 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
30
30
|
|
31
31
|
### Security
|
32
32
|
|
33
|
+
## [1.1.27] - 2025-09-20
|
34
|
+
|
35
|
+
- TAG: [v1.1.27][1.1.27t]
|
36
|
+
- COVERAGE: 96.33% -- 3860/4007 lines in 26 files
|
37
|
+
- BRANCH COVERAGE: 81.09% -- 1591/1962 branches in 26 files
|
38
|
+
- 79.12% documented
|
39
|
+
|
40
|
+
### Changed
|
41
|
+
|
42
|
+
- Use obfuscated URLs, and avatars from Open Collective in ReadmeBackers
|
43
|
+
|
44
|
+
### Fixed
|
45
|
+
|
46
|
+
- improved handling of flaky truffleruby builds in workflow templates
|
47
|
+
- fixed handling of kettle-release when checksums are present and unchanged causing the gem_checksums script to fail
|
48
|
+
|
33
49
|
## [1.1.25] - 2025-09-18
|
34
50
|
|
35
51
|
- TAG: [v1.1.25][1.1.25t]
|
@@ -980,7 +996,11 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
980
996
|
- Selecting will run the selected workflow via `act`
|
981
997
|
- This may move to its own gem in the future.
|
982
998
|
|
983
|
-
[Unreleased]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.
|
999
|
+
[Unreleased]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.27...HEAD
|
1000
|
+
[1.1.27]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.25...v1.1.27
|
1001
|
+
[1.1.27t]: https://github.com/kettle-rb/kettle-dev/releases/tag/v1.1.27
|
1002
|
+
[1.1.26]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.25...v1.1.26
|
1003
|
+
[1.1.26t]: https://github.com/kettle-rb/kettle-dev/releases/tag/v1.1.26
|
984
1004
|
[1.1.25]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.24...v1.1.25
|
985
1005
|
[1.1.25t]: https://github.com/kettle-rb/kettle-dev/releases/tag/v1.1.25
|
986
1006
|
[1.1.24]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.23...v1.1.24
|
data/README.md
CHANGED
@@ -579,20 +579,24 @@ and [Tidelift][🏙️entsup-tidelift].
|
|
579
579
|
|
580
580
|
### Open Collective for Individuals
|
581
581
|
|
582
|
+
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/kettle-rb#backer)]
|
583
|
+
|
584
|
+
NOTE: [kettle-readme-backers][kettle-readme-backers] updates this list every day, automatically.
|
585
|
+
|
582
586
|
<!-- OPENCOLLECTIVE-INDIVIDUALS:START -->
|
583
587
|
No backers yet. Be the first!
|
584
588
|
<!-- OPENCOLLECTIVE-INDIVIDUALS:END -->
|
585
589
|
|
586
|
-
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/kettle-rb#backer)]
|
587
|
-
|
588
590
|
### Open Collective for Organizations
|
589
591
|
|
592
|
+
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-rb#sponsor)]
|
593
|
+
|
594
|
+
NOTE: [kettle-readme-backers][kettle-readme-backers] updates this list every day, automatically.
|
595
|
+
|
590
596
|
<!-- OPENCOLLECTIVE-ORGANIZATIONS:START -->
|
591
597
|
No sponsors yet. Be the first!
|
592
598
|
<!-- OPENCOLLECTIVE-ORGANIZATIONS:END -->
|
593
599
|
|
594
|
-
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-rb#sponsor)]
|
595
|
-
|
596
600
|
### Another way to support open-source
|
597
601
|
|
598
602
|
> How wonderful it is that nobody need wait a single moment before starting to improve the world.<br/>
|
@@ -921,7 +925,7 @@ Thanks for RTFM. ☺️
|
|
921
925
|
[📌gitmoji]:https://gitmoji.dev
|
922
926
|
[📌gitmoji-img]:https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
|
923
927
|
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
924
|
-
[🧮kloc-img]: https://img.shields.io/badge/KLOC-
|
928
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-4.007-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
925
929
|
[🔐security]: SECURITY.md
|
926
930
|
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
927
931
|
[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
|
data/README.md.example
CHANGED
@@ -166,19 +166,25 @@ and [Tidelift][🏙️entsup-tidelift].
|
|
166
166
|
|
167
167
|
### Open Collective for Individuals
|
168
168
|
|
169
|
+
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/kettle-rb#backer)]
|
170
|
+
|
171
|
+
NOTE: [kettle-readme-backers][kettle-readme-backers] updates this list every day, automatically.
|
172
|
+
|
169
173
|
<!-- OPENCOLLECTIVE-INDIVIDUALS:START -->
|
170
174
|
No backers yet. Be the first!
|
171
175
|
<!-- OPENCOLLECTIVE-INDIVIDUALS:END -->
|
172
176
|
|
173
|
-
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/kettle-rb#backer)]
|
174
|
-
|
175
177
|
### Open Collective for Organizations
|
176
178
|
|
179
|
+
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-rb#sponsor)]
|
180
|
+
|
181
|
+
NOTE: [kettle-readme-backers][kettle-readme-backers] updates this list every day, automatically.
|
182
|
+
|
177
183
|
<!-- OPENCOLLECTIVE-ORGANIZATIONS:START -->
|
178
184
|
No sponsors yet. Be the first!
|
179
185
|
<!-- OPENCOLLECTIVE-ORGANIZATIONS:END -->
|
180
186
|
|
181
|
-
|
187
|
+
[kettle-readme-backers]: https://github.com/kettle-rb/kettle-dev/blob/main/exe/kettle-readme-backers
|
182
188
|
|
183
189
|
### Another way to support open-source
|
184
190
|
|
@@ -513,7 +519,7 @@ Thanks for RTFM. ☺️
|
|
513
519
|
[📌gitmoji]:https://gitmoji.dev
|
514
520
|
[📌gitmoji-img]:https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
|
515
521
|
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
516
|
-
[🧮kloc-img]: https://img.shields.io/badge/KLOC-
|
522
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-4.007-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
517
523
|
[🔐security]: SECURITY.md
|
518
524
|
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
519
525
|
[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
|
data/Rakefile.example
CHANGED
@@ -18,26 +18,44 @@ module Kettle
|
|
18
18
|
|
19
19
|
public
|
20
20
|
|
21
|
-
|
22
|
-
README_PATH = File.expand_path("
|
21
|
+
# Default README is the one in the current working directory of the host project
|
22
|
+
README_PATH = File.expand_path("README.md", Dir.pwd)
|
23
23
|
README_OSC_TAG_DEFAULT = "OPENCOLLECTIVE"
|
24
24
|
COMMIT_SUBJECT_DEFAULT = "💸 Thanks 🙏 to our new backers 🎒 and subscribers 📜"
|
25
25
|
# Deprecated constant maintained for backwards compatibility in tests/specs.
|
26
|
-
# Prefer OpenCollectiveConfig.yaml_path going forward.
|
27
|
-
OC_YML_PATH = OpenCollectiveConfig.yaml_path
|
26
|
+
# Prefer OpenCollectiveConfig.yaml_path going forward, but resolve to the host project root.
|
27
|
+
OC_YML_PATH = OpenCollectiveConfig.yaml_path(Dir.pwd)
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# Emit a debug log line when kettle-dev debugging is enabled.
|
32
|
+
# Controlled by KETTLE_DEV_DEBUG=true (or DEBUG=true as fallback).
|
33
|
+
# @param msg [String]
|
34
|
+
# @return [void]
|
35
|
+
def debug_log(msg)
|
36
|
+
return unless Kettle::Dev::DEBUGGING
|
37
|
+
Kernel.warn("[readme_backers] #{msg}")
|
38
|
+
rescue StandardError
|
39
|
+
# never raise from a standard error within debug logging
|
40
|
+
end
|
41
|
+
|
42
|
+
public
|
28
43
|
|
29
44
|
# Ruby 2.3 compatibility: Struct keyword_init added in Ruby 2.5
|
30
45
|
# Switch to struct when dropping ruby < 2.5
|
31
46
|
# Backer = Struct.new(:name, :image, :website, :profile, keyword_init: true)
|
32
47
|
# Fallback for Ruby < 2.5 where Struct keyword_init is unsupported
|
33
48
|
class Backer
|
34
|
-
|
49
|
+
ROLE = "BACKER"
|
50
|
+
attr_accessor :name, :image, :website, :profile, :oc_type, :oc_index
|
35
51
|
|
36
|
-
def initialize(name: nil, image: nil, website: nil, profile: nil, **_ignored)
|
52
|
+
def initialize(name: nil, image: nil, website: nil, profile: nil, oc_type: nil, oc_index: nil, **_ignored)
|
37
53
|
@name = name
|
38
54
|
@image = image
|
39
55
|
@website = website
|
40
56
|
@profile = profile
|
57
|
+
@oc_type = oc_type # "backer" or "organization"
|
58
|
+
@oc_index = oc_index # Integer index within type for OC URL generation
|
41
59
|
end
|
42
60
|
end
|
43
61
|
|
@@ -71,24 +89,86 @@ module Kettle
|
|
71
89
|
end
|
72
90
|
|
73
91
|
def run!
|
92
|
+
validate
|
93
|
+
debug_log("Starting run: handle=#{@handle.inspect}, readme=#{@readme_path}")
|
94
|
+
debug_log("Resolved OSC tag base=#{readme_osc_tag.inspect}")
|
74
95
|
readme = File.read(@readme_path)
|
75
96
|
|
76
97
|
# Identify previous entries for diffing/mentions
|
77
98
|
b_start, b_end = detect_backer_tags(readme)
|
78
|
-
prev_backer_identities = extract_section_identities(readme, b_start, b_end)
|
79
99
|
s_start_prev, s_end_prev = detect_sponsor_tags(readme)
|
100
|
+
debug_log("Backer tags present=#{b_start != :not_found && b_end != :not_found}; Sponsor tags present=#{s_start_prev != :not_found && s_end_prev != :not_found}")
|
101
|
+
prev_backer_identities = extract_section_identities(readme, b_start, b_end)
|
80
102
|
prev_sponsor_identities = extract_section_identities(readme, s_start_prev, s_end_prev)
|
81
103
|
|
82
|
-
#
|
83
|
-
|
104
|
+
# Fetch all BACKER-role members once and partition by tier
|
105
|
+
debug_log("Fetching OpenCollective members JSON for handle=#{@handle} ...")
|
106
|
+
raw = fetch_all_backers_raw
|
107
|
+
debug_log("Fetched #{Array(raw).size} members (role=#{Backer::ROLE}) before tier partitioning")
|
108
|
+
# Build OpenCollective type-index map to generate stable avatar/website links
|
109
|
+
index_map = build_oc_index_map(raw)
|
110
|
+
if Kettle::Dev::DEBUGGING
|
111
|
+
tier_counts = Array(raw).group_by { |h| (h["tier"] || "").to_s.strip }.transform_values(&:size)
|
112
|
+
debug_log("Tier distribution: #{tier_counts}")
|
113
|
+
empty_tier = Array(raw).select { |h| h["tier"].to_s.strip.empty? }
|
114
|
+
unless empty_tier.empty?
|
115
|
+
debug_log("Members with empty tier: count=#{empty_tier.size}; showing up to 5 samples:")
|
116
|
+
empty_tier.first(5).each_with_index do |m, i|
|
117
|
+
debug_log(" [empty-tier ##{i + 1}] name=#{m["name"].inspect}, isActive=#{m["isActive"].inspect}, profile=#{m["profile"].inspect}, website=#{m["website"].inspect}")
|
118
|
+
end
|
119
|
+
end
|
120
|
+
other_tiers = Array(raw).map { |h| h["tier"].to_s.strip }.reject { |t| t.empty? || t.casecmp("Backer").zero? || t.casecmp("Sponsor").zero? }
|
121
|
+
unless other_tiers.empty?
|
122
|
+
counts = other_tiers.group_by { |t| t }.transform_values(&:size)
|
123
|
+
debug_log("Non-standard tiers present (excluding Backer/Sponsor): #{counts}")
|
124
|
+
end
|
125
|
+
end
|
126
|
+
backers_hashes = Array(raw).select { |h| h["tier"].to_s.strip.casecmp("Backer").zero? }
|
127
|
+
sponsors_hashes = Array(raw).select { |h| h["tier"].to_s.strip.casecmp("Sponsor").zero? }
|
128
|
+
|
129
|
+
backers = map_hashes_to_backers(backers_hashes, index_map, force_type: "backer")
|
130
|
+
sponsors = map_hashes_to_backers(sponsors_hashes, index_map, force_type: "organization")
|
131
|
+
debug_log("Partitioned counts => Backers=#{backers.size}, Sponsors=#{sponsors.size}")
|
132
|
+
if Kettle::Dev::DEBUGGING && backers.empty? && sponsors.empty? && Array(raw).any?
|
133
|
+
debug_log("No Backer or Sponsor tiers matched among #{Array(raw).size} BACKER-role records. If tiers are empty, they will not appear in Backers/Sponsors sections.")
|
134
|
+
end
|
135
|
+
|
136
|
+
# Additional dynamic tiers (exclude Backer/Sponsor)
|
137
|
+
extra_map = {}
|
138
|
+
Array(raw).group_by { |h| h["tier"].to_s.strip }.each do |tier, members|
|
139
|
+
normalized = tier.empty? ? "Donors" : tier
|
140
|
+
next if normalized.casecmp("Backer").zero? || normalized.casecmp("Sponsor").zero?
|
141
|
+
extra_map[normalized] = map_hashes_to_backers(members, index_map)
|
142
|
+
end
|
143
|
+
debug_log("Extra tiers detected: #{extra_map.keys.sort}") unless extra_map.empty?
|
144
|
+
|
84
145
|
backers_md = generate_markdown(backers, empty_message: "No backers yet. Be the first!", default_name: "Backer")
|
85
|
-
|
146
|
+
sponsors_md_base = generate_markdown(sponsors, empty_message: "No sponsors yet. Be the first!", default_name: "Sponsor")
|
147
|
+
|
148
|
+
extra_tiers_md = generate_extra_tiers_markdown(extra_map)
|
149
|
+
sponsors_md = if extra_tiers_md.empty?
|
150
|
+
sponsors_md_base
|
151
|
+
else
|
152
|
+
[sponsors_md_base, "", extra_tiers_md].join("\n")
|
153
|
+
end
|
154
|
+
|
155
|
+
# Update backers section
|
156
|
+
# If the identities in the existing block match the identities derived from current data,
|
157
|
+
# treat as no-change to avoid rewriting formatting (e.g., Markdown -> HTML OC anchors).
|
158
|
+
semantically_same_backers = semantically_same_section?(prev_backer_identities, backers)
|
159
|
+
updated = if semantically_same_backers
|
160
|
+
:no_change
|
161
|
+
else
|
162
|
+
replace_between_tags(readme, b_start, b_end, backers_md)
|
163
|
+
end
|
86
164
|
case updated
|
87
165
|
when :not_found
|
166
|
+
debug_log("Backers tag block not found; skipping backers section update")
|
88
167
|
updated_readme = readme
|
89
168
|
backers_changed = false
|
90
169
|
new_backers = []
|
91
170
|
when :no_change
|
171
|
+
debug_log("Backers section unchanged (identities match or generated markdown matches existing block)")
|
92
172
|
updated_readme = readme
|
93
173
|
backers_changed = false
|
94
174
|
new_backers = []
|
@@ -96,19 +176,35 @@ module Kettle
|
|
96
176
|
updated_readme = updated
|
97
177
|
backers_changed = true
|
98
178
|
new_backers = compute_new_members(prev_backer_identities, backers)
|
179
|
+
debug_log("Backers section updated; new_backers=#{new_backers.size}")
|
99
180
|
end
|
100
181
|
|
101
|
-
#
|
102
|
-
sponsors = fetch_members("sponsors.json")
|
103
|
-
sponsors_md = generate_markdown(sponsors, empty_message: "No sponsors yet. Be the first!", default_name: "Sponsor")
|
182
|
+
# Update sponsors section (with extra tiers appended when present)
|
104
183
|
s_start, s_end = detect_sponsor_tags(updated_readme)
|
105
|
-
|
184
|
+
# If there is no sponsors section but there is a backers section, append extra tiers to backers instead.
|
185
|
+
if s_start == :not_found && !extra_tiers_md.empty? && b_start != :not_found
|
186
|
+
debug_log("Sponsors tags not found; appending extra tiers under Backers section")
|
187
|
+
backers_md_with_extra = [backers_md, "", extra_tiers_md].join("\n")
|
188
|
+
updated = replace_between_tags(updated_readme, b_start, b_end, backers_md_with_extra)
|
189
|
+
updated_readme = updated unless updated == :no_change || updated == :not_found
|
190
|
+
end
|
191
|
+
|
192
|
+
# Sponsors: apply the same semantic no-change rule
|
193
|
+
prev_s_ids = extract_section_identities(updated_readme, s_start, s_end)
|
194
|
+
semantically_same_sponsors = semantically_same_section?(prev_s_ids, sponsors)
|
195
|
+
updated2 = if semantically_same_sponsors
|
196
|
+
:no_change
|
197
|
+
else
|
198
|
+
replace_between_tags(updated_readme, s_start, s_end, sponsors_md)
|
199
|
+
end
|
106
200
|
case updated2
|
107
201
|
when :not_found
|
202
|
+
debug_log("Sponsors tag block not found; skipping sponsors section update")
|
108
203
|
sponsors_changed = false
|
109
204
|
final = updated_readme
|
110
205
|
new_sponsors = []
|
111
206
|
when :no_change
|
207
|
+
debug_log("Sponsors section unchanged (identities match or generated markdown matches existing block)")
|
112
208
|
sponsors_changed = false
|
113
209
|
final = updated_readme
|
114
210
|
new_sponsors = []
|
@@ -116,6 +212,7 @@ module Kettle
|
|
116
212
|
sponsors_changed = true
|
117
213
|
final = updated2
|
118
214
|
new_sponsors = compute_new_members(prev_sponsor_identities, sponsors)
|
215
|
+
debug_log("Sponsors section updated; new_sponsors=#{new_sponsors.size}")
|
119
216
|
end
|
120
217
|
|
121
218
|
if !backers_changed && !sponsors_changed
|
@@ -123,9 +220,11 @@ module Kettle
|
|
123
220
|
ts = tag_strings
|
124
221
|
warn("No recognized Open Collective tags found in #{@readme_path}. Expected one or more of: " \
|
125
222
|
"#{ts[:generic_start]}/#{ts[:generic_end]}, #{ts[:individuals_start]}/#{ts[:individuals_end]}, #{ts[:orgs_start]}/#{ts[:orgs_end]}.")
|
223
|
+
debug_log("Missing tags: looked for #{ts}")
|
126
224
|
# Do not exit the process during tests or library use; just return.
|
127
225
|
return
|
128
226
|
end
|
227
|
+
debug_log("No changes detected after processing; Backers=#{backers.size}, Sponsors=#{sponsors.size}, ExtraTiers=#{extra_map.keys.size}")
|
129
228
|
puts "No changes to backers or sponsors sections in #{@readme_path}."
|
130
229
|
return
|
131
230
|
end
|
@@ -146,9 +245,9 @@ module Kettle
|
|
146
245
|
env = ENV["KETTLE_DEV_BACKER_README_OSC_TAG"].to_s
|
147
246
|
return env unless env.strip.empty?
|
148
247
|
|
149
|
-
if File.file?(
|
248
|
+
if File.file?(OC_YML_PATH)
|
150
249
|
begin
|
151
|
-
yml = YAML.safe_load(File.read(
|
250
|
+
yml = YAML.safe_load(File.read(OC_YML_PATH))
|
152
251
|
if yml.is_a?(Hash)
|
153
252
|
from_yml = yml["readme-osc-tag"] || yml[:"readme-osc-tag"]
|
154
253
|
from_yml = from_yml.to_s if from_yml
|
@@ -174,11 +273,13 @@ module Kettle
|
|
174
273
|
end
|
175
274
|
|
176
275
|
def resolve_handle
|
177
|
-
OpenCollectiveConfig.handle(required: true)
|
276
|
+
OpenCollectiveConfig.handle(required: true, root: Dir.pwd)
|
178
277
|
end
|
179
278
|
|
180
|
-
def
|
181
|
-
|
279
|
+
def fetch_all_backers_raw
|
280
|
+
api_path = "members/all.json"
|
281
|
+
url = URI("https://opencollective.com/#{@handle}/#{api_path}")
|
282
|
+
debug_log("GET #{url}")
|
182
283
|
response = Net::HTTP.start(url.host, url.port, use_ssl: url.scheme == "https") do |conn|
|
183
284
|
conn.read_timeout = 10
|
184
285
|
conn.open_timeout = 5
|
@@ -186,36 +287,169 @@ module Kettle
|
|
186
287
|
req["User-Agent"] = "kettle-dev/README-backers"
|
187
288
|
conn.request(req)
|
188
289
|
end
|
189
|
-
|
290
|
+
unless response.is_a?(Net::HTTPSuccess)
|
291
|
+
body_len = (response.respond_to?(:body) && response.body) ? response.body.bytesize : 0
|
292
|
+
code = response.respond_to?(:code) ? response.code : response.class.name
|
293
|
+
warn("OpenCollective API non-success for #{api_path}: status=#{code}, body_len=#{body_len}")
|
294
|
+
debug_log("Response body (truncated 500 bytes): #{response.body.to_s[0, 500]}") if Kettle::Dev::DEBUGGING && body_len.to_i > 0
|
295
|
+
return []
|
296
|
+
end
|
190
297
|
|
191
298
|
parsed = JSON.parse(response.body)
|
192
|
-
Array(parsed)
|
299
|
+
all = Array(parsed)
|
300
|
+
filtered = all.select { |h| h["role"].to_s.upcase == Backer::ROLE }
|
301
|
+
debug_log("Parsed #{all.size} records; filtered BACKER => #{filtered.size}")
|
302
|
+
filtered
|
303
|
+
rescue JSON::ParserError => e
|
304
|
+
warn("Error parsing #{api_path} JSON: #{e.message}")
|
305
|
+
debug_log("Body that failed to parse (truncated 500): #{response&.body.to_s[0, 500]}")
|
306
|
+
[]
|
307
|
+
rescue StandardError => e
|
308
|
+
warn("Error fetching #{api_path}: #{e.class}: #{e.message}")
|
309
|
+
debug_log(e.backtrace.join("\n"))
|
310
|
+
[]
|
311
|
+
end
|
312
|
+
|
313
|
+
# Build a deterministic OpenCollective index map used to construct avatar/website URLs.
|
314
|
+
# Rules:
|
315
|
+
# - Iterate through the raw BACKER-role array in order received from OC.
|
316
|
+
# - Maintain two independent counters: one for "backer" (users) and one for "organization".
|
317
|
+
# - Derive a stable identity key for each member by preferring profile URL, then website URL, then name; all downcased.
|
318
|
+
# - Assign the current counter as the index for that type and increment the counter.
|
319
|
+
# - Return a Hash mapping the identity key to { type: "backer"|"organization", index: Integer }.
|
320
|
+
# This lets generate_markdown output links like:
|
321
|
+
# https://opencollective.com/<handle>/<type>/<index>/website and avatar.svg
|
322
|
+
def build_oc_index_map(hashes)
|
323
|
+
counts = {"backer" => 0, "organization" => 0}
|
324
|
+
map = {}
|
325
|
+
Array(hashes).each do |h|
|
326
|
+
type = (h["type"].to_s.upcase == "ORGANIZATION") ? "organization" : "backer"
|
327
|
+
key = if h["profile"].to_s.strip != ""
|
328
|
+
h["profile"].to_s.strip.downcase
|
329
|
+
elsif h["website"].to_s.strip != ""
|
330
|
+
h["website"].to_s.strip.downcase
|
331
|
+
else
|
332
|
+
h["name"].to_s.strip.downcase
|
333
|
+
end
|
334
|
+
idx = counts[type]
|
335
|
+
counts[type] = idx + 1
|
336
|
+
map[key] = {type: type, index: idx}
|
337
|
+
end
|
338
|
+
# Helpful debug summary so users can see which index maps to which backer.
|
339
|
+
if Kettle::Dev::DEBUGGING
|
340
|
+
samples = map.first(5).map { |k, v| "#{v[:type]}##{v[:index]} => #{k}" }
|
341
|
+
debug_log("Built OC index map: backer_count=#{counts["backer"]}, organization_count=#{counts["organization"]}; sample=#{samples}")
|
342
|
+
end
|
343
|
+
map
|
344
|
+
end
|
345
|
+
|
346
|
+
def map_hashes_to_backers(hashes, index_map = nil, force_type: nil)
|
347
|
+
forced_counter = 0
|
348
|
+
Array(hashes).map do |h|
|
349
|
+
key = if h["profile"].to_s.strip != ""
|
350
|
+
h["profile"].to_s.strip.downcase
|
351
|
+
elsif h["website"].to_s.strip != ""
|
352
|
+
h["website"].to_s.strip.downcase
|
353
|
+
else
|
354
|
+
h["name"].to_s.strip.downcase
|
355
|
+
end
|
356
|
+
oc = index_map ? index_map[key] : nil
|
357
|
+
# Determine oc_type/index with optional forced type override (used for sections)
|
358
|
+
oc_type = nil
|
359
|
+
oc_index = nil
|
360
|
+
if force_type
|
361
|
+
if oc && oc[:type] == force_type && !oc[:index].nil?
|
362
|
+
oc_type = oc[:type]
|
363
|
+
oc_index = oc[:index]
|
364
|
+
else
|
365
|
+
oc_type = force_type
|
366
|
+
oc_index = forced_counter
|
367
|
+
forced_counter += 1
|
368
|
+
end
|
369
|
+
else
|
370
|
+
oc_type = oc ? oc[:type] : nil
|
371
|
+
oc_index = oc ? oc[:index] : nil
|
372
|
+
end
|
193
373
|
Backer.new(
|
194
374
|
name: h["name"],
|
195
|
-
image:
|
375
|
+
image: begin
|
376
|
+
# Prefer OpenCollective's "avatar" key; fallback to legacy "image"
|
377
|
+
img = h["avatar"]
|
378
|
+
img = h["image"] if img.to_s.strip.empty?
|
379
|
+
img.to_s.strip.empty? ? nil : img
|
380
|
+
end,
|
196
381
|
website: (h["website"].to_s.strip.empty? ? nil : h["website"]),
|
197
382
|
profile: (h["profile"].to_s.strip.empty? ? nil : h["profile"]),
|
383
|
+
oc_type: oc_type,
|
384
|
+
oc_index: oc_index,
|
198
385
|
)
|
199
386
|
end
|
200
|
-
rescue JSON::ParserError => e
|
201
|
-
warn("Error parsing #{path} JSON: #{e.message}")
|
202
|
-
[]
|
203
|
-
rescue StandardError => e
|
204
|
-
warn("Error fetching #{path}: #{e.class}: #{e.message}")
|
205
|
-
[]
|
206
387
|
end
|
207
388
|
|
208
389
|
def generate_markdown(members, empty_message:, default_name:)
|
209
390
|
return empty_message if members.nil? || members.empty?
|
210
391
|
|
211
392
|
members.map do |m|
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
393
|
+
# Prefer deterministic OpenCollective avatar/link form when index is available
|
394
|
+
if m.oc_type && !m.oc_type.to_s.strip.empty? && !m.oc_index.nil?
|
395
|
+
type = m.oc_type
|
396
|
+
idx = m.oc_index
|
397
|
+
href = "https://opencollective.com/#{@handle}/#{type}/#{idx}/website"
|
398
|
+
img = "https://opencollective.com/#{@handle}/#{type}/#{idx}/avatar.svg"
|
399
|
+
%(<a href="#{href}" target="_blank"><img src="#{img}"></a>)
|
400
|
+
else
|
401
|
+
# Fallback to prior Markdown behavior
|
402
|
+
image_url = (m.image && !m.image.to_s.strip.empty?) ? m.image : nil
|
403
|
+
primary_link = (m.website && !m.website.to_s.strip.empty?) ? m.website : nil
|
404
|
+
fallback_link = (m.profile && !m.profile.to_s.strip.empty?) ? m.profile : nil
|
405
|
+
link = primary_link || fallback_link || "#"
|
406
|
+
name = (m.name && !m.name.strip.empty?) ? m.name : default_name
|
407
|
+
if image_url
|
408
|
+
"[](#{link})"
|
409
|
+
else
|
410
|
+
"[#{escape_text(name)}](#{link})"
|
411
|
+
end
|
412
|
+
end
|
216
413
|
end.join(" ")
|
217
414
|
end
|
218
415
|
|
416
|
+
# Build markdown for any additional tiers beyond Backer/Sponsor.
|
417
|
+
# Accepts a Hash of { tier_name => [Backer, ...] }.
|
418
|
+
# Returns an empty string when there are no extra tiers.
|
419
|
+
def generate_extra_tiers_markdown(extra_map)
|
420
|
+
return "" if extra_map.nil? || extra_map.empty?
|
421
|
+
|
422
|
+
blocks = []
|
423
|
+
extra_map.keys.sort.each do |tier|
|
424
|
+
members = extra_map[tier]
|
425
|
+
next if members.nil? || members.empty?
|
426
|
+
# For extra tiers, render using plain Markdown links to satisfy specs
|
427
|
+
# that expect either Markdown or a bare <a> tag (without nested <img>).
|
428
|
+
members_plain = Array(members).map do |m|
|
429
|
+
Backer.new(
|
430
|
+
name: m.name,
|
431
|
+
image: m.image,
|
432
|
+
website: m.website,
|
433
|
+
profile: m.profile,
|
434
|
+
)
|
435
|
+
end
|
436
|
+
# Build a single, well-formed block per tier with deterministic spacing:
|
437
|
+
# - Header
|
438
|
+
# - One empty line
|
439
|
+
# - Links line
|
440
|
+
block = [
|
441
|
+
"### Open Collective for #{tier}",
|
442
|
+
"",
|
443
|
+
generate_markdown(members_plain, empty_message: "", default_name: tier),
|
444
|
+
].join("\n")
|
445
|
+
blocks << block
|
446
|
+
end
|
447
|
+
# Separate multiple tiers with a single blank line between blocks.
|
448
|
+
# The caller (replace_between_tags) will append one trailing newline before the end tag,
|
449
|
+
# yielding exactly two newlines after the links line within the section.
|
450
|
+
blocks.join("\n\n")
|
451
|
+
end
|
452
|
+
|
219
453
|
def replace_between_tags(content, start_tag, end_tag, new_content)
|
220
454
|
return :not_found if start_tag == :not_found || end_tag == :not_found
|
221
455
|
|
@@ -225,11 +459,14 @@ module Kettle
|
|
225
459
|
|
226
460
|
before = content[0..start_index + start_tag.length - 1]
|
227
461
|
after = content[end_index..-1]
|
228
|
-
replacement = "#{start_tag}\n#{new_content}\n#{end_tag}"
|
229
462
|
current_block = content[start_index..end_index + end_tag.length - 1]
|
230
|
-
|
463
|
+
# Treat as unchanged if the block matches either single or double newline spacing
|
464
|
+
unchanged_a = "#{start_tag}\n#{new_content}\n#{end_tag}"
|
465
|
+
unchanged_b = "#{start_tag}\n#{new_content}\n\n#{end_tag}"
|
466
|
+
return :no_change if current_block == unchanged_a || current_block == unchanged_b
|
231
467
|
|
232
468
|
trailing = after[end_tag.length..-1] || ""
|
469
|
+
# Use a single blank line between content and end tag for normalized output
|
233
470
|
"#{before}\n#{new_content}\n#{end_tag}#{trailing}"
|
234
471
|
end
|
235
472
|
|
@@ -262,14 +499,28 @@ module Kettle
|
|
262
499
|
|
263
500
|
block = content[(start_index + start_tag.length)...end_index]
|
264
501
|
identities = Set.new
|
502
|
+
# 1) Image-style link wrappers: [](HREF)
|
265
503
|
block.to_s.scan(/\[!\[[^\]]*\]\([^\)]*\)\]\(([^\)]+)\)/) do |m|
|
266
504
|
href = (m[0] || "").strip
|
267
505
|
identities << href.downcase unless href.empty?
|
268
506
|
end
|
507
|
+
# 2) Capture ALT text from image-style wrappers for name identity
|
269
508
|
block.to_s.scan(/\[!\[([^\]]*)\]\([^\)]*\)\]\([^\)]*\)/) do |m|
|
270
509
|
alt = (m[0] || "").strip
|
271
510
|
identities << alt.downcase unless alt.empty?
|
272
511
|
end
|
512
|
+
# 3) Plain markdown links: [TEXT](HREF)
|
513
|
+
block.to_s.scan(/\[([^!][^\]]*)\]\(([^\)]+)\)/) do |m|
|
514
|
+
text = (m[0] || "").strip
|
515
|
+
href = (m[1] || "").strip
|
516
|
+
identities << href.downcase unless href.empty?
|
517
|
+
identities << text.downcase unless text.empty?
|
518
|
+
end
|
519
|
+
# 4) HTML anchors: <a href="HREF">...</a>
|
520
|
+
block.to_s.scan(/<a\s+[^>]*href=["']([^"']+)["'][^>]*>/i) do |m|
|
521
|
+
href = (m[0] || "").strip
|
522
|
+
identities << href.downcase unless href.empty?
|
523
|
+
end
|
273
524
|
identities
|
274
525
|
end
|
275
526
|
|
@@ -281,6 +532,71 @@ module Kettle
|
|
281
532
|
end
|
282
533
|
end
|
283
534
|
|
535
|
+
# Build the identity set for a list of members using the same precedence
|
536
|
+
# as compute_new_members/identity_for_member. Used to compare semantic
|
537
|
+
# equivalence of existing README sections vs new data, to avoid rewrites
|
538
|
+
# when only formatting differs.
|
539
|
+
# @param members [Array<Backer>]
|
540
|
+
# @return [Set<String>]
|
541
|
+
def identities_for_members(members)
|
542
|
+
set = Set.new
|
543
|
+
Array(members).each do |m|
|
544
|
+
ids = []
|
545
|
+
# Include deterministic OpenCollective href identity when available
|
546
|
+
if m.oc_type && !m.oc_type.to_s.strip.empty? && !m.oc_index.nil?
|
547
|
+
ids << "https://opencollective.com/#{@handle}/#{m.oc_type}/#{m.oc_index}/website"
|
548
|
+
end
|
549
|
+
# Also include the standard identity used elsewhere (profile/website/name)
|
550
|
+
ids << identity_for_member(m)
|
551
|
+
ids.compact.each do |id|
|
552
|
+
norm = id.to_s.strip.downcase
|
553
|
+
set << norm unless norm.empty?
|
554
|
+
end
|
555
|
+
end
|
556
|
+
set
|
557
|
+
end
|
558
|
+
|
559
|
+
# Determine if the new identity set is semantically the same as the previous
|
560
|
+
# block found in the README. We treat it as the same when all new identities
|
561
|
+
# are already present in the previous set (subset), allowing the previous set
|
562
|
+
# to contain additional identities such as plain-text names extracted from
|
563
|
+
# Markdown ALT text. This avoids unnecessary rewrites when only formatting
|
564
|
+
# changes (e.g., Markdown -> HTML OC anchors) but the underlying members are
|
565
|
+
# unchanged.
|
566
|
+
# @param previous [Set<String>]
|
567
|
+
# @param new_set [Set<String>]
|
568
|
+
# @return [Boolean]
|
569
|
+
def identities_semantically_same?(previous, new_set)
|
570
|
+
return false if new_set.nil? || new_set.empty?
|
571
|
+
return false if previous.nil? || previous.empty?
|
572
|
+
new_set.all? { |id| previous.include?(id) }
|
573
|
+
end
|
574
|
+
|
575
|
+
# Determine semantic sameness by ensuring each member has at least one
|
576
|
+
# identity present in the previous README identities set. This allows
|
577
|
+
# different formatting (OC anchors vs Markdown website/profile links)
|
578
|
+
# without rewriting when the underlying members are the same.
|
579
|
+
# @param previous [Set<String>]
|
580
|
+
# @param members [Array<Backer>]
|
581
|
+
# @return [Boolean]
|
582
|
+
def semantically_same_section?(previous, members)
|
583
|
+
return false if previous.nil? || previous.empty?
|
584
|
+
Array(members).all? do |m|
|
585
|
+
candidates = []
|
586
|
+
if m.oc_type && !m.oc_type.to_s.strip.empty? && !m.oc_index.nil?
|
587
|
+
candidates << "https://opencollective.com/#{@handle}/#{m.oc_type}/#{m.oc_index}/website"
|
588
|
+
end
|
589
|
+
candidates << m.profile
|
590
|
+
candidates << m.website
|
591
|
+
candidates << m.name
|
592
|
+
candidates.compact!
|
593
|
+
candidates.any? { |id|
|
594
|
+
s = id.to_s.strip.downcase
|
595
|
+
!s.empty? && previous.include?(s)
|
596
|
+
}
|
597
|
+
end
|
598
|
+
end
|
599
|
+
|
284
600
|
def identity_for_member(m)
|
285
601
|
if m.profile && !m.profile.strip.empty?
|
286
602
|
m.profile.strip.downcase
|
@@ -20,14 +20,32 @@ module Kettle
|
|
20
20
|
def run_cmd!(cmd)
|
21
21
|
# For Bundler-invoked build/release, explicitly prefix SKIP_GEM_SIGNING so
|
22
22
|
# the signing step is skipped even when Bundler scrubs ENV.
|
23
|
+
# Always do this on CI to avoid interactive prompts; locally only when explicitly requested.
|
23
24
|
if ENV["SKIP_GEM_SIGNING"] && cmd =~ /\Abundle(\s+exec)?\s+rake\s+(build|release)\b/
|
24
25
|
cmd = "SKIP_GEM_SIGNING=true #{cmd}"
|
25
26
|
end
|
26
27
|
puts "$ #{cmd}"
|
27
28
|
# Pass a plain Hash for the environment to satisfy tests and avoid ENV object oddities
|
28
29
|
env_hash = ENV.respond_to?(:to_hash) ? ENV.to_hash : ENV.to_h
|
29
|
-
|
30
|
-
|
30
|
+
|
31
|
+
# Capture output so we can surface clear diagnostics on failure
|
32
|
+
stdout_str, stderr_str, status = Open3.capture3(env_hash, cmd)
|
33
|
+
|
34
|
+
# Echo command output to match prior behavior
|
35
|
+
$stdout.print(stdout_str) unless stdout_str.nil? || stdout_str.empty?
|
36
|
+
$stderr.print(stderr_str) unless stderr_str.nil? || stderr_str.empty?
|
37
|
+
|
38
|
+
unless status.success?
|
39
|
+
exit_code = status.exitstatus
|
40
|
+
# Keep the original prefix to avoid breaking any tooling/tests that grep for it,
|
41
|
+
# but add the exit status and a brief diagnostic tail from stderr.
|
42
|
+
diag = ""
|
43
|
+
unless stderr_str.to_s.empty?
|
44
|
+
tail = stderr_str.lines.last(20).join
|
45
|
+
diag = "\n--- STDERR (last 20 lines) ---\n#{tail}".rstrip
|
46
|
+
end
|
47
|
+
abort("Command failed: #{cmd} (exit #{exit_code})#{diag}")
|
48
|
+
end
|
31
49
|
end
|
32
50
|
end
|
33
51
|
|
@@ -260,7 +260,7 @@ module Kettle
|
|
260
260
|
|
261
261
|
# We need to sleep a bit here to ensure the terminal is ready for both
|
262
262
|
# input and writing status updates to each workflow's line
|
263
|
-
sleep(0.2)
|
263
|
+
sleep(0.2)
|
264
264
|
|
265
265
|
selected = nil
|
266
266
|
# Create input thread always so specs that assert its cleanup/exception behavior can exercise it,
|
data/lib/kettle/dev/version.rb
CHANGED
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kettle-dev
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.27
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Peter H. Boling
|
@@ -404,10 +404,10 @@ licenses:
|
|
404
404
|
- MIT
|
405
405
|
metadata:
|
406
406
|
homepage_uri: https://kettle-dev.galtzo.com/
|
407
|
-
source_code_uri: https://github.com/kettle-rb/kettle-dev/tree/v1.1.
|
408
|
-
changelog_uri: https://github.com/kettle-rb/kettle-dev/blob/v1.1.
|
407
|
+
source_code_uri: https://github.com/kettle-rb/kettle-dev/tree/v1.1.27
|
408
|
+
changelog_uri: https://github.com/kettle-rb/kettle-dev/blob/v1.1.27/CHANGELOG.md
|
409
409
|
bug_tracker_uri: https://github.com/kettle-rb/kettle-dev/issues
|
410
|
-
documentation_uri: https://www.rubydoc.info/gems/kettle-dev/1.1.
|
410
|
+
documentation_uri: https://www.rubydoc.info/gems/kettle-dev/1.1.27
|
411
411
|
funding_uri: https://github.com/sponsors/pboling
|
412
412
|
wiki_uri: https://github.com/kettle-rb/kettle-dev/wiki
|
413
413
|
news_uri: https://www.railsbling.com/tags/kettle-dev
|
metadata.gz.sig
CHANGED
Binary file
|