1
0
Fork 0

feat(config): load and validate config directly, without js-config lib

BREAKING CHANGE: this updates config loading to use the fs.promises
API and does not resolve paths from HOME or __dirname. Using an
absolute or relative path in the args option is still supported.
This commit is contained in:
ssube 2021-07-15 19:41:02 -05:00
parent c487efb5ed
commit c32ac312df
Signed by: ssube
GPG Key ID: 3EED7B957D362AF1
4 changed files with 66 additions and 79 deletions

View File

@ -11,9 +11,8 @@
"author": "ssube",
"license": "MIT",
"devDependencies": {
"@apextoaster/js-config": "0.2.0-3",
"@apextoaster/js-utils": "0.3.0",
"@apextoaster/js-yaml-schema": "0.4.0-4",
"@apextoaster/js-yaml-schema": "0.4.0",
"@gitbeaker/browser": "23.7.0",
"@gitbeaker/core": "23.7.0",
"@gitbeaker/node": "23.7.0",
@ -60,6 +59,7 @@
"jsdom": "16.6.0",
"jsdom-global": "3.0.2",
"lodash": "4.17.21",
"memfs": "^3.2.2",
"mobx": "6.3.2",
"mobx-react": "7.2.0",
"mocha": "8.4.0",

View File

@ -1,15 +1,32 @@
import { createConfig } from '@apextoaster/js-config';
import { IncludeOptions } from '@apextoaster/js-yaml-schema';
import { createSchema } from '@apextoaster/js-yaml-schema';
import Ajv from 'ajv';
import { existsSync, readFileSync, realpathSync } from 'fs';
import { DEFAULT_SCHEMA } from 'js-yaml';
import { promises } from 'fs';
import { load } from 'js-yaml';
import { LogLevel } from 'noicejs';
import { join } from 'path';
import { FlagLabel, StateLabel } from '../labels';
import { RemoteOptions } from '../remote';
import * as SCHEMA_DATA from './schema.yml';
let { readFile } = promises;
export const FILE_ENCODING = 'utf-8';
export type Filesystem = Pick<typeof promises, 'readFile'>;
/**
* Hook for tests to override the fs fns.
*/
export function setFs(fs: Filesystem) {
const originalRead = readFile;
readFile = fs.readFile;
return () => {
readFile = originalRead;
};
}
export interface LoggerConfig {
level: LogLevel;
name: string;
@ -64,27 +81,24 @@ export const CONFIG_SCHEMA_KEY = 'cautious-journey#/definitions/config';
/**
* Load the config from files.
*/
export async function initConfig(path: string, include = SCHEMA_OPTIONS): Promise<ConfigData> {
export async function initConfig(path: string): Promise<ConfigData> {
const schema = createSchema({});
const validator = new Ajv(AJV_OPTIONS);
validator.addSchema(SCHEMA_DATA, 'cautious-journey');
const config = createConfig<ConfigData>({
config: {
key: CONFIG_SCHEMA_KEY,
sources: [{
name: '.',
paths: [path],
type: 'file',
}],
},
process,
schema: {
include,
},
validator,
const data = await readFile(path, {
encoding: 'utf8',
});
return config.getData();
const config = load(data, {
schema,
});
if (validator.validate(CONFIG_SCHEMA_KEY, config) === true) {
return config as ConfigData;
} else {
throw new Error('invalid config data');
}
}
export const AJV_OPTIONS: Ajv.Options = {
@ -96,11 +110,3 @@ export const AJV_OPTIONS: Ajv.Options = {
useDefaults: true,
verbose: true,
};
export const SCHEMA_OPTIONS: IncludeOptions = {
exists: existsSync,
join,
read: readFileSync,
resolve: realpathSync,
schema: DEFAULT_SCHEMA,
};

View File

@ -1,60 +1,34 @@
import { IncludeOptions } from '@apextoaster/js-yaml-schema';
import { expect } from 'chai';
import { DEFAULT_SCHEMA } from 'js-yaml';
import { match, stub } from 'sinon';
import { vol } from 'memfs';
import { initConfig } from '../../src/config';
import { Filesystem, initConfig, setFs } from '../../src/config';
describe('config', () => {
describe('init config', () => {
it('should load a valid config', async () => {
const path = 'valid.yml';
const include: IncludeOptions = {
exists: stub(),
join: stub().returns(path),
read: stub().returns(`
vol.fromJSON({
[path]: `
logger:
level: info
name: test
projects: []
`),
resolve: stub(),
schema: DEFAULT_SCHEMA,
};
projects: []`,
});
const restore = setFs(vol.promises as Filesystem);
const config = await initConfig(path);
restore();
const config = await initConfig(path, include);
expect(include.read).to.have.been.calledWith(path, match.object);
expect(config.logger.name).to.equal('test');
});
it('should throw on invalid config', async () => {
const include: IncludeOptions = {
exists: stub(),
join: stub().returnsArg(0),
read: stub().returns(`
logger: []
projects: {}
`),
resolve: stub(),
schema: DEFAULT_SCHEMA,
};
await expect(initConfig('./invalid.yml', include)).to.eventually.be.rejectedWith(Error);
await expect(initConfig('./invalid.yml')).to.eventually.be.rejectedWith(Error);
});
it('should throw on missing paths', async () => {
const err = new Error();
Reflect.set(err, 'code', 'ENOENT');
const include: IncludeOptions = {
exists: stub().returns(false),
join: stub().returnsArg(0),
read: stub().throws(err),
resolve: stub(),
schema: DEFAULT_SCHEMA,
};
await expect(initConfig('.fake', include)).to.eventually.be.rejectedWith(Error);
await expect(initConfig('.fake')).to.eventually.be.rejectedWith(Error);
});
});
});

View File

@ -2,20 +2,15 @@
# yarn lockfile v1
"@apextoaster/js-config@0.2.0-3":
version "0.2.0-3"
resolved "https://artifacts.apextoaster.com/repository/group-npm/@apextoaster/js-config/-/js-config-0.2.0-3.tgz#077b9916fcf6541af038ddb9bd585026c4c4c08a"
integrity sha512-mcsbrWmKJ0tTcsX0v0BU4+VMlvQKsZ+A6zCUcD7rxyTu43w5+gbx8M94mMpA1hhCkC9Q12aKUl+8vgqXhUCOmw==
"@apextoaster/js-utils@0.3.0":
version "0.3.0"
resolved "https://artifacts.apextoaster.com/repository/group-npm/@apextoaster/js-utils/-/js-utils-0.3.0.tgz#c592732d22c95c9df6b97447da641047b4f69e01"
integrity sha512-CUJk4N/69XtiLMz4KFR9kXIvX2tqCqeqGd6Pa+LQ1u0iGD7IOZVJej0Dkr2qFeWwsPamS+XwwSvs8JV02m8/FA==
"@apextoaster/js-yaml-schema@0.4.0-4":
version "0.4.0-4"
resolved "https://artifacts.apextoaster.com/repository/group-npm/@apextoaster/js-yaml-schema/-/js-yaml-schema-0.4.0-4.tgz#fe9aa034f94a395b8c7eb62b638883a8bc6cbe7d"
integrity sha512-StRuVWApi4O5esIoEeMfYIImK821OcMTiJ2siihQKJgXAc/HDWuAEeq0K0DgZBZP0lHjEuFOheeDNHeCLNH/fA==
"@apextoaster/js-yaml-schema@0.4.0":
version "0.4.0"
resolved "https://artifacts.apextoaster.com/repository/group-npm/@apextoaster/js-yaml-schema/-/js-yaml-schema-0.4.0.tgz#6008bb90bbe958a5a645e2224317d14e8dcfa3b9"
integrity sha512-On4e0AytT6lpXIw9nHMYbHNyzRLNORzZwcETm4CssfGWRzF3FEzyLhSLfvX7ZNK+Z7l/pntVxiYWZu0S3Vv6Yw==
"@babel/code-frame@7.12.11":
version "7.12.11"
@ -2709,6 +2704,11 @@ fs-extra@~7.0.1:
jsonfile "^4.0.0"
universalify "^0.1.0"
fs-monkey@1.0.3:
version "1.0.3"
resolved "https://artifacts.apextoaster.com/repository/group-npm/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3"
integrity sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://artifacts.apextoaster.com/repository/group-npm/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@ -3927,6 +3927,13 @@ matched@^5.0.0:
glob "^7.1.6"
picomatch "^2.2.1"
memfs@^3.2.2:
version "3.2.2"
resolved "https://artifacts.apextoaster.com/repository/group-npm/memfs/-/memfs-3.2.2.tgz#5de461389d596e3f23d48bb7c2afb6161f4df40e"
integrity sha512-RE0CwmIM3CEvpcdK3rZ19BC4E6hv9kADkMN5rPduRak58cNArWLi/9jFLsa4rhsjfVxMP3v0jO7FHXq7SvFY5Q==
dependencies:
fs-monkey "1.0.3"
meow@^3.3.0:
version "3.7.0"
resolved "https://artifacts.apextoaster.com/repository/group-npm/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"