featurevisor 0.3.0 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 42c8ec3e5bf6293eeae189365b0383588a8cf7fdd5a5ee249a6fd44a2676403c
4
- data.tar.gz: ce5794865b3dd5a057b9e0841814dab1c276b52237f230e23da6317f8dea3f70
3
+ metadata.gz: 202758e9fe9a2c81f67c3ea7456be5d068c1d9aa8ccf35f721a4ca447a94196a
4
+ data.tar.gz: bf2cae186ce5b6d3b483f4eab14768553cc5d2e09148c16ebbaf3ac60af7d7a0
5
5
  SHA512:
6
- metadata.gz: 41aefa056806a97bc1f5bf13d847007fd4b38fb176f97d1a09868434644fda2ed8cd58698ad9f5002a6ef5d30f7fc105accf7d505eb41fb71abe28a5bda6e47c
7
- data.tar.gz: e4376d46699b8fe730d4dc4f73890fd43c6e879c446bc484dc6a47cc6b2c31f715af41b83b5f6189f9b8eeed3fd61b48213ec589855d909fe7ebccb702c04303
6
+ metadata.gz: aa565646ae2e3b54d018044a79e92d9172e0ac8478dbb2dbee1699605a9457fbea6b694dc42c9027b40d4d2a3358c3146be946cfe307319119ab6ba2d03657ed
7
+ data.tar.gz: e8e689efcc96ac4ca3bd0ec7469895fbb1c9b4e70737d0f56f87fa9f0733395c0e0de6938a7b6c7c4c6bd7daef9f9818daa9163e508b0242836b750aa83e82f4
data/README.md CHANGED
@@ -1,12 +1,13 @@
1
1
  # Featurevisor Ruby SDK <!-- omit in toc -->
2
2
 
3
- This is a port of Featurevisor [JavaScript SDK](https://featurevisor.com/docs/sdks/javascript/) v2.x to Ruby, providing a way to evaluate feature flags, variations, and variables in your Ruby applications.
3
+ This is a port of Featurevisor [JavaScript SDK](https://featurevisor.com/docs/sdks/javascript/) v3.x to Ruby, providing a way to evaluate feature flags, variations, and variables in your Ruby applications.
4
4
 
5
- This SDK is compatible with [Featurevisor](https://featurevisor.com/) v2.0 projects and above.
5
+ This SDK is compatible with Featurevisor v3 projects and v2 datafiles.
6
6
 
7
7
  ## Table of contents <!-- omit in toc -->
8
8
 
9
9
  - [Installation](#installation)
10
+ - [Public API](#public-api)
10
11
  - [Initialization](#initialization)
11
12
  - [Evaluation types](#evaluation-types)
12
13
  - [Context](#context)
@@ -23,22 +24,26 @@ This SDK is compatible with [Featurevisor](https://featurevisor.com/) v2.0 proje
23
24
  - [Initialize with sticky](#initialize-with-sticky)
24
25
  - [Set sticky afterwards](#set-sticky-afterwards)
25
26
  - [Setting datafile](#setting-datafile)
27
+ - [Merging by default](#merging-by-default)
28
+ - [Replacing](#replacing)
29
+ - [Loading datafiles on demand](#loading-datafiles-on-demand)
26
30
  - [Updating datafile](#updating-datafile)
27
31
  - [Interval-based update](#interval-based-update)
28
- - [Logging](#logging)
32
+ - [Evaluation details](#evaluation-details)
33
+ - [Diagnostics](#diagnostics)
29
34
  - [Levels](#levels)
30
- - [Customizing levels](#customizing-levels)
31
35
  - [Handler](#handler)
32
36
  - [Events](#events)
33
37
  - [`datafile_set`](#datafile_set)
34
38
  - [`context_set`](#context_set)
35
39
  - [`sticky_set`](#sticky_set)
36
- - [Evaluation details](#evaluation-details)
37
- - [Hooks](#hooks)
38
- - [Defining a hook](#defining-a-hook)
39
- - [Registering hooks](#registering-hooks)
40
+ - [`error`](#error)
41
+ - [Modules](#modules)
42
+ - [Defining a module](#defining-a-module)
43
+ - [Registering modules](#registering-modules)
40
44
  - [Child instance](#child-instance)
41
45
  - [Close](#close)
46
+ - [OpenFeature](#openfeature)
42
47
  - [CLI usage](#cli-usage)
43
48
  - [Test](#test)
44
49
  - [Test against local monorepo's example-1](#test-against-local-monorepos-example-1)
@@ -72,6 +77,18 @@ Or install it yourself as:
72
77
  $ gem install featurevisor
73
78
  ```
74
79
 
80
+ ## Public API
81
+
82
+ The main runtime API is `Featurevisor.create_featurevisor`:
83
+
84
+ ```ruby
85
+ f = Featurevisor.create_featurevisor(
86
+ datafile: datafile_content
87
+ )
88
+ ```
89
+
90
+ Most applications only need this factory and the returned `Featurevisor::Instance`. Public extension and observability APIs include modules, diagnostics, events, and the datafile structures accepted by the factory.
91
+
75
92
  ## Initialization
76
93
 
77
94
  The SDK can be initialized by passing [datafile](https://featurevisor.com/docs/building-datafiles/) content directly:
@@ -89,7 +106,7 @@ response = Net::HTTP.get_response(URI(datafile_url))
89
106
  datafile_content = JSON.parse(response.body, symbolize_names: true)
90
107
 
91
108
  # Create SDK instance
92
- f = Featurevisor.create_instance(
109
+ f = Featurevisor.create_featurevisor(
93
110
  datafile: datafile_content
94
111
  )
95
112
  ```
@@ -101,10 +118,10 @@ Alternatively, you can pass a JSON string directly and the SDK will parse it aut
101
118
  ```ruby
102
119
  # Option 1: Parse JSON yourself (recommended)
103
120
  datafile_content = JSON.parse(json_string, symbolize_names: true)
104
- f = Featurevisor.create_instance(datafile: datafile_content)
121
+ f = Featurevisor.create_featurevisor(datafile: datafile_content)
105
122
 
106
123
  # Option 2: Pass JSON string directly (automatic parsing)
107
- f = Featurevisor.create_instance(datafile: json_string)
124
+ f = Featurevisor.create_featurevisor(datafile: json_string)
108
125
  ```
109
126
 
110
127
  ## Evaluation types
@@ -142,7 +159,7 @@ You can set context at the time of initialization:
142
159
  ```ruby
143
160
  require 'featurevisor'
144
161
 
145
- f = Featurevisor.create_instance(
162
+ f = Featurevisor.create_featurevisor(
146
163
  context: {
147
164
  deviceId: '123',
148
165
  country: 'nl'
@@ -274,6 +291,8 @@ f.get_variable_object(feature_key, variable_key, context = {})
274
291
  f.get_variable_json(feature_key, variable_key, context = {})
275
292
  ```
276
293
 
294
+ Type specific methods do not coerce values. `get_variable_integer` returns `nil` for the string `"1"`, and boolean getters return `nil` for non-boolean values.
295
+
277
296
  ## Getting all evaluations
278
297
 
279
298
  You can get evaluations of all features available in the SDK instance:
@@ -304,12 +323,14 @@ This is handy especially when you want to pass all evaluations from a backend ap
304
323
 
305
324
  For the lifecycle of the SDK instance in your application, you can set some features with sticky values, meaning that they will not be evaluated against the fetched [datafile](https://featurevisor.com/docs/building-datafiles/):
306
325
 
326
+ Sticky values belong to an SDK or child instance. Evaluation options do not accept sticky overrides; use `spawn(context, sticky: ...)` when a child needs its own sticky state.
327
+
307
328
  ### Initialize with sticky
308
329
 
309
330
  ```ruby
310
331
  require 'featurevisor'
311
332
 
312
- f = Featurevisor.create_instance(
333
+ f = Featurevisor.create_featurevisor(
313
334
  sticky: {
314
335
  myFeatureKey: {
315
336
  enabled: true,
@@ -362,6 +383,49 @@ f.set_datafile(json_string)
362
383
 
363
384
  **Important**: When calling `set_datafile()`, ensure JSON is parsed with `symbolize_names: true` if you're parsing it yourself.
364
385
 
386
+ ### Merging by default
387
+
388
+ By default, `set_datafile(datafile)` merges the incoming datafile into the SDK's current datafile:
389
+
390
+ - top-level metadata such as `schemaVersion`, `revision`, and `featurevisorVersion` comes from the incoming datafile
391
+ - `segments` are merged, with incoming entries overriding existing ones
392
+ - `features` are merged, with incoming entries overriding existing ones
393
+
394
+ This means you can call `set_datafile` more than once with different datafiles, and the SDK instance accumulates their features and segments together.
395
+
396
+ ### Replacing
397
+
398
+ To fully replace the stored datafile, pass `true` as the second argument:
399
+
400
+ ```ruby
401
+ f.set_datafile(datafile_content, true)
402
+ ```
403
+
404
+ ### Loading datafiles on demand
405
+
406
+ Because merging is the default, a single SDK instance can start with a small datafile and load more datafiles later as your application needs them, instead of downloading every feature upfront.
407
+
408
+ This pairs well with [targets](https://featurevisor.com/docs/targets/), where each target produces a smaller datafile for a specific part of your application:
409
+
410
+ ```ruby
411
+ require "open-uri"
412
+
413
+ f = Featurevisor.create_featurevisor({})
414
+
415
+ def load_datafile(f, target)
416
+ url = "https://cdn.yoursite.com/production/featurevisor-#{target}.json"
417
+ datafile = JSON.parse(URI.open(url).read, symbolize_names: true)
418
+
419
+ # merges into whatever was loaded before
420
+ f.set_datafile(datafile)
421
+ end
422
+
423
+ load_datafile(f, "products")
424
+
425
+ # later, when the user reaches checkout
426
+ load_datafile(f, "checkout")
427
+ ```
428
+
365
429
  ### Updating datafile
366
430
 
367
431
  You can set the datafile as many times as you want in your application, which will result in emitting a [`datafile_set`](#datafile_set) event that you can listen and react to accordingly.
@@ -400,65 +464,38 @@ end
400
464
  Thread.new { update_datafile(f, datafile_url) }
401
465
  ```
402
466
 
403
- ## Logging
467
+ ## Diagnostics
404
468
 
405
- By default, Featurevisor SDKs will print out logs to the console for `info` level and above.
469
+ By default, Featurevisor reports diagnostics to the console for `info` level and above with a `[Featurevisor]` prefix.
406
470
 
407
471
  ### Levels
408
472
 
409
- These are all the available log levels:
410
-
411
- - `error`
412
- - `warn`
413
- - `info`
414
- - `debug`
415
-
416
- ### Customizing levels
473
+ Available diagnostic levels are `fatal`, `error`, `warn`, `info`, and `debug`.
417
474
 
418
- If you choose `debug` level to make the logs more verbose, you can set it at the time of SDK initialization.
419
-
420
- Setting `debug` level will print out all logs, including `info`, `warn`, and `error` levels.
475
+ Set the level during initialization or update it afterwards:
421
476
 
422
477
  ```ruby
423
- require 'featurevisor'
424
-
425
- f = Featurevisor.create_instance(
426
- logger: Featurevisor.create_logger(level: 'debug')
427
- )
428
- ```
429
-
430
- Alternatively, you can also set `log_level` directly:
431
-
432
- ```ruby
433
- f = Featurevisor.create_instance(
434
- log_level: 'debug'
435
- )
436
- ```
437
-
438
- You can also set log level from SDK instance afterwards:
439
-
440
- ```ruby
441
- f.set_log_level('debug')
478
+ f = Featurevisor.create_featurevisor(log_level: "debug")
479
+ f.set_log_level("info")
442
480
  ```
443
481
 
444
482
  ### Handler
445
483
 
446
- You can also pass your own log handler, if you do not wish to print the logs to the console:
484
+ Use `on_diagnostic` to send structured diagnostics to your observability system:
447
485
 
448
486
  ```ruby
449
- require 'featurevisor'
450
-
451
- f = Featurevisor.create_instance(
452
- logger: Featurevisor.create_logger(
453
- level: 'info',
454
- handler: ->(level, message, details) {
455
- # do something with the log
456
- }
457
- )
487
+ f = Featurevisor.create_featurevisor(
488
+ log_level: "info",
489
+ on_diagnostic: ->(diagnostic) {
490
+ puts "#{diagnostic[:level]} #{diagnostic[:code]} #{diagnostic[:message]}"
491
+ }
458
492
  )
459
493
  ```
460
494
 
461
- Further log levels like `info` and `debug` will help you understand how the feature variations and variables are evaluated in the runtime against given context.
495
+ Every diagnostic has `:level`, `:code`, `:message`, and an object-shaped `:details` hash. Optional `:module`, `:moduleName`, and `:originalError` fields describe provenance. Evaluation metadata belongs in `:details`.
496
+
497
+ Diagnostic handlers are isolated from SDK behavior. An exception in a handler does not stop other handlers or evaluations.
498
+
462
499
 
463
500
  ## Events
464
501
 
@@ -471,8 +508,8 @@ You can listen to these events that can occur at various stages in your applicat
471
508
  ```ruby
472
509
  unsubscribe = f.on('datafile_set') do |event|
473
510
  revision = event[:revision] # new revision
474
- previous_revision = event[:previous_revision]
475
- revision_changed = event[:revision_changed] # true if revision has changed
511
+ previous_revision = event[:previousRevision]
512
+ revision_changed = event[:revisionChanged] # true if revision has changed
476
513
 
477
514
  # list of feature keys that have new updates,
478
515
  # and you should re-evaluate them
@@ -493,6 +530,8 @@ The `features` array will contain keys of features that have either been:
493
530
 
494
531
  compared to the previous datafile content that existed in the SDK instance.
495
532
 
533
+ The event also includes `replaced`, which is `true` when the datafile replaced the previous content instead of merging into it.
534
+
496
535
  ### `context_set`
497
536
 
498
537
  ```ruby
@@ -515,6 +554,20 @@ unsubscribe = f.on('sticky_set') do |event|
515
554
  end
516
555
  ```
517
556
 
557
+ ### `error`
558
+
559
+ ```ruby
560
+ unsubscribe = f.on('error') do |event|
561
+ diagnostic = event[:diagnostic]
562
+ code = diagnostic[:code]
563
+ message = diagnostic[:message]
564
+
565
+ puts "Featurevisor error: #{code} #{message}"
566
+ end
567
+ ```
568
+
569
+ The `error` event is emitted for diagnostics reported with `level: "error"`.
570
+
518
571
  ## Evaluation details
519
572
 
520
573
  Besides logging with debug level enabled, you can also get more details about how the feature variations and variables are evaluated in the runtime against given context:
@@ -547,22 +600,33 @@ And optionally these properties depending on whether you are evaluating a featur
547
600
  - `variable_value`: the variable value
548
601
  - `variable_schema`: the variable schema
549
602
 
550
- ## Hooks
603
+ ## Modules
551
604
 
552
- Hooks allow you to intercept the evaluation process and customize it further as per your needs.
605
+ Modules allow you to intercept the evaluation process and customize SDK behavior.
553
606
 
554
- ### Defining a hook
607
+ ### Defining a module
555
608
 
556
- A hook is a simple hash with a unique required `name` and optional functions:
609
+ A module is a simple hash with a unique recommended `name` and optional lifecycle functions:
610
+
611
+ If `setup` raises an exception, the module is not registered. Featurevisor removes subscriptions created during setup, reports `module_setup_error`, and calls `close` when present.
557
612
 
558
613
  ```ruby
559
614
  require 'featurevisor'
560
615
 
561
- my_custom_hook = {
562
- # only required property
563
- name: 'my-custom-hook',
616
+ my_custom_module = {
617
+ # recommended, and used for duplicate detection/removal
618
+ name: 'my-custom-module',
619
+
620
+ # rest of the properties below are all optional per module
564
621
 
565
- # rest of the properties below are all optional per hook
622
+ # setup once, when the module is registered
623
+ setup: ->(api) {
624
+ revision = api[:get_revision].call
625
+
626
+ api[:on_diagnostic].call(->(diagnostic) {
627
+ puts diagnostic[:message]
628
+ })
629
+ },
566
630
 
567
631
  # before evaluation
568
632
  before: ->(options) {
@@ -592,26 +656,43 @@ my_custom_hook = {
592
656
  bucket_value: ->(options) {
593
657
  # return custom bucket value
594
658
  options[:bucket_value]
659
+ },
660
+
661
+ # cleanup when module is removed or SDK is closed
662
+ close: -> {
663
+ # cleanup here
595
664
  }
596
665
  }
597
666
  ```
598
667
 
599
- ### Registering hooks
668
+ The module API passed to `setup` exposes:
600
669
 
601
- You can register hooks at the time of SDK initialization:
670
+ - `get_revision`
671
+ - `on_diagnostic`
672
+ - `report_diagnostic`
673
+
674
+ ### Registering modules
675
+
676
+ You can register modules at the time of SDK initialization:
602
677
 
603
678
  ```ruby
604
679
  require 'featurevisor'
605
680
 
606
- f = Featurevisor.create_instance(
607
- hooks: [my_custom_hook]
681
+ f = Featurevisor.create_featurevisor(
682
+ modules: [my_custom_module]
608
683
  )
609
684
  ```
610
685
 
611
686
  Or after initialization:
612
687
 
613
688
  ```ruby
614
- f.add_hook(my_custom_hook)
689
+ remove_module = f.add_module(my_custom_module)
690
+
691
+ # remove later by calling the returned function
692
+ remove_module.call
693
+
694
+ # or remove by name
695
+ f.remove_module('my-custom-module')
615
696
  ```
616
697
 
617
698
  ## Child instance
@@ -687,29 +768,24 @@ $ bundle exec featurevisor test \
687
768
  --quiet|--verbose \
688
769
  --onlyFailures \
689
770
  --keyPattern="myFeatureKey" \
690
- --assertionPattern="#1" \
691
- --with-scopes \
692
- --with-tags
771
+ --assertionPattern="#1"
693
772
  ```
694
773
 
695
- `--with-scopes` and `--with-tags` make the Ruby test runner build scoped/tagged datafiles in memory (via `npx featurevisor build --json`) and evaluate matching assertions against those exact datafiles.
774
+ The Ruby test runner builds base datafiles and Target datafiles in memory via `npx featurevisor build --json`. When an assertion contains `target`, it is evaluated against the matching Target datafile.
696
775
 
697
- If an assertion references `scope` and `--with-scopes` is not provided, the runner still evaluates the assertion by merging that scope's configured context into the assertion context (without building scoped datafiles).
698
-
699
- For compatibility, camelCase aliases are also supported: `--withScopes` and `--withTags`.
776
+ All three commands accept repeatable `--target=<target>` options. `test` builds only the selected Target datafiles and runs untargeted assertions plus assertions for those targets. `benchmark` and `assess-distribution` run independently against every selected Target datafile. Without `--target`, existing project-wide behavior is preserved. Project definitions, test specs, Target discovery, and datafile generation continue to come from the Node.js CLI.
700
777
 
701
778
  ### Test against local monorepo's example-1
702
779
 
703
780
  ```bash
704
781
  $ cd /absolute/path/to/featurevisor-ruby
705
- $ bundle exec featurevisor test --projectDirectoryPath=./monorepo/examples/example-1
706
- $ bundle exec featurevisor test --projectDirectoryPath=./monorepo/examples/example-1 --with-scopes
707
- $ bundle exec featurevisor test --projectDirectoryPath=./monorepo/examples/example-1 --with-tags
782
+ $ bundle exec ruby bin/featurevisor test --projectDirectoryPath=../featurevisor/examples/example-1 --onlyFailures
783
+ $ make test-example-1
708
784
  ```
709
785
 
710
786
  ### Benchmark
711
787
 
712
- Learn more about benchmarking [here](https://featurevisor.com/docs/cmd/#benchmarking).
788
+ Learn more about benchmarking [here](https://featurevisor.com/docs/cli/#benchmarking).
713
789
 
714
790
  ```bash
715
791
  $ bundle exec featurevisor benchmark \
@@ -722,7 +798,7 @@ $ bundle exec featurevisor benchmark \
722
798
 
723
799
  ### Assess distribution
724
800
 
725
- Learn more about assessing distribution [here](https://featurevisor.com/docs/cmd/#assess-distribution).
801
+ Learn more about assessing distribution [here](https://featurevisor.com/docs/cli/#assess-distribution).
726
802
 
727
803
  ```bash
728
804
  $ bundle exec featurevisor assess-distribution \
@@ -736,30 +812,100 @@ $ bundle exec featurevisor assess-distribution \
736
812
  --n=1000
737
813
  ```
738
814
 
815
+ ## OpenFeature
816
+
817
+ The OpenFeature provider is published as a separate gem. This keeps OpenFeature code and dependencies out of applications that only use the Featurevisor SDK.
818
+
819
+ The provider currently requires Ruby 3.4 or newer because that is the minimum version supported by the official OpenFeature Ruby SDK.
820
+
821
+ Install the provider:
822
+
823
+ ```ruby
824
+ gem "featurevisor-openfeature", "~> 1.1"
825
+ ```
826
+
827
+ 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.
828
+
829
+ ```ruby
830
+ require "featurevisor/openfeature_provider"
831
+
832
+ provider = Featurevisor::OpenFeatureProvider.new(
833
+ datafile: datafile_content,
834
+ )
835
+
836
+ OpenFeature::SDK.configure do |config|
837
+ config.set_provider_and_wait(provider)
838
+ end
839
+
840
+ client = OpenFeature::SDK.build_client
841
+ enabled = client.fetch_boolean_value(
842
+ flag_key: "checkout",
843
+ default_value: false,
844
+ evaluation_context: OpenFeature::SDK::EvaluationContext.new(targeting_key: "user-123"),
845
+ )
846
+ ```
847
+
848
+ 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.
849
+
850
+ OpenFeature's targeting key maps to `userId` by default. `targeting_key_field`, `key_separator`, and `variation_key` can customize the mapping.
851
+
852
+ You can pass any Featurevisor initialization options directly to the provider. These options are used to create the Featurevisor instance owned by the provider:
853
+
854
+ ```ruby
855
+ provider = Featurevisor::OpenFeatureProvider.new(
856
+ datafile: datafile_content,
857
+ targeting_key_field: "accountId",
858
+ key_separator: "/",
859
+ variation_key: "$variation",
860
+ )
861
+ ```
862
+
863
+ You can also reuse an existing Featurevisor instance:
864
+
865
+ ```ruby
866
+ featurevisor = Featurevisor.create_featurevisor(datafile: datafile_content)
867
+ provider = Featurevisor::OpenFeatureProvider.new(featurevisor: featurevisor)
868
+ ```
869
+
870
+ 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.
871
+
872
+ See the [OpenFeature provider guide](https://featurevisor.com/docs/sdks/openfeature/) for resolution reasons, errors, metadata, tracking, lifecycle, and providers for other languages.
873
+
739
874
  <!-- FEATUREVISOR_DOCS_END -->
740
875
 
741
876
  ## Development
742
877
 
743
878
  ### Setting up
744
879
 
745
- 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.
880
+ After checking out the repository, run `make install` to install dependencies.
746
881
 
747
882
  ### Running tests
748
883
 
749
884
  ```bash
750
885
  $ bundle exec rspec
886
+ $ make test-base
887
+ $ make test-example-1
888
+ ```
889
+
890
+ Build and verify both gems with:
891
+
892
+ ```bash
893
+ $ make build
751
894
  ```
752
895
 
753
- 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).
896
+ 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.
754
897
 
755
898
  ### Releasing
756
899
 
757
- - Update version in `lib/featurevisor/version.rb`
900
+ - Update the shared version in `lib/featurevisor/version.rb`
758
901
  - Run `bundle install`
759
902
  - Push commit to `main` branch
760
903
  - Wait for CI to complete
761
- - Tag the release with the version number
762
- - This will trigger a new release to [RubyGems](https://rubygems.org/gems/featurevisor)
904
+ - Tag the release with the same version number, for example `v1.1.0`
905
+ - The workflow verifies that the tag matches the shared version
906
+ - The workflow publishes `featurevisor` first, followed by `featurevisor-openfeature`
907
+
908
+ 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.
763
909
 
764
910
  ## License
765
911
 
data/bin/cli.rb CHANGED
@@ -7,12 +7,13 @@ module FeaturevisorCLI
7
7
  attr_accessor :command, :assertion_pattern, :context, :environment, :feature,
8
8
  :key_pattern, :n, :only_failures, :quiet, :variable, :variation,
9
9
  :verbose, :inflate, :show_datafile, :schema_version, :project_directory_path,
10
- :populate_uuid, :with_scopes, :with_tags
10
+ :populate_uuid, :with_scopes, :with_tags, :targets
11
11
 
12
12
  def initialize
13
13
  @n = 1000
14
14
  @project_directory_path = Dir.pwd
15
15
  @populate_uuid = []
16
+ @targets = []
16
17
  end
17
18
  end
18
19
 
@@ -82,15 +83,15 @@ module FeaturevisorCLI
82
83
  options.show_datafile = true
83
84
  end
84
85
 
85
- opts.on("--schemaVersion=VERSION", "Schema version") do |v|
86
+ opts.on("--schemaVersion=VERSION", "--schema-version=VERSION", "Legacy schema version option accepted and ignored") do |v|
86
87
  options.schema_version = v
87
88
  end
88
89
 
89
- opts.on("--with-scopes", "--withScopes", "Test scoped assertions against scoped datafiles") do
90
+ opts.on("--with-scopes", "--withScopes", "Legacy scope option accepted and ignored") do
90
91
  options.with_scopes = true
91
92
  end
92
93
 
93
- opts.on("--with-tags", "--withTags", "Test tagged assertions against tagged datafiles") do
94
+ opts.on("--with-tags", "--withTags", "Legacy tag option accepted and ignored") do
94
95
  options.with_tags = true
95
96
  end
96
97
 
@@ -102,6 +103,10 @@ module FeaturevisorCLI
102
103
  options.populate_uuid << v
103
104
  end
104
105
 
106
+ opts.on("--target=TARGET", "Target datafile; repeat for multiple targets") do |v|
107
+ options.targets << v unless options.targets.include?(v)
108
+ end
109
+
105
110
  opts.on("-h", "--help", "Show this help message") do
106
111
  puts opts
107
112
  exit