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:
parent
c487efb5ed
commit
c32ac312df
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
25
yarn.lock
25
yarn.lock
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue