Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 52 additions & 27 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,50 +3,75 @@ name: Checks
on:
push:
branches:
- "**"
- main
pull_request:
types:
- opened
- synchronize # when a PR is updated
- reopened # when a PR is reopened
- ready_for_review # when a PR is ready for review
- review_requested # when a PR is requested for review

permissions:
contents: read

concurrency:
group: checks-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
ci:
base-sdk:
name: Base SDK on Ruby ${{ matrix.ruby-version }}
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
ruby-version: ["3.0", "3.4"]
env:
BUNDLE_GEMFILE: gemfiles/base.gemfile

steps:
- name: Check out
uses: actions/checkout@v4
- name: Check out repository
uses: actions/checkout@v6

- name: Set up Ruby ${{ matrix.ruby-version }}
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby-version }}
bundler-cache: true

- name: Install
run: |
make install
- name: Test base SDK without OpenFeature dependencies
run: bundle exec rspec spec/ --exclude-pattern spec/openfeature_provider_spec.rb

- name: Build
run: |
make build
full:
name: Full SDK and provider
runs-on: ubuntu-latest
timeout-minutes: 20

- name: Test
run: |
make test
steps:
- name: Check out repository
uses: actions/checkout@v6

- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: "3.4"
bundler-cache: true

- name: Test both packages
run: make test

- name: Build and verify both gem artifacts
run: make build

- uses: actions/setup-node@v4
- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
node-version-file: .nvmrc

- name: Setup Featurevisor example-1 project
- name: Set up Featurevisor example-1 project
run: |
mkdir example-1
(cd example-1 && npx @featurevisor/cli@2.x init --example=1)
(cd example-1 && npm install)
(cd example-1 && npx featurevisor test)
cd example-1
npx --yes @featurevisor/cli@3 init --example=1
npm install
npx featurevisor build
npx featurevisor test --onlyFailures

- name: Run Featurevisor project tests against Ruby SDK
run: ./bin/featurevisor test --projectDirectoryPath=./example-1
run: bundle exec ruby bin/featurevisor test --projectDirectoryPath=example-1 --onlyFailures
49 changes: 34 additions & 15 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,51 @@ on:
tags:
- "v*.*.*"

permissions:
contents: read

concurrency:
group: publish-${{ github.ref }}
cancel-in-progress: false

jobs:
publish:
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- name: Check out
uses: actions/checkout@v4
- name: Check out repository
uses: actions/checkout@v6

- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: "3.4"
bundler-cache: true

- name: Configure RubyGems credentials
run: |
mkdir -p ~/.gem
echo ":rubygems_api_key: $RUBYGEMS_API_KEY" > ~/.gem/credentials
chmod 0600 ~/.gem/credentials
env:
RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
- name: Test both packages
run: make test

- name: Build
run: |
make build
ls -l *.gem
- name: Build and verify both gem artifacts
run: make build

- name: Push to RubyGems
- name: Verify tag matches package version
id: version
shell: bash
run: |
gem push *.gem
version="$(bundle exec ruby -Ilib -rfeaturevisor/version -e 'print Featurevisor::VERSION')"
if [ "$GITHUB_REF_NAME" != "v$version" ]; then
echo "Tag $GITHUB_REF_NAME does not match gem version $version" >&2
exit 1
fi
echo "version=$version" >> "$GITHUB_OUTPUT"

- name: Publish Featurevisor SDK
env:
GEM_HOST_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
run: gem push "featurevisor-${{ steps.version.outputs.version }}.gem"

- name: Publish Featurevisor OpenFeature provider
env:
GEM_HOST_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
run: gem push "featurevisor-openfeature-${{ steps.version.outputs.version }}.gem"
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
20
24
6 changes: 4 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
source "https://rubygems.org"

# Specify your gem's dependencies in featurevisor.gemspec
gemspec
gemspec name: "featurevisor"

# Needed only when developing and testing the separately packaged provider.
gem "openfeature-sdk", "~> 0.6.5"
6 changes: 4 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
PATH
remote: .
specs:
featurevisor (1.0.0)
benchmark
featurevisor (1.1.0)
benchmark (>= 0, < 1)

GEM
remote: https://rubygems.org/
specs:
benchmark (0.4.0)
diff-lcs (1.6.2)
openfeature-sdk (0.6.5)
rake (13.3.0)
rspec (3.13.1)
rspec-core (~> 3.13.0)
Expand All @@ -30,6 +31,7 @@ PLATFORMS

DEPENDENCIES
featurevisor!
openfeature-sdk (~> 0.6.5)
rake (~> 13.0)
rspec (~> 3.12)

Expand Down
10 changes: 8 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
.PHONY: install build test test-example-1 setup-monorepo update-monorepo
.PHONY: install build verify-artifacts test test-base test-example-1 setup-monorepo update-monorepo

install:
bundle install

build:
gem build featurevisor.gemspec
bundle exec rake build

verify-artifacts:
bundle exec ruby scripts/verify_gems.rb

test:
bundle exec rspec spec/

test-base:
BUNDLE_GEMFILE=gemfiles/base.gemfile bundle exec rspec spec/ --exclude-pattern spec/openfeature_provider_spec.rb

test-example-1:
bundle exec rspec spec/
bundle exec ruby bin/featurevisor test --projectDirectoryPath=../featurevisor/examples/example-1 --onlyFailures
Expand Down
83 changes: 77 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ This SDK is compatible with Featurevisor v3 projects and v2 datafiles.
- [Registering modules](#registering-modules)
- [Child instance](#child-instance)
- [Close](#close)
- [OpenFeature](#openfeature)
- [CLI usage](#cli-usage)
- [Test](#test)
- [Test against local monorepo's example-1](#test-against-local-monorepos-example-1)
Expand Down Expand Up @@ -778,7 +779,7 @@ All three commands accept repeatable `--target=<target>` options. `test` builds

```bash
$ cd /absolute/path/to/featurevisor-ruby
$ bundle exec ruby bin/featurevisor test --projectDirectoryPath=/Users/fahad/Projects/featurevisor/featurevisor/examples/example-1 --onlyFailures
$ bundle exec ruby bin/featurevisor test --projectDirectoryPath=../featurevisor/examples/example-1 --onlyFailures
$ make test-example-1
```

Expand Down Expand Up @@ -811,30 +812,100 @@ $ bundle exec featurevisor assess-distribution \
--n=1000
```

## OpenFeature

The OpenFeature provider is published as a separate gem. This keeps OpenFeature code and dependencies out of applications that only use the Featurevisor SDK.

The provider currently requires Ruby 3.4 or newer because that is the minimum version supported by the official OpenFeature Ruby SDK.

Install the provider:

```ruby
gem "featurevisor-openfeature", "~> 1.1"
```

It installs the matching `featurevisor` gem and the official `openfeature-sdk` dependency. The provider and base SDK deliberately share the same version, and the provider requires that exact Featurevisor version.

```ruby
require "featurevisor/openfeature_provider"

provider = Featurevisor::OpenFeatureProvider.new(
datafile: datafile_content,
)

OpenFeature::SDK.configure do |config|
config.set_provider_and_wait(provider)
end

client = OpenFeature::SDK.build_client
enabled = client.fetch_boolean_value(
flag_key: "checkout",
default_value: false,
evaluation_context: OpenFeature::SDK::EvaluationContext.new(targeting_key: "user-123"),
)
```

Use `checkout` for a flag, `checkout:variation` for its variation, and `checkout:title` for its `title` variable. Boolean variables use the boolean resolver. Arrays, hashes, and JSON variables use the object resolver.

OpenFeature's targeting key maps to `userId` by default. `targeting_key_field`, `key_separator`, and `variation_key` can customize the mapping.

You can pass any Featurevisor initialization options directly to the provider. These options are used to create the Featurevisor instance owned by the provider:

```ruby
provider = Featurevisor::OpenFeatureProvider.new(
datafile: datafile_content,
targeting_key_field: "accountId",
key_separator: "/",
variation_key: "$variation",
)
```

You can also reuse an existing Featurevisor instance:

```ruby
featurevisor = Featurevisor.create_featurevisor(datafile: datafile_content)
provider = Featurevisor::OpenFeatureProvider.new(featurevisor: featurevisor)
```

The caller owns an instance passed this way. Provider shutdown does not close it. Call `featurevisor.close` when every consumer is finished with it. When the provider creates the instance from options, the provider owns and closes it. If both are supplied, `featurevisor` takes precedence over the options hash.

See the [OpenFeature provider guide](https://featurevisor.com/docs/sdks/openfeature/) for resolution reasons, errors, metadata, tracking, lifecycle, and providers for other languages.

<!-- FEATUREVISOR_DOCS_END -->

## Development

### Setting up

After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
After checking out the repository, run `make install` to install dependencies.

### Running tests

```bash
$ bundle exec rspec
$ make test-base
$ make test-example-1
```

To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
Build and verify both gems with:

```bash
$ make build
```

The build produces `featurevisor-VERSION.gem` and `featurevisor-openfeature-VERSION.gem`. The artifact verifier confirms that OpenFeature code and dependencies are absent from the base gem and present only in the provider gem.

### Releasing

- Update version in `lib/featurevisor/version.rb`
- Update the shared version in `lib/featurevisor/version.rb`
- Run `bundle install`
- Push commit to `main` branch
- Wait for CI to complete
- Tag the release with the version number
- This will trigger a new release to [RubyGems](https://rubygems.org/gems/featurevisor)
- Tag the release with the same version number, for example `v1.1.0`
- The workflow verifies that the tag matches the shared version
- The workflow publishes `featurevisor` first, followed by `featurevisor-openfeature`

The gems are separate RubyGems packages built from the same repository. Publishing the base gem first ensures the provider's exact Featurevisor dependency is available when the provider is published. If the second push fails, rerun or retry the provider publication without republishing the existing base version.

## License

Expand Down
11 changes: 8 additions & 3 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
require "rspec/core/rake_task"
require "fileutils"
require_relative "lib/featurevisor/version"

RSpec::Core::RakeTask.new(:spec)

task default: :spec

desc "Build the gem"
desc "Build and verify both gems"
task :build do
system "gem build featurevisor.gemspec"
sh "gem build featurevisor.gemspec"
sh "gem build featurevisor-openfeature.gemspec"
sh "ruby scripts/verify_gems.rb"
end

desc "Install the gem locally"
task :install => :build do
system "gem install featurevisor-*.gem"
sh "gem install featurevisor-#{Featurevisor::VERSION}.gem"
sh "gem install featurevisor-openfeature-#{Featurevisor::VERSION}.gem"
end

desc "Clean up built gems"
Expand Down
Loading