From 2d06c4758fdac7d74aed5e08a5f49ae1badfd1e6 Mon Sep 17 00:00:00 2001 From: James Sumners <321201+jsumners@users.noreply.github.com> Date: Fri, 11 Oct 2024 13:10:35 -0400 Subject: [PATCH] Add support for yaml config file (#105) * Add support for yaml config file * add documentation --- README.md | 27 ++++++++++ borp.js | 6 +++ fixtures/conf/glob-files.yaml | 3 ++ fixtures/conf/relative-reporter.yaml | 2 + fixtures/conf/reporters.yaml | 3 ++ lib/conf.js | 80 ++++++++++++++++++++++++++++ package-lock.json | 15 +++++- package.json | 3 +- test/config.test.js | 45 ++++++++++++++++ 9 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 fixtures/conf/glob-files.yaml create mode 100644 fixtures/conf/relative-reporter.yaml create mode 100644 fixtures/conf/reporters.yaml create mode 100644 lib/conf.js create mode 100644 test/config.test.js diff --git a/README.md b/README.md index 728c60f..e700e4d 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,33 @@ Here are the available reporters: * `dot`: outputs the test results in a compact format, where each passing test is represented by a ., and each failing test is represented by a X. * `junit`: outputs test results in a jUnit XML format +## Config File Support + +A limited set of options may be specified via a configuration file. The +configuration file is expected to be in the process's working directory, and +named either `.borp.yaml` or `.borp.yml`; it may also be specified by +defining the environment variable `BORP_CONF_FILE` and setting it to the +full path to some yaml file. + +The current supported options are: + ++ `files` (string[]): An array of test files to include. Globs are supported. ++ `reporters` (string[]): An array of reporters to use. May be relative path +strings, or module name strings. + +### Example + +```yaml +files: + - 'test/one.test.js' + - 'test/foo/*.test.js' + +reporters: + - './test/lib/my-reporter.js' + - spec + - '@reporters/silent' +``` + ## License MIT diff --git a/borp.js b/borp.js index c728ba3..2aa9071 100755 --- a/borp.js +++ b/borp.js @@ -15,6 +15,7 @@ import { checkCoverages } from 'c8/lib/commands/check-coverage.js' import os from 'node:os' import { execa } from 'execa' import { pathToFileURL } from 'node:url' +import loadConfig from './lib/conf.js' /* c8 ignore next 4 */ process.on('unhandledRejection', (err) => { @@ -22,6 +23,11 @@ process.on('unhandledRejection', (err) => { process.exit(1) }) +const foundConfig = await loadConfig() +if (foundConfig.length > 0) { + Array.prototype.push.apply(process.argv, foundConfig) +} + const args = parseArgs({ args: process.argv.slice(2), options: { diff --git a/fixtures/conf/glob-files.yaml b/fixtures/conf/glob-files.yaml new file mode 100644 index 0000000..909005f --- /dev/null +++ b/fixtures/conf/glob-files.yaml @@ -0,0 +1,3 @@ +files: + - 'test1/*.test.js' + - 'test2/**/*.test.js' \ No newline at end of file diff --git a/fixtures/conf/relative-reporter.yaml b/fixtures/conf/relative-reporter.yaml new file mode 100644 index 0000000..262dbf2 --- /dev/null +++ b/fixtures/conf/relative-reporter.yaml @@ -0,0 +1,2 @@ +reporters: + - './reporter.js' \ No newline at end of file diff --git a/fixtures/conf/reporters.yaml b/fixtures/conf/reporters.yaml new file mode 100644 index 0000000..080756b --- /dev/null +++ b/fixtures/conf/reporters.yaml @@ -0,0 +1,3 @@ +reporters: + - spec + - '@reporters/silent' \ No newline at end of file diff --git a/lib/conf.js b/lib/conf.js new file mode 100644 index 0000000..3741b6d --- /dev/null +++ b/lib/conf.js @@ -0,0 +1,80 @@ +import { cwd } from 'node:process' +import { open, readFile } from 'node:fs/promises' +import { join } from 'node:path' +import YAML from 'yaml' + +async function readYamlFile () { + let target + let fd + if (process.env.BORP_CONF_FILE) { + target = process.env.BORP_CONF_FILE + try { + fd = await open(target, 'r') + } catch { + return + } + } else { + const CWD = cwd() + try { + target = join(CWD, '.borp.yaml') + fd = await open(target, 'r') + } catch { + target = join(CWD, '.borp.yml') + try { + fd = await open(target, 'r') + } catch { + // Neither file is available. If we had an application logger that writes + // to stderr, we'd log an error message. But, as it is, we will just + // assume that all errors are "file does not exist."" + return + } + } + } + + let fileData + try { + fileData = await readFile(fd, { encoding: 'utf8' }) + } catch { + // Same thing as noted above. Skip it. + return + } finally { + await fd.close() + } + + return fileData +} + +async function loadConfig () { + const result = [] + const fileData = await readYamlFile() + if (typeof fileData !== 'string') { + return result + } + + let options + try { + options = YAML.parse(fileData) + } catch { + // We just don't care. + return result + } + + if (options.reporters) { + for (const reporter of options.reporters) { + result.push('--reporter') + result.push(reporter) + } + } + + // Append files AFTER all other supported config keys. The runner expects + // them as positional parameters. + if (options.files) { + for (const file of options.files) { + result.push(file) + } + } + + return result +} + +export default loadConfig diff --git a/package-lock.json b/package-lock.json index 5c7e97a..6a0f78f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,8 @@ "c8": "^10.0.0", "execa": "^9.3.0", "find-up": "^7.0.0", - "glob": "^10.3.10" + "glob": "^10.3.10", + "yaml": "^2.5.1" }, "bin": { "borp": "borp.js" @@ -4733,6 +4734,18 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/yaml": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", + "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index 57e2df0..4512e32 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "c8": "^10.0.0", "execa": "^9.3.0", "find-up": "^7.0.0", - "glob": "^10.3.10" + "glob": "^10.3.10", + "yaml": "^2.5.1" } } diff --git a/test/config.test.js b/test/config.test.js new file mode 100644 index 0000000..225060d --- /dev/null +++ b/test/config.test.js @@ -0,0 +1,45 @@ +import { test } from 'node:test' +import { execa } from 'execa' +import { join } from 'desm' +import { strictEqual } from 'node:assert' +import path from 'node:path' + +const borp = join(import.meta.url, '..', 'borp.js') +const confFilesDir = join(import.meta.url, '..', 'fixtures', 'conf') + +test('reporter from node_modules', async () => { + const cwd = join(import.meta.url, '..', 'fixtures', 'ts-esm') + const { stdout } = await execa('node', [borp], { + cwd, + env: { + BORP_CONF_FILE: path.join(confFilesDir, 'reporters.yaml') + } + }) + + strictEqual(stdout.indexOf('tests 2') >= 0, true) +}) + +test('reporter from relative path', async () => { + const cwd = join(import.meta.url, '..', 'fixtures', 'relative-reporter') + const { stdout } = await execa('node', [borp], { + cwd, + env: { + BORP_CONF_FILE: path.join(confFilesDir, 'relative-reporter.yaml') + } + }) + + strictEqual(/passed:.+add\.test\.js/.test(stdout), true) + strictEqual(/passed:.+add2\.test\.js/.test(stdout), true) +}) + +test('interprets globs for files', async () => { + const cwd = join(import.meta.url, '..', 'fixtures', 'files-glob') + const { stdout } = await execa('node', [borp], { + cwd, + env: { + BORP_CONF_FILE: path.join(confFilesDir, 'glob-files.yaml') + } + }) + + strictEqual(stdout.indexOf('tests 2') >= 0, true) +})