diff --git a/docs/api/js-yaml-schema.config_schema.md b/docs/api/js-yaml-schema.config_schema.md new file mode 100644 index 0000000..272b833 --- /dev/null +++ b/docs/api/js-yaml-schema.config_schema.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@apextoaster/js-yaml-schema](./js-yaml-schema.md) > [CONFIG\_SCHEMA](./js-yaml-schema.config_schema.md) + +## CONFIG\_SCHEMA variable + +Signature: + +```typescript +CONFIG_SCHEMA: Schema +``` diff --git a/docs/api/js-yaml-schema.envtype.md b/docs/api/js-yaml-schema.envtype.md new file mode 100644 index 0000000..86dbb85 --- /dev/null +++ b/docs/api/js-yaml-schema.envtype.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@apextoaster/js-yaml-schema](./js-yaml-schema.md) > [envType](./js-yaml-schema.envtype.md) + +## envType variable + +Signature: + +```typescript +envType: YamlType +``` diff --git a/docs/api/js-yaml-schema.includeschema.md b/docs/api/js-yaml-schema.includeschema.md new file mode 100644 index 0000000..72f32bd --- /dev/null +++ b/docs/api/js-yaml-schema.includeschema.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@apextoaster/js-yaml-schema](./js-yaml-schema.md) > [includeSchema](./js-yaml-schema.includeschema.md) + +## includeSchema variable + +Signature: + +```typescript +includeSchema: { + schema: import("js-yaml").Schema; +} +``` diff --git a/docs/api/js-yaml-schema.includetype.md b/docs/api/js-yaml-schema.includetype.md new file mode 100644 index 0000000..8302a07 --- /dev/null +++ b/docs/api/js-yaml-schema.includetype.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@apextoaster/js-yaml-schema](./js-yaml-schema.md) > [includeType](./js-yaml-schema.includetype.md) + +## includeType variable + +Signature: + +```typescript +includeType: YamlType +``` diff --git a/docs/api/js-yaml-schema.md b/docs/api/js-yaml-schema.md index c59e68c..02a7e14 100644 --- a/docs/api/js-yaml-schema.md +++ b/docs/api/js-yaml-schema.md @@ -4,3 +4,14 @@ ## js-yaml-schema package +## Variables + +| Variable | Description | +| --- | --- | +| [CONFIG\_SCHEMA](./js-yaml-schema.config_schema.md) | | +| [envType](./js-yaml-schema.envtype.md) | | +| [includeSchema](./js-yaml-schema.includeschema.md) | | +| [includeType](./js-yaml-schema.includetype.md) | | +| [regexpType](./js-yaml-schema.regexptype.md) | | +| [streamType](./js-yaml-schema.streamtype.md) | | + diff --git a/docs/api/js-yaml-schema.regexptype.md b/docs/api/js-yaml-schema.regexptype.md new file mode 100644 index 0000000..548324f --- /dev/null +++ b/docs/api/js-yaml-schema.regexptype.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@apextoaster/js-yaml-schema](./js-yaml-schema.md) > [regexpType](./js-yaml-schema.regexptype.md) + +## regexpType variable + +Signature: + +```typescript +regexpType: YamlType +``` diff --git a/docs/api/js-yaml-schema.streamtype.md b/docs/api/js-yaml-schema.streamtype.md new file mode 100644 index 0000000..f8c17cb --- /dev/null +++ b/docs/api/js-yaml-schema.streamtype.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@apextoaster/js-yaml-schema](./js-yaml-schema.md) > [streamType](./js-yaml-schema.streamtype.md) + +## streamType variable + +Signature: + +```typescript +streamType: YamlType +``` diff --git a/package.json b/package.json index a79a2bb..091a8bc 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "author": "ssube", "license": "MIT", "devDependencies": { + "@apextoaster/js-utils": "^0.1.1", "@istanbuljs/nyc-config-typescript": "1.0.1", "@microsoft/api-documenter": "7.7.15", "@microsoft/api-extractor": "7.7.10", diff --git a/src/error/InvalidArgumentError.ts b/src/error/InvalidArgumentError.ts deleted file mode 100644 index 463fe29..0000000 --- a/src/error/InvalidArgumentError.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { BaseError } from 'noicejs'; - -export class InvalidArgumentError extends BaseError { - constructor(msg = 'invalid argument passed', ...nested: Array) { - super(msg, ...nested); - } -} diff --git a/src/error/NotFoundError.ts b/src/error/NotFoundError.ts deleted file mode 100644 index d5b179c..0000000 --- a/src/error/NotFoundError.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { BaseError } from 'noicejs'; - -export class NotFoundError extends BaseError { - constructor(msg = 'value not found', ...nested: Array) { - super(msg, ...nested); - } -} diff --git a/src/index.ts b/src/index.ts index 154ca87..cc9573a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,11 @@ import { main } from './app'; +export { CONFIG_SCHEMA } from './schema'; +export { envType } from './type/Env'; +export { includeSchema, includeType } from './type/Include'; +export { regexpType } from './type/Regexp'; +export { streamType } from './type/Stream'; + const STATUS_ERROR = 1; /** diff --git a/src/schema.ts b/src/schema.ts index 283cb19..4e6314a 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -1,7 +1,7 @@ import { DEFAULT_SAFE_SCHEMA, Schema } from 'js-yaml'; import { envType } from './type/Env'; -import { includeType } from './type/Include'; +import { includeSchema, includeType } from './type/Include'; import { regexpType } from './type/Regexp'; import { streamType } from './type/Stream'; @@ -11,3 +11,5 @@ export const CONFIG_SCHEMA = Schema.create([DEFAULT_SAFE_SCHEMA], [ regexpType, streamType, ]); + +includeSchema.schema = CONFIG_SCHEMA; diff --git a/src/type/Env.ts b/src/type/Env.ts index d984a12..35ddde6 100644 --- a/src/type/Env.ts +++ b/src/type/Env.ts @@ -1,7 +1,6 @@ +import { NotFoundError } from '@apextoaster/js-utils'; import { Type as YamlType } from 'js-yaml'; -import { NotFoundError } from '../error/NotFoundError'; - export const envType = new YamlType('!env', { kind: 'scalar', resolve(name: string) { diff --git a/src/type/Include.ts b/src/type/Include.ts index a989a8d..3d3e12c 100644 --- a/src/type/Include.ts +++ b/src/type/Include.ts @@ -1,10 +1,8 @@ +import { InvalidArgumentError, NotFoundError } from '@apextoaster/js-utils'; import { existsSync, readFileSync, realpathSync } from 'fs'; import { SAFE_SCHEMA, safeLoad, Type as YamlType } from 'js-yaml'; import { join } from 'path'; -import { InvalidArgumentError } from '../error/InvalidArgumentError'; -import { NotFoundError } from '../error/NotFoundError'; - // work around the circular dependency by setting the schema later export const includeSchema = { schema: SAFE_SCHEMA, diff --git a/src/type/Regexp.ts b/src/type/Regexp.ts index c3c71f1..2c32869 100644 --- a/src/type/Regexp.ts +++ b/src/type/Regexp.ts @@ -1,8 +1,7 @@ +import { InvalidArgumentError } from '@apextoaster/js-utils'; import { Type as YamlType } from 'js-yaml'; import { isNil } from 'lodash'; -import { InvalidArgumentError } from '../error/InvalidArgumentError'; - export const REGEXP_REGEXP = /^\/(.+)\/([gimsuy]*)$/; export const regexpType = new YamlType('!regexp', { diff --git a/src/type/Stream.ts b/src/type/Stream.ts index bb079d0..6070372 100644 --- a/src/type/Stream.ts +++ b/src/type/Stream.ts @@ -1,7 +1,6 @@ +import { NotFoundError } from '@apextoaster/js-utils'; import { Type as YamlType } from 'js-yaml'; -import { NotFoundError } from '../error/NotFoundError'; - const ALLOWED_STREAMS = new Set([ 'stdout', 'stderr', diff --git a/test/helpers/async.ts b/test/helpers/async.ts deleted file mode 100644 index b9769be..0000000 --- a/test/helpers/async.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { AsyncHook, createHook } from 'async_hooks'; - -// this will pull Mocha internals out of the stacks -/* eslint-disable-next-line @typescript-eslint/no-var-requires */ -const { stackTraceFilter } = require('mocha/lib/utils'); -const filterStack = stackTraceFilter(); - -type AsyncMochaTest = (this: Mocha.Context | void) => Promise; -type AsyncMochaSuite = (this: Mocha.Suite) => Promise; - -/* eslint-disable-next-line @typescript-eslint/ban-types */ -function isNil(val: T | null | undefined): val is null | undefined { - /* eslint-disable-next-line no-null/no-null */ - return val === null || val === undefined; -} - -export interface TrackedResource { - source: string; - triggerAsyncId: number; - type: string; -} - -function debugMode() { - return Reflect.has(process.env, 'DEBUG'); -} - -/** - * Async resource tracker using node's internal hooks. - * - * This probably won't work in a browser. It does not hold references to the resource, to avoid leaks. - * Adapted from https://gist.github.com/boneskull/7fe75b63d613fa940db7ec990a5f5843#file-async-dump-js - */ -export class Tracker { - public static getStack(): string { - const err = new Error(); - if (isNil(err.stack)) { - return 'no stack trace available'; - } else { - return filterStack(err.stack); - } - } - - private readonly hook: AsyncHook; - private readonly resources: Map; - - constructor() { - this.resources = new Map(); - this.hook = createHook({ - destroy: (id: number) => { - this.resources.delete(id); - }, - init: (id: number, type: string, triggerAsyncId: number) => { - const source = Tracker.getStack(); - // @TODO: exclude async hooks, including this one - this.resources.set(id, { - source, - triggerAsyncId, - type, - }); - }, - promiseResolve: (id: number) => { - this.resources.delete(id); - }, - }); - } - - public clear() { - this.resources.clear(); - } - - public disable() { - this.hook.disable(); - } - - /* eslint-disable no-console, no-invalid-this */ - public dump() { - console.error(`tracking ${this.resources.size} async resources`); - this.resources.forEach((res, id) => { - console.error(`${id}: ${res.type}`); - if (debugMode()) { - console.error(res.source); - console.error('\n'); - } - }); - } - - public enable() { - this.hook.enable(); - } - - public get size(): number { - return this.resources.size; - } -} - -/** - * Describe a suite of async tests. This wraps mocha's describe to track async resources and report leaks. - */ -export function describeLeaks(description: string, cb: AsyncMochaSuite): Mocha.Suite { - return describe(description, function trackSuite(this: Mocha.Suite) { - const tracker = new Tracker(); - - beforeEach(() => { - tracker.enable(); - }); - - afterEach(() => { - tracker.disable(); - const leaked = tracker.size; - - // @TODO: this should only exclude the single Immediate set by the Tracker - if (leaked > 1) { - tracker.dump(); - const msg = `test leaked ${leaked - 1} async resources`; - if (debugMode()) { - throw new Error(msg); - } else { - /* eslint-disable-next-line no-console */ - console.warn(msg); - } - } - - tracker.clear(); - }); - - const suite: PromiseLike | undefined = cb.call(this); - if (isNil(suite) || !Reflect.has(suite, 'then')) { - /* eslint-disable-next-line no-console */ - console.error(`test suite '${description}' did not return a promise`); - } - - return suite; - }); -} - -/** - * Run an asynchronous test with unhandled rejection guards. - * - * This function may not have any direct test coverage. It is too simple to reasonably mock. - */ -export function itLeaks(expectation: string, cb?: AsyncMochaTest): Mocha.Test { - if (isNil(cb)) { - return it(expectation); - } - - return it(expectation, function trackTest(this: Mocha.Context) { - return new Promise((res, rej) => { - cb.call(this).then((value: unknown) => { - res(value); - }, (err: Error) => { - rej(err); - }); - }); - }); -} diff --git a/test/type/TestEnv.ts b/test/type/TestEnv.ts index 2e978e8..38a6c78 100644 --- a/test/type/TestEnv.ts +++ b/test/type/TestEnv.ts @@ -1,22 +1,21 @@ import { expect } from 'chai'; -import { NotFoundError } from '../../src/error/NotFoundError'; +import { NotFoundError } from '@apextoaster/js-utils'; import { envType } from '../../src/type/Env'; import { VERSION_INFO } from '../../src/version'; -import { describeLeaks, itLeaks } from '../helpers/async'; -describeLeaks('env config type', async () => { - itLeaks('should throw on missing variables', async () => { +describe('env config type', async () => { + it('should throw on missing variables', async () => { expect(() => { envType.resolve('DOES_NOT_EXIST_'); }).to.throw(NotFoundError); }); - itLeaks('should resolve existing variables', async () => { + it('should resolve existing variables', async () => { expect(envType.resolve('CI_COMMIT_SHA')).to.equal(true); }); - itLeaks('should construct a value from variables', async () => { + it('should construct a value from variables', async () => { expect(envType.construct('CI_COMMIT_SHA')).to.equal(VERSION_INFO.git.commit); }); }); diff --git a/test/type/TestInclude.ts b/test/type/TestInclude.ts index bebc357..5cf37cb 100644 --- a/test/type/TestInclude.ts +++ b/test/type/TestInclude.ts @@ -1,29 +1,27 @@ import { expect } from 'chai'; import { join } from 'path'; -import { InvalidArgumentError } from '../../src/error/InvalidArgumentError'; -import { NotFoundError } from '../../src/error/NotFoundError'; +import { InvalidArgumentError, NotFoundError } from '@apextoaster/js-utils'; import { includeType } from '../../src/type/Include'; -import { describeLeaks, itLeaks } from '../helpers/async'; const TEST_ROOT = '../test/type'; -describeLeaks('include config type', async () => { - itLeaks('should resolve existing files', async () => { +describe('include config type', async () => { + it('should resolve existing files', async () => { expect(includeType.resolve(join(TEST_ROOT, 'include.yml'))).to.equal(true); }); - itLeaks('should throw when resolving missing files', async () => { + it('should throw when resolving missing files', async () => { expect(() => { includeType.resolve(join(TEST_ROOT, 'missing.yml')); }).to.throw(NotFoundError); }); - itLeaks('should construct data from file', async () => { + it('should construct data from file', async () => { expect(includeType.construct(join(TEST_ROOT, 'include.yml'))).to.equal('test'); }); - itLeaks('should throw when constructing missing files', async () => { + it('should throw when constructing missing files', async () => { expect(() => { includeType.construct(join(TEST_ROOT, 'missing.yml')); }).to.throw(InvalidArgumentError); diff --git a/test/type/TestRegexp.ts b/test/type/TestRegexp.ts index 5a1c1b0..1727717 100644 --- a/test/type/TestRegexp.ts +++ b/test/type/TestRegexp.ts @@ -1,27 +1,26 @@ import { expect } from 'chai'; import { regexpType } from '../../src/type/Regexp'; -import { describeLeaks, itLeaks } from '../helpers/async'; -describeLeaks('regexp config type', async () => { - itLeaks('match slashed strings', async () => { +describe('regexp config type', async () => { + it('match slashed strings', async () => { expect(regexpType.resolve('/foo/')).to.equal(true); }); - itLeaks('should match flags', async () => { + it('should match flags', async () => { const regexp: RegExp = regexpType.construct('/foo/g'); expect(regexp.flags).to.equal('g'); }); - itLeaks('should not match bare strings', async () => { + it('should not match bare strings', async () => { expect(regexpType.resolve('foo')).to.equal(false); }); - itLeaks('should not match invalid flags', async () => { + it('should not match invalid flags', async () => { expect(regexpType.resolve('/foo/notrealflags')).to.equal(false); }); - itLeaks('should not match regex embedded in a longer string', async () => { + it('should not match regex embedded in a longer string', async () => { expect(regexpType.resolve('some/regex/with-padding')).to.equal(false); }); }); diff --git a/yarn.lock b/yarn.lock index debb13a..5032631 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@apextoaster/js-utils@^0.1.1": + version "0.1.1" + resolved "https://artifacts.apextoaster.com/repository/group-npm/@apextoaster/js-utils/-/js-utils-0.1.1.tgz#035548ce76f12c1de4b9c20f9b9aeebd0c840546" + integrity sha512-hJZY3smbpTPdKidZIaa+VDROkCIjaEahhPqqF7YCqvFP5E/CNbwbiNqASxZjz97Dde2Dw6II8YGDV5Ef8yfO3g== + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.5.5": version "7.5.5" resolved "https://artifacts.apextoaster.com/repository/group-npm/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d"