Featurevisor

Extending Featurevisor

Custom parsers

Definitions are YAML by default. Switch the whole project to JSON, or give it a parser for any other format.

YAML

YAML needs no configuration. Every example in these docs is YAML, and the YAML example project is a complete setup.

JSON

Set parser to json in the configuration:

featurevisor.config.js
module.exports = {
parser: 'json',
}

Every definition is then a .json file. The checkout feature from the Quick start becomes:

features/checkout.json
{
"description": "Checkout redesign",
"bucketBy": "userId",
"rules": [
{
"key": "netherlands",
"conditions": "netherlands",
"percentage": 100
},
{
"key": "everyone",
"conditions": "*",
"percentage": 25
}
]
}

The JSON example project is a complete setup.

Another format

A custom parser is an object with three properties, and two optional ones for formatting:

PropertyPurpose
extensionThe file extension, such as toml
parse(content, filePath?)Turns a file's text into a definition object
stringify(content, filePath?)Turns a definition object back into text
format(source, layout)Optional. Lays out a file for fmt
listComments(source)Optional. Lists a file's comments for fmt

Both functions are synchronous. The file path is there so a parser can say where an error is. Featurevisor checks that both exist when it loads the configuration. It writes definitions with stringify, for example during promote --apply, so a parser must write as well as read.

For TOML, smol-toml does both:

Command
npm install --save-dev smol-toml
featurevisor.config.js
const toml = require('smol-toml')
module.exports = {
parser: {
extension: 'toml',
parse: (content) => toml.parse(content),
stringify: (content) => toml.stringify(content),
},
}

The TOML example project is a complete setup.

Pick a format that can hold every value your definitions use. TOML has no null, for example. Commands that write definitions keep the lines they do not change, but a file they cannot merge a change into is written whole through stringify. That can change its formatting and drop comments, so check the Git diff after a command writes definitions.

Format files

featurevisor fmt lays out a file with its parser's format function. The built-in YAML parser applies the whole house style, and the JSON parser all that JSON can express: key order, indentation and blank lines. A custom parser without format keeps its files as they are, because only the parser knows what its format can keep, such as comments.

format receives the file's text and a layout for its kind of definition, and returns the new text:

layout propertyGives
indentSpaces per level of nesting
orderKeys(path, keys)The keys of the map at path, in Featurevisor's order
spaceEntries(path)Whether the map at path has blank lines between entries
spaceItems(path)Whether the list at path has blank lines between items
keepBlankLinesWhether a blank line the author wrote between two entries or items stays, one at most
orderValue(value)A parsed definition with every map's keys in order
formatText(path, text)The text of the string at path in house style, such as an expression in conditions, or undefined to keep it
optionsFeaturevisor's choices for your file format, when it has some for that extension, such as YAML's quotes. Empty for other formats

formatText is there in projects with condition expressions on. A parser that writes from values, as this TOML one does, can pass a value through applyFormatText(value, layout.formatText) from @featurevisor/parsers, which applies it to every string.

Apply what your format can express and ignore the rest, since later versions may add to layout. Return undefined to leave a file as it is, and fmt reports it as left by the parser. smol-toml writes values but not comments, so this format orders keys only in a file without a #:

featurevisor.config.js
const toml = require('smol-toml')
module.exports = {
parser: {
extension: 'toml',
parse: (content) => toml.parse(content),
stringify: (content) => toml.stringify(content),
format: (source, layout) =>
source.includes('#')
? undefined
: toml.stringify(layout.orderValue(toml.parse(source))),
},
}

A checkout written with its keys out of order:

features/checkout.toml
bucketBy = "userId"
description = "Checkout redesign"
[[rules]]
percentage = 100
key = "netherlands"
conditions = "netherlands"
[[rules]]
key = "everyone"
percentage = 25
conditions = "*"

reads like this after npx featurevisor fmt:

features/checkout.toml
description = "Checkout redesign"
bucketBy = "userId"
[[rules]]
key = "netherlands"
conditions = "netherlands"
percentage = 100
[[rules]]
key = "everyone"
conditions = "*"
percentage = 25

fmt keeps each file's line endings and ends it with one newline, so format does not need to. It also leaves a file with # featurevisor-ignore at its top as it is, without calling format. In a format with comments, keep a line that ends with # featurevisor-ignore-line as written. @featurevisor/parsers exports both comments as IGNORE_FILE_COMMENT and IGNORE_LINE_COMMENT. It checks that the result parses to the same values and stays the same when formatted again. Give the parser listComments, returning every comment in a file, and fmt checks that none is lost as well. A file that fails a check is left as it is.

format is for fmt, and for convert, which formats a file it rewrites when the file was formatted before. Promote and other commands that write definitions use stringify alone.

Previous
Skills for AI agents