Featurevisor

Tools

Converting

featurevisor convert changes a project's shape between environments, sets and neither, rewriting every definition, test spec, state file and the configuration to match. It writes nothing until a converted copy of the project lints, tests and builds the same.

Preview a conversion

The default project has neither environments nor sets. Pass the shape you want with --to, here staging and production environments:

Command
npx featurevisor convert --to=environments
Output
Convert no environments and no sets to environments
Environments: staging, production
Definitions and specs: 2 to write, 0 to remove
featurevisor.config.js: adds environments staging, production
Checks on a converted copy:
✔ Lint passes
✔ Features and variables build the same in staging, production
✔ Tests pass, 7 cases before and 11 after
After converting:
- Datafiles move to datafiles/<environment>/, such as datafiles/production/. Point applications at them, and clear datafiles/ before the next build.
Nothing was written. Run with --apply to convert.

convert previews by default. The first lines say how many files it would write and remove, and what changes in featurevisor.config.js. Then it converts a copy of the project and checks that:

  • lint passes on the copy
  • every feature and global variable builds the same in each environment as it did before
  • the tests pass, before and after

The four assertions of showCookieBanner now run once in each environment, so 7 test cases become 11. The three segment assertions need no environment, and run once. "After converting" lists what changes outside the definitions, here where the datafiles go. When a check fails, the preview says which and exits with code 2.

Pass --environments to choose other names, such as --environments=dev,production. Without it, a project gets staging and production.

Apply it

When the preview looks right, add --apply:

Command
npx featurevisor convert --to=environments --apply

It runs the same checks, then writes the files and ends with ✔ Converted. showCookieBanner now has the same rules in each environment:

features/showCookieBanner.yml
description: Show cookie banner to users from the Netherlands
bucketBy: userId
rules:
staging:
- key: nl
conditions: netherlands
percentage: 100
- key: everyone
conditions: "*"
percentage: 0
production:
- key: nl
conditions: netherlands
percentage: 100
- key: everyone
conditions: "*"
percentage: 0

Each of its test assertions gets a matrix that runs it in both environments:

tests/features/showCookieBanner.spec.yml
# ...
assertions:
- description: Enabled for users in the Netherlands at bucket position 10
matrix:
environment:
- staging
- production
environment: ${{ environment }}
at: 10
context:
country: nl
expectedToBeEnabled: true

And the configuration lists the environments:

featurevisor.config.js
/** @type {import('@featurevisor/core').ProjectConfig} */
module.exports = {
environments: ['staging', 'production'],
prettyState: true,
};

Add sets

--to=sets gives each set a copy of the whole project under sets/<name>/. Name the sets with --sets, in the order changes should move between them:

Command
npx featurevisor convert --to=sets --sets=dev,production
Output
Convert no environments and no sets to sets
Sets: dev, production
Definitions and specs: 16 to write, 8 to remove
featurevisor.config.js: adds sets: true, adds promotionFlows dev to production
Checks on a converted copy:
✔ Lint passes
✔ Features and variables build the same in dev, production
✔ Tests pass, 7 cases before and 14 after
After converting:
- Datafiles move to datafiles/<set>/, such as datafiles/production/. Point applications at them, and clear datafiles/ before the next build.
Nothing was written. Run with --apply to convert.

Each set starts as today's project, so the 7 test cases run once in each. The state files are copied into each set too. promotionFlows lets promotions move changes from dev to production, and not back.

A single set, such as --sets=storefront, gets no promotionFlows, as Sets shows. Without --sets, a project gets staging and production.

Turn environments into sets

A project with environments becomes one set per environment. For the example with environments, from init --example=with-environments:

Command
npx featurevisor convert --to=sets
Output
Convert environments to sets
Sets: staging, production
Definitions and specs: 16 to write, 8 to remove
featurevisor.config.js: removes environments, adds sets: true, adds promotionFlows staging to production
Checks on a converted copy:
✔ Lint passes
✔ Features and variables build the same in staging, production
✔ Tests pass, 8 cases before and 11 after
After converting:
- Pass --set=<name> where CI and scripts passed --environment=<name>. Applications load the same datafile paths.
Nothing was written. Run with --apply to convert.

Each environment becomes a set with its own copy of the attributes, segments, schemas and targets. Each feature keeps only that environment's rules, force and expose, and each global variable its overrides. A feature that left an environment out gets rules: [] there, so it stays disabled. promotionFlows follows the order of environments.

An assertion that names an environment moves to that set. One that takes its environment from a matrix goes to each set the matrix lists. Tests without an environment, such as segment tests, now run in every set, which is why there are more cases.

A set's datafiles carry only what the set uses, while an environment's datafiles also carry the segments and features that other environments' rules use. The checks list what each set's datafiles stop carrying. No evaluation in that set reads them, so results stay the same.

Go back to environments

--to=environments turns each set into an environment. On the project from the last section, after applying:

Command
npx featurevisor convert --to=environments
Output
Convert sets to environments
Sets: staging, production
Definitions and specs: 8 to write, 16 to remove
featurevisor.config.js: removes sets and promotionFlows, adds environments staging, production
Checks on a converted copy:
✔ Lint passes
✔ Features and variables build the same in staging, production
✔ Tests pass, 11 cases before and 8 after
After converting:
- Pass --environment=<name> where CI and scripts passed --set=<name>. Applications load the same datafile paths. Promotions need sets, so they end.
Nothing was written. Run with --apply to convert.

The sets merge into one tree. A feature's rules, force and expose become maps keyed by set, and so does a global variable's overrides. Feature, variable and scenario assertions gain environment. The environments follow the order of promotionFlows. Pass every set to --environments for another order, such as --environments=production,staging.

Environments share their attributes, segments, schemas and targets, so these must be the same in every set, and a global variable must be in every set. A feature may differ between sets only in its rules, force and expose. A feature missing from a set gets expose: false in that environment, so its datafiles still leave it out.

When anything else differs, convert lists it and writes nothing. The release lanes example, from init --example=environments, describes checkoutFlow and its all target differently in each set:

Command
npx featurevisor convert --to=environments
Output
Convert sets to environments
Sets: dev, staging, production
Cannot convert:
- The feature checkoutFlow.yml differs between sets dev, staging, production outside its lanes: description.
- The target all.yml differs between sets dev, staging, production: description.

Make those fields the same in every set, then run it again.

Remove environments or sets

--to=flat keeps one environment or set and drops the others. Name the one to keep with --keep, and the preview lists what the others lose:

Command
npx featurevisor convert --to=flat --keep=production
Output
Convert environments to no environments and no sets, keeping production
Environments: staging, production
Definitions and specs: 2 to write, 0 to remove
featurevisor.config.js: removes environments
Differences:
- Drops staging from rules in 1 file, keeping production.
- Drops staging from test assertions in 1 file, keeping production.
Checks on a converted copy:
✔ Lint passes
✔ Features and variables build the same in production
✔ Tests pass, 8 cases before and 7 after
After converting:
- Datafiles move from datafiles/production/ to datafiles/. Point applications at them, and clear datafiles/ before the next build.
Nothing was written. Run with --apply to convert.

The rules and state file of production stay, and its test assertions lose environment. From sets, the kept set's tree moves to the top level and the other sets are deleted.

Edit the configuration by hand

convert edits featurevisor.config.js as text, where module.exports = { writes a setting out. When it cannot reach a setting it has to change, such as environments spread in from another file, it converts everything else and prints the edit for you to make:

shared.config.js
module.exports = {
environments: ['staging', 'production'],
}
featurevisor.config.js
const shared = require('./shared.config')
module.exports = {
...shared,
}
Command
npx featurevisor convert --to=sets
Output
Convert environments to sets
Sets: staging, production
Definitions and specs: 16 to write, 8 to remove
featurevisor.config.js: for you to edit after converting, as below
Checks on a converted copy:
✔ Lint passes
✔ Features and variables build the same in staging, production
✔ Tests pass, 8 cases before and 11 after
Edit featurevisor.config.js by hand after converting, since it sets environments where convert cannot edit it, such as in an object spread into its settings:
- Remove environments
- Add these settings to the object it exports:
sets: true,
promotionFlows: [{ from: 'staging', to: 'production' }],
Until then, Featurevisor reads the project in its old shape. Run lint once it is edited.
After converting:
- Pass --set=<name> where CI and scripts passed --environment=<name>. Applications load the same datafile paths.
Nothing was written. Run with --apply to convert, then edit featurevisor.config.js.

The checks run with the edit made, so the conversion is still checked in full. --apply writes everything else and ends with ✔ Converted, except featurevisor.config.js. Make the edit straight away. Until then, Featurevisor looks for definitions where they were before and finds none, so lint passes with nothing to check.

The same happens when the file does not set module.exports to an object, such as module.exports = require('./shared.config').

After converting

convert changes nothing outside the project. Every feature and global variable builds the same, so users get the same results. What changes is where datafiles go and how commands name them, as "After converting" in the preview says:

  • Update CI and scripts that pass --environment or --set.
  • When datafile paths move, clear datafiles/ before the next build, so a deployment does not publish stale files beside the new ones.
  • Deploy the new datafiles, and point applications at their new paths.

Details

What it keeps

convert changes only the lines a conversion needs, such as moving rules under each environment. The other lines of a file stay as they are, comments included. A file formatted as fmt writes it stays formatted. The revision stays the same, and each environment or set keeps today's state, so saved allocations do not move.

The preview lists two changes under "Differences" for you to check:

  • A YAML file with anchors and aliases has its aliases written out in full, since splitting lanes apart could leave an alias without its anchor.
  • A file of a custom parser whose lines it cannot keep is written as the parser writes it. Check it for comments.

What it does not convert

convert refuses a project with sets and environments together, such as storefront and admin sets each with staging and production, and never makes one. To give each set environments, turn sets on and move each directory into sets/<name>/ by hand, as Sets shows.

Options

OptionDescription
--toRequired. flat for neither, environments or sets.
--applyWrite the conversion. Without it, the command previews and writes nothing.
--environments, --setsThe lanes for a project with neither. From sets, --environments sets their order.
--keepThe environment or set to keep. Required with --to=flat.
--jsonPrint the plan and its checks as JSON. Add --pretty to indent it.

Exit codes

convert exits with code 0 after a preview or a conversion, including one that leaves the configuration for you to edit. It exits with code 2, writing nothing, when a check fails, when something differs that it cannot convert, or when an option is missing or wrong.

JSON output

--json prints the plan and its checks: from, to, lanes, keep with --to=flat, the paths it writes and removes, the config edits, conflicts, differences, the next steps, the check results and whether it was applied. An edit left for you comes as configByHand, with its reason and the settings to remove and add.

Previous
Formatting