Tools
Formatting
featurevisor fmt rewrites your definitions and test specs in one house style, so every file reads the same and a review shows only what changed. It never changes what a definition means, and the datafiles a build writes stay the same, byte for byte.
Format your project#
A teammate edits checkout in a hurry and leaves its keys out of order:
bucketBy: userIddescription: Checkout redesignrules: - key: netherlands percentage: 100 conditions: netherlands - percentage: 25 key: everyone conditions: '*'Run fmt from the project's root:
npx featurevisor fmtFormatted features/checkout.yml✔ Formatted 1 file of 9The file now reads like every other feature in the project:
description: Checkout redesignbucketBy: userIdrules: - key: netherlands conditions: netherlands percentage: 100 - key: everyone conditions: "*" percentage: 25checkout itself did not change. Its rules match the same users, and build writes the same datafiles as before. '*' became "*" because fmt uses double quotes where YAML needs quotes at all. Both mean the same.
There are no options. Every Featurevisor project formatted with fmt reads the same, so nobody has to agree on a style first.
See what would change#
--diff prints the changes as a unified diff and writes nothing. For a checkout written with its keys run together and one rule on a single line:
npx featurevisor fmt --diff--- features/checkout.yml+++ features/checkout.yml@@ -1,7 +1,12 @@-description: "Checkout redesign"+description: Checkout redesign+ bucketBy: userId+ rules:- - {key: netherlands, conditions: netherlands, percentage: 100}+ - key: netherlands+ conditions: netherlands+ percentage: 100+ - key: everyone- conditions: '*'+ conditions: "*" percentage: 251 file of 9 needs formatting. Run: npx featurevisor fmtCheck formatting in CI#
--check lists the files that need formatting, writes nothing, and exits with code 1 when any do:
npx featurevisor fmt --checkfeatures/checkout.yml1 file of 9 needs formatting. Run: npx featurevisor fmtRun it on every pull request beside lint and test, so an unformatted file is caught in review:
npx featurevisor fmt --checknpx featurevisor lintnpx featurevisor testGitHub Actions shows a complete workflow. --diff on its own exits with code 0, since it is for reading. Pass both to print what to change and still fail:
npx featurevisor fmt --check --diffA file that cannot be formatted, such as one that does not parse, exits with code 2, so CI tells a broken file apart from a file to format. fmt exits with code 0 when every file is formatted.
Check only what changed#
A project that is not formatted yet can start with the files each pull request changes. --since keeps the files changed since the branch left the given Git reference, uncommitted and untracked files included:
npx featurevisor fmt --check --since=origin/mainIn CI, fetch the history it compares with, such as fetch-depth: 0 in GitHub Actions. Without the reference, fmt exits with code 2.
Format part of a project#
Pass file paths to format only those, for example the staged files in a pre-commit hook. A directory formats the definitions in it:
npx featurevisor fmt features/checkout.yml segmentsA path that holds no definition of the project is skipped with a note. --entity-type and --key-pattern narrow the files as they do for lint:
npx featurevisor fmt --entity-type=feature --key-pattern=checkoutIn a project with Sets, fmt formats every set in one run and reports them together. Run it that way, so every set reads the same. --set picks one.
Leave lines as written#
Some lines read better as you wrote them. End a line with # featurevisor-ignore-line, and fmt keeps its key and value as written, in the place you put them:
description: Checkout redesignbucketBy: [userId, deviceId] # featurevisor-ignore-linerules: - key: netherlands conditions: netherlands percentage: 100 - key: everyone conditions: "*" percentage: 25Without the comment, bucketBy would become a list with one item per line. On a key whose list or map starts on the next line, the comment keeps the key in its place, and fmt still formats the lines below it.
To leave a whole file as written, put # featurevisor-ignore among the comments at its top:
# featurevisor-ignorebucketBy: userIddescription: Checkout redesignrules: - key: everyone conditions: '*' percentage: 25npx featurevisor fmt1 file left as written by featurevisor-ignore✔ The other 8 files are formattedPrettier does not read these comments. If it formats your definitions too, add # prettier-ignore above the line, or the file to .prettierignore.
Format in your editor#
--stdin-filepath formats text from standard input as the file at that path would be, and prints it. This is what an editor runs to format a file on save, before the file is written:
npx featurevisor fmt --stdin-filepath=features/checkout.ymlNothing is written. Text for a path that holds no definition of the project comes back as it is, and an error goes to standard error with exit code 2.
Editor settings#
New projects come with an .editorconfig, which most editors read, so files you type match what fmt writes:
root = true[*]charset = utf-8end_of_line = lfindent_style = spaceindent_size = 2insert_final_newline = truetrim_trailing_whitespace = truetrim_trailing_whitespace also removes spaces at the end of a line in a YAML block of text, such as description: |, where they belong to the value. fmt keeps them. Set it to false in a section for *.yml if your text needs them.
fmt keeps each file's line endings. To give every file \n line endings, including on Windows, add a .gitattributes to the project:
* text=auto eol=lfWhat it changes#
- A blank line between top level keys, between rules, force entries, variations and global variable overrides, between the environments that group them and those under
expose, and between test assertions. Elsewhere, a blank line you write between two keys or two items stays, and a run of them becomes one. - Featurevisor's own keys in one order, such as
description,tagsandbucketByfirst in a feature, andkey,conditionsandpercentagein a rule. Key order lists them all. - Lists in block style, one item per line. Empty lists stay
[]. - Quotes only where YAML needs them, double unless the text contains a double quote. Text such as
noor a date is always quoted, because older YAML tools would read it as a boolean or a date. - Characters a reader cannot see, such as a no break space, a zero width space or a bidi control, written as escapes in double quotes, such as
"Checkout\u00A0page", so a review shows them. Text in a|or>block stays as written. - Long text stays on one line.
- Comments stay with the key or rule below them, wherever that key moves. A comment after a key stays on its line.
- YAML aliases written out in full. A value written once with an anchor, such as
&rules, and repeated with*rules, appears in full in both places, and the anchor goes. Each value then reads where it is used. - Two space indentation and one newline at the end of each file.
What it never changes#
- What a definition means. Every file must parse to the same values after formatting, or
fmtleaves it as it is and reports it. - The datafiles.
buildwrites the same bytes before and afterfmt. - The order of lists. Rules, variations, overrides, conditions and mutations are evaluated in order, so they stay in yours.
- Values you own: variable values, contexts, conditions, and maps whose keys you choose, such as
variablesSchema. - Keys it does not know. They keep their order after the keys it does, and lint reports them.
- Line endings. A file written with Windows line endings keeps them.
Files that do not parse#
A file needs to parse, not to pass lint, so you can format a definition you are still writing. A file that does not parse is reported with its line and column, and left as it is:
segments/payingAccounts.yml:7:1 => Error: Flow sequence in block collection must be sufficiently indented and end with a ]1 file could not be formattedThe other files are still formatted, and the command exits with code 2. Fix the file, then run fmt again.
Formatting and promotions#
fmt and promote work apart. A promotion keeps the lines of each file it does not change, writes the lines it changes as the parser writes them, and does not format them. Run fmt after a promotion when fmt --check reports files.
convert is the one other command that formats. It formats a file it rewrites when the file was formatted before, so fmt --check still passes after a conversion.
Other file formats#
Each parser lays out its own files. In a JSON project, fmt orders keys, indents with two spaces, and puts blank lines where it would in YAML, keeping the ones you write. It lays out lists as Prettier does at its default width of 80 columns, so the two agree, and a list on one line has no blank lines. A custom parser takes part through an optional format function, and applies what its format can express. It can leave a file as it is, such as a TOML file with comments it cannot keep, and fmt names that file. Without format, fmt says so and leaves all the files as they are.
Details#
Key order#
In attributes, schemas, segments, features and global variables, archived, deprecated and promotable come first. Keys a file does not have are left out.
- Feature:
description,tags,bucketBy,conditions,disabledVariationValue,variablesSchema,variations,force,rules,expose. - Rule:
key,description,promotable,conditions,percentage,enabled,variation,variationWeights,variables,variableOverrides. - Force entry:
conditions,enabled,variation,variables. - Variation:
value,description,weight,variables,variableOverrides. - Variable override in a feature:
key,description,promotable,conditions,value,mutate. - Variable in
variablesSchema:description,deprecated, the schema keys,defaultValue,disabledValue,useDefaultWhenDisabled. - Global variable:
description,tags,compose, the schema keys,defaultValue,conditions,disabledValue,useDefaultWhenDisabled,overrides. - Global variable override:
key,description,promotable,conditions,continue,value,mutate,overrides. - Segment:
description,conditions. - Attribute and schema:
description, then the schema keys. - Target:
promotable,description,tag,tags,includeFeatures,excludeFeatures,includeVariables,excludeVariables,context,schemaVersion. - Test spec:
feature,segmentorvariable, thenpromotableandassertions. - Assertion:
key,description,promotable,matrix,environment,target,at,context,stickyFeatures,stickyVariables, the default values, the expected values,checks,children. - Check:
feature,segmentorvariable, thenkey,description,path,operator,value,compareTo,regexFlags,enabled,variation,matches, and last a group of checks underand,orornot, each in this same order. - Scenario spec: the assertion order, at the top level of the file.
The schema keys are type, schema, oneOf, format, enum, const, minimum, maximum, minLength, maxLength, pattern, items, minItems, maxItems, uniqueItems, properties, required and additionalProperties.
JSON output#
--json prints each file with its status, changed, unchanged, declined, ignored or error, and the error's code, message, line and column. declined is a file the parser left as it is, and ignored a file with # featurevisor-ignore at its top. With --diff, each changed file carries its diff too, and with --stdin-filepath, its formatted output. In a project with Sets, each file carries its set. A project whose parser has no format reports unsupported: true.

