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:
npx featurevisor convert --to=environmentsConvert 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, productionChecks on a converted copy: ✔ Lint passes ✔ Features and variables build the same in staging, production ✔ Tests pass, 7 cases before and 11 afterAfter 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:
npx featurevisor convert --to=environments --applyIt runs the same checks, then writes the files and ends with ✔ Converted. showCookieBanner now has the same rules in each environment:
description: Show cookie banner to users from the NetherlandsbucketBy: userIdrules: staging: - key: nl conditions: netherlands percentage: 100 - key: everyone conditions: "*" percentage: 0 production: - key: nl conditions: netherlands percentage: 100 - key: everyone conditions: "*" percentage: 0Each of its test assertions gets a matrix that runs it in both environments:
# ...assertions: - description: Enabled for users in the Netherlands at bucket position 10 matrix: environment: - staging - production environment: ${{ environment }} at: 10 context: country: nl expectedToBeEnabled: trueAnd the configuration lists the environments:
/** @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:
npx featurevisor convert --to=sets --sets=dev,productionConvert 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 productionChecks on a converted copy: ✔ Lint passes ✔ Features and variables build the same in dev, production ✔ Tests pass, 7 cases before and 14 afterAfter 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:
npx featurevisor convert --to=setsConvert 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 productionChecks on a converted copy: ✔ Lint passes ✔ Features and variables build the same in staging, production ✔ Tests pass, 8 cases before and 11 afterAfter 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:
npx featurevisor convert --to=environmentsConvert 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, productionChecks on a converted copy: ✔ Lint passes ✔ Features and variables build the same in staging, production ✔ Tests pass, 11 cases before and 8 afterAfter 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:
npx featurevisor convert --to=environmentsConvert sets to environments Sets: dev, staging, productionCannot 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:
npx featurevisor convert --to=flat --keep=productionConvert 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 environmentsDifferences: - 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 afterAfter 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:
module.exports = { environments: ['staging', 'production'],}const shared = require('./shared.config')module.exports = { ...shared,}npx featurevisor convert --to=setsConvert 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 belowChecks on a converted copy: ✔ Lint passes ✔ Features and variables build the same in staging, production ✔ Tests pass, 8 cases before and 11 afterEdit 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
--environmentor--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#
| Option | Description |
|---|---|
--to | Required. flat for neither, environments or sets. |
--apply | Write the conversion. Without it, the command previews and writes nothing. |
--environments, --sets | The lanes for a project with neither. From sets, --environments sets their order. |
--keep | The environment or set to keep. Required with --to=flat. |
--json | Print 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.

