New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

unified-engine

Package Overview
Dependencies
Maintainers
1
Versions
45
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

unified-engine - npm Package Compare versions

Comparing version

to
11.0.0

index.d.ts

633

lib/configuration.js

@@ -1,140 +0,313 @@

'use strict'
/**
* @typedef {import('unified').Plugin<Array<unknown>>} Plugin
* @typedef {import('unified').PluginTuple<Array<unknown>>} PluginTuple
* @typedef {import('unified').Preset} Preset
* @typedef {import('unified').Settings} Settings
*/
var path = require('path')
var Module = require('module')
var yaml = require('js-yaml')
var json = require('parse-json')
var debug = require('debug')('unified-engine:configuration')
var resolve = require('load-plugin').resolve
var fault = require('fault')
var xtend = require('xtend')
var object = require('is-object')
var string = require('x-is-string')
var FindUp = require('./find-up')
/**
* @callback Callback
* Callback called when loading a config.
* @param {Error | undefined} error
* Error if something happened.
* @param {Result | undefined} [result]
* Result.
* @returns {undefined}
* Nothing.
*
* @callback ConfigTransform
* Transform arbitrary configs to our format.
* @param {any} config
* Arbitrary config.
* @param {string} filePath
* File path of config file.
* @returns {PresetSupportingSpecifiers}
* Our config format.
*
* @typedef ImportResult
* Result from an `import` call.
* @property {unknown} [default]
* Default field.
*
* Note: we can’t use `@-callback` because TS doesn’t deal with `this` correctly.
* @typedef {(this: Configuration, buf: Buffer, filePath: string) => Promise<PresetSupportingSpecifiers | undefined>} Loader
* Loader for different config files.
*
*
* @typedef MergeConfiguration
* How to merge.
* @property {string | undefined} prefix
* Plugin prefix.
* @property {string} root
* File path to merge from.
*
* Used to resolve plugins.
*
* @typedef {Array<PluggableSupportingSpecifiers>} PluggableListSupportingSpecifiers
* List of plugins and configuration, with support for specifiers.
*
* @typedef {Record<string, unknown>} PluggableMap
* Map where each key is a plugin specifier and each value is its primary parameter.
*
* @typedef {PluginSupportingSpecifiers | PluginTupleSupportingSpecifiers | Preset} PluggableSupportingSpecifiers
* Usable values, with support for specifiers.
*
* @typedef {Plugin | string} PluginSupportingSpecifiers
* A plugin, or a specifier to one.
*
* @typedef {[plugin: string, ...parameters: Array<unknown>] | PluginTuple} PluginTupleSupportingSpecifiers
* A plugin with configuration, with support for specifiers.
*
* @typedef PresetSupportingSpecifiers
* Sharable configuration, with support for specifiers.
*
* Specifiers should *not* be used in actual presets (because they can’t be
* used by regular unified), but they can be used in config files locally,
* as those are only for the engine.
*
* They can contain plugins and settings.
* @property {PluggableListSupportingSpecifiers | PluggableMap | undefined} [plugins]
* List of plugins and presets (optional).
* @property {Settings | undefined} [settings]
* Shared settings for parsers and compilers (optional).
*
* @typedef Result
* Resolved configuration.
* @property {string | undefined} filePath
* File path of found configuration.
* @property {Settings} settings
* Resolved settings.
* @property {Array<PluginTuple>} plugins
* Resolved plugins.
*
*/
module.exports = Config
import assert from 'node:assert/strict'
import path from 'node:path'
import {pathToFileURL} from 'node:url'
import structuredClone from '@ungap/structured-clone'
import createDebug from 'debug'
import isPlainObj from 'is-plain-obj'
import {resolvePlugin} from 'load-plugin'
import parseJson from 'parse-json'
import yaml from 'yaml'
import {FindUp} from './find-up.js'
var own = {}.hasOwnProperty
var extname = path.extname
var basename = path.basename
var dirname = path.dirname
var relative = path.relative
const debug = createDebug('unified-engine:configuration')
var loaders = {
'.json': loadJSON,
'.js': loadScript,
'.yaml': loadYAML,
'.yml': loadYAML
const own = {}.hasOwnProperty
/** @type {Record<string, Loader>} */
const loaders = {
'.json': loadJson,
'.cjs': loadScriptOrModule,
'.mjs': loadScriptOrModule,
'.js': loadScriptOrModule,
'.yaml': loadYaml,
'.yml': loadYaml
}
var defaultLoader = loadJSON
const defaultLoader = loadJson
Config.prototype.load = load
/**
* @typedef Options
* Configuration.
* @property {ConfigTransform | undefined} [configTransform]
* Transform config files from a different schema (optional).
* @property {string} cwd
* Base (required).
* @property {PresetSupportingSpecifiers | undefined} [defaultConfig]
* Default configuration to use if no config file is given or found
* (optional).
* @property {boolean | undefined} [detectConfig]
* Whether to search for configuration files.
* @property {string | undefined} [packageField]
* Field where configuration can be found in `package.json` files
* (optional).
* @property {string | undefined} [pluginPrefix]
* Prefix to use when searching for plugins (optional).
* @property {PluggableListSupportingSpecifiers | PluggableMap | undefined} [plugins]
* Plugins to use (optional).
* @property {string | undefined} [rcName]
* Name of configuration files to load (optional).
* @property {string | undefined} [rcPath]
* Filepath to a configuration file to load (optional).
* @property {Settings | undefined} [settings]
* Configuration for the parser and compiler of the processor (optional).
*/
function Config(options) {
var rcName = options.rcName
var packageField = options.packageField
var names = []
export class Configuration {
/**
* Internal class to load configuration files.
*
* Exposed to build more complex integrations.
*
* @param {Options} options
* Configuration (required).
* @returns
* Self.
*/
constructor(options) {
/** @type {Array<string>} */
const names = []
this.cwd = options.cwd
this.packageField = options.packageField
this.pluginPrefix = options.pluginPrefix
this.configTransform = options.configTransform
this.defaultConfig = options.defaultConfig
/** @type {string} */
this.cwd = options.cwd
/** @type {string | undefined} */
this.packageField = options.packageField
/** @type {string | undefined} */
this.pluginPrefix = options.pluginPrefix
/** @type {ConfigTransform | undefined} */
this.configTransform = options.configTransform
/** @type {PresetSupportingSpecifiers | undefined} */
this.defaultConfig = options.defaultConfig
if (rcName) {
names.push(rcName, rcName + '.js', rcName + '.yml', rcName + '.yaml')
debug('Looking for `%s` configuration files', names)
}
if (options.rcName) {
names.push(
options.rcName,
...Object.keys(loaders).map(function (d) {
return options.rcName + d
})
)
debug('Looking for `%s` configuration files', names)
}
if (packageField) {
names.push('package.json')
debug('Looking for `%s` fields in `package.json` files', packageField)
}
if (options.packageField) {
names.push('package.json')
debug(
'Looking for `%s` fields in `package.json` files',
options.packageField
)
}
this.given = {settings: options.settings, plugins: options.plugins}
this.create = create.bind(this)
/** @type {PresetSupportingSpecifiers} */
this.given = {plugins: options.plugins, settings: options.settings}
this.create = this.create.bind(this)
this.findUp = new FindUp({
filePath: options.rcPath,
cwd: options.cwd,
detect: options.detectConfig,
names: names,
create: this.create
})
}
/** @type {FindUp<Result>} */
this.findUp = new FindUp({
create: this.create,
cwd: options.cwd,
detect: options.detectConfig,
filePath: options.rcPath,
names
})
}
function load(filePath, callback) {
var searchPath = filePath || path.resolve(this.cwd, 'stdin.js')
var self = this
/**
* Get the config for a file.
*
* @param {string} filePath
* File path to load.
* @param {Callback} callback
* Callback.
* @returns {undefined}
* Nothing.
*/
load(filePath, callback) {
const self = this
self.findUp.load(searchPath, done)
this.findUp.load(
filePath || path.resolve(this.cwd, 'stdin.js'),
function (error, file) {
if (error || file) {
return callback(error, file)
}
function done(err, res) {
if (err || res) {
return callback(err, res)
}
callback(null, self.create())
self.create(undefined, undefined).then(function (result) {
callback(undefined, result)
}, callback)
}
)
}
}
function create(buf, filePath) {
var self = this
var transform = self.configTransform
var defaults = self.defaultConfig
var fn = (filePath && loaders[extname(filePath)]) || defaultLoader
var options = {prefix: self.pluginPrefix, cwd: self.cwd}
var result = {settings: {}, plugins: []}
var contents = buf ? fn.apply(self, arguments) : undefined
/**
* This is an internal method, consider it private.
*
* @param {Buffer | undefined} buf
* File value.
* @param {string | undefined} filePath
* File path.
* @returns {Promise<Result | undefined>}
* Result.
*/
async create(buf, filePath) {
const options = {cwd: this.cwd, prefix: this.pluginPrefix}
/** @type {Result} */
const result = {filePath: undefined, plugins: [], settings: {}}
const extname = filePath ? path.extname(filePath) : undefined
const loader =
extname && extname in loaders ? loaders[extname] : defaultLoader
/** @type {PresetSupportingSpecifiers | undefined} */
let value
if (transform && contents !== undefined) {
contents = transform(contents, filePath)
}
if (filePath && buf) {
value = await loader.call(this, buf, filePath)
/* Exit if we did find a `package.json`, but it doesn’t have configuration. */
if (buf && contents === undefined && basename(filePath) === 'package.json') {
return
}
if (this.configTransform && value !== undefined) {
value = this.configTransform(value, filePath)
}
}
if (contents === undefined) {
if (defaults) {
merge(result, defaults, null, xtend(options, {root: self.cwd}))
// Exit if we did find a `package.json`, but it does not have configuration.
if (
filePath &&
path.basename(filePath) === 'package.json' &&
value === undefined
) {
filePath = undefined
}
} else {
merge(result, contents, null, xtend(options, {root: dirname(filePath)}))
}
merge(result, self.given, null, xtend(options, {root: self.cwd}))
if (value === undefined) {
if (this.defaultConfig) {
await merge(result, this.defaultConfig, {...options, root: this.cwd})
}
} else {
assert(typeof filePath === 'string', 'Expected `filePath` to be set')
await merge(result, value, {...options, root: path.dirname(filePath)})
}
return result
}
await merge(result, this.given, {...options, root: this.cwd})
/* Basically `Module.prototype.load`, but for a buffer instead
* of a filepath. */
function loadScript(buf, filePath) {
var submodule = Module._cache[filePath]
result.filePath = filePath
if (!submodule) {
submodule = new Module(filePath, module)
submodule.filename = filePath
submodule.paths = Module._nodeModulePaths(dirname(filePath))
submodule._compile(String(buf), filePath)
submodule.loaded = true
Module._cache[filePath] = submodule
return result
}
}
return submodule.exports
/**
* @this {Configuration}
* Class.
* @type {Loader}
* Loader.
*/
async function loadScriptOrModule(_, filePath) {
// Assume it’s a config.
const result = /** @type {Result} */ (
await loadFromAbsolutePath(filePath, this.cwd)
)
return result
}
function loadYAML(buf, filePath) {
return yaml.safeLoad(buf, {filename: basename(filePath)})
/** @type {Loader} */
async function loadYaml(buf) {
return yaml.parse(String(buf))
}
function loadJSON(buf, filePath) {
var result = json(buf, filePath)
/**
* @this {Configuration}
* Class.
* @type {Loader}
* Loader.
*/
async function loadJson(buf, filePath) {
/** @type {Record<string, unknown>} */
const data = parseJson(String(buf), filePath)
if (basename(filePath) === 'package.json') {
result = result[this.packageField]
}
// Assume it’s a config.
const result = /** @type {Result} */ (
this.packageField && path.basename(filePath) === 'package.json'
? data[this.packageField]
: data
)

@@ -144,9 +317,15 @@ return result

function merge(target, raw, val, options) {
var root = options.root
var cwd = options.cwd
var prefix = options.prefix
if (object(raw)) {
addPreset(raw)
/**
* @param {Result} target
* Result to merge into.
* @param {PresetSupportingSpecifiers} raw
* Raw found config.
* @param {MergeConfiguration} options
* Configuration.
* @returns {Promise<undefined>}
* Nothing.
*/
async function merge(target, raw, options) {
if (raw !== null && typeof raw === 'object') {
await addPreset(raw)
} else {

@@ -156,15 +335,15 @@ throw new Error('Expected preset, not `' + raw + '`')

return target
/**
* @param {PresetSupportingSpecifiers} result
* Configuration file.
* @returns {Promise<undefined>}
* Nothing.
*/
async function addPreset(result) {
const plugins = result.plugins
function addPreset(result) {
var plugins = result.plugins
if (plugins === null || plugins === undefined) {
/* Empty. */
} else if (object(plugins)) {
if ('length' in plugins) {
addEach(plugins)
} else {
addIn(plugins)
}
// Empty.
} else if (plugins !== null && typeof plugins === 'object') {
await (Array.isArray(plugins) ? addEach(plugins) : addIn(plugins))
} else {

@@ -176,17 +355,23 @@ throw new Error(

target.settings = xtend(target.settings, result.settings)
target.settings = structuredClone({...target.settings, ...result.settings})
}
function addEach(result) {
var length = result.length
var index = -1
var value
/**
* @param {PluggableListSupportingSpecifiers} result
* List of plugins.
* @returns {Promise<undefined>}
* Nothing.
*/
async function addEach(result) {
let index = -1
while (++index < length) {
value = result[index]
while (++index < result.length) {
const value = result[index]
if (object(value) && 'length' in value) {
use.apply(null, value)
// Keep order sequential instead of parallel.
if (Array.isArray(value)) {
const [plugin, primaryValue] = value
await use(plugin, primaryValue)
} else {
use(value)
await use(value, undefined)
}

@@ -196,53 +381,82 @@ }

function addIn(result) {
var key
/**
* @param {PluggableMap} result
* Map of plugins.
* @returns {Promise<undefined>}
* Nothing.
*/
async function addIn(result) {
/** @type {string} */
let key
for (key in result) {
use(key, result[key])
if (own.call(result, key)) {
// Keep order sequential instead of parallel.
await use(key, result[key])
}
}
}
function use(usable, value) {
if (string(usable)) {
addModule(usable, value)
/**
* @param {PluginSupportingSpecifiers | Preset} usable
* Usable value.
* @param {unknown} value
* Primary parameter.
* @returns {Promise<undefined>}
* Nothing.
*/
async function use(usable, value) {
if (typeof usable === 'string') {
await addModule(usable, value)
} else if (typeof usable === 'function') {
addPlugin(usable, value)
} else {
merge(target, usable, value, options)
await merge(target, usable, options)
}
}
function addModule(id, value) {
var fp = resolve(id, {cwd: root, prefix: prefix})
var res
/**
* @param {string} id
* Specifier.
* @param {unknown} value
* Primary parameter.
* @returns {Promise<undefined>}
* Nothing.
*/
async function addModule(id, value) {
/** @type {string} */
let fp
if (fp) {
try {
res = require(fp) // eslint-disable-line import/no-dynamic-require
} catch (err) {
throw fault(
'Cannot parse script `%s`\n%s',
relative(root, fp),
err.stack
)
}
try {
fp = await resolvePlugin(id, {
cwd: options.root,
prefix: options.prefix
})
} catch (error) {
addPlugin(function () {
throw new Error('Cannot find module `' + id + '`', {cause: error})
}, value)
return
}
try {
if (typeof res === 'function') {
addPlugin(res, value)
} else {
merge(target, res, value, xtend(options, {root: dirname(fp)}))
}
} catch (err) {
throw fault(
'Error: Expected preset or plugin, not %s, at `%s`',
res,
relative(root, fp)
)
const result = await loadFromAbsolutePath(fp, options.root)
try {
if (typeof result === 'function') {
// Assume plugin.
const plugin = /** @type {Plugin} */ (result)
addPlugin(plugin, value)
} else {
// Assume preset.
const preset = /** @type {Preset} */ (result)
await merge(target, preset, {...options, root: path.dirname(fp)})
}
} else {
fp = relative(cwd, path.resolve(root, id))
addPlugin(
failingModule(fp, new Error('Could not find module `' + id + '`')),
value
} catch (error) {
throw new Error(
'Expected preset or plugin, not `' +
result +
'`, at `' +
path.relative(options.root, fp) +
'`',
{cause: error}
)

@@ -252,9 +466,21 @@ }

function addPlugin(result, value) {
var entry = find(target.plugins, result)
/**
* @param {Plugin} plugin
* Plugin.
* @param {unknown} value
* Primary parameter.
* @returns {undefined}
* Nothing.
*/
function addPlugin(plugin, value) {
const entry = find(target.plugins, plugin)
if (value === null) {
value = undefined
}
if (entry) {
reconfigure(entry, value)
} else {
target.plugins.push([result, value])
target.plugins.push([plugin, value])
}

@@ -264,5 +490,13 @@ }

/**
* @param {PluginTuple} entry
* Tuple.
* @param {unknown} value
* Primary value.
* @returns {undefined}
* Nothing.
*/
function reconfigure(entry, value) {
if (value !== false && entry[1] !== false && object(value)) {
value = xtend(entry[1], value)
if (isPlainObj(entry[1]) && isPlainObj(value)) {
value = structuredClone({...entry[1], ...value})
}

@@ -273,10 +507,15 @@

/**
* @param {Array<PluginTuple>} entries
* Tuples.
* @param {Plugin} plugin
* Plugin.
* @returns {PluginTuple | undefined}
* Tuple.
*/
function find(entries, plugin) {
var length = entries.length
var index = -1
var entry
let index = -1
while (++index < length) {
entry = entries[index]
while (++index < entries.length) {
const entry = entries[index]
if (entry[0] === plugin) {

@@ -288,9 +527,27 @@ return entry

function failingModule(id, err) {
var cache = failingModule.cache || (failingModule.cache = {})
var submodule = own.call(cache, id) ? cache[id] : (cache[id] = fail)
return submodule
function fail() {
throw err
/**
* @param {string} fp
* Specifier.
* @param {string} base
* Base.
* @returns {Promise<unknown>}
* Result.
*/
async function loadFromAbsolutePath(fp, base) {
try {
/** @type {ImportResult} */
const result = await import(pathToFileURL(fp).href)
if (!('default' in result)) {
throw new Error(
'Expected a plugin or preset exported as the default export'
)
}
return result.default
} catch (error) {
throw new Error('Cannot import `' + path.relative(base, fp) + '`', {
cause: error
})
}
}

@@ -1,70 +0,93 @@

'use strict'
/**
* @typedef {import('trough').Callback} Callback
*
* @typedef {import('vfile').VFile} VFile
*
* @typedef {import('./index.js').Context} Context
*/
var debug = require('debug')('unified-engine:file-pipeline:configure')
var stats = require('vfile-statistics')
var fnName = require('fn-name')
var object = require('is-object')
var empty = require('is-empty')
import createDebug from 'debug'
import isEmpty from 'is-empty'
import {statistics} from 'vfile-statistics'
module.exports = configure
const debug = createDebug('unified-engine:file-pipeline:configure')
/* Collect configuration for a file based on the context. */
function configure(context, file, fileSet, next) {
var config = context.configuration
var processor = context.processor
if (stats(file).fatal) {
return next()
/**
* Collect configuration for a file based on the context.
*
* @param {Context} context
* Context.
* @param {VFile} file
* File.
* @param {Callback} next
* Callback
* @returns {undefined}
* Nothing
*/
export function configure(context, file, next) {
if (statistics(file).fatal || file.data.unifiedEngineIgnored) {
next()
return
}
config.load(file.path, handleConfiguration)
context.configuration.load(
file.path,
/**
* @returns {undefined}
* Nothing.
*/
function (error, configuration) {
let index = -1
function handleConfiguration(err, configuration) {
var plugins
var options
var plugin
var length
var index
var name
if (!configuration) {
next(error)
return
}
if (err) {
return next(err)
}
// If there was no explicit corresponding config file found
if (!configuration.filePath && context.settings.ignoreUnconfigured) {
debug('Ignoring file w/o corresponding config file')
file.data.unifiedEngineIgnored = true
} else {
/* c8 ignore next 1 -- could be missing if a `configTransform` returns weird things. */
const plugins = configuration.plugins || []
/* Store configuration on the context object. */
debug('Using settings `%j`', configuration.settings)
processor.data('settings', configuration.settings)
// Store configuration on the context object.
debug('Using settings `%j`', configuration.settings)
context.processor.data('settings', configuration.settings)
plugins = configuration.plugins
length = plugins.length
index = -1
debug('Using `%d` plugins', plugins.length)
debug('Using `%d` plugins', length)
while (++index < plugins.length) {
const plugin = plugins[index][0]
let options = plugins[index][1]
while (++index < length) {
plugin = plugins[index][0]
options = plugins[index][1]
if (options === false) {
continue
}
if (options === false) {
continue
}
/* c8 ignore next 6 -- allow for default arguments in es2020. */
if (
options === null ||
(typeof options === 'object' && isEmpty(options))
) {
options = undefined
}
/* Allow for default arguments in es2020. */
if (options === null || (object(options) && empty(options))) {
options = undefined
debug(
'Using plugin `%s`, with options `%j`',
/* c8 ignore next 4 -- V8 is good at inferring names. */
('displayName' in plugin ? plugin.displayName : 'name') ||
plugin.name ||
'function',
options
)
context.processor.use(plugin, options, context.fileSet)
}
}
name = fnName(plugin) || 'function'
debug('Using plug-in `%s`, with options `%j`', name, options)
try {
processor.use(plugin, options, fileSet)
} catch (err) {
/* istanbul ignore next - Shouldn’t happen anymore! */
return next(err)
}
next()
}
next()
}
)
}

@@ -1,69 +0,88 @@

'use strict'
/**
* @typedef {import('trough').Callback} Callback
*
* @typedef {import('vfile').VFile} VFile
*
* @typedef {import('./index.js').Context} Context
*/
var fs = require('fs')
var path = require('path')
var debug = require('debug')('unified-engine:file-pipeline:copy')
var string = require('x-is-string')
import fs from 'node:fs'
import path from 'node:path'
import {fileURLToPath} from 'node:url'
import createDebug from 'debug'
module.exports = copy
const debug = createDebug('unified-engine:file-pipeline:copy')
var stat = fs.stat
var dirname = path.dirname
var resolve = path.resolve
var relative = path.relative
/**
* Move a file.
*
* @param {Context} context
* Context.
* @param {VFile} file
* File.
* @param {Callback} next
* Callback.
* @returns {undefined}
* Nothing.
*/
export function copy(context, file, next) {
const output = context.settings.output
const currentPath = file.path
/* Move a file. */
function copy(context, file, fileSet, next) {
var output = context.output
var multi = fileSet.expected > 1
var outpath = output
var currentPath = file.path
if (!string(outpath)) {
if (
output === undefined ||
typeof output === 'boolean' ||
file.data.unifiedEngineIgnored
) {
debug('Not copying')
return next()
next()
return
}
outpath = resolve(context.cwd, outpath)
const outputPath = typeof output === 'object' ? fileURLToPath(output) : output
const outpath = path.resolve(context.settings.cwd, outputPath)
debug('Copying `%s`', currentPath)
stat(outpath, onstatfile)
function onstatfile(err, stats) {
if (err) {
fs.stat(outpath, function (error, stats) {
if (error) {
if (
err.code !== 'ENOENT' ||
output.charAt(output.length - 1) === path.sep
error.code !== 'ENOENT' ||
outputPath.charAt(outputPath.length - 1) === path.sep
) {
return next(
new Error('Cannot read output directory. Error:\n' + err.message)
)
return next(new Error('Cannot read output folder', {cause: error}))
}
stat(dirname(outpath), onstatparent)
// This is either given an error, or the parent exists which is a folder,
// but we should keep the basename of the given file.
fs.stat(path.dirname(outpath), function (error) {
if (error) {
next(new Error('Cannot access parent folder', {cause: error}))
} else {
done(false)
}
})
} else {
done(stats.isDirectory())
}
}
})
/* This is either given an error, or the parent exists which
* is a directory, but we should keep the basename of the
* given file. */
function onstatparent(err) {
if (err) {
next(new Error('Cannot read parent directory. Error:\n' + err.message))
} else {
done(false)
}
}
function done(directory) {
if (!directory && multi) {
return next(
new Error('Cannot write multiple files to single output: ' + outpath)
/**
* @param {boolean} folder
* Whether the output is a folder.
* @returns {undefined}
* Nothing.
*/
function done(folder) {
if (!folder && context.fileSet.expected > 1) {
next(
new Error(
'Cannot write multiple files to single output `' + outpath + '`'
)
)
return
}
file[directory ? 'dirname' : 'path'] = relative(file.cwd, outpath)
file[folder ? 'dirname' : 'path'] = path.relative(file.cwd, outpath)

@@ -70,0 +89,0 @@ debug('Copying document from %s to %s', currentPath, file.path)

@@ -1,40 +0,61 @@

'use strict'
/**
* @typedef {import('trough').Callback} Callback
*
* @typedef {import('vfile').VFile} VFile
*
* @typedef {import('./index.js').Context} Context
*/
var fs = require('fs')
var path = require('path')
var debug = require('debug')('unified-engine:file-pipeline:file-system')
import fs from 'node:fs'
import path from 'node:path'
import createDebug from 'debug'
import {statistics} from 'vfile-statistics'
module.exports = fileSystem
const debug = createDebug('unified-engine:file-pipeline:file-system')
var writeFile = fs.writeFile
var resolve = path.resolve
/* Write a virtual file to the file-system.
* Ignored when `output` is not given. */
function fileSystem(context, file, fileSet, next) {
var destinationPath
if (!context.output) {
/**
* Write a virtual file to the file-system.
* Ignored when `output` is not given.
*
* @param {Context} context
* Context.
* @param {VFile} file
* File.
* @param {Callback} next
* Callback.
* @returns {undefined}
* Nothing.
*/
export function fileSystem(context, file, next) {
if (!context.settings.output) {
debug('Ignoring writing to file-system')
return next()
next()
return
}
if (!file.data.unifiedEngineGiven) {
debug('Ignoring programmatically added file')
return next()
if (!file.data.unifiedEngineGiven || file.data.unifiedEngineIgnored) {
debug('Ignoring programmatically added or ignored file')
next()
return
}
destinationPath = file.path
let destinationPath = file.path
if (!destinationPath) {
debug('Cannot write file without a `destinationPath`')
return next(new Error('Cannot write file without an output path '))
next(new Error('Cannot write file without an output path'))
return
}
destinationPath = resolve(context.cwd, destinationPath)
if (statistics(file).fatal) {
debug('Cannot write file with a fatal error')
next()
return
}
destinationPath = path.resolve(context.settings.cwd, destinationPath)
debug('Writing document to `%s`', destinationPath)
file.stored = true
writeFile(destinationPath, file.toString(), next)
fs.writeFile(destinationPath, file.toString(), next)
}

@@ -1,66 +0,110 @@

'use strict'
/**
* @typedef {import('trough').Pipeline} Pipeline
*
* @typedef {import('unified').Processor} Processor
*
* @typedef {import('unist').Node} Node
*
* @typedef {import('vfile').VFile} VFile
*
* @typedef {import('vfile-message').VFileMessage} VFileMessage
*
* @typedef {import('../configuration.js').Configuration} Configuration
* @typedef {import('../file-set.js').FileSet} FileSet
* @typedef {import('../index.js').Settings} Settings
*/
var trough = require('trough')
var read = require('./read')
var configure = require('./configure')
var parse = require('./parse')
var transform = require('./transform')
var queue = require('./queue')
var stringify = require('./stringify')
var copy = require('./copy')
var stdout = require('./stdout')
var fileSystem = require('./file-system')
/**
* @typedef Context
* Context.
* @property {Configuration} configuration
* Configuration.
* @property {FileSet} fileSet
* File set.
* @property {Processor} processor
* Processor.
* @property {Settings} settings
* Settings.
* @property {Node | undefined} [tree]
* Tree.
*
* @callback Next
* Callback.
* @returns {undefined}
* Nothing.
*/
/* Expose: This pipeline ensures each of the pipes
* always runs: even if the read pipe fails,
* queue and write trigger. */
module.exports = trough()
.use(
chunk(
trough()
.use(read)
.use(configure)
.use(parse)
.use(transform)
)
)
import {trough} from 'trough'
import {configure} from './configure.js'
import {copy} from './copy.js'
import {fileSystem} from './file-system.js'
import {parse} from './parse.js'
import {queue} from './queue.js'
import {read} from './read.js'
import {stdout} from './stdout.js'
import {stringify} from './stringify.js'
import {transform} from './transform.js'
// This pipeline ensures each of the pipes always runs: even if the read pipe
// fails, queue and write run.
export const filePipeline = trough()
.use(chunk(trough().use(configure).use(read).use(parse).use(transform)))
.use(chunk(trough().use(queue)))
.use(
chunk(
trough()
.use(stringify)
.use(copy)
.use(stdout)
.use(fileSystem)
)
)
.use(chunk(trough().use(stringify).use(copy).use(stdout).use(fileSystem)))
/* Factory to run a pipe. Wraps a pipe to trigger an
* error on the `file` in `context`, but still call
* `next`. */
/**
* Factory to run a pipe.
* Wraps a pipe to trigger an error on the `file` in `context`, but still call
* `next`.
*
* @param {Pipeline} pipe
* Pipe.
* @returns
* Run function.
*/
function chunk(pipe) {
return run
/* Run the bound bound pipe and handles any errors. */
function run(context, file, fileSet, next) {
pipe.run(context, file, fileSet, one)
/**
* Run the bound pipe and handle any errors.
*
* @param {Context} context
* Context.
* @param {VFile} file
* File.
* @param {Next} next
* Callback.
* @returns {undefined}
* Nothing.
*/
function run(context, file, next) {
pipe.run(
context,
file,
/**
* @param {VFileMessage | undefined} error
* Error.
* @returns {undefined}
* Nothing.
*/
function (error) {
const messages = file.messages
function one(err) {
var messages = file.messages
var index
if (error) {
const index = messages.indexOf(error)
if (err) {
index = messages.indexOf(err)
if (index === -1) {
err = file.message(err)
index = messages.length - 1
if (index === -1) {
const message = file.message('Cannot process file', {
cause: error
})
message.fatal = true
} else {
messages[index].fatal = true
}
}
messages[index].fatal = true
next()
}
next()
}
)
}
}

@@ -1,37 +0,53 @@

'use strict'
/**
* @typedef {import('unist').Node} Node
*
* @typedef {import('vfile').VFile} VFile
*
* @typedef {import('./index.js').Context} Context
*/
var debug = require('debug')('unified-engine:file-pipeline:parse')
var stats = require('vfile-statistics')
var json = require('parse-json')
import createDebug from 'debug'
import parseJson from 'parse-json'
import {statistics} from 'vfile-statistics'
module.exports = parse
const debug = createDebug('unified-engine:file-pipeline:parse')
/* Fill a file with a tree. */
function parse(context, file) {
var message
if (stats(file).fatal) {
/**
* Fill a file with a tree.
*
* @param {Context} context
* Context.
* @param {VFile} file
* File.
* @returns {undefined}
* Nothing.
*/
export function parse(context, file) {
if (statistics(file).fatal || file.data.unifiedEngineIgnored) {
return
}
if (context.treeIn) {
if (context.settings.treeIn) {
debug('Not parsing already parsed document')
try {
context.tree = json(file.toString())
} catch (err) {
message = file.message(
new Error('Cannot read file as JSON\n' + err.message)
// Assume it’s a valid node.
const tree = /** @type {Node} */ (
/** @type {unknown} */ (parseJson(file.toString()))
)
context.tree = tree
} catch (error) {
const cause = /** @type {Error} */ (error)
const message = file.message('Cannot read file as JSON', {cause})
message.fatal = true
}
/* Add the preferred extension to ensure the file, when compiled, is
* correctly recognized. Only add it if there’s a path — not if the
* file is for example stdin. */
// Add the preferred extension to ensure the file, when serialized, is
// correctly recognised.
// Only add it if there is a path — not if the file is for example stdin.
if (file.path) {
file.extname = context.extensions[0]
file.extname = context.settings.extensions[0]
}
file.contents = ''
file.value = ''

@@ -38,0 +54,0 @@ return

@@ -1,20 +0,33 @@

'use strict'
/**
* @typedef {import('trough').Callback} Callback
*
* @typedef {import('vfile').VFile} VFile
*
* @typedef {import('./index.js').Context} Context
*/
var debug = require('debug')('unified-engine:file-pipeline:queue')
var stats = require('vfile-statistics')
import createDebug from 'debug'
import {statistics} from 'vfile-statistics'
module.exports = queue
const debug = createDebug('unified-engine:file-pipeline:queue')
/* Queue all files which came this far.
* When the last file gets here, run the file-set pipeline
* and flush the queue. */
function queue(context, file, fileSet, next) {
var origin = file.history[0]
var map = fileSet.complete
var complete = true
const own = {}.hasOwnProperty
if (!map) {
map = {}
fileSet.complete = map
}
/**
* Queue all files which came this far.
* When the last file gets here, run the file-set pipeline and flush the queue.
*
* @param {Context} context
* Context.
* @param {VFile} file
* File.
* @param {Callback} next
* Callback.
* @returns {undefined}
* Nothing.
*/
export function queue(context, file, next) {
let origin = file.history[0]
const map = context.fileSet.complete
let complete = true

@@ -25,3 +38,7 @@ debug('Queueing `%s`', origin)

fileSet.valueOf().forEach(each)
const files = context.fileSet.valueOf()
let index = -1
while (++index < files.length) {
each(files[index])
}

@@ -33,10 +50,15 @@ if (!complete) {

fileSet.complete = {}
context.fileSet.complete = {}
context.fileSet.pipeline.run(context.fileSet, done)
fileSet.pipeline.run(fileSet, done)
/**
* @param {VFile} file
* File.
* @returns {undefined}
* Nothing.
*/
function each(file) {
var key = file.history[0]
const key = file.history[0]
if (stats(file).fatal) {
if (statistics(file).fatal || file.data.unifiedEngineIgnored) {
return

@@ -53,10 +75,18 @@ }

function done(err) {
/**
* @param {Error | undefined} error
* Error.
* @returns {undefined}
* Nothing.
*/
function done(error) {
debug('Flushing: all files can be flushed')
/* Flush. */
// Flush.
for (origin in map) {
map[origin](err)
if (own.call(map, origin)) {
map[origin](error)
}
}
}
}

@@ -1,37 +0,52 @@

'use strict'
/**
* @typedef {import('trough').Callback} Callback
*
* @typedef {import('vfile').VFile} VFile
*
* @typedef {import('./index.js').Context} Context
*/
var fs = require('fs')
var path = require('path')
var debug = require('debug')('unified-engine:file-pipeline:read')
var stats = require('vfile-statistics')
import fs from 'node:fs'
import path from 'node:path'
import createDebug from 'debug'
import {statistics} from 'vfile-statistics'
module.exports = read
const debug = createDebug('unified-engine:file-pipeline:read')
var resolve = path.resolve
var readFile = fs.readFile
/**
* Fill a file with its value when not already filled.
*
* @param {Context} context
* Context.
* @param {VFile} file
* File.
* @param {Callback} next
* Callback.
* @returns {undefined}
* Nothing.
*/
export function read(context, file, next) {
let filePath = file.path
/* Fill a file with its contents when not already filled. */
function read(context, file, fileSet, next) {
var filePath = file.path
if (file.contents || file.data.unifiedEngineStreamIn) {
debug('Not reading file `%s` with contents', filePath)
if (
(file.value !== null && file.value !== undefined) ||
file.data.unifiedEngineStreamIn
) {
debug('Not reading file `%s` with `value`', filePath)
next()
} else if (stats(file).fatal) {
debug('Not reading failed file `%s`', filePath)
} else if (statistics(file).fatal || file.data.unifiedEngineIgnored) {
debug('Not reading failed or ignored file `%s`', filePath)
next()
} else {
filePath = resolve(context.cwd, filePath)
filePath = path.resolve(context.settings.cwd, filePath)
debug('Reading `%s` in `%s`', filePath, 'utf8')
readFile(filePath, 'utf8', onread)
}
fs.readFile(filePath, 'utf8', function (error, value) {
debug('Read `%s` (error: %s)', filePath, error)
function onread(err, contents) {
debug('Read `%s` (err: %s)', filePath, err)
file.value = value || ''
file.contents = contents || ''
next(err)
next(error)
})
}
}

@@ -1,16 +0,38 @@

'use strict'
/**
* @typedef {import('trough').Callback} Callback
*
* @typedef {import('vfile').VFile} VFile
*
* @typedef {import('./index.js').Context} Context
*/
var debug = require('debug')('unified-engine:file-pipeline:stdout')
var stats = require('vfile-statistics')
import createDebug from 'debug'
import {statistics} from 'vfile-statistics'
module.exports = stdout
const debug = createDebug('unified-engine:file-pipeline:stdout')
/* Write a virtual file to `streamOut`.
* Ignored when `output` is given, more than one file
* was processed, or `out` is false. */
function stdout(context, file, fileSet, next) {
/**
* Write a virtual file to `streamOut`.
* Ignored when `output` is given, more than one file was processed, or `out`
* is false.
*
* @param {Context} context
* Context.
* @param {VFile} file
* File.
* @param {Callback} next
* Callback.
* @returns {undefined}
* Nothing.
*/
export function stdout(context, file, next) {
if (!file.data.unifiedEngineGiven) {
debug('Ignoring programmatically added file')
next()
} else if (stats(file).fatal || context.output || !context.out) {
} else if (
statistics(file).fatal ||
file.data.unifiedEngineIgnored ||
context.settings.output ||
!context.settings.out
) {
debug('Ignoring writing to `streamOut`')

@@ -20,4 +42,4 @@ next()

debug('Writing document to `streamOut`')
context.streamOut.write(file.toString(), next)
context.settings.streamOut.write(file.toString(), next)
}
}

@@ -1,21 +0,38 @@

'use strict'
/**
* @typedef {import('vfile').VFile} VFile
*
* @typedef {import('./index.js').Context} Context
*/
var debug = require('debug')('unified-engine:file-pipeline:stringify')
var stats = require('vfile-statistics')
var inspect = require('unist-util-inspect')
import assert from 'node:assert/strict'
import createDebug from 'debug'
import {inspectColor, inspectNoColor} from 'unist-util-inspect'
import {statistics} from 'vfile-statistics'
module.exports = stringify
const debug = createDebug('unified-engine:file-pipeline:stringify')
/* Stringify a tree. */
function stringify(context, file) {
var processor = context.processor
var tree = context.tree
var value
/**
* Stringify a tree.
*
* @param {Context} context
* Context.
* @param {VFile} file
* File.
* @returns {undefined}
* Nothing.
*/
export function stringify(context, file) {
/** @type {unknown} */
let value
if (stats(file).fatal) {
debug('Not compiling failed document')
if (statistics(file).fatal || file.data.unifiedEngineIgnored) {
debug('Not compiling failed or ignored document')
return
}
if (!context.output && !context.out && !context.alwaysStringify) {
if (
!context.settings.output &&
!context.settings.out &&
!context.settings.alwaysStringify
) {
debug('Not compiling document without output settings')

@@ -27,4 +44,4 @@ return

if (context.inspect) {
/* Add a `txt` extension if there’s a path. */
if (context.settings.inspect) {
// Add a `txt` extension if there is a path.
if (file.path) {

@@ -34,6 +51,8 @@ file.extname = '.txt'

value = inspect[context.color ? 'color' : 'noColor'](tree) + '\n'
} else if (context.treeOut) {
/* Add a `json` extension to ensure the file is correctly seen as JSON.
* Only add it if there’s a path — not if the file is for example stdin. */
value =
(context.settings.color ? inspectColor : inspectNoColor)(context.tree) +
'\n'
} else if (context.settings.treeOut) {
// Add a `json` extension to ensure the file is correctly seen as JSON.
// Only add it if there is a path — not if the file is for example stdin.
if (file.path) {

@@ -43,11 +62,35 @@ file.extname = '.json'

/* Add the line break to create a valid UNIX file. */
value = JSON.stringify(tree, null, 2) + '\n'
// Add the line feed to create a valid UNIX file.
value = JSON.stringify(context.tree, undefined, 2) + '\n'
} else {
value = processor.stringify(tree, file)
assert(context.tree, '`tree` is defined if we came this far')
value = context.processor.stringify(context.tree, file)
}
file.contents = value
if (value === null || value === undefined) {
// Empty.
} else if (typeof value === 'string' || isUint8Array(value)) {
file.value = value
} else {
file.result = value
}
debug('Compiled document')
debug('Serialized document')
}
/**
* Assert `value` is an `Uint8Array`.
*
* @param {unknown} value
* Thing.
* @returns {value is Uint8Array}
* Whether `value` is an `Uint8Array`.
*/
function isUint8Array(value) {
return Boolean(
value &&
typeof value === 'object' &&
'byteLength' in value &&
'byteOffset' in value
)
}

@@ -1,23 +0,39 @@

'use strict'
/**
* @typedef {import('trough').Callback} Callback
*
* @typedef {import('vfile').VFile} VFile
*
* @typedef {import('./index.js').Context} Context
*/
var debug = require('debug')('unified-engine:file-pipeline:transform')
var stats = require('vfile-statistics')
import assert from 'node:assert/strict'
import createDebug from 'debug'
import {statistics} from 'vfile-statistics'
module.exports = transform
const debug = createDebug('unified-engine:file-pipeline:transform')
/* Transform the tree associated with a file with
* configured plug-ins. */
function transform(context, file, fileSet, next) {
if (stats(file).fatal) {
/**
* Transform the tree associated with a file with configured plugins.
*
* @param {Context} context
* Context.
* @param {VFile} file
* File.
* @param {Callback} next
* Callback.
* @returns {undefined}
* Nothing.
*/
export function transform(context, file, next) {
if (statistics(file).fatal || file.data.unifiedEngineIgnored) {
next()
} else {
assert(context.tree, '`tree` is defined at this point')
debug('Transforming document `%s`', file.path)
context.processor.run(context.tree, file, onrun)
context.processor.run(context.tree, file, function (error, node) {
debug('Transformed document (error: %s)', error)
context.tree = node
next(error)
})
}
function onrun(err, node) {
debug('Transformed document (error: %s)', err)
context.tree = node
next(err)
}
}

@@ -1,9 +0,24 @@

'use strict'
/**
* @typedef {import('../index.js').Settings} Settings
*/
var Configuration = require('../configuration')
/**
* @typedef Context
* Context.
* @property {Configuration | undefined} [configuration]
* Configuration.
*/
module.exports = configure
import {Configuration} from '../configuration.js'
function configure(context, settings) {
/**
* @param {Context} context
* Context.
* @param {Settings} settings
* Settings.
* @returns {undefined}
* Nothing.
*/
export function configure(context, settings) {
context.configuration = new Configuration(settings)
}

@@ -1,21 +0,41 @@

'use strict'
/**
* @typedef {import('trough').Callback} Callback
*
* @typedef {import('vfile').VFile} VFile
*
* @typedef {import('../configuration.js').Configuration} Configuration
* @typedef {import('../index.js').Settings} Settings
*/
var Ignore = require('../ignore')
var find = require('../finder')
/**
* @typedef Context
* Context.
* @property {Array<VFile | string>} files
* Files.
* @property {Configuration | undefined} [configuration]
* Configuration.
*/
module.exports = fileSystem
import {finder} from '../finder.js'
import {Ignore} from '../ignore.js'
/* Find files from the file-system. */
function fileSystem(context, settings, next) {
var input = context.files
if (input.length === 0) {
/**
* @param {Context} context
* Context.
* @param {Settings} settings
* Settings.
* @param {Callback} next
* Callback.
* @returns {undefined}
* Nothing.
*/
export function fileSystem(context, settings, next) {
if (context.files.length === 0) {
next()
} else {
find(
input,
finder(
context.files,
{
cwd: settings.cwd,
extensions: settings.extensions,
silentlyIgnore: settings.silentlyIgnore,
ignore: new Ignore({

@@ -25,35 +45,50 @@ cwd: settings.cwd,

ignoreName: settings.ignoreName,
ignorePath: settings.ignorePath
})
ignorePath: settings.ignorePath,
ignorePathResolveFrom: settings.ignorePathResolveFrom
}),
ignorePatterns: settings.ignorePatterns,
silentlyIgnore: settings.silentlyIgnore
},
onfound
)
}
function (error, result) {
/* c8 ignore next 4 -- glob errors are unusual. */
if (!result) {
next(error)
return
}
function onfound(err, result) {
var output = result.files
const output = result.files
/* Sort alphabetically. Everything’s unique so we don’t care
* about cases where left and right are equal. */
output.sort(sortAlphabetically)
// Sort alphabetically.
// Everything is unique so we do not care about cases where left and right
// are equal.
output.sort(sortAlphabetically)
/* Mark as given. This allows outputting files,
* which can be pretty dangerous, so it’s “hidden”. */
output.forEach(markAsGiven)
// Mark as given.
// This allows outputting files, which can be pretty dangerous, so it’s
// “hidden”.
let index = -1
while (++index < output.length) {
output[index].data.unifiedEngineGiven = true
}
context.files = output
context.files = output
/* If `out` wasn’t set, detect it based on
* whether one file was given. */
if (settings.out === null || settings.out === undefined) {
settings.out = result.oneFileMode
}
// If `out` was not set, detect it based on whether one file was given.
if (settings.out === undefined) {
settings.out = result.oneFileMode
}
next(err)
next(error)
}
)
}
function markAsGiven(file) {
file.data.unifiedEngineGiven = true
}
/**
* @param {VFile} left
* File.
* @param {VFile} right
* Other file.
* @returns {number}
* Order.
*/
function sortAlphabetically(left, right) {

@@ -60,0 +95,0 @@ return left.path < right.path ? -1 : 1

@@ -1,11 +0,9 @@

'use strict'
import {trough} from 'trough'
import {configure} from './configure.js'
import {fileSystem} from './file-system.js'
import {log} from './log.js'
import {stdin} from './stdin.js'
import {transform} from './transform.js'
var trough = require('trough')
var configure = require('./configure')
var fileSystem = require('./file-system')
var stdin = require('./stdin')
var transform = require('./transform')
var log = require('./log')
module.exports = trough()
export const fileSetPipeline = trough()
.use(configure)

@@ -12,0 +10,0 @@ .use(fileSystem)

@@ -1,32 +0,63 @@

'use strict'
/**
* @typedef {import('vfile').VFile} VFile
*
* @typedef {import('../configuration.js').Configuration} Configuration
* @typedef {import('../index.js').Settings} Settings
* @typedef {import('../index.js').VFileReporter} VFileReporter
*/
var xtend = require('xtend')
var load = require('load-plugin')
var report = require('vfile-reporter')
var string = require('x-is-string')
import {loadPlugin} from 'load-plugin'
import {reporter} from 'vfile-reporter'
module.exports = log
/**
* @typedef Context
* Context.
* @property {Array<VFile>} files
* Files.
* @property {Configuration | undefined} [configuration]
* Configuration.
*/
var prefix = 'vfile-reporter'
/**
* @param {Context} context
* Context.
* @param {Settings} settings
* Settings.
* @returns {Promise<undefined>}
* Nothing.
*/
export async function log(context, settings) {
/** @type {VFileReporter} */
let func = reporter
function log(context, settings, next) {
var reporter = settings.reporter || report
var diagnostics
if (typeof settings.reporter === 'string') {
try {
// Assume a valid reporter.
const result = /** @type {VFileReporter} */ (
await loadPlugin(settings.reporter, {
cwd: settings.cwd,
prefix: 'vfile-reporter'
})
)
if (string(reporter)) {
try {
reporter = load(reporter, {cwd: settings.cwd, prefix: prefix})
} catch (err) {
next(new Error('Could not find reporter `' + reporter + '`'))
return
func = result
} catch (error) {
throw new Error('Cannot find reporter `' + settings.reporter + '`', {
cause: error
})
}
} else if (settings.reporter) {
func = settings.reporter
}
diagnostics = reporter(
context.files.filter(given),
xtend(settings.reporterOptions, {
let diagnostics = await func(
context.files.filter(function (file) {
return file.data.unifiedEngineGiven && !file.data.unifiedEngineIgnored
}),
{
...settings.reporterOptions,
color: settings.color,
quiet: settings.quiet,
silent: settings.silent,
color: settings.color
})
silent: settings.silent
}
)

@@ -39,10 +70,6 @@

settings.streamError.write(diagnostics, next)
} else {
next()
return new Promise(function (resolve) {
settings.streamError.write(diagnostics, () => resolve(undefined))
})
}
}
function given(file) {
return file.data.unifiedEngineGiven
}

@@ -1,23 +0,44 @@

'use strict'
/**
* @typedef {import('trough').Callback} Callback
*
* @typedef {import('../index.js').Settings} Settings
*/
var debug = require('debug')('unified-engine:file-set-pipeline:stdin')
var vfile = require('to-vfile')
var concat = require('concat-stream')
/**
* @typedef Context
* Context.
* @property {Array<VFile | string>} files
* Files.
*/
module.exports = stdin
import concatStream from 'concat-stream'
import createDebug from 'debug'
import {VFile} from 'vfile'
function stdin(context, settings, next) {
var streamIn = settings.streamIn
var err
const debug = createDebug('unified-engine:file-set-pipeline:stdin')
if (settings.files && settings.files.length !== 0) {
/**
* @param {Context} context
* Context.
* @param {Settings} settings
* Settings.
* @param {Callback} next
* Callback.
* @returns {undefined}
* Nothing.
*/
export function stdin(context, settings, next) {
if (settings.files && settings.files.length > 0) {
debug('Ignoring `streamIn`')
/** @type {Error | undefined} */
let error
if (settings.filePath) {
err = new Error(
'Do not pass both `--file-path` and real files.\nDid you mean to pass stdin instead of files?'
error = new Error(
'Do not pass both `filePath` and real files.\nDid you mean to pass stdin instead of files?'
)
}
next(err)
next(error)

@@ -27,3 +48,3 @@ return

if (streamIn.isTTY) {
if ('isTTY' in settings.streamIn && settings.streamIn.isTTY) {
debug('Cannot read from `tty` stream')

@@ -37,22 +58,21 @@ next(new Error('No input'))

streamIn.pipe(concat({encoding: 'string'}, read))
settings.streamIn.pipe(
concatStream({encoding: 'string'}, function (value) {
const file = new VFile({path: settings.filePath})
function read(value) {
var file = vfile(settings.filePath || undefined)
debug('Read from `streamIn`')
debug('Read from `streamIn`')
file.cwd = settings.cwd
file.value = value
file.data.unifiedEngineGiven = true
file.data.unifiedEngineStreamIn = true
file.cwd = settings.cwd
file.contents = value
file.data.unifiedEngineGiven = true
file.data.unifiedEngineStreamIn = true
context.files = [file]
context.files = [file]
// If `out` was not set, set `out`.
settings.out = settings.out === undefined ? true : settings.out
/* If `out` wasn’t set, set `out`. */
settings.out =
settings.out === null || settings.out === undefined ? true : settings.out
next()
}
next()
})
)
}

@@ -1,57 +0,82 @@

'use strict'
/**
* @typedef {import('trough').Callback} Callback
*
* @typedef {import('vfile').VFile} VFile
*
* @typedef {import('../configuration.js').Configuration} Configuration
* @typedef {import('../index.js').Settings} Settings
*/
var FileSet = require('../file-set')
var filePipeline = require('../file-pipeline')
/**
* @typedef Context
* Context.
* @property {Array<VFile>} files
* Files.
* @property {Configuration} configuration
* Configuration.
* @property {FileSet} fileSet
* File set.
*/
module.exports = transform
import {filePipeline} from '../file-pipeline/index.js'
import {FileSet} from '../file-set.js'
/* Transform all files. */
function transform(context, settings, next) {
var fileSet = new FileSet()
/**
* Transform all files.
*
* @param {Context} context
* Context.
* @param {Settings} settings
* Settings.
* @param {Callback} next
* Callback.
* @returns {undefined}
* Nothing.
*/
export function transform(context, settings, next) {
const fileSet = new FileSet()
context.fileSet = fileSet
fileSet.on('add', add).on('done', next)
if (context.files.length === 0) {
next()
} else {
context.files.forEach(fileSet.add, fileSet)
}
function add(file) {
fileSet.on('add', function (/** @type {VFile} */ file) {
filePipeline.run(
{
configuration: context.configuration,
fileSet,
// Needed `any`s
// type-coverage:ignore-next-line
processor: settings.processor(),
cwd: settings.cwd,
extensions: settings.extensions,
pluginPrefix: settings.pluginPrefix,
treeIn: settings.treeIn,
treeOut: settings.treeOut,
inspect: settings.inspect,
color: settings.color,
out: settings.out,
output: settings.output,
streamOut: settings.streamOut,
alwaysStringify: settings.alwaysStringify
settings
},
file,
fileSet,
done
)
/**
* @param {Error | undefined} error
* Error.
* @returns {undefined}
* Nothing.
*/
function (error) {
// Does not occur, all failures in `filePipeLine` are failed on each
// file.
/* c8 ignore next 4 -- extra handling that currently isn’t used. */
if (error) {
const message = file.message('Cannot transform file', {cause: error})
message.fatal = true
}
function done(err) {
/* istanbul ignore next - doesn’t occur as all
* failures in `filePipeLine` are failed on each
* file. Still, just to ensure things work in
* the future, we add an extra check. */
if (err) {
err = file.message(err)
err.fatal = true
fileSet.emit('one', file)
}
)
})
fileSet.emit('one', file)
fileSet.on('done', next)
if (context.files.length === 0) {
next()
} else {
let index = -1
while (++index < context.files.length) {
fileSet.add(context.files[index])
}
}
}

@@ -1,120 +0,188 @@

'use strict'
/**
* @typedef {import('trough').Pipeline} Pipeline
*/
var events = require('events')
var inherits = require('util').inherits
var trough = require('trough')
var vfile = require('to-vfile')
var string = require('x-is-string')
/**
* @typedef {(CompleterCallback | CompleterRegular) & {pluginId?: string | symbol | undefined}} Completer
* Completer.
*
* @callback CompleterCallback
* Handle a set having processed, in callback-style.
* @param {FileSet} set
* File set.
* @param {CompleterCallbackNext} next
* Callback called when done.
* @returns {undefined | void}
* Result.
*
* Note: `void` included because TS sometimes infers it.
*
* @callback CompleterCallbackNext
* Callback called when done.
* @param {Error | null | undefined} [error]
* Error.
* @returns {undefined}
* Nothing.
*
* @callback CompleterRegular
* Handle a set having processed.
* @param {FileSet} set
* File set.
* @returns {Promise<undefined> | undefined | void}
* Nothing.
*
* Note: `void` included because TS sometimes infers it.
*/
module.exports = FileSet
import {EventEmitter} from 'node:events'
import {trough} from 'trough'
import {VFile} from 'vfile'
/* FileSet constructor. */
function FileSet() {
var self = this
export class FileSet extends EventEmitter {
/**
* FileSet.
*
* A FileSet is created to process multiple files through unified processors.
* This set, containing all files, is exposed to plugins as an argument to the
* attacher.
*/
constructor() {
super()
self.files = []
self.origins = []
const self = this
self.expected = 0
self.actual = 0
/**
* @deprecated
* Internal field that should be considered private.
* @type {number}
*/
this.actual = 0
/**
* This is used by the `queue` to stash async work.
*
* @deprecated
* Internal field that should be considered private.
* @type {Record<string, Function>}
*/
this.complete = {}
/**
* @deprecated
* Internal field that should be considered private.
* @type {number}
*/
this.expected = 0
/**
* @deprecated
* Internal field that should be considered private.
* @type {Array<VFile>}
*/
this.files = []
/**
* @deprecated
* Internal field that should be considered private.
* @type {Array<string>}
*/
this.origins = []
/**
* @deprecated
* Internal field that should be considered private.
* @type {Pipeline}
*/
this.pipeline = trough()
/**
* @deprecated
* Internal field that should be considered private.
* @type {Array<Completer>}
*/
this.plugins = []
self.pipeline = trough()
self.plugins = []
// Called when a single file has completed it’s pipeline, triggering `done`
// when all files are complete.
this.on('one', function () {
self.actual++
events.init.call(self)
self.on('one', one.bind(self))
}
/* Events. */
inherits(FileSet, events.EventEmitter)
/* Expose methods. */
FileSet.prototype.valueOf = valueOf
FileSet.prototype.use = use
FileSet.prototype.add = add
/* Create an array representation of `fileSet`. */
function valueOf() {
return this.files
}
/* Attach middleware to the pipeline on `fileSet`. */
function use(plugin) {
var self = this
var pipeline = self.pipeline
var duplicate = false
if (plugin && plugin.pluginId) {
duplicate = self.plugins.some(matches)
if (self.actual >= self.expected) {
self.emit('done')
}
})
}
if (!duplicate && self.plugins.indexOf(plugin) !== -1) {
duplicate = true
/**
* Get files in a set.
*/
valueOf() {
return this.files
}
if (!duplicate) {
self.plugins.push(plugin)
pipeline.use(plugin)
}
/**
* Add middleware to be called when done.
*
* @param {Completer} completer
* Plugin.
* @returns
* Self.
*/
use(completer) {
const pipeline = this.pipeline
let duplicate = false
return self
if (completer && completer.pluginId) {
duplicate = this.plugins.some(function (fn) {
return fn.pluginId === completer.pluginId
})
}
function matches(fn) {
return fn.pluginId === plugin.pluginId
}
}
if (!duplicate && this.plugins.includes(completer)) {
duplicate = true
}
/* Add a file to be processed.
*
* Ignores duplicate files (based on the `filePath` at time
* of addition).
*
* Only runs `file-pipeline` on files which have not
* `failed` before addition. */
function add(file) {
var self = this
var origin
if (!duplicate) {
this.plugins.push(completer)
pipeline.use(completer)
}
if (string(file)) {
file = vfile(file)
return this
}
/* Prevent files from being added multiple times. */
origin = file.history[0]
/**
* Add a file.
*
* The given file is processed like other files with a few differences:
*
* * Ignored when their file path is already added
* * Never written to the file system or `streamOut`
* * Not included in the report
*
* @param {VFile | string} file
* File or file path.
* @returns
* Self.
*/
add(file) {
const self = this
if (self.origins.indexOf(origin) !== -1) {
return self
}
if (typeof file === 'string') {
file = new VFile({path: file})
}
self.origins.push(origin)
// Prevent files from being added multiple times.
if (this.origins.includes(file.history[0])) {
return this
}
/* Add. */
self.valueOf().push(file)
self.expected++
this.origins.push(file.history[0])
/* Force an asynchronous operation.
* This ensures that files which fall through
* the file pipeline immediately (e.g., when
* already fatally failed) still queue up
* correctly. */
setImmediate(add)
// Add.
this.valueOf().push(file)
this.expected++
return self
// Force an asynchronous operation.
// This ensures that files which fall through the file pipeline immediately
// (such as, when already fatally failed) still queue up correctly.
setImmediate(function () {
self.emit('add', file)
})
function add() {
self.emit('add', file)
return this
}
}
/* Utility invoked when a single file has completed it's
* pipeline, triggering `done` when all files are complete. */
function one() {
var self = this
self.actual++
if (self.actual >= self.expected) {
self.emit('done')
}
}

@@ -1,188 +0,307 @@

'use strict'
/**
* @template Value
* Value type.
* @callback Callback
* Callback called when something is found.
* @param {Error | undefined} error
* Error.
* @param {Value | undefined} [result]
* Value.
* @returns {undefined}
* Nothing.
*/
var fs = require('fs')
var path = require('path')
var fault = require('fault')
var debug = require('debug')('unified-engine:find-up')
var object = require('is-object')
/**
* @template Value
* Value type.
* @callback Create
* Transform a file to a certain value.
* @param {Buffer} value
* File contents.
* @param {string} filePath
* File path.
* @returns {Promise<Value | undefined> | Value | undefined}
* Value.
*/
module.exports = FindUp
/**
* @typedef FindValue
* Bare interface of value.
* @property {string | undefined} filePath
* File path.
*/
var read = fs.readFile
var resolve = path.resolve
var relative = path.relative
var join = path.join
var dirname = path.dirname
/**
* @template Value
* Value type.
* @typedef Options
* Configuration.
* @property {string} cwd
* Base.
* @property {URL | string | undefined} filePath
* File path of a given file.
* @property {boolean | undefined} [detect=false]
* Whether to detect files (default: `false`).
* @property {Array<string>} names
* Basenames of files to look for.
* @property {Create<Value>} create
* Turn a found file into a value.
*/
FindUp.prototype.load = load
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
import {fileURLToPath} from 'node:url'
import createDebug from 'debug'
import {wrap} from 'trough'
function FindUp(options) {
var self = this
var fp = options.filePath
const debug = createDebug('unified-engine:find-up')
self.cache = {}
self.cwd = options.cwd
self.detect = options.detect
self.names = options.names
self.create = options.create
/**
* @template {FindValue} Value
* Value to find.
*/
export class FindUp {
/**
* @param {Options<Value>} options
* Configuration.
* @returns
* Self.
*/
constructor(options) {
/** @type {Record<string, Array<Callback<Value>> | Value | Error | undefined>} */
this.cache = {}
/** @type {string} */
this.cwd = options.cwd
/** @type {boolean | undefined} */
this.detect = options.detect
/** @type {Array<string>} */
this.names = options.names
/** @type {Create<Value>} */
this.create = options.create
if (fp) {
self.givenFilePath = resolve(options.cwd, fp)
/** @type {string | undefined} */
this.givenFilePath = options.filePath
? path.resolve(
options.cwd,
typeof options.filePath === 'object'
? fileURLToPath(options.filePath)
: options.filePath
)
: undefined
/** @type {Array<Callback<Value>> | Error | Value | undefined} */
this.givenFile
}
}
function load(filePath, callback) {
var self = this
var cache = self.cache
var givenFilePath = self.givenFilePath
var givenFile = self.givenFile
var names = self.names
var create = self.create
var cwd = self.cwd
var parent
/**
* @param {string} filePath
* File path to look from.
* @param {Callback<Value>} callback
* Callback called when done.
* @returns {undefined}
* Nothing.
*/
load(filePath, callback) {
const self = this
const givenFile = this.givenFile
const {givenFilePath} = this
if (givenFilePath) {
if (givenFile) {
apply(callback, givenFile)
} else {
givenFile = [callback]
self.givenFile = givenFile
debug('Checking given file `%s`', givenFilePath)
read(givenFilePath, loadGiven)
}
if (givenFilePath) {
if (givenFile) {
apply(callback, givenFile)
} else {
const self = this
const cbs = [callback]
this.givenFile = cbs
debug('Checking given file `%s`', givenFilePath)
fs.readFile(givenFilePath, function (cause, buf) {
if (cause) {
/** @type {NodeJS.ErrnoException} */
const result = new Error(
'Cannot read given file `' +
path.relative(self.cwd, givenFilePath) +
'`',
{cause}
)
// In `finder.js`, we check for `syscall`, to improve the error.
result.code = 'ENOENT'
result.path = cause.path
result.syscall = cause.syscall
loaded(result)
} else {
wrap(self.create, function (cause, /** @type {Value} */ result) {
if (cause) {
debug(cause.message)
loaded(
new Error(
'Cannot parse given file `' +
path.relative(self.cwd, givenFilePath) +
'`',
{cause}
)
)
} else {
debug('Read given file `%s`', givenFilePath)
loaded(result)
}
})(buf, givenFilePath)
}
return
}
/**
* @param {Error | Value} result
* Result.
* @returns {undefined}
* Nothing.
*/
function loaded(result) {
self.givenFile = result
applyAll(cbs, result)
}
})
}
if (!self.detect) {
return callback()
}
return
}
filePath = resolve(cwd, filePath)
parent = dirname(filePath)
if (!this.detect) {
return callback(undefined)
}
if (parent in cache) {
apply(callback, cache[parent])
} else {
cache[parent] = [callback]
find(parent)
}
filePath = path.resolve(this.cwd, filePath)
const parent = path.dirname(filePath)
function loadGiven(err, buf) {
var cbs = self.givenFile
var result
if (err) {
result = fault(
'Cannot read given file `%s`\n%s',
relative(cwd, givenFilePath),
err.stack
)
result.code = 'ENOENT'
result.path = err.path
result.syscall = err.syscall
if (parent in this.cache) {
apply(callback, this.cache[parent])
} else {
try {
result = create(buf, givenFilePath)
debug('Read given file `%s`', givenFilePath)
} catch (err) {
result = fault(
'Cannot parse given file `%s`\n%s',
relative(cwd, givenFilePath),
err.stack
)
debug(err.message)
}
this.cache[parent] = [callback]
find(parent)
}
givenFile = result
self.givenFile = result
applyAll(cbs, result)
}
/**
* @param {string} folder
* Folder.
* @returns {undefined}
* Nothing.
*/
function find(folder) {
let index = -1
function find(directory) {
var index = -1
var length = names.length
next()
next()
/**
* @returns {undefined}
* Nothing.
*/
function next() {
// Try to read the next file.
// We do not use `readdir` because on huge folders, that could be
// *very* slow.
if (++index < self.names.length) {
fs.readFile(path.join(folder, self.names[index]), done)
} else {
const parent = path.dirname(folder)
function next() {
var parent
/* Try to read the next file. We don’t use `readdir` because on
* huge directories, that could be *very* slow. */
if (++index < length) {
read(join(directory, names[index]), done)
} else {
parent = dirname(directory)
if (directory === parent) {
debug('No files found for `%s`', filePath)
found()
} else if (parent in cache) {
apply(found, cache[parent])
} else {
cache[parent] = [found]
find(parent)
if (folder === parent) {
debug('No files found for `%s`', filePath)
found(undefined)
} else if (parent in self.cache) {
apply(found, self.cache[parent])
} else {
self.cache[parent] = [found]
find(parent)
}
}
}
}
function done(err, buf) {
var name = names[index]
var fp = join(directory, name)
var contents
/**
* @param {NodeJS.ErrnoException | null} error
* Error.
* @param {Buffer | undefined} [buf]
* File value.
* @returns {undefined}
* Nothing.
*/
function done(error, buf) {
const fp = path.join(folder, self.names[index])
/* istanbul ignore if - Hard to test. */
if (err) {
if (err.code === 'ENOENT') {
return next()
if (error) {
if (error.code === 'ENOENT') {
return next()
/* c8 ignore next 11 -- hard to test other errors. */
}
debug(error.message)
return found(
new Error(
'Cannot read file `' + path.relative(self.cwd, fp) + '`',
{cause: error}
)
)
}
err = fault('Cannot read file `%s`\n%s', relative(cwd, fp), err.message)
debug(err.message)
return found(err)
wrap(self.create, function (cause, /** @type {Value} */ result) {
if (cause) {
found(
new Error(
'Cannot parse file `' + path.relative(self.cwd, fp) + '`',
{cause}
)
)
} else if (result && result.filePath) {
debug('Read file `%s`', fp)
found(undefined, result)
} else {
next()
}
})(buf, fp)
}
try {
contents = create(buf, fp)
} catch (err) {
return found(
fault('Cannot parse file `%s`\n%s', relative(cwd, fp), err.message)
)
/**
* @type {Callback<Value>}
* Callback called when done.
*/
function found(error, result) {
const cbs = self.cache[folder]
assert(Array.isArray(cbs), 'always a list if found')
self.cache[folder] = error || result
applyAll(cbs, error || result)
return undefined
}
/* istanbul ignore else - maybe used in the future. */
if (contents) {
debug('Read file `%s`', fp)
found(null, contents)
} else {
next()
}
}
function found(err, result) {
var cbs = cache[directory]
cache[directory] = err || result
applyAll(cbs, err || result)
}
}
/**
* @param {Array<Callback<Value>>} cbs
* Callbacks.
* @param {Error | Value | undefined} result
* Result.
* @returns {undefined}
* Nothing.
*/
function applyAll(cbs, result) {
let index = cbs.length
function applyAll(cbs, result) {
var index = cbs.length
while (index--) {
apply(cbs[index], result)
while (index--) {
apply(cbs[index], result)
}
}
}
function apply(cb, result) {
if (object(result) && typeof result[0] === 'function') {
result.push(cb)
} else if (result instanceof Error) {
cb(result)
} else {
cb(null, result)
/**
* @param {Callback<Value>} cb
* Callback.
* @param {Array<Callback<Value>> | Error | Value | undefined} result
* Result.
* @returns {undefined}
* Nothing.
*/
function apply(cb, result) {
if (Array.isArray(result)) {
result.push(cb)
} else if (result instanceof Error) {
cb(result)
} else {
cb(undefined, result)
}
}
}
}

@@ -1,66 +0,200 @@

'use strict'
/**
* @typedef {import('node:fs').Stats} Stats
* @typedef {import('ignore').Ignore} IgnorePackageClass
* @typedef {import('./ignore.js').Ignore} Ignore
*/
var path = require('path')
var fs = require('fs')
var glob = require('glob')
var vfile = require('to-vfile')
var xtend = require('xtend')
var hidden = require('is-hidden')
var string = require('x-is-string')
/**
* @callback CheckCallback
* Callback called when a file is checked.
* @param {NodeJS.ErrnoException | undefined} error
* Error.
* @param {CheckResult | undefined} [result]
* Result.
* @returns {undefined}
* Nothing.
*
* @typedef {CheckOptionsFields & Options} CheckOptions
* Check options.
*
* @typedef CheckOptionsFields
* Extra options for `check`.
* @property {IgnorePackageClass} extraIgnore
* Extra ignore.
*
* @typedef CheckResult
* Result.
* @property {Stats | undefined} stats
* Stats.
* @property {boolean | undefined} ignored
* Whether the file is ignored.
*
* @callback ExpandCallback
* Callback called when files are expanded.
* @param {Error | undefined} error
* Error.
* @param {ExpandResult | undefined} [result]
* Result.
* @returns {undefined}
* Nothing.
*
* @typedef ExpandResult
* Results.
* @property {Array<VFile | string>} input
* Input.
* @property {Array<VFile>} output
* Output.
*
* @callback FindCallback
* Callback called when files are found.
* @param {Error | undefined} error
* Error.
* @param {FindResult | undefined} [result]
* Result.
* @returns {undefined}
* Nothing.
*
* @typedef FindResult
* Results.
* @property {boolean} oneFileMode
* Whether we looked for an explicit single file only.
* @property {Array<VFile>} files
* Results.
*
* @typedef Options
* Configuration.
* @property {string} cwd
* Base.
* @property {Array<string>} extensions
* Extnames.
* @property {boolean | undefined} silentlyIgnore
* Whether to silently ignore errors.
*
* The default is to throw if an explicitly given file is explicitly ignored.
* @property {Array<string>} ignorePatterns
* Extra ignore patterns.
* @property {Ignore} ignore
* Ignore.
*
* @callback SearchCallback
* Callback called after searching.
* @param {Error | undefined} error
* Error.
* @param {Array<VFile> | undefined} [result]
* Result.
* @returns {undefined}
* Nothing.
*
* @typedef {Options & SearchOptionsFields} SearchOptions
* Search options.
*
* @typedef SearchOptionsFields
* Extra search fields.
* @property {boolean | undefined} [nested]
* Whether this is a nested search.
*/
var readdir = fs.readdir
var stat = fs.stat
var join = path.join
var relative = path.relative
var resolve = path.resolve
var basename = path.basename
var extname = path.extname
var magic = glob.hasMagic
import path from 'node:path'
import fs from 'node:fs'
import {glob, hasMagic} from 'glob'
import ignore_ from 'ignore'
import {VFile} from 'vfile'
module.exports = find
// @ts-expect-error: types of `ignore` are wrong.
const ignore = /** @type {import('ignore')['default']} */ (ignore_)
/* Search `patterns`, a mix of globs, paths, and files. */
function find(input, options, callback) {
expand(input, options, done)
function done(err, result) {
/* istanbul ignore if - glob errors are unusual.
* other errors are on the vfile results. */
if (err) {
callback(err)
/**
* Search `input`, a mix of globs, paths, and files.
*
* @param {Array<VFile | string>} input
* Files, file paths, and globs.
* @param {Options} options
* Configuration (required).
* @param {FindCallback} callback
* Callback.
* @returns {undefined}
* Nothing.
*/
export function finder(input, options, callback) {
expand(input, options, function (error, result) {
/* c8 ignore next 2 -- glob errors are unusual. */
if (error || !result) {
callback(error)
} else {
callback(null, {oneFileMode: oneFileMode(result), files: result.output})
callback(undefined, {
files: result.output,
oneFileMode: oneFileMode(result)
})
}
}
})
}
/* Expand the given glob patterns, search given and found
* directories, and map to vfiles. */
/**
* Expand the given glob patterns, search given and found folders, and map
* to vfiles.
*
* @param {Array<VFile | string>} input
* List of files, file paths, and globs.
* @param {Options} options
* Configuration (required).
* @param {ExpandCallback} next
* Callback.
* @returns {undefined}
* Nothing.
*/
function expand(input, options, next) {
var cwd = options.cwd
var paths = []
var actual = 0
var expected = 0
var failed
/** @type {Array<VFile | string>} */
const paths = []
let actual = 0
let expected = 0
let index = -1
/** @type {boolean | undefined} */
let failed
input.forEach(each)
while (++index < input.length) {
let file = input[index]
if (typeof file === 'string') {
if (hasMagic(file)) {
expected++
glob(file, {cwd: options.cwd}).then(
function (files) {
/* c8 ignore next 3 -- glob errors are unusual. */
if (failed) {
return
}
if (!expected) {
search(paths, options, done)
}
actual++
paths.push(...files)
function each(file) {
if (string(file)) {
if (magic(file)) {
expected++
glob(file, {cwd: cwd}, one)
if (actual === expected) {
search(paths, options, done1)
}
},
/**
* @param {Error} error
* Error.
* @returns {undefined}
* Nothing.
*/
/* c8 ignore next 8 -- glob errors are unusual. */
function (error) {
if (failed) {
return
}
failed = true
done1(error)
}
)
} else {
/* `relative` to make the paths canonical. */
file = relative(cwd, resolve(cwd, file)) || '.'
// `relative` to make the paths canonical.
file =
path.relative(options.cwd, path.resolve(options.cwd, file)) || '.'
paths.push(file)
}
} else {
file.cwd = cwd
file.path = relative(cwd, file.path)
file.history = [file.path]
const fp = file.path ? path.relative(options.cwd, file.path) : options.cwd
file.cwd = options.cwd
file.path = fp
file.history = [fp]
paths.push(file)

@@ -70,28 +204,20 @@ }

function one(err, files) {
/* istanbul ignore if - glob errors are unusual. */
if (failed) {
return
}
/* istanbul ignore if - glob errors are unusual. */
if (err) {
failed = true
done(err)
} else {
actual++
paths = paths.concat(files)
if (actual === expected) {
search(paths, options, done)
}
}
if (!expected) {
search(paths, options, done1)
}
function done(err, files) {
/* istanbul ignore if - `search` currently does not give errors. */
if (err) {
next(err)
/**
* @param {Error | undefined} error
* Error.
* @param {Array<VFile> | undefined} [files]
* List of files.
* @returns {undefined}
* Nothing.
*/
function done1(error, files) {
/* c8 ignore next 2 -- `search` currently does not give errors. */
if (error || !files) {
next(error)
} else {
next(null, {input: paths, output: files})
next(undefined, {input: paths, output: files})
}

@@ -101,24 +227,47 @@ }

/* Search `paths`. */
/**
* Search `paths`.
*
* @param {Array<VFile | string>} input
* List of files, file paths, and globs.
* @param {SearchOptions} options
* Configuration (required).
* @param {SearchCallback} next
* Callback.
* @returns {undefined}
* Nothing.
*/
function search(input, options, next) {
var cwd = options.cwd
var silent = options.silentlyIgnore
var nested = options.nested
var extensions = options.extensions
var files = []
var expected = 0
var actual = 0
const extraIgnore = ignore().add(options.ignorePatterns)
let expected = 0
let actual = 0
let index = -1
/** @type {Array<VFile>} */
const files = []
input.forEach(each)
while (++index < input.length) {
each(input[index])
}
if (!expected) {
next(null, files)
next(undefined, files)
}
return each
/**
* @param {VFile | string} file
* File or file path.
* @returns {undefined}
* Nothing.
*/
function each(file) {
var part = base(file)
const ext = typeof file === 'string' ? path.extname(file) : file.extname
if (nested && (hidden(part) || part === 'node_modules')) {
// Normalise globs.
if (typeof file === 'string') {
file = file.split('/').join(path.sep)
}
const part = base(file)
if (options.nested && part && part === 'node_modules') {
return

@@ -129,67 +278,96 @@ }

statAndIgnore(file, options, handle)
check(
file,
{...options, extraIgnore},
/**
* @returns {undefined}
* Nothing.
*/
function (error, result) {
const ignored = result && result.ignored
const dir = result && result.stats && result.stats.isDirectory()
function handle(err, result) {
var ignored = result && result.ignored
var dir = result && result.stats && result.stats.isDirectory()
if (ignored && (options.nested || options.silentlyIgnore)) {
return one(undefined, [])
}
if (ignored && (nested || silent)) {
return one(null, [])
}
if (!ignored && dir) {
fs.readdir(
path.resolve(options.cwd, filePath(file)),
function (error, basenames) {
/* c8 ignore next 11 -- should not happen: the folder is `stat`ed ok, but reading it is not. */
if (error) {
const otherFile = new VFile({path: filePath(file)})
otherFile.cwd = options.cwd
if (!ignored && dir) {
return readdir(resolve(cwd, filePath(file)), directory)
}
try {
otherFile.fail('Cannot read folder')
} catch {
// Empty.
}
if (
nested &&
!dir &&
extensions.length !== 0 &&
extensions.indexOf(extname(file)) === -1
) {
return one(null, [])
}
one(undefined, [otherFile])
} else {
search(
basenames.map(function (name) {
return path.join(filePath(file), name)
}),
{...options, nested: true},
one
)
}
}
)
return
}
file = vfile(file)
file.cwd = cwd
if (
!dir &&
options.nested &&
options.extensions.length > 0 &&
(!ext || !options.extensions.includes(ext))
) {
return one(undefined, [])
}
if (ignored) {
try {
file.fail('Cannot process specified file: it’s ignored')
} catch (err) {}
}
file = typeof file === 'string' ? new VFile({path: file}) : file
file.cwd = options.cwd
if (err && err.code === 'ENOENT') {
try {
file.fail(err.syscall === 'stat' ? 'No such file or directory' : err)
} catch (err) {}
}
if (ignored) {
const message = file.message(
'Cannot process specified file: it’s ignored'
)
message.fatal = true
}
one(null, [file])
}
if (error && error.code === 'ENOENT') {
if (error.syscall === 'stat') {
const message = file.message('No such file or folder', {
cause: error
})
message.fatal = true
} else {
const message = file.message('Cannot find file', {cause: error})
message.fatal = true
}
}
function directory(err, basenames) {
var file
/* istanbul ignore if - Should not happen often: the directory
* is `stat`ed first, which was ok, but reading it is not. */
if (err) {
file = vfile(filePath(file))
file.cwd = cwd
try {
file.fail('Cannot read directory')
} catch (err) {}
one(null, [file])
} else {
search(basenames.map(concat), xtend(options, {nested: true}), one)
one(undefined, [file])
}
}
)
/* Error is never given. Always given `results`. */
/**
* Error is never given. Always given `results`.
*
* @param {Error | undefined} _
* Error.
* @param {Array<VFile> | undefined} [results]
* Results.
* @returns {undefined}
* Nothing.
*/
function one(_, results) {
/* istanbul ignore else - always given. */
/* istanbul ignore else - Always given. */
if (results) {
files = files.concat(results)
files.push(...results)
}

@@ -200,45 +378,71 @@

if (actual === expected) {
next(null, files)
next(undefined, files)
}
}
function concat(value) {
return join(filePath(file), value)
}
}
}
function statAndIgnore(file, options, callback) {
var ignore = options.ignore
var fp = resolve(options.cwd, filePath(file))
var expected = 1
var actual = 0
var stats
var ignored
/**
* @param {VFile | string} file
* File.
* @param {CheckOptions} options
* Configuration.
* @param {CheckCallback} callback
* Callback.
* @returns {undefined}
* Nothing.
*/
function check(file, options, callback) {
const fp = path.resolve(options.cwd, filePath(file))
const normal = path.relative(options.cwd, fp)
let expected = 1
let actual = 0
/** @type {Stats | undefined} */
let stats
/** @type {boolean | undefined} */
let ignored
if (!file.contents) {
if (
typeof file === 'string' ||
file.value === null ||
file.value === undefined
) {
expected++
stat(fp, handleStat)
fs.stat(fp, function (error, value) {
stats = value
onStatOrCheck(error || undefined)
})
}
ignore.check(fp, handleIgnore)
options.ignore.check(fp, function (error, value) {
ignored = value
function handleStat(err, value) {
stats = value
one(err)
}
// `ignore.check` is sometimes sync, we need to force async behavior.
setImmediate(onStatOrCheck, error || undefined)
})
function handleIgnore(err, value) {
ignored = value
one(err)
}
function one(err) {
/**
* @param {Error | undefined} error
* Error.
* @returns {undefined}
* Nothing.
*/
function onStatOrCheck(error) {
actual++
if (err) {
callback(err)
if (error) {
callback(error)
actual = -1
} else if (actual === expected) {
callback(null, {stats: stats, ignored: ignored})
callback(undefined, {
ignored:
ignored ||
(normal === '' ||
normal === '..' ||
normal.charAt(0) === path.sep ||
normal.slice(0, 3) === '..' + path.sep
? false
: options.extraIgnore.ignores(normal)),
stats
})
}

@@ -248,10 +452,28 @@ }

/**
* @param {VFile | string} file
* File.
* @returns {string | undefined}
* Basename.
*/
function base(file) {
return string(file) ? basename(file) : file.basename
return typeof file === 'string' ? path.basename(file) : file.basename
}
/**
* @param {VFile | string} file
* File.
* @returns {string}
* File path.
*/
function filePath(file) {
return string(file) ? file : file.path
return typeof file === 'string' ? file : file.path
}
/**
* @param {ExpandResult} result
* Result.
* @returns {boolean}
* Whether we looked for an explicit single file only.
*/
function oneFileMode(result) {

@@ -258,0 +480,0 @@ return (

@@ -1,50 +0,124 @@

'use strict'
/**
* @typedef {import('ignore').Ignore} IgnorePackageClass
*/
var path = require('path')
var gitignore = require('ignore')
var FindUp = require('./find-up')
/**
* @callback Callback
* Callback.
* @param {Error | undefined} error
* Error.
* @param {boolean | undefined} [result]
* Whether to ignore.
* @returns {undefined}
* Nothing.
*
* @typedef Options
* Configuration.
* @property {string} cwd
* Base.
* @property {boolean | undefined} detectIgnore
* Whether to detect ignore files.
* @property {string | undefined} ignoreName
* Basename of ignore files.
* @property {URL | string | undefined} ignorePath
* Explicit path to an ignore file.
* @property {ResolveFrom | undefined} ignorePathResolveFrom
* How to resolve.
*
* @typedef {'cwd' | 'dir'} ResolveFrom
* How to resolve.
*
* @typedef {IgnorePackageClass & ResultFields} Result
* Result.
*
* @typedef ResultFields
* Extra fields.
* @property {string} filePath
* File path.
*
*/
module.exports = Ignore
import path from 'node:path'
import ignore_ from 'ignore'
import {FindUp} from './find-up.js'
Ignore.prototype.check = check
// @ts-expect-error: types of `ignore` are wrong.
const ignore = /** @type {import('ignore')['default']} */ (ignore_)
var dirname = path.dirname
var relative = path.relative
var resolve = path.resolve
export class Ignore {
/**
* @param {Options} options
* Configuration.
* @returns
* Self.
*/
constructor(options) {
/** @type {string} */
this.cwd = options.cwd
/** @type {ResolveFrom | undefined} */
this.ignorePathResolveFrom = options.ignorePathResolveFrom
function Ignore(options) {
this.cwd = options.cwd
/** @type {FindUp<Result>} */
this.findUp = new FindUp({
create,
cwd: options.cwd,
detect: options.detectIgnore,
filePath: options.ignorePath,
names: options.ignoreName ? [options.ignoreName] : []
})
}
this.findUp = new FindUp({
filePath: options.ignorePath,
cwd: options.cwd,
detect: options.detectIgnore,
names: options.ignoreName ? [options.ignoreName] : [],
create: create
})
}
/**
* @param {string} filePath
* File path.
* @param {Callback} callback
* Callback
* @returns {undefined}
* Nothing.
*/
check(filePath, callback) {
const self = this
function check(filePath, callback) {
var self = this
this.findUp.load(filePath, function (error, ignoreSet) {
if (error) {
callback(error)
} else if (ignoreSet) {
const normal = path.relative(
path.resolve(
self.cwd,
self.ignorePathResolveFrom === 'cwd' ? '.' : ignoreSet.filePath
),
path.resolve(self.cwd, filePath)
)
self.findUp.load(filePath, done)
function done(err, ignore) {
var normal
if (err) {
callback(err)
} else if (ignore) {
normal = relative(ignore.filePath, resolve(self.cwd, filePath))
callback(null, normal ? ignore.ignores(normal) : false)
} else {
callback(null, false)
}
if (
normal === '' ||
normal === '..' ||
normal.charAt(0) === path.sep ||
normal.slice(0, 3) === '..' + path.sep
) {
callback(undefined, false)
} else {
callback(undefined, ignoreSet.ignores(normal))
}
} else {
callback(undefined, false)
}
})
}
}
/**
* @param {Buffer} buf
* File value.
* @param {string} filePath
* File path.
* @returns {Result}
* Result.
*/
function create(buf, filePath) {
var ignore = gitignore().add(String(buf))
ignore.filePath = dirname(filePath)
return ignore
// Cast so we can patch `filePath`.
const result = /** @type {Result} */ (ignore().add(String(buf)))
result.filePath = path.dirname(filePath)
return result
}

@@ -1,29 +0,231 @@

'use strict'
/**
* @typedef {import('unified').Processor<any, any, any, any, any>} Processor
* @typedef {import('unified').Settings} UnifiedSettings
*
* @typedef {import('vfile').VFile} VFile
*
* @typedef {import('vfile-reporter').Options} VFileReporterKnownFields
*
* @typedef {import('./configuration.js').ConfigTransform} ConfigTransform
* @typedef {import('./configuration.js').PresetSupportingSpecifiers} PresetSupportingSpecifiers
* @typedef {import('./file-set.js').FileSet} FileSet
* @typedef {import('./ignore.js').ResolveFrom} ResolveFrom
*/
var PassThrough = require('stream').PassThrough
var statistics = require('vfile-statistics')
var fileSetPipeline = require('./file-set-pipeline')
/**
* @callback Callback
* Callback called when done.
*
* Called with a fatal error if things went horribly wrong (probably due to
* incorrect configuration), or a status code and the processing context.
* @param {Error | undefined} error
* Error.
* @param {0 | 1 | undefined} [code]
* Exit code, `0` if successful or `1` if unsuccessful.
* @param {Context | undefined} [context]
* Processing context.
* @returns {undefined | void}
* Nothing.
*
* Note: `void` included because `promisify` otherwise fails.
*
* @typedef Context
* Processing context.
* @property {FileSet | undefined} fileSet
* Internally used info.
* @property {Array<VFile> | undefined} files
* Processed files.
*
* @typedef Options
* Configuration.
*
* > 👉 **Note**: `options.processor` is required.
* @property {boolean | undefined} [alwaysStringify=false]
* Whether to always serialize successfully processed files (default:
* `false`).
* @property {boolean | undefined} [color=false]
* Whether to report with ANSI color sequences (default: `false`); given to
* the reporter.
* @property {ConfigTransform | undefined} [configTransform]
* Transform config files from a different schema (optional).
* @property {URL | string | undefined} [cwd]
* Folder to search files in, load plugins from, and more (default:
* `process.cwd()`).
* @property {PresetSupportingSpecifiers | undefined} [defaultConfig]
* Default configuration to use if no config file is given or found
* (optional).
* @property {boolean | undefined} [detectConfig]
* Whether to search for configuration files (default: `true` if
* `options.packageField` or `options.rcName`).
* @property {boolean | undefined} [detectIgnore]
* Whether to search for ignore files (default: `true` if
* `options.ignoreName`).
* @property {Array<string> | undefined} [extensions]
* Search for files with these extensions, when folders are passed
* (optional); generated files are also given the first extension if `treeIn`
* is on and `output` is on or points to a folder.
* @property {URL | string | undefined} [filePath]
* File path to process the given file on `streamIn` as (optional).
* @property {Array<URL | VFile | string> | undefined} [files]
* Paths or globs to files and folders, or virtual files, to process
* (optional).
* @property {boolean | undefined} [frail=false]
* Call back with an unsuccessful (`1`) code on warnings as well as errors
* (default: `false`).
* @property {string | undefined} [ignoreName]
* Name of ignore files to load (optional).
* @property {URL | string | undefined} [ignorePath]
* Filepath to an ignore file to load (optional).
* @property {ResolveFrom | undefined} [ignorePathResolveFrom]
* Resolve patterns in `ignorePath` from the current working
* directory (`'cwd'`) or the ignore file’s folder (`'dir'`) (default:
* `'dir'`).
* @property {Array<string> | undefined} [ignorePatterns]
* Patterns to ignore in addition to ignore files (optional).
* @property {boolean | undefined} [ignoreUnconfigured=false]
* Ignore files that do not have an associated detected configuration file
* (default: `false`); either `rcName` or `packageField` must be defined too;
* cannot be combined with `rcPath` or `detectConfig: false`.
* @property {boolean | undefined} [inspect=false]
* Whether to output a formatted syntax tree for debugging (default:
* `false`).
* @property {boolean | undefined} [out=false]
* Whether to write the processed file to `streamOut` (default: `false`).
* @property {URL | boolean | string | undefined} [output=false]
* Whether to write successfully processed files, and where to (default:
* `false`).
*
* * When `true`, overwrites the given files
* * When `false`, does not write to the file system
* * When pointing to an existing folder, files are written to that
* folder and keep their original basenames
* * When the parent folder of the given path exists and one file is
* processed, the file is written to the given path
* @property {string | undefined} [packageField]
* Field where configuration can be found in `package.json` files
* (optional).
* @property {string | undefined} [pluginPrefix]
* Prefix to use when searching for plugins (optional).
* @property {PresetSupportingSpecifiers['plugins'] | undefined} [plugins]
* Plugins to use (optional).
* @property {() => Processor} processor
* Unified processor to transform files (required).
* @property {boolean | undefined} [quiet=false]
* Do not report successful files (default: `false`); given to the reporter.
* @property {string | undefined} [rcName]
* Name of configuration files to load (optional).
* @property {string | undefined} [rcPath]
* Filepath to a configuration file to load (optional).
* @property {VFileReporter | string | undefined} [reporter]
* Reporter to use (default: `'vfile-reporter'`); if a `string` is passed,
* it’s loaded from `cwd`, and `'vfile-reporter-'` can be omitted
* @property {VFileReporterOptions | undefined} [reporterOptions]
* Config to pass to the used reporter (optional).
* @property {UnifiedSettings | undefined} [settings]
* Configuration for the parser and compiler of the processor (optional).
* @property {boolean | undefined} [silent=false]
* Report only fatal errors (default: `false`); given to the reporter.
* @property {boolean | undefined} [silentlyIgnore=false]
* Skip given files if they are ignored (default: `false`).
* @property {NodeJS.WritableStream | undefined} [streamError]
* Stream to write the report (if any) to (default: `process.stderr`).
* @property {NodeJS.ReadableStream | undefined} [streamIn]
* Stream to read from if no files are given (default: `process.stdin`).
* @property {NodeJS.WritableStream | undefined} [streamOut]
* Stream to write processed files to (default: `process.stdout`); nothing is
* streamed if either `out` is `false`, `output` is not `false`, multiple
* files are processed, or a fatal error occurred while processing a file.
* @property {boolean | undefined} [tree=false]
* Whether to treat both input and output as a syntax tree (default:
* `false`).
* @property {boolean | undefined} [treeIn]
* Whether to treat input as a syntax tree (default: `options.tree`).
* @property {boolean | undefined} [treeOut]
* Whether to output as a syntax tree (default: `options.tree`).
*
* @typedef Settings
* Resolved {@link Options `Options`} passed around.
* @property {Options['processor']} processor
* @property {Exclude<Options['cwd'], URL | undefined>} cwd
* @property {Array<VFile | string>} files
* @property {Exclude<Options['extensions'], undefined>} extensions
* @property {Exclude<Options['streamIn'], undefined>} streamIn
* @property {Options['filePath']} filePath
* @property {Exclude<Options['streamOut'], undefined>} streamOut
* @property {Exclude<Options['streamError'], undefined>} streamError
* @property {Options['out']} out
* @property {Options['output']} output
* @property {Options['alwaysStringify']} alwaysStringify
* @property {Options['tree']} tree
* @property {Options['treeIn']} treeIn
* @property {Options['treeOut']} treeOut
* @property {Options['inspect']} inspect
* @property {Options['rcName']} rcName
* @property {Options['packageField']} packageField
* @property {Options['detectConfig']} detectConfig
* @property {Options['rcPath']} rcPath
* @property {Exclude<Options['settings'], undefined>} settings
* @property {Options['ignoreName']} ignoreName
* @property {Options['detectIgnore']} detectIgnore
* @property {Options['ignorePath']} ignorePath
* @property {Options['ignorePathResolveFrom']} ignorePathResolveFrom
* @property {Exclude<Options['ignorePatterns'], undefined>} ignorePatterns
* @property {Exclude<Options['ignoreUnconfigured'], undefined>} ignoreUnconfigured
* @property {Exclude<Options['silentlyIgnore'], undefined>} silentlyIgnore
* @property {Options['plugins']} plugins
* @property {Options['pluginPrefix']} pluginPrefix
* @property {Options['configTransform']} configTransform
* @property {Options['defaultConfig']} defaultConfig
* @property {Options['reporter']} reporter
* @property {Options['reporterOptions']} reporterOptions
* @property {Options['color']} color
* @property {Options['silent']} silent
* @property {Options['quiet']} quiet
* @property {Options['frail']} frail
*
* @callback VFileReporter
* Reporter.
*
* This is essentially the interface of `vfile-reporter`, with added support
* for unknown fields in options and async support.
* @param {Array<VFile>} files
* Files.
* @param {VFileReporterOptions} options
* Configuration.
* @returns {Promise<string> | string}
* Report.
*
* @typedef {VFileReporterKnownFields & {[key: string]: unknown}} VFileReporterOptions
* Configuration.
*/
module.exports = run
import process from 'node:process'
import {PassThrough} from 'node:stream'
import {fileURLToPath} from 'node:url'
import {statistics} from 'vfile-statistics'
import {fileSetPipeline} from './file-set-pipeline/index.js'
/* Run the file set pipeline once.
* `callback` is invoked with a fatal error,
* or with a status code (`0` on success, `1` on failure). */
function run(options, callback) {
var settings = {}
var stdin = new PassThrough()
var tree
var detectConfig
var hasConfig
var detectIgnore
var hasIgnore
/**
* Process.
*
* @param {Options} options
* Configuration (required).
* @param {Callback} callback
* Callback.
* @returns {undefined}
* Nothing.
*/
export function engine(options, callback) {
/** @type {Settings} */
const settings = {}
/** @type {NodeJS.ReadStream | PassThrough} */
let stdin = new PassThrough()
try {
stdin = process.stdin
} catch (err) {
/* Obscure bug in Node (seen on windows):
* - https://github.com/nodejs/node/blob/f856234/lib/internal/
* process/stdio.js#L82;
* - https://github.com/AtomLinter/linter-markdown/pull/85.
*/
// See: <https://github.com/nodejs/node/blob/f856234/lib/internal/process/stdio.js#L82>,
// <https://github.com/AtomLinter/linter-markdown/pull/85>.
/* c8 ignore next 3 -- obscure bug in Node (seen on Windows). */
} catch {
// Empty.
}

@@ -39,16 +241,24 @@

/* Processor. */
// Processor.
settings.processor = options.processor
/* Path to run as. */
settings.cwd = options.cwd || process.cwd()
// Path to run as.
settings.cwd =
typeof options.cwd === 'object'
? fileURLToPath(options.cwd)
: options.cwd || process.cwd()
/* Input. */
settings.files = options.files || []
settings.extensions = (options.extensions || []).map(extension)
// Input.
settings.files = (options.files || []).map(function (d) {
const result = isUrl(d) ? fileURLToPath(d) : d
return result
})
settings.extensions = (options.extensions || []).map(function (ext) {
return ext.charAt(0) === '.' ? ext : '.' + ext
})
settings.filePath = options.filePath || null
settings.filePath = options.filePath
settings.streamIn = options.streamIn || stdin
/* Output. */
// Output.
settings.streamOut = options.streamOut || process.stdout

@@ -60,3 +270,3 @@ settings.streamError = options.streamError || process.stderr

/* Null overwrites config settings, `undefined` doesn’t. */
// Null overwrites config settings, `undefined` does not.
if (settings.output === null || settings.output === undefined) {

@@ -70,4 +280,4 @@ settings.output = undefined

/* Process phase management. */
tree = options.tree || false
// Process phase management.
const tree = options.tree || false

@@ -86,5 +296,5 @@ settings.treeIn = options.treeIn

/* Configuration. */
detectConfig = options.detectConfig
hasConfig = Boolean(options.rcName || options.packageField)
// Configuration.
const detectConfig = options.detectConfig
const hasConfig = Boolean(options.rcName || options.packageField)

@@ -101,5 +311,5 @@ if (detectConfig && !hasConfig) {

: detectConfig
settings.rcName = options.rcName || null
settings.rcPath = options.rcPath || null
settings.packageField = options.packageField || null
settings.rcName = options.rcName
settings.rcPath = options.rcPath
settings.packageField = options.packageField
settings.settings = options.settings || {}

@@ -109,5 +319,6 @@ settings.configTransform = options.configTransform

/* Ignore. */
detectIgnore = options.detectIgnore
hasIgnore = Boolean(options.ignoreName)
// Ignore.
const detectIgnore = options.detectIgnore
const hasIgnore = Boolean(options.ignoreName)
const ignoreUnconfigured = Boolean(options.ignoreUnconfigured)

@@ -118,6 +329,33 @@ settings.detectIgnore =

: detectIgnore
settings.ignoreName = options.ignoreName || null
settings.ignorePath = options.ignorePath || null
settings.ignoreName = options.ignoreName
settings.ignorePath = options.ignorePath
settings.ignorePathResolveFrom = options.ignorePathResolveFrom || 'dir'
settings.ignorePatterns = options.ignorePatterns || []
settings.ignoreUnconfigured = ignoreUnconfigured
settings.silentlyIgnore = Boolean(options.silentlyIgnore)
if (ignoreUnconfigured && settings.rcPath) {
return next(
new Error(
'Cannot accept both `rcPath` and `ignoreUnconfigured`, as former prevents looking for configuration but the latter requires it'
)
)
}
if (ignoreUnconfigured && !hasConfig) {
return next(
new Error(
'Missing `rcName` or `packageField` with `ignoreUnconfigured`, the former are needed to look for configuration'
)
)
}
if (ignoreUnconfigured && !settings.detectConfig) {
return next(
new Error(
'Cannot use `detectConfig: false` with `ignoreUnconfigured`, the former prevents looking for configuration but the latter requires it'
)
)
}
if (detectIgnore && !hasIgnore) {

@@ -127,27 +365,35 @@ return next(new Error('Missing `ignoreName` with `detectIgnore`'))

/* Plug-ins. */
settings.pluginPrefix = options.pluginPrefix || null
settings.plugins = options.plugins || {}
// Plugins.
settings.pluginPrefix = options.pluginPrefix
settings.plugins = options.plugins || []
/* Reporting. */
settings.reporter = options.reporter || null
settings.reporterOptions = options.reporterOptions || null
// Reporting.
settings.reporter = options.reporter
settings.reporterOptions = options.reporterOptions
settings.color = options.color || false
settings.silent = options.silent || false
settings.quiet = options.quiet || false
settings.frail = options.frail || false
settings.silent = options.silent
settings.quiet = options.quiet
settings.frail = options.frail
/* Process. */
fileSetPipeline.run({files: options.files || []}, settings, next)
// Process.
fileSetPipeline.run({files: settings.files}, settings, next)
function next(err, context) {
var stats = statistics((context || {}).files)
var failed = Boolean(
/**
* @param {Error | undefined} error
* Error.
* @param {Context | undefined} [context]
* Context.
* @returns {undefined}
* Nothing.
*/
function next(error, context) {
const stats = statistics((context || {}).files || [])
const failed = Boolean(
settings.frail ? stats.fatal || stats.warn : stats.fatal
)
if (err) {
callback(err)
if (error) {
callback(error)
} else {
callback(null, failed ? 1 : 0, context)
callback(undefined, failed ? 1 : 0, context)
}

@@ -157,4 +403,18 @@ }

function extension(ext) {
return ext.charAt(0) === '.' ? ext : '.' + ext
/**
*
* @param {unknown} value
* Value.
* @returns {value is URL}
* Whether `value` is a URL.
*/
function isUrl(value) {
return (
value !== null &&
typeof value === 'object' &&
'href' in value &&
'searchParams' in value &&
// Extra, for vfiles.
!('path' in value)
)
}
{
"name": "unified-engine",
"version": "6.0.1",
"description": "Engine to process multiple files with unified",
"version": "11.0.0",
"description": "unified engine to process multiple files, lettings users configure from the file system",
"license": "MIT",
"keywords": [
"unified",
"engine",
"processor",
"engine"
"unified"
],
"repository": "unifiedjs/unified-engine",
"bugs": "https://github.com/unifiedjs/unified-engine/issues",
"author": "Titus Wormer <tituswormer@gmail.com> (http://wooorm.com)",
"author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
},
"contributors": [
"Titus Wormer <tituswormer@gmail.com> (http://wooorm.com)"
"Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
"Christian Murphy <christian.murphy.42@gmail.com>"
],
"main": "lib/index.js",
"sideEffects": false,
"type": "module",
"exports": "./index.js",
"files": [
"lib/"
"lib/",
"index.d.ts",
"index.js"
],
"dependencies": {
"concat-stream": "^1.5.1",
"debug": "^3.1.0",
"fault": "^1.0.0",
"fn-name": "^2.0.1",
"glob": "^7.0.3",
"ignore": "^3.2.0",
"@types/concat-stream": "^2.0.0",
"@types/debug": "^4.0.0",
"@types/is-empty": "^1.0.0",
"@types/node": "^20.0.0",
"@types/unist": "^3.0.0",
"@ungap/structured-clone": "^1.0.0",
"concat-stream": "^2.0.0",
"debug": "^4.0.0",
"glob": "^10.0.0",
"ignore": "^5.0.0",
"is-empty": "^1.0.0",
"is-hidden": "^1.0.1",
"is-object": "^1.0.1",
"js-yaml": "^3.6.1",
"load-plugin": "^2.0.0",
"parse-json": "^4.0.0",
"to-vfile": "^4.0.0",
"trough": "^1.0.0",
"unist-util-inspect": "^4.1.2",
"vfile-reporter": "^5.0.0",
"vfile-statistics": "^1.1.0",
"x-is-string": "^0.1.0",
"xtend": "^4.0.1"
"is-plain-obj": "^4.0.0",
"load-plugin": "^5.0.0",
"parse-json": "^7.0.0",
"trough": "^2.0.0",
"unist-util-inspect": "^8.0.0",
"vfile": "^6.0.0",
"vfile-message": "^4.0.0",
"vfile-reporter": "^8.0.0",
"vfile-statistics": "^3.0.0",
"yaml": "^2.0.0"
},
"devDependencies": {
"nyc": "^11.0.0",
"prettier": "^1.12.1",
"remark-cli": "^5.0.0",
"remark-preset-wooorm": "^4.0.0",
"strip-ansi": "^4.0.0",
"tape": "^4.4.0",
"unified": "^7.0.0",
"vfile-reporter-json": "^1.0.1",
"vfile-reporter-pretty": "^1.0.1",
"xo": "^0.21.0"
"@types/parse-json": "^4.0.0",
"@types/ungap__structured-clone": "^0.3.0",
"c8": "^8.0.0",
"prettier": "^3.0.0",
"remark": "^14.0.0",
"remark-cli": "^11.0.0",
"remark-preset-wooorm": "^9.0.0",
"remark-toc": "^8.0.0",
"strip-ansi": "^7.0.0",
"type-coverage": "^2.0.0",
"typescript": "^5.0.0",
"unified": "^11.0.0",
"vfile-reporter-json": "^4.0.0",
"vfile-reporter-pretty": "^7.0.0",
"xo": "^0.56.0"
},
"scripts": {
"format": "remark . -qfo && prettier --write '**/*.js' && xo --fix",
"test-api": "node test",
"test-coverage": "nyc --reporter lcov tape test/index.js",
"test": "npm run format && npm run test-coverage"
"build": "tsc --build --clean && tsc --build && type-coverage",
"format": "remark . --frail --output --quiet && prettier . --log-level warn --write && xo --fix",
"prepack": "npm run build && npm run format",
"test": "npm run build && npm run format && npm run test-coverage",
"test-api": "node --conditions development test/index.js",
"test-coverage": "c8 --100 --check-coverage --reporter lcov npm run test-api"
},
"nyc": {
"check-coverage": true,
"lines": 100,
"functions": 100,
"branches": 100
"prettier": {
"bracketSpacing": false,
"singleQuote": true,
"semi": false,
"tabWidth": 2,
"trailingComma": "none",
"useTabs": false
},
"remarkConfig": {
"plugins": [
"preset-wooorm"
"remark-preset-wooorm",
[
"remark-toc",
{
"heading": "contents",
"maxDepth": 3,
"tight": true
}
]
]
},
"prettier": {
"tabWidth": 2,
"useTabs": false,
"singleQuote": true,
"bracketSpacing": false,
"semi": false,
"trailingComma": "none"
},
"xo": {
"overrides": [
{
"files": "test/fixtures/**/*.js",
"rules": {
"unicorn/no-empty-file": "off",
"unicorn/prefer-module": "off"
}
}
],
"prettier": true,
"esnext": false,
"rules": {
"no-var": "off",
"prefer-arrow-callback": "off",
"object-shorthand": "off",
"complexity": "off",
"guard-for-in": "off"
"no-await-in-loop": "off",
"no-unused-expressions": "off",
"unicorn/no-this-assignment": "off",
"unicorn/prefer-at": "off",
"unicorn/prefer-event-target": "off",
"unicorn/prefer-string-replace-all": "off"
}
},
"typeCoverage": {
"atLeast": 100,
"detail": true,
"ignoreCatch": true,
"#": "needed `any`s",
"ignoreFiles": [
"lib/index.d.ts",
"lib/configuration.d.ts"
],
"strict": true
}
}

@@ -1,26 +0,117 @@

# unified-engine [![Build Status][travis-badge]][travis] [![Coverage Status][codecov-badge]][codecov]
# unified-engine
Engine to process multiple files with [**unified**][unified], allowing users
to [configure][] from the file-system.
[![Build][build-badge]][build]
[![Coverage][coverage-badge]][coverage]
[![Downloads][downloads-badge]][downloads]
[![Sponsors][sponsors-badge]][collective]
[![Backers][backers-badge]][collective]
[![Chat][chat-badge]][chat]
## Projects
**[unified][]** engine to process multiple files, lettings users
[configure][config-files] from the file system.
The following projects wrap the engine:
## Contents
* [`unified-args`][args] — Create CLIs for processors
* [`unified-engine-gulp`][gulp] — Create Gulp plug-ins
* [`unified-engine-atom`][atom] — Create Atom Linters for processors
* [What is this?](#what-is-this)
* [When should I use this?](#when-should-i-use-this)
* [Install](#install)
* [Use](#use)
* [API](#api)
* [`engine(options, callback)`](#engineoptions-callback)
* [`Configuration`](#configuration)
* [`Completer`](#completer)
* [`Callback`](#callback)
* [`ConfigTransform`](#configtransform)
* [`Context`](#context)
* [`FileSet`](#fileset)
* [`Options`](#options)
* [`Preset`](#preset)
* [`ResolveFrom`](#resolvefrom)
* [`VFileReporter`](#vfilereporter)
* [Config files](#config-files)
* [Explicit configuration](#explicit-configuration)
* [Implicit configuration](#implicit-configuration)
* [Examples](#examples)
* [Ignore files](#ignore-files)
* [Explicit ignoring](#explicit-ignoring)
* [Implicit ignoring](#implicit-ignoring)
* [Extra ignoring](#extra-ignoring)
* [Ignoring](#ignoring)
* [Examples](#examples-1)
* [Plugins](#plugins)
* [Examples](#examples-2)
* [`options.alwaysStringify`](#optionsalwaysstringify)
* [`options.configTransform`](#optionsconfigtransform)
* [`options.defaultConfig`](#optionsdefaultconfig)
* [`options.detectConfig`](#optionsdetectconfig)
* [`options.detectIgnore`](#optionsdetectignore)
* [`options.extensions`](#optionsextensions)
* [`options.filePath`](#optionsfilepath)
* [`options.files`](#optionsfiles)
* [`options.frail`](#optionsfrail)
* [`options.ignoreName`](#optionsignorename)
* [`options.ignorePath`](#optionsignorepath)
* [`options.ignorePathResolveFrom`](#optionsignorepathresolvefrom)
* [`options.ignorePatterns`](#optionsignorepatterns)
* [`options.ignoreUnconfigured`](#optionsignoreunconfigured)
* [`options.inspect`](#optionsinspect)
* [`options.out`](#optionsout)
* [`options.output`](#optionsoutput)
* [`options.packageField`](#optionspackagefield)
* [`options.pluginPrefix`](#optionspluginprefix)
* [`options.plugins`](#optionsplugins)
* [`options.processor`](#optionsprocessor)
* [`options.quiet`](#optionsquiet)
* [`options.rcName`](#optionsrcname)
* [`options.rcPath`](#optionsrcpath)
* [`options.reporter` and `options.reporterOptions`](#optionsreporter-and-optionsreporteroptions)
* [`options.settings`](#optionssettings)
* [`options.silent`](#optionssilent)
* [`options.streamError`](#optionsstreamerror)
* [`options.streamIn`](#optionsstreamin)
* [`options.streamOut`](#optionsstreamout)
* [`options.tree`](#optionstree)
* [`options.treeIn`](#optionstreein)
* [`options.treeOut`](#optionstreeout)
* [Types](#types)
* [Compatibility](#compatibility)
* [Security](#security)
* [Contribute](#contribute)
* [License](#license)
## Installation
## What is this?
[npm][]:
This package is the engine.
It’s what you use underneath when you use [`remark-cli`][remark-cli] or a
language server.
Compared to unified, this deals with multiple files, often from the file
system, and with [configuration files][config-files] and
[ignore files][ignore-files].
```bash
## When should I use this?
You typically use something that wraps this, such as:
* [`unified-args`][unified-args]
— create CLIs
* [`unified-engine-gulp`][unified-engine-gulp]
— create Gulp plugins
* [`unified-language-server`][unified-language-server]
— create language servers
You can use this to make such things.
## Install
This package is [ESM only][esm].
In Node.js (version 16+), install with [npm][]:
```sh
npm install unified-engine
```
## Usage
## Use
The following example processes all files in the current directory with a
markdown extension with [**remark**][remark], allows [configuration][configure]
The following example processes all files in the current folder with a
markdown extension with **[remark][]**, allows [configuration][config-files]
from `.remarkrc` and `package.json` files, ignoring files from `.remarkignore`

@@ -30,15 +121,20 @@ files, and more.

```js
var engine = require('unified-engine')
var remark = require('remark')
/**
* @typedef {import('unified-engine').Callback} Callback
*/
import process from 'node:process'
import {remark} from 'remark'
import {engine} from 'unified-engine'
engine(
{
processor: remark,
color: true,
extensions: ['md', 'markdown', 'mkd', 'mkdn', 'mkdown'],
files: ['.'],
extensions: ['md', 'markdown', 'mkd', 'mkdn', 'mkdown'],
ignoreName: '.remarkignore',
packageField: 'remarkConfig',
pluginPrefix: 'remark',
rcName: '.remarkrc',
packageField: 'remarkConfig',
ignoreName: '.remarkignore',
color: true
processor: remark,
rcName: '.remarkrc'
},

@@ -48,134 +144,1416 @@ done

function done(err) {
if (err) throw err
/** @type {Callback} */
function done(error, code) {
if (error) throw error
process.exitCode = code
}
```
## Table of Contents
## API
* [API](#api)
* [engine(options, callback)](#engineoptions-callback)
* [Plug-ins](#plug-ins)
* [Configuration](#configuration)
* [Ignoring](#ignoring)
* [Contribute](#contribute)
* [License](#license)
This package exports the identifiers [`Configuration`][api-configuration] and
[`engine`][api-engine].
There is no default export.
## API
### `engine(options, callback)`
Process files according to `options` and invoke [`callback`][callback] when
done.
Process.
###### [`options`][options]
###### Parameters
* [`processor`][processor] ([`Processor`][unified-processor])
— Unified processor to transform files
* [`cwd`][cwd] (`string`, default: `process.cwd()`)
— Directory to search files in, load plug-ins from, and more
* [`files`][files] (`Array.<string|VFile>`, optional)
— Paths or globs to files and directories, or virtual files, to process
* [`extensions`][extensions] (`Array.<string>`, optional)
— If `files` matches directories, include files with `extensions`
* [`streamIn`][stream-in] (`ReadableStream`, default: `process.stdin`)
— Stream to read from if no files are found or given
* [`filePath`][file-path] (`string`, optional)
— File path to process the given file on `streamIn` as
* [`streamOut`][stream-out] (`WritableStream`, default: `process.stdout`)
— Stream to write processed files to
* [`streamError`][stream-error] (`WritableStream`, default: `process.stderr`)
— Stream to write the report (if any) to
* [`out`][out] (`boolean`, default: depends)
— Whether to write the processed file to `streamOut`
* [`output`][output] (`boolean` or `string`, default: `false`)
— Whether to write successfully processed files, and where to
* [`alwaysStringify`][always-stringify] (`boolean`, default: `false`)
— Whether to always compile successfully processed files
* [`tree`][tree] (`boolean`, default: `false`)
— Whether to treat both input and output as a syntax tree
* [`treeIn`][tree-in] (`boolean`, default: `tree`)
— Whether to treat input as a syntax tree
* [`treeOut`][tree-out] (`boolean`, default: `tree`)
— Whether to treat output as a syntax tree
* [`inspect`][inspect] (`boolean`, default: `false`)
— Whether to output a formatted syntax tree
* [`rcName`][rc-name] (`string`, optional)
— Name of configuration files to load
* [`packageField`][package-field] (`string`, optional)
— Property at which configuration can be found in `package.json` files
* [`detectConfig`][detect-config] (`boolean`, default: whether `rcName` or
`packageField` is given)
— Whether to search for configuration files
* [`rcPath`][rc-path] (`string`, optional)
— File-path to a configuration file to load
* [`settings`][settings] (`Object`, optional)
— Configuration for the parser and compiler of the processor
* [`ignoreName`][ignore-name] (`string`, optional)
— Name of ignore files to load
* [`detectIgnore`][detect-ignore] (`boolean`, default: whether `ignoreName`
is given)
— Whether to search for ignore files
* [`ignorePath`][ignore-path] (`string`, optional)
— File-path to an ignore file to load
* [`silentlyIgnore`][silently-ignore] (`boolean`, default: `false`)
— Skip given files if they are ignored
* [`plugins`][plugins] (`Array|Object`, optional)
— Plug-ins to use
* [`pluginPrefix`][plugin-prefix] (`string`, optional)
— Optional prefix to use when searching for plug-ins
* [`configTransform`][config-transform] (`Function`, optional)
— Transform config files from a different schema
* [`reporter`][reporter] (`string` or `function`, default:
`require('vfile-reporter')`)
— Reporter to use
* [`reporterOptions`][reporteroptions] (`Object?`, optional)
— Config to pass to the used reporter
* [`color`][color] (`boolean`, default: `false`)
— Whether to report with ANSI colour sequences
* [`silent`][silent] (`boolean`, default: `false`)
— Report only fatal errors
* [`quiet`][quiet] (`boolean`, default: `silent`)
— Do not report successful files
* [`frail`][frail] (`boolean`, default: `false`)
— Call back with an unsuccessful (`1`) code on warnings as well as errors
* `options` ([`Options`][api-options], required)
— configuration
* `callback` ([`Callback`][api-callback], required)
— configuration
#### `function callback(err[, code, context])`
###### Returns
Callback invoked when processing according to `options` is complete.
Invoked with either a fatal error if processing went horribly wrong
(probably due to incorrect configuration), or a status code and the
processing context.
Nothing (`undefined`).
### `Configuration`
Internal class to load configuration files.
Exposed to build more complex integrations.
###### Parameters
* `err` (`Error`) — Fatal error
* `code` (`number`) — Either `0` if successful, or `1` if
unsuccessful. The latter occurs if [fatal][] errors
happen when processing individual files, or if [`frail`][frail]
is set and warnings occur
* `context` (`Object`) — Processing context, containing internally
used information and a `files` array with the processed files
* `options` (subset of [`Options`][api-options], required)
— configuration (`cwd` is required)
## Plug-ins
###### Fields
[`doc/plug-ins.md`][plug-ins] describes in detail how plug-ins
can add more files to be processed and handle all transformed files.
* `load(string, (Error?[, Result?]): undefined): undefined`
— get the config for a file
## Configuration
### `Completer`
[`doc/configure.md`][configure] describes in detail how configuration
files work.
Completer (TypeScript type).
## Ignoring
###### Type
[`doc/ignore.md`][ignore] describes in detail how ignore files work.
```ts
type Completer = (CompleterCallback | CompleterRegular) & {
pluginId?: string | symbol | undefined
}
type CompleterCallback = (set: FileSet, next: CompleterCallbackNext) => undefined
type CompleterCallbackNext = (error?: Error | null | undefined) => undefined
type CompleterRegular = (set: FileSet) => Promise<undefined> | undefined
```
### `Callback`
Callback called when done (TypeScript type).
Called with a fatal error if things went horribly wrong (probably due to
incorrect configuration), or a status code and the processing context.
###### Parameters
* `error` (`Error`, optional)
— error
* `code` (`0` or `1`, optional)
— exit code, `0` if successful or `1` if unsuccessful
* `context` ([`Context`][api-context], optional)
— processing context
###### Returns
Nothing (`undefined`).
### `ConfigTransform`
Transform arbitrary configs to our format (TypeScript type).
###### Parameters
* `config` (`unknown`)
— arbitrary config
* `filePath` (`string`)
— file path of config file
###### Returns
Our config format ([`Preset`][api-preset]).
### `Context`
Processing context (TypeScript type).
###### Fields
* `fileSet` ([`FileSet`][api-file-set])
— internally used info
* `files` ([`Array<VFile>`][vfile])
— processed files
### `FileSet`
A FileSet is created to process multiple files through unified processors
(TypeScript type).
This set, containing all files, is exposed to plugins as the second parameter.
###### Parameters
None.
###### Fields
* `valueOf(): Array<VFile>`
— get files in a set
* `use(completer: Completer): this`
— add middleware to be called when done (see: [`Completer`][api-completer])
* `add(file: VFile | string): this`
— add a file; the given file is processed like other files with a few
differences: it’s ignored when their file path is already added, never
written to the file system or `streamOut`, and not included in the report
### `Options`
Configuration (TypeScript type).
> 👉 **Note**: `options.processor` is required.
###### Fields
* `alwaysStringify` (`boolean`, default: `false`)
— whether to always serialize successfully processed files
* `color` (`boolean`, default: `false`)
— whether to report with ANSI color sequences; given to the reporter
* `configTransform` ([`ConfigTransform`][api-config-transform], optional)
— transform config files from a different schema
* `cwd` (`URL` or `string`, default: `process.cwd()`)
— folder to search files in, load plugins from, and more
* `defaultConfig` ([`Preset`][api-preset], optional)
— default configuration to use if no config file is given or found
* `detectConfig` (`boolean`, default: `true` if `options.packageField` or
`options.rcName`)
— whether to search for configuration files
* `detectIgnore` (`boolean`, default: `true` if `options.ignoreName`)
— whether to search for ignore files
* `extensions` (`Array<string>`, optional)
— search for files with these extensions, when folders are passed;
generated files are also given the first extension if `treeIn` is on and
`output` is on or points to a folder
* `filePath` (`URL` or `string`, optional)
— file path to process the given file on `streamIn` as
* `files` (`Array<URL | VFile | string>`, optional)
— paths or [globs][node-glob] to files and folder, or virtual files, to
process
* `frail` (`boolean`, default: `false`)
— call back with an unsuccessful (`1`) code on warnings as well as errors
* `ignoreName` (`string`, optional)
— name of ignore files to load
* `ignorePath` (`URL` or `string`, optional)
— filepath to an ignore file to load
* `ignorePathResolveFrom` ([`ResolveFrom`][api-resolve-from], default:
`'dir'`)
— resolve patterns in `ignorePath` from the current working folder
(`'cwd'`) or the ignore file’s folder (`'dir'`)
* `ignorePatterns` (optional)
— patterns to ignore in addition to ignore files
* `ignoreUnconfigured` (`boolean`, default: `false`)
— ignore files that do not have an associated detected configuration file;
either `rcName` or `packageField` must be defined too; cannot be combined
with `rcPath` or `detectConfig: false`
* `inspect` (`boolean`, default: `false`)
— whether to output a formatted syntax tree for debugging
* `out` (`boolean`, default: `false`)
— whether to write the processed file to `streamOut`
* `output` (`URL`, `boolean` or `string`, default: `false`)
— whether to write successfully processed files, and where to; when `true`,
overwrites the given files, when `false`, does not write to the file system;
when pointing to an existing folder, files are written to that folder and
keep their original basenames; when the parent folder of the given path
exists and one file is processed, the file is written to the given path
* `packageField` (`string`, optional)
— field where configuration can be found in `package.json` files
* `pluginPrefix` (`string`, optional)
— prefix to use when searching for plugins
* `plugins` ([`Preset['plugins']`][api-preset], optional)
— plugins to use
* `processor` ([`Processor`][unified-processor], **required**)
— unified processor to transform files
* `quiet` (`boolean`, default: `false`)
— do not report successful files; given to the reporter
* `rcName` (`string`, optional)
— name of configuration files to load
* `rcPath` (`URL` or `string`, optional)
— filepath to a configuration file to load
* `reporter` ([`VFileReporter`][api-vfile-reporter] or `string`, default:
`vfile-reporter`)
— reporter to use; if a `string` is passed, it’s loaded from `cwd`, and
`'vfile-reporter-'` can be omitted
* `reporterOptions` ([`Options`][vfile-reporter-options] from
`vfile-reporter`, optional)
— config to pass to the used reporter
* `settings` ([`Settings`][unified-settings] from `unified`, optional)
— configuration for the parser and compiler of the processor
* `silent` (`boolean`, default: `false`)
— report only fatal errors; given to the reporter
* `silentlyIgnore` (`boolean`, default: `false`)
— skip given files if they are ignored
* `streamError` ([`WritableStream`][node-writable-stream] from Node.js,
default: `process.stderr`)
— stream to write the report (if any) to
* `streamIn` ([`ReadableStream`][node-readable-stream] from Node.js,
default: `process.stdin`)
— stream to read from if no files are found or given
* `streamOut` ([`WritableStream`][node-writable-stream] from Node.js,
default: `process.stdout`)
— stream to write processed files to, nothing is streamed if either `out`
is `false`, `output` is not `false`, multiple files are processed, or a
fatal error occurred while processing a file
* `tree` (`boolean`, default: `false`)
— whether to treat both input and output as a syntax tree
* `treeIn` (`boolean`, default: `options.tree`)
— whether to treat input as a syntax tree
* `treeOut` (`boolean`, default: `options.tree`)
— whether to output as a syntax tree
### `Preset`
Sharable configuration, with support for specifiers (TypeScript type).
Specifiers should *not* be used in actual presets (because they can’t be
used by regular unified), but they can be used in config files locally,
as those are only for the engine.
They can contain plugins and settings.
###### Type
```ts
import type {
Plugin as UnifiedPlugin,
PluginTuple as UnifiedPluginTuple,
Preset as UnifiedPreset,
Settings
} from 'unified'
type Preset = {
plugins?: PluggableList | PluggableMap | undefined
settings?: Settings | undefined
}
type Pluggable = Plugin | PluginTuple | UnifiedPreset
type PluggableList = Array<Pluggable>
type PluggableMap = Record<string, unknown>
type Plugin = UnifiedPlugin | string
type PluginTupleSupportingSpecifiers =
| [plugin: string, ...parameters: Array<unknown>]
| UnifiedPluginTuple
```
### `ResolveFrom`
How to resolve (TypeScript type).
###### Type
```ts
type ResolveFrom = 'cwd' | 'dir';
```
### `VFileReporter`
Transform arbitrary configs to our format (TypeScript type).
This is essentially the interface of [`vfile-reporter`][vfile-reporter], with
added support for unknown fields in options and async support.
###### Parameters
* `files` ([`Array<VFile>`][vfile])
— files
* `options` ([`Options`][vfile-reporter-options] from `vfile-reporter`,
optional)
— configuration
###### Returns
Report (`Promise<string>` or `string`).
## Config files
`unified-engine` accepts configuration through options and through
configuration files (*rc files*).
### Explicit configuration
One configuration file can be given through `options.rcPath`, this is loaded
regardless of `options.detectConfig` and `options.rcName`.
### Implicit configuration
Otherwise, configuration files are detected if `options.detectConfig` is turned
on, depending on the following options:
* if `options.rcName` is given, `$rcName` (JSON), `$rcName.js` (CommonJS or
ESM depending on the `type` field of the closest `package.json`),
`$rcName.cjs` (CommonJS), `$rcName.mjs` (ESM), `$rcName.yml` (YAML),
and `$rcName.yaml` (YAML) are loaded
* if `options.packageField` is given, `package.json` (JSON) files are loaded
and the configuration at their `$packageField` field is used
The first file that is searched for in a folder is used as the configuration.
If no file is found, the parent folder is searched, and so on.
The schema (type) of rc files is [`Preset`][api-preset].
### Examples
An example **rc** file could look as follows:
```json
{
"plugins": [
"remark-inline-links",
"remark-lint-recommended"
],
"settings": {
"bullet": "*",
"ruleRepetition": 3,
"fences": true
}
}
```
Another example, **rc.js**, could look as follows:
```js
exports.plugins = [
'./script/natural-language.js',
'remark-lint-recommended',
'remark-license'
]
exports.settings = {bullet: '*'}
```
When using ESM (ECMAScript modules), **rc.mjs** could look as folows:
```js
export default {
plugins: [
'./script/natural-language.js',
'remark-lint-recommended',
'remark-license'
],
settings: {bullet: '*'}
}
```
Another example, **rc.yaml**, could look as follows:
```js
plugins:
- 'rehype-document'
- 'rehype-preset-minify'
settings:
preferUnquoted: true
quote: "'"
quoteSmart: true
verbose: true
```
## Ignore files
`unified-engine` accepts patterns to ignore when searching for files to process
through ignore files.
### Explicit ignoring
One ignore file can be given through `options.ignorePath`, this is loaded
regardless of `options.detectIgnore` and `options.ignoreName`.
### Implicit ignoring
Otherwise, ignore files are detected if `options.detectIgnore` is turned on and
`options.ignoreName` is given.
The first file named `$ignoreName` in the parent folder of a checked file is
used.
Or, if no file is found, the parent folder if searched, and so on.
### Extra ignoring
In addition to explicit and implicit ignore files, other patterns can be given
with `options.ignorePatterns`.
The format of each pattern in `ignorePatterns` is the same as a line in an
ignore file.
Patterns and files are resolved based on the current working folder.
It is also possible to ignore files that do not have an associated detected
configuration file by turning on `options.ignoreUnconfigured`.
### Ignoring
Ignoring is used when searching for files in folders.
If paths (including those expanded from [globs][node-glob]) are passed in that
are ignored, an error is thrown.
These files can be silently ignored by turning on `options.silentlyIgnore`.
Normally, files are ignored based on the path of the found ignore file and the
patterns inside it.
Patterns passed with `options.ignorePatterns` are resolved based on the current
working directory.
Patterns in an explicit ignore file passed in with `options.ignorePath` can be
resolved from the current working directory instead, by setting
`options.ignorePathResolveFrom` to `'cwd'` instead of `'dir'` (default).
If paths or globs to folders are given to the engine, they will be searched
for matching files, but `node_modules` are normally not searched.
Pass paths (or globs) to the `node_modules` you want to include in
`options.files` to search them.
The format for ignore files is the same as [`.gitignore`][gitignore], so it’s
possible to pass a `.gitignore` in as `options.ignorePath`.
[`node-ignore`][node-ignore] is used under the hood, see its documentation
for more information.
### Examples
An example **ignore** file could look as follows:
```ini
# Ignore files in `.github`.
.github/
# Bower.
bower_components/
# Duo dependencies.
components/
# Fixtures.
test/{input,tree}/
```
If we had an ignore file `folder/.remarkignore`, with the value: `index.txt`,
and our file system looked as follows:
```txt
folder/.remarkignore
folder/index.txt
index.txt
```
Then `folder/index.txt` would be ignored but `index.txt` would not be.
## Plugins
Normally, **unified** plugins receive a single `options` argument upon attaching
(an `Object` users can provide to configure the plugin).
If a plugin is attached by **unified-engine**, a second argument is given:
[`FileSet`][api-file-set].
## Examples
`unified-engine` can be configured extensively by engine authors.
### `options.alwaysStringify`
This example shows how you can use `options.alwaysStringify` when you don’t
want the engine to write to the file system, but still want to get the compiled
results.
One example that does this is `unified-engine-gulp`.
```js
import {remark} from 'remark'
import {engine} from 'unified-engine'
import {VFile} from 'vfile'
const file = new VFile({path: 'example.md', value: '_hi_'})
engine(
{alwaysStringify: true, files: [file], processor: remark},
function (error, code, context) {
if (error) throw error
console.log(context?.files.map((d) => String(d)))
}
)
```
Yields:
```txt
example.md: no issues found
```
```js
[ '*hi*\n' ]
```
### `options.configTransform`
To support custom rc files, that have a different format than what the engine
supports, pass as [`ConfigTransform`][api-config-transform].
This example processes `readme.md` and loads options from `custom` (from a
`package.json`).
`configTransform` is called with those options and transforms it to
configuration `unified-engine` understands.
```js
import {remark} from 'remark'
import {engine} from 'unified-engine'
engine(
{
configTransform,
files: ['readme.md'],
packageField: 'custom',
processor: remark()
},
function (error) {
if (error) throw error
}
)
function configTransform(config) {
return {settings: (config || {}).options}
}
```
Where `package.json` contains:
```json
{
"name": "foo",
"private": true,
"custom": {
"options": {
"bullet": "+"
}
}
}
```
### `options.defaultConfig`
This example processes `readme.md`.
If `package.json` exists, that config is used, otherwise the configuration at
`defaultConfig` is used.
```js
import {remark} from 'remark'
import {engine} from 'unified-engine'
engine(
{
defaultConfig: {settings: {bullet: '+'}},
files: ['readme.md'],
packageField: 'remarkConfig',
processor: remark()
},
function (error) {
if (error) throw error
}
)
```
Where `package.json` contains:
```json
{
"name": "foo",
"private": true,
"remarkConfig": {
"settings": {
"bullet": "-"
}
}
}
```
### `options.detectConfig`
This example processes `readme.md` but does **not** allow configuration from
`.remarkrc` or `package.json` files, as `detectConfig` is `false`.
```js
import {remark} from 'remark'
import {engine} from 'unified-engine'
engine(
{
detectConfig: false,
files: ['readme.md'],
processor: remark(),
packageField: 'remarkConfig',
rcName: '.remarkrc'
},
function (error) {
if (error) throw error
}
)
```
### `options.detectIgnore`
This example processes files in the current working directory with an `md`
extension but does **not** ignore file paths from the closest `.remarkignore`
file, because `detectIgnore` is `false`.
```js
import {remark} from 'remark'
import {engine} from 'unified-engine'
engine(
{
detectIgnore: false,
extensions: ['md'],
files: ['.'],
ignoreName: '.remarkignore',
processor: remark()
},
function (error) {
if (error) throw error
}
)
```
### `options.extensions`
This example reformats all files with `md`, `markdown`, and `mkd`
extensions in the current folder.
```js
import {remark} from 'remark'
import {engine} from 'unified-engine'
engine(
{
extensions: ['md', 'mkd', 'markdown'],
files: ['.'],
output: true,
processor: remark
},
function (error) {
if (error) throw error
}
)
```
### `options.filePath`
This example shows that `streamIn` is named as `filePath`:
```js
import {PassThrough} from 'node:stream'
import {remark} from 'remark'
import remarkPresetLintRecommended from 'remark-preset-lint-recommended'
import {engine} from 'unified-engine'
const streamIn = new PassThrough()
streamIn.write('doc')
setImmediate(function () {
streamIn.end('ument')
})
engine(
{
filePath: '~/alpha/bravo/charlie.md',
out: false,
plugins: [remarkPresetLintRecommended],
processor: remark(),
streamIn
},
function (error) {
if (error) throw error
}
)
```
Yields:
```txt
~/alpha/bravo/charlie.md
1:1 warning Missing newline character at end of file final-newline remark-lint
⚠ 1 warning
```
### `options.files`
This example processes `LICENSE` and all files with an `md` extension in `doc`.
```js
import {remark} from 'remark'
import {engine} from 'unified-engine'
engine(
{
extensions: ['md'],
files: ['LICENSE', 'doc/'],
processor: remark
},
function (error) {
if (error) throw error
}
)
```
### `options.frail`
This example uses [`remark-lint`][remark-lint] to lint `readme.md` and exits
with the given exit code.
Normally, only errors turn the `code` to `1`, but in `frail` mode lint warnings
result in the same.
```js
import process from 'node:process'
import {remark} from 'remark'
import {engine} from 'unified-engine'
engine(
{
files: ['readme.md'],
frail: true,
plugins: ['remark-preset-lint-recommended'],
processor: remark()
},
function (error, code) {
process.exitCode = error ? 1 : code
}
)
```
### `options.ignoreName`
This example processes files in the current working directory with an `md`
extension, and is configured to ignore file paths from the closest
`.remarkignore` file.
```js
import {remark} from 'remark'
import {engine} from 'unified-engine'
engine(
{
extensions: ['md'],
files: ['.'],
ignoreName: '.remarkignore',
processor: remark()
},
function (error) {
if (error) throw error
}
)
```
### `options.ignorePath`
This example processes files in the current working directory with an `md`
extension and ignores file paths specified in `.gitignore`.
```js
import {remark} from 'remark'
import {engine} from 'unified-engine'
engine(
{
extensions: ['md'],
files: ['.'],
ignorePath: '.gitignore',
processor: remark()
},
function (error) {
if (error) throw error
}
)
```
### `options.ignorePathResolveFrom`
This example processes files in the current working directory with an `md`
extension and takes a reusable configuration file from a dependency.
```js
import {remark} from 'remark'
import {engine} from 'unified-engine'
engine(
{
extensions: ['md'],
files: ['.'],
ignorePath: 'node_modules/my-config/my-ignore',
ignorePathResolveFrom: 'cwd',
processor: remark()
},
function (error) {
if (error) throw error
}
)
```
### `options.ignorePatterns`
This example processes files in the current working directory with an `md`
extension, except for `readme.md`:
```js
import {remark} from 'remark'
import {engine} from 'unified-engine'
engine(
{
extensions: ['md'],
files: ['.'],
ignorePatterns: ['readme.md'],
processor: remark()
},
function (error) {
if (error) throw error
}
)
```
### `options.ignoreUnconfigured`
This example processes files in the current working directory with an
`md` extension, but only if there is an explicit `.remarkrc` config file near
(upwards) to them:
```js
import {remark} from 'remark'
import {engine} from 'unified-engine'
engine(
{
extensions: ['md'],
files: ['.'],
ignoreUnconfigured: true,
processor: remark(),
rcName: '.remarkrc'
},
function (error) {
if (error) throw error
}
)
```
### `options.inspect`
This example shows a module which reads and parses `doc.md`, then
[`remark-unlink`][remark-unlink] transforms the syntax tree, the tree is
formatted with [`unist-util-inspect`][unist-util-inspect], and finally written
to **stdout**(4).
```js
import {remark} from 'remark'
import remarkUnlink from 'remark-unlink'
import {engine} from 'unified-engine'
engine(
{
files: ['doc.md'],
inspect: true,
plugins: [remarkUnlink],
processor: remark()
},
function (error) {
if (error) throw error
}
)
```
Where `doc.md` looks as follows:
```markdown
[foo](https://example.com)
```
Yields:
```txt
root[1] (1:1-2:1, 0-27)
└─ paragraph[1] (1:1-1:27, 0-26)
└─ text: "foo" (1:2-1:5, 1-4)
```
### `options.out`
This example uses [`remark-lint`][remark-lint] to lint `readme.md`, writes the
report, and ignores the serialized document.
```js
import {remark} from 'remark'
import remarkPresetLintRecommended from 'remark-preset-lint-recommended'
import {engine} from 'unified-engine'
engine(
{
files: ['readme.md'],
out: false,
plugins: [remarkPresetLintRecommended],
processor: remark()
},
function (error) {
if (error) throw error
}
)
```
### `options.output`
This example writes all files in `src/` with an `md` extension compiled to
`dest/`.
```js
import {remark} from 'remark'
import {engine} from 'unified-engine'
engine(
{
extensions: ['md'],
files: ['src/'],
output: 'dest/',
processor: remark()
},
function (error) {
if (error) throw error
}
)
```
### `options.packageField`
This example processes `readme.md`, and allows configuration from
`remarkConfig` fields in `package.json` files.
```js
import {remark} from 'remark'
import {engine} from 'unified-engine'
engine(
{
files: ['readme.md'],
packageField: 'remarkConfig',
processor: remark()
},
function (error) {
if (error) throw error
}
)
```
### `options.pluginPrefix`
This example processes `readme.md` and loads the
`preset-lint-recommended` plugin.
Because `pluginPrefix` is given, this resolves to
[`remark-preset-lint-recommended`][remark-preset-lint-recommended] (from
`node_modules/`) if available.
```js
import {remark} from 'remark'
import {engine} from 'unified-engine'
engine(
{
files: ['readme.md'],
pluginPrefix: 'remark',
plugins: ['preset-lint-recommended'],
processor: remark()
},
function (error) {
if (error) throw error
}
)
```
### `options.plugins`
This example processes `readme.md` and loads the
[`remark-preset-lint-recommended`][remark-preset-lint-recommended]
preset.
```js
import {remark} from 'remark'
import {engine} from 'unified-engine'
engine(
{
files: ['readme.md'],
plugins: ['remark-preset-lint-recommended'],
processor: remark()
},
function (error) {
if (error) throw error
}
)
```
### `options.processor`
This example reformats **stdin**(4) using [remark][], writes the report
to **stderr**(4), and formatted document to **stdout**(4).
```js
import {remark} from 'remark'
import {engine} from 'unified-engine'
engine({processor: remark}, function (error) {
if (error) throw error
})
```
### `options.quiet`
This example uses [`remark-lint`][remark-lint] to lint `readme.md`.
Nothing is reported if the file processed successfully.
```js
import {remark} from 'remark'
import {engine} from 'unified-engine'
engine(
{
files: ['readme.md'],
plugins: ['remark-preset-lint-recommended'],
processor: remark(),
quiet: true
},
function (error) {
if (error) throw error
}
)
```
### `options.rcName`
This example processes `readme.md` and allows configuration from `.remarkrc`,
`.remarkrc.json`, `.remarkrc.yml`, `.remarkrc.yaml`, `.remarkrc.js`,
`.remarkrc.cjs`, and `.remarkrc.mjs` files.
```js
import {remark} from 'remark'
import {engine} from 'unified-engine'
engine(
{files: ['readme.md'], processor: remark(), rcName: '.remarkrc'},
function (error) {
if (error) throw error
}
)
```
### `options.rcPath`
This example processes `readme.md` and loads configuration from `config.json`.
```js
import {remark} from 'remark'
import {engine} from 'unified-engine'
engine(
{files: ['readme.md'], processor: remark(), rcPath: 'config.json'},
function (error) {
if (error) throw error
}
)
```
### `options.reporter` and `options.reporterOptions`
This example processes all HTML files in the current folder with rehype,
configures the processor with `.rehyperc` files, and prints a report in
JSON using [`vfile-reporter-json`][vfile-reporter-json] with
[reporter options][vfile-reporter-options].
```js
import {rehype} from 'rehype'
import {engine} from 'unified-engine'
engine(
{
extensions: ['html'],
files: ['.'],
processor: rehype(),
rcName: '.rehyperc',
reporter: 'json',
reporterOptions: {pretty: true}
},
function (error) {
if (error) throw error
}
)
```
### `options.settings`
This example processes `readme.md` and configures the compiler
([`remark-stringify`][remark-stringify]) with `bullet: '+'`.
```js
import {remark} from 'remark'
import {engine} from 'unified-engine'
engine(
{files: ['readme.md'], processor: remark(), settings: {bullet: '+'}},
function (error) {
if (error) throw error
}
)
```
### `options.silent`
This example uses [`remark-lint`][remark-lint] to lint `readme.md` but does not
report any warnings or success messages, only fatal errors, if they occur.
```js
import {remark} from 'remark'
import {engine} from 'unified-engine'
engine(
{
files: ['readme.md'],
plugins: ['remark-preset-lint-recommended'],
processor: remark(),
silent: true
},
function (error) {
if (error) throw error
}
)
```
### `options.streamError`
This example uses [`remark-lint`][remark-lint] to lint `readme.md` and writes
the report to `report.txt`.
```js
import fs from 'node:fs'
import {remark} from 'remark'
import remarkPresetLintRecommended from 'remark-preset-lint-recommended'
import {engine} from 'unified-engine'
engine(
{
files: ['readme.md'],
out: false,
plugins: [remarkPresetLintRecommended],
processor: remark(),
streamErr: fs.createWriteStream('report.txt')
},
function (error) {
if (error) throw error
}
)
```
### `options.streamIn`
This example uses [`remark-lint`][remark-lint] to lint an incoming
stream.
```js
import {PassThrough} from 'node:stream'
import {remark} from 'remark'
import remarkPresetLintRecommended from 'remark-preset-lint-recommended'
import {engine} from 'unified-engine'
const streamIn = new PassThrough()
streamIn.write('doc')
setImmediate(function () {
streamIn.end('ument')
})
engine(
{
out: false,
plugins: [remarkPresetLintRecommended],
processor: remark(),
streamIn
},
function (error) {
if (error) throw error
}
)
```
Yields:
```txt
<stdin>
1:1 warning Missing newline character at end of file final-newline remark-lint
⚠ 1 warning
```
### `options.streamOut`
This example reads `readme.md` and writes the serialized document to
`readme-two.md`.
This can also be achieved by passing `output: 'readme-two.md'` instead of
`streamOut`.
```js
import fs from 'node:fs'
import {remark} from 'remark'
import {engine} from 'unified-engine'
const streamOut = fs.createWriteStream('readme-two.md')
engine(
{files: ['readme.md'], processor: remark(), streamOut},
function (error) {
if (error) throw error
}
)
```
### `options.tree`
This example reads `tree.json`, then [`remark-unlink`][remark-unlink]
transforms the syntax tree, and the transformed tree is written to
**stdout**(4).
```js
import {remark} from 'remark'
import remarkUnlink from 'remark-unlink'
import {engine} from 'unified-engine'
engine(
{
files: ['tree.json'],
plugins: [remarkUnlink],
processor: remark(),
tree: true
},
function (error) {
if (error) throw error
}
)
```
Where `tree.json` looks as follows:
```json
{
"type": "paragraph",
"children": [{
"type": "link",
"url": "https://example.com",
"children": [{
"type": "text",
"value": "foo"
}]
}]
}
```
Yields:
```json
{
"type": "paragraph",
"children": [{
"type": "text",
"value": "foo"
}]
}
```
### `options.treeIn`
This example reads `tree.json`, then [`remark-unlink`][remark-unlink]
transforms the syntax tree, the tree is serialized, and the resulting document
is written to **stdout**(4).
```js
import {remark} from 'remark'
import remarkUnlink from 'remark-unlink'
import {engine} from 'unified-engine'
engine(
{
files: ['tree.json'],
plugins: [remarkUnlink],
processor: remark(),
treeIn: true
},
function (error) {
if (error) throw error
}
)
```
Where `tree.json` looks as follows:
```json
{
"type": "paragraph",
"children": [{
"type": "link",
"url": "https://example.com",
"children": [{
"type": "text",
"value": "foo"
}]
}]
}
```
Yields:
```markdown
foo
```
### `options.treeOut`
This example shows a module which reads and parses `doc.md`, then
[`remark-unlink`][remark-unlink] transforms the syntax tree, and the tree is
written to **stdout**(4).
```js
import {remark} from 'remark'
import remarkUnlink from 'remark-unlink'
import {engine} from 'unified-engine'
engine(
{
files: ['doc.md'],
plugins: [remarkUnlink],
processor: remark(),
treeOut: true
},
function (error) {
if (error) throw error
}
)
```
Where `doc.md` looks as follows:
```markdown
[foo](https://example.com)
```
Yields:
```json
{
"type": "paragraph",
"children": [{
"type": "text",
"value": "foo"
}]
}
```
## Types
This package is fully typed with [TypeScript][].
It exports the additional types
[`Completer`][api-completer],
[`Callback`][api-callback],
[`ConfigTransform`][api-config-transform],
[`Context`][api-context],
[`FileSet`][api-file-set],
[`Options`][api-options],
[`Preset`][api-preset],
[`ResolveFrom`][api-resolve-from], and
[`VFileReporter`][api-vfile-reporter].
## Compatibility
Projects maintained by the unified collective are compatible with maintained
versions of Node.js.
When we cut a new major release, we drop support for unmaintained versions of
Node.
This means we try to keep the current release line, `unified-engine@^11`,
compatible with Node.js 16.
## Security
`unified-engine` loads and evaluates configuration files, plugins, and presets
from the file system (often from `node_modules/`).
That means code that is on your file system runs.
Make sure you trust the workspace where you run `unified-engine` and be careful
with packages from npm and changes made by contributors.
## Contribute
See [`contributing.md` in `unifiedjs/unified`][contributing] for ways to get
started.
See [`contributing.md`][contributing] in [`unifiedjs/.github`][health] for ways
to get started.
See [`support.md`][support] for ways to get help.
This organisation has a [Code of Conduct][coc]. By interacting with this
repository, organisation, or community you agree to abide by its terms.
This project has a [code of conduct][coc].
By interacting with this repository, organization, or community you agree to
abide by its terms.

@@ -188,108 +1566,110 @@ ## License

[travis-badge]: https://img.shields.io/travis/unifiedjs/unified-engine.svg
[build-badge]: https://github.com/unifiedjs/unified-engine/workflows/main/badge.svg
[travis]: https://travis-ci.org/unifiedjs/unified-engine
[build]: https://github.com/unifiedjs/unified-engine/actions
[codecov-badge]: https://img.shields.io/codecov/c/github/unifiedjs/unified-engine.svg
[coverage-badge]: https://img.shields.io/codecov/c/github/unifiedjs/unified-engine.svg
[codecov]: https://codecov.io/github/unifiedjs/unified-engine
[coverage]: https://codecov.io/github/unifiedjs/unified-engine
[npm]: https://docs.npmjs.com/cli/install
[downloads-badge]: https://img.shields.io/npm/dm/unified-engine.svg
[license]: LICENSE
[downloads]: https://www.npmjs.com/package/unified-engine
[author]: http://wooorm.com
[sponsors-badge]: https://opencollective.com/unified/sponsors/badge.svg
[unified]: https://github.com/unifiedjs/unified
[backers-badge]: https://opencollective.com/unified/backers/badge.svg
[unified-processor]: https://github.com/unifiedjs/unified#processor
[collective]: https://opencollective.com/unified
[remark]: https://github.com/remarkjs/remark
[chat-badge]: https://img.shields.io/badge/chat-discussions-success.svg
[fatal]: https://github.com/vfile/vfile#vfilefailreason-position-ruleid
[chat]: https://github.com/unifiedjs/unified/discussions
[callback]: #function-callbackerr-code-context
[npm]: https://docs.npmjs.com/cli/install
[options]: doc/options.md#options
[esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
[processor]: doc/options.md#optionsprocessor
[typescript]: https://www.typescriptlang.org
[cwd]: doc/options.md#optionscwd
[health]: https://github.com/unifiedjs/.github
[extensions]: doc/options.md#optionsextensions
[contributing]: https://github.com/unifiedjs/.github/blob/main/contributing.md
[stream-in]: doc/options.md#optionsstreamin
[support]: https://github.com/unifiedjs/.github/blob/main/support.md
[file-path]: doc/options.md#optionsfilepath
[coc]: https://github.com/unifiedjs/.github/blob/main/code-of-conduct.md
[stream-out]: doc/options.md#optionsstreamout
[license]: license
[stream-error]: doc/options.md#optionsstreamerror
[author]: https://wooorm.com
[out]: doc/options.md#optionsout
[gitignore]: https://git-scm.com/docs/gitignore
[output]: doc/options.md#optionsoutput
[node-glob]: https://github.com/isaacs/node-glob#glob-primer
[always-stringify]: doc/options.md#optionsalwaysstringify
[node-ignore]: https://github.com/kaelzhang/node-ignore
[tree]: doc/options.md#optionstree
[remark]: https://github.com/remarkjs/remark
[tree-in]: doc/options.md#optionstreein
[remark-cli]: https://github.com/remarkjs/remark/tree/main/packages/remark-cli#readme
[tree-out]: doc/options.md#optionstreeout
[remark-lint]: https://github.com/remarkjs/remark-lint
[inspect]: doc/options.md#optionsinspect
[remark-preset-lint-recommended]: https://github.com/remarkjs/remark-lint/tree/main/packages/remark-preset-lint-recommended
[detect-config]: doc/options.md#optionsdetectconfig
[remark-stringify]: https://github.com/remarkjs/remark/tree/main/packages/remark-stringify
[rc-name]: doc/options.md#optionsrcname
[remark-unlink]: https://github.com/remarkjs/remark-unlink
[package-field]: doc/options.md#optionspackagefield
[unified]: https://github.com/unifiedjs/unified
[rc-path]: doc/options.md#optionsrcpath
[unified-processor]: https://github.com/unifiedjs/unified#processor-1
[settings]: doc/options.md#optionssettings
[unified-args]: https://github.com/unifiedjs/unified-args
[detect-ignore]: doc/options.md#optionsdetectignore
[unified-engine-gulp]: https://github.com/unifiedjs/unified-engine-gulp
[ignore-name]: doc/options.md#optionsignorename
[unified-language-server]: https://github.com/unifiedjs/unified-language-server
[ignore-path]: doc/options.md#optionsignorepath
[unified-settings]: https://github.com/unifiedjs/unified#settings
[silently-ignore]: doc/options.md#optionssilentlyignore
[unist-util-inspect]: https://github.com/syntax-tree/unist-util-inspect
[plugin-prefix]: doc/options.md#optionspluginprefix
[vfile]: https://github.com/vfile/vfile
[config-transform]: doc/options.md#optionsconfigtransform
[vfile-reporter]: https://github.com/vfile/vfile-reporter
[plugins]: doc/options.md#optionsplugins
[vfile-reporter-json]: https://github.com/vfile/vfile-reporter-json
[reporter]: doc/options.md#optionsreporter
[vfile-reporter-options]: https://github.com/vfile/vfile-reporter#options
[reporteroptions]: doc/options.md#optionsreporteroptions
[node-readable-stream]: https://nodejs.org/api/stream.html#readable-streams
[color]: doc/options.md#optionscolor
[node-writable-stream]: https://nodejs.org/api/stream.html#writable-streams
[silent]: doc/options.md#optionssilent
[config-files]: #config-files
[quiet]: doc/options.md#optionsquiet
[ignore-files]: #ignore-files
[frail]: doc/options.md#optionsfrail
[api-configuration]: #configuration
[files]: doc/options.md#optionsfiles
[api-engine]: #engineoptions-callback
[configure]: doc/configure.md
[api-completer]: #completer
[ignore]: doc/ignore.md
[api-callback]: #callback
[plug-ins]: doc/plug-ins.md
[api-config-transform]: #configtransform
[atom]: https://github.com/unifiedjs/unified-engine-atom
[api-context]: #context
[gulp]: https://github.com/unifiedjs/unified-engine-gulp
[api-file-set]: #fileset
[args]: https://github.com/unifiedjs/unified-args
[api-options]: #options
[contributing]: https://github.com/unifiedjs/unified/blob/master/contributing.md
[api-preset]: #preset
[coc]: https://github.com/unifiedjs/unified/blob/master/code-of-conduct.md
[api-resolve-from]: #resolvefrom
[api-vfile-reporter]: #vfilereporter