rollout 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/release.yml +33 -7
- data/.github/workflows/test.yml +43 -34
- data/README.md +57 -0
- data/lib/rollout/definition.rb +31 -0
- data/lib/rollout/version.rb +1 -1
- data/lib/rollout.rb +23 -0
- data/spec/rollout/definition_spec.rb +83 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ecf20a456dab296cc321049b845972bff0c72075e4bb7e30e2e5314e33cff1b2
|
|
4
|
+
data.tar.gz: d66bce69c2ec03dc790f922635501b970b790886281651708b98fd133d6c3c3e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dddafc84d403c006b8aafe0ef961b9a883b192a7ba1e4d7816a28bf72c38b60c86b7c86e5e6d10a9a8fa5921b5d8493361023553aecb455f6e2c954eed4bacdd
|
|
7
|
+
data.tar.gz: 1b2de6537a44270239c74d403051eac56cad4580fb995beaaedef5597e354cb7f839cca9b581f814d25451454f0ebfeaa16e0f2ca1e21469106c3413fa4cdee8
|
|
@@ -10,14 +10,16 @@ on:
|
|
|
10
10
|
permissions:
|
|
11
11
|
contents: write
|
|
12
12
|
id-token: write
|
|
13
|
+
pull-requests: read
|
|
13
14
|
|
|
14
15
|
jobs:
|
|
15
16
|
publish:
|
|
16
|
-
runs-on: ubuntu-
|
|
17
|
+
runs-on: ubuntu-26.04
|
|
17
18
|
outputs:
|
|
18
19
|
name: ${{ steps.package.outputs.name }}
|
|
19
20
|
version: ${{ steps.package.outputs.version }}
|
|
20
21
|
previous_tag: ${{ steps.package.outputs.previous_tag }}
|
|
22
|
+
paths: ${{ steps.package.outputs.paths }}
|
|
21
23
|
steps:
|
|
22
24
|
- name: Checkout code
|
|
23
25
|
uses: actions/checkout@v5
|
|
@@ -31,9 +33,9 @@ jobs:
|
|
|
31
33
|
id: package
|
|
32
34
|
run: |
|
|
33
35
|
case "$GITHUB_REF_NAME" in
|
|
34
|
-
rollout/v*) name=rollout; directory
|
|
35
|
-
rollout-redis-adapter/v*) name=rollout-redis-adapter; directory=rollout-redis-adapter ;;
|
|
36
|
-
rollout-active_record-adapter/v*) name=rollout-active_record-adapter; directory=rollout-active_record-adapter ;;
|
|
36
|
+
rollout/v*) name=rollout; directory=.; paths="lib rollout.gemspec" ;;
|
|
37
|
+
rollout-redis-adapter/v*) name=rollout-redis-adapter; directory=rollout-redis-adapter; paths=rollout-redis-adapter ;;
|
|
38
|
+
rollout-active_record-adapter/v*) name=rollout-active_record-adapter; directory=rollout-active_record-adapter; paths=rollout-active_record-adapter ;;
|
|
37
39
|
*) echo "Unexpected tag $GITHUB_REF_NAME" >&2; exit 1 ;;
|
|
38
40
|
esac
|
|
39
41
|
version="${GITHUB_REF_NAME##*/v}"
|
|
@@ -48,6 +50,7 @@ jobs:
|
|
|
48
50
|
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
49
51
|
echo "directory=$directory" >> "$GITHUB_OUTPUT"
|
|
50
52
|
echo "previous_tag=$previous_tag" >> "$GITHUB_OUTPUT"
|
|
53
|
+
echo "paths=$paths" >> "$GITHUB_OUTPUT"
|
|
51
54
|
- name: Build selected gem
|
|
52
55
|
working-directory: ${{ steps.package.outputs.directory }}
|
|
53
56
|
run: gem build "${{ steps.package.outputs.name }}.gemspec"
|
|
@@ -59,14 +62,37 @@ jobs:
|
|
|
59
62
|
|
|
60
63
|
github-release:
|
|
61
64
|
needs: publish
|
|
62
|
-
runs-on: ubuntu-
|
|
65
|
+
runs-on: ubuntu-26.04
|
|
63
66
|
steps:
|
|
67
|
+
- name: Checkout code
|
|
68
|
+
uses: actions/checkout@v5
|
|
69
|
+
with:
|
|
70
|
+
fetch-depth: 0
|
|
71
|
+
# GitHub's generated notes list every PR in the repo between tags; keep
|
|
72
|
+
# only PRs that touched the released gem's files.
|
|
73
|
+
- name: Build release notes
|
|
74
|
+
env:
|
|
75
|
+
GH_TOKEN: ${{ github.token }}
|
|
76
|
+
PREVIOUS_TAG: ${{ needs.publish.outputs.previous_tag }}
|
|
77
|
+
PATHS: ${{ needs.publish.outputs.paths }}
|
|
78
|
+
run: |
|
|
79
|
+
{
|
|
80
|
+
echo "## What's Changed"
|
|
81
|
+
git log --reverse --format=%H "$PREVIOUS_TAG..$GITHUB_REF_NAME" -- $PATHS |
|
|
82
|
+
while read -r sha; do
|
|
83
|
+
gh api "repos/$GITHUB_REPOSITORY/commits/$sha/pulls" \
|
|
84
|
+
--jq '.[] | "* \(.title) by @\(.user.login) in \(.html_url)"'
|
|
85
|
+
done |
|
|
86
|
+
awk '!seen[$0]++'
|
|
87
|
+
echo
|
|
88
|
+
echo "**Full Changelog**: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/compare/$PREVIOUS_TAG...$GITHUB_REF_NAME"
|
|
89
|
+
} > release-notes.md
|
|
90
|
+
cat release-notes.md
|
|
64
91
|
- name: Create GitHub Release
|
|
65
92
|
uses: softprops/action-gh-release@v3
|
|
66
93
|
with:
|
|
67
94
|
tag_name: ${{ github.ref }}
|
|
68
95
|
name: ${{ needs.publish.outputs.name }} ${{ needs.publish.outputs.version }}
|
|
69
|
-
|
|
70
|
-
previous_tag: ${{ needs.publish.outputs.previous_tag }}
|
|
96
|
+
body_path: release-notes.md
|
|
71
97
|
draft: false
|
|
72
98
|
prerelease: false
|
data/.github/workflows/test.yml
CHANGED
|
@@ -10,18 +10,24 @@ on:
|
|
|
10
10
|
|
|
11
11
|
jobs:
|
|
12
12
|
core:
|
|
13
|
-
runs-on: ubuntu-
|
|
13
|
+
runs-on: ubuntu-26.04
|
|
14
|
+
# setup-ruby has no Ruby 3.1 or older builds for ubuntu-26.04, so use the official Ruby images.
|
|
15
|
+
container: ruby:${{ matrix.ruby-version }}
|
|
14
16
|
strategy:
|
|
15
17
|
matrix:
|
|
16
18
|
ruby-version: ['3.3', '3.2', '3.1', '3.0', '2.7', '2.6', '2.5', '2.4']
|
|
17
19
|
steps:
|
|
18
20
|
- name: Checkout code
|
|
19
21
|
uses: actions/checkout@v5
|
|
20
|
-
- name:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
- name: Install Bundler
|
|
23
|
+
# The Ruby 2.7 and older images ship Bundler 1.17 or 2.1; install the versions setup-ruby used.
|
|
24
|
+
run: |
|
|
25
|
+
case "$RUBY_VERSION" in
|
|
26
|
+
2.4.*|2.5.*) gem install bundler -v '~> 2.3.0' ;;
|
|
27
|
+
2.6.*|2.7.*) gem install bundler -v '~> 2.4.0' ;;
|
|
28
|
+
esac
|
|
29
|
+
- name: Install gems
|
|
30
|
+
run: bundle install --jobs 4
|
|
25
31
|
- name: Run core tests
|
|
26
32
|
run: |
|
|
27
33
|
mkdir -p test_results
|
|
@@ -35,15 +41,17 @@ jobs:
|
|
|
35
41
|
retention-days: 30
|
|
36
42
|
|
|
37
43
|
redis:
|
|
38
|
-
runs-on: ubuntu-
|
|
44
|
+
runs-on: ubuntu-26.04
|
|
45
|
+
# setup-ruby has no Ruby 3.1 or older builds for ubuntu-26.04, so use the official Ruby images.
|
|
46
|
+
container: ruby:${{ matrix.ruby-version }}
|
|
39
47
|
strategy:
|
|
40
48
|
matrix:
|
|
41
49
|
ruby-version: ['3.3', '3.2', '3.1', '3.0', '2.7', '2.6', '2.5', '2.4']
|
|
50
|
+
env:
|
|
51
|
+
REDIS_HOST: redis
|
|
42
52
|
services:
|
|
43
53
|
redis:
|
|
44
54
|
image: redis:7-alpine
|
|
45
|
-
ports:
|
|
46
|
-
- 6379:6379
|
|
47
55
|
options: >-
|
|
48
56
|
--health-cmd "redis-cli ping"
|
|
49
57
|
--health-interval 10s
|
|
@@ -52,12 +60,16 @@ jobs:
|
|
|
52
60
|
steps:
|
|
53
61
|
- name: Checkout code
|
|
54
62
|
uses: actions/checkout@v5
|
|
55
|
-
- name:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
63
|
+
- name: Install Bundler
|
|
64
|
+
# The Ruby 2.7 and older images ship Bundler 1.17 or 2.1; install the versions setup-ruby used.
|
|
65
|
+
run: |
|
|
66
|
+
case "$RUBY_VERSION" in
|
|
67
|
+
2.4.*|2.5.*) gem install bundler -v '~> 2.3.0' ;;
|
|
68
|
+
2.6.*|2.7.*) gem install bundler -v '~> 2.4.0' ;;
|
|
69
|
+
esac
|
|
70
|
+
- name: Install gems
|
|
71
|
+
working-directory: rollout-redis-adapter
|
|
72
|
+
run: bundle install --jobs 4
|
|
61
73
|
- name: Run Redis adapter tests
|
|
62
74
|
working-directory: rollout-redis-adapter
|
|
63
75
|
run: |
|
|
@@ -72,7 +84,9 @@ jobs:
|
|
|
72
84
|
retention-days: 30
|
|
73
85
|
|
|
74
86
|
active_record:
|
|
75
|
-
runs-on: ubuntu-
|
|
87
|
+
runs-on: ubuntu-26.04
|
|
88
|
+
# setup-ruby has no Ruby 3.1 or older builds for ubuntu-26.04, so use the official Ruby images.
|
|
89
|
+
container: ruby:${{ matrix.ruby-version }}
|
|
76
90
|
strategy:
|
|
77
91
|
fail-fast: false
|
|
78
92
|
matrix:
|
|
@@ -104,20 +118,18 @@ jobs:
|
|
|
104
118
|
env:
|
|
105
119
|
ACTIVERECORD_VERSION: ${{ matrix.activerecord }}
|
|
106
120
|
ROLLOUT_AR_ADAPTER: ${{ matrix.db }}
|
|
107
|
-
POSTGRES_HOST:
|
|
121
|
+
POSTGRES_HOST: postgres
|
|
108
122
|
POSTGRES_USER: postgres
|
|
109
123
|
POSTGRES_PASSWORD: postgres
|
|
110
124
|
POSTGRES_DB: rollout_test
|
|
111
|
-
MYSQL_HOST:
|
|
125
|
+
MYSQL_HOST: mysql
|
|
112
126
|
MYSQL_USER: root
|
|
113
127
|
MYSQL_PASSWORD: root
|
|
114
128
|
MYSQL_DATABASE: rollout_test
|
|
115
|
-
REDIS_HOST:
|
|
129
|
+
REDIS_HOST: redis
|
|
116
130
|
services:
|
|
117
131
|
redis:
|
|
118
132
|
image: redis:7-alpine
|
|
119
|
-
ports:
|
|
120
|
-
- 6379:6379
|
|
121
133
|
options: >-
|
|
122
134
|
--health-cmd "redis-cli ping"
|
|
123
135
|
--health-interval 10s
|
|
@@ -129,8 +141,6 @@ jobs:
|
|
|
129
141
|
POSTGRES_USER: postgres
|
|
130
142
|
POSTGRES_PASSWORD: postgres
|
|
131
143
|
POSTGRES_DB: rollout_test
|
|
132
|
-
ports:
|
|
133
|
-
- 5432:5432
|
|
134
144
|
options: >-
|
|
135
145
|
--health-cmd "pg_isready -U postgres"
|
|
136
146
|
--health-interval 10s
|
|
@@ -141,8 +151,6 @@ jobs:
|
|
|
141
151
|
env:
|
|
142
152
|
MYSQL_ROOT_PASSWORD: root
|
|
143
153
|
MYSQL_DATABASE: rollout_test
|
|
144
|
-
ports:
|
|
145
|
-
- 3306:3306
|
|
146
154
|
options: >-
|
|
147
155
|
--health-cmd "mysqladmin ping -h 127.0.0.1 -uroot -proot"
|
|
148
156
|
--health-interval 10s
|
|
@@ -151,15 +159,16 @@ jobs:
|
|
|
151
159
|
steps:
|
|
152
160
|
- name: Checkout code
|
|
153
161
|
uses: actions/checkout@v5
|
|
154
|
-
- name: Install
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
162
|
+
- name: Install Bundler
|
|
163
|
+
# The Ruby 2.7 and older images ship Bundler 1.17 or 2.1; install the versions setup-ruby used.
|
|
164
|
+
run: |
|
|
165
|
+
case "$RUBY_VERSION" in
|
|
166
|
+
2.4.*|2.5.*) gem install bundler -v '~> 2.3.0' ;;
|
|
167
|
+
2.6.*|2.7.*) gem install bundler -v '~> 2.4.0' ;;
|
|
168
|
+
esac
|
|
169
|
+
- name: Install gems
|
|
170
|
+
working-directory: rollout-active_record-adapter
|
|
171
|
+
run: bundle install --jobs 4
|
|
163
172
|
- name: Run Active Record adapter tests
|
|
164
173
|
working-directory: rollout-active_record-adapter
|
|
165
174
|
run: |
|
data/README.md
CHANGED
|
@@ -184,6 +184,63 @@ feature.to_hash
|
|
|
184
184
|
# => { percentage: 5.0, groups: [:caretakers], users: ["1"], data: {} }
|
|
185
185
|
```
|
|
186
186
|
|
|
187
|
+
## Defining features
|
|
188
|
+
|
|
189
|
+
Describe each feature in its own YAML file, named after the feature. Rollout
|
|
190
|
+
looks in `config/rollouts` under the current directory, which is your app's
|
|
191
|
+
root when you start it from there.
|
|
192
|
+
|
|
193
|
+
```text
|
|
194
|
+
config/rollouts/
|
|
195
|
+
├── chat.yml
|
|
196
|
+
└── new_inbox.yml
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Only `.yml` files directly in that folder are read. Subfolders, `.yaml` files
|
|
200
|
+
and files starting with a dot are ignored. The file name without `.yml` is the
|
|
201
|
+
feature name, the same one you pass to `$rollout.active?`.
|
|
202
|
+
|
|
203
|
+
```yaml
|
|
204
|
+
# config/rollouts/chat.yml
|
|
205
|
+
description: Let members chat in groups.
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
```yaml
|
|
209
|
+
# config/rollouts/new_inbox.yml
|
|
210
|
+
description: Show conversations as threads.
|
|
211
|
+
owner: messaging
|
|
212
|
+
release_date: "2026-11-15"
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
```ruby
|
|
216
|
+
Rollout.definitions.keys # => [:chat, :new_inbox]
|
|
217
|
+
Rollout.definitions[:new_inbox].description # => "Show conversations as threads."
|
|
218
|
+
Rollout.definitions[:new_inbox].path # => "/app/config/rollouts/new_inbox.yml"
|
|
219
|
+
Rollout.definitions[:new_inbox].attributes
|
|
220
|
+
# => { "description" => "Show conversations as threads.", "owner" => "messaging", "release_date" => "2026-11-15" }
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Definitions only describe features: they don't create or change stored
|
|
224
|
+
features, and features without a file work as before. Rollout reads
|
|
225
|
+
`description`. Every key, including your own like `owner`, is kept in
|
|
226
|
+
`attributes` with string keys for your application.
|
|
227
|
+
|
|
228
|
+
`Rollout.definitions` reads the folder on every call. A missing folder means no
|
|
229
|
+
definitions. `description` is optional.
|
|
230
|
+
|
|
231
|
+
Files are read with `YAML.safe_load`, so values can be strings, numbers,
|
|
232
|
+
booleans, `null`, lists and mappings. Quote dates and times
|
|
233
|
+
(`release_date: "2026-10-28"`). A file raises `Rollout::InvalidDefinition` if
|
|
234
|
+
it isn't valid YAML, isn't a mapping, uses anything else (an unquoted date, an
|
|
235
|
+
alias, a `:symbol`), or sets `description` to something other than a string.
|
|
236
|
+
|
|
237
|
+
Use another folder, or turn definitions off:
|
|
238
|
+
|
|
239
|
+
```ruby
|
|
240
|
+
Rollout.definitions_path = Rails.root.join("config", "features")
|
|
241
|
+
Rollout.definitions_path = nil
|
|
242
|
+
```
|
|
243
|
+
|
|
187
244
|
## Namespacing
|
|
188
245
|
|
|
189
246
|
Rollout separates its keys from other keys in the data store using the
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
class Rollout
|
|
6
|
+
class InvalidDefinition < ArgumentError
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class Definition
|
|
10
|
+
attr_reader :name, :path, :description, :attributes
|
|
11
|
+
|
|
12
|
+
def self.load(path)
|
|
13
|
+
attributes = YAML.safe_load(File.read(path))
|
|
14
|
+
raise InvalidDefinition, "#{path}: definition must be a mapping" unless attributes.is_a?(Hash)
|
|
15
|
+
|
|
16
|
+
new(name: File.basename(path, '.yml'), attributes: attributes, path: path)
|
|
17
|
+
rescue Psych::Exception => e
|
|
18
|
+
raise InvalidDefinition, "#{path}: #{e.message}"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize(name:, attributes: {}, path: nil)
|
|
22
|
+
@name = name.to_sym
|
|
23
|
+
@path = path
|
|
24
|
+
@attributes = attributes.freeze
|
|
25
|
+
@description = attributes['description']
|
|
26
|
+
return if @description.nil? || @description.is_a?(String)
|
|
27
|
+
|
|
28
|
+
raise InvalidDefinition, "#{path || name}: description must be a string"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/rollout/version.rb
CHANGED
data/lib/rollout.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'rollout/definition'
|
|
3
4
|
require 'rollout/feature'
|
|
4
5
|
require 'rollout/logging'
|
|
5
6
|
require 'rollout/version'
|
|
@@ -13,6 +14,28 @@ class Rollout
|
|
|
13
14
|
|
|
14
15
|
RAND_BASE = (2**32 - 1) / 100.0
|
|
15
16
|
|
|
17
|
+
class << self
|
|
18
|
+
attr_writer :definitions_path
|
|
19
|
+
|
|
20
|
+
def definitions_path
|
|
21
|
+
return @definitions_path if defined?(@definitions_path)
|
|
22
|
+
|
|
23
|
+
File.expand_path(File.join('config', 'rollouts'))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Reads the folder on every call, so new files show up without a restart.
|
|
27
|
+
def definitions
|
|
28
|
+
folder = definitions_path&.to_s
|
|
29
|
+
return {} unless folder && File.directory?(folder)
|
|
30
|
+
|
|
31
|
+
# Dir.entries rather than a glob, so brackets or asterisks in the folder's path are taken literally.
|
|
32
|
+
Dir.entries(folder).grep(/\A[^.].*\.yml\z/).sort.each_with_object({}) do |file, definitions|
|
|
33
|
+
definition = Definition.load(File.join(folder, file))
|
|
34
|
+
definitions[definition.name] = definition
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
16
39
|
attr_reader :options, :adapter
|
|
17
40
|
|
|
18
41
|
def initialize(adapter:, **options)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "tmpdir"
|
|
3
|
+
|
|
4
|
+
RSpec.describe "Rollout.definitions" do
|
|
5
|
+
around do |example|
|
|
6
|
+
Dir.mktmpdir do |dir|
|
|
7
|
+
@dir = dir
|
|
8
|
+
example.run
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
after do
|
|
13
|
+
Rollout.send(:remove_instance_variable, :@definitions_path) if Rollout.instance_variable_defined?(:@definitions_path)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def write(name, content)
|
|
17
|
+
File.write(File.join(@dir, name), content)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "loads each .yml file directly in the folder, named after the file" do
|
|
21
|
+
write("chat.yml", "description: Group chat.\ncache: user\n")
|
|
22
|
+
write("notes.yaml", "description: Not loaded.\n")
|
|
23
|
+
FileUtils.mkdir_p(File.join(@dir, "drafts"))
|
|
24
|
+
write("drafts/inbox.yml", "description: Not loaded.\n")
|
|
25
|
+
Rollout.definitions_path = @dir
|
|
26
|
+
|
|
27
|
+
chat = Rollout.definitions.fetch(:chat)
|
|
28
|
+
|
|
29
|
+
expect(Rollout.definitions.keys).to eq [:chat]
|
|
30
|
+
expect(chat.description).to eq "Group chat."
|
|
31
|
+
expect(chat.attributes).to eq("description" => "Group chat.", "cache" => "user")
|
|
32
|
+
expect(chat.path).to eq File.join(@dir, "chat.yml")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "defaults to config/rollouts under the current directory" do
|
|
36
|
+
folder = File.join(@dir, "config", "rollouts")
|
|
37
|
+
FileUtils.mkdir_p(folder)
|
|
38
|
+
File.write(File.join(folder, "chat.yml"), "description: Group chat.\n")
|
|
39
|
+
|
|
40
|
+
Dir.chdir(@dir) do
|
|
41
|
+
expect(Rollout.definitions.keys).to eq [:chat]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "picks up a file added after an earlier call" do
|
|
46
|
+
Rollout.definitions_path = @dir
|
|
47
|
+
expect(Rollout.definitions).to eq({})
|
|
48
|
+
|
|
49
|
+
write("chat.yml", "description: Group chat.\n")
|
|
50
|
+
expect(Rollout.definitions.keys).to eq [:chat]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "takes brackets in the folder's path literally" do
|
|
54
|
+
folder = File.join(@dir, "rollouts[1]")
|
|
55
|
+
FileUtils.mkdir_p(folder)
|
|
56
|
+
File.write(File.join(folder, "chat.yml"), "description: Group chat.\n")
|
|
57
|
+
Rollout.definitions_path = folder
|
|
58
|
+
|
|
59
|
+
expect(Rollout.definitions.keys).to eq [:chat]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "has no definitions when the folder is missing or the path is nil" do
|
|
63
|
+
Rollout.definitions_path = File.join(@dir, "missing")
|
|
64
|
+
expect(Rollout.definitions).to eq({})
|
|
65
|
+
|
|
66
|
+
Rollout.definitions_path = nil
|
|
67
|
+
expect(Rollout.definitions).to eq({})
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "names the file that is not valid YAML" do
|
|
71
|
+
write("chat.yml", "description: [unclosed\n")
|
|
72
|
+
Rollout.definitions_path = @dir
|
|
73
|
+
|
|
74
|
+
expect { Rollout.definitions }.to raise_error(Rollout::InvalidDefinition, /chat\.yml/)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "rejects a description that is not a string" do
|
|
78
|
+
write("chat.yml", "description: 42\n")
|
|
79
|
+
Rollout.definitions_path = @dir
|
|
80
|
+
|
|
81
|
+
expect { Rollout.definitions }.to raise_error(Rollout::InvalidDefinition, /description must be a string/)
|
|
82
|
+
end
|
|
83
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rollout
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- James Golick
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: observer
|
|
@@ -142,11 +142,13 @@ files:
|
|
|
142
142
|
- Rakefile
|
|
143
143
|
- docs/upgrading-to-v3.md
|
|
144
144
|
- lib/rollout.rb
|
|
145
|
+
- lib/rollout/definition.rb
|
|
145
146
|
- lib/rollout/feature.rb
|
|
146
147
|
- lib/rollout/feature_state.rb
|
|
147
148
|
- lib/rollout/logging.rb
|
|
148
149
|
- lib/rollout/version.rb
|
|
149
150
|
- rollout.gemspec
|
|
151
|
+
- spec/rollout/definition_spec.rb
|
|
150
152
|
- spec/rollout/feature_spec.rb
|
|
151
153
|
- spec/rollout/feature_state_spec.rb
|
|
152
154
|
- spec/rollout/logging_spec.rb
|