1
0
Fork 0

fix: refactor duplicate utils into js-utils, export symbols

BREAKING CHANGE: extract typed errors and use the equivalent
from js-utils
This commit is contained in:
ssube 2020-03-29 08:13:57 -05:00
parent bbd354d2e2
commit 859baa33f9
Signed by: ssube
GPG Key ID: 3EED7B957D362AF1
21 changed files with 115 additions and 200 deletions

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [@apextoaster/js-yaml-schema](./js-yaml-schema.md) &gt; [CONFIG\_SCHEMA](./js-yaml-schema.config_schema.md)
## CONFIG\_SCHEMA variable
<b>Signature:</b>
```typescript
CONFIG_SCHEMA: Schema
```

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [@apextoaster/js-yaml-schema](./js-yaml-schema.md) &gt; [envType](./js-yaml-schema.envtype.md)
## envType variable
<b>Signature:</b>
```typescript
envType: YamlType
```

View File

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [@apextoaster/js-yaml-schema](./js-yaml-schema.md) &gt; [includeSchema](./js-yaml-schema.includeschema.md)
## includeSchema variable
<b>Signature:</b>
```typescript
includeSchema: {
schema: import("js-yaml").Schema;
}
```

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [@apextoaster/js-yaml-schema](./js-yaml-schema.md) &gt; [includeType](./js-yaml-schema.includetype.md)
## includeType variable
<b>Signature:</b>
```typescript
includeType: YamlType
```

View File

@ -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) | |

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [@apextoaster/js-yaml-schema](./js-yaml-schema.md) &gt; [regexpType](./js-yaml-schema.regexptype.md)
## regexpType variable
<b>Signature:</b>
```typescript
regexpType: YamlType
```

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [@apextoaster/js-yaml-schema](./js-yaml-schema.md) &gt; [streamType](./js-yaml-schema.streamtype.md)
## streamType variable
<b>Signature:</b>
```typescript
streamType: YamlType
```

View File

@ -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",

View File

@ -1,7 +0,0 @@
import { BaseError } from 'noicejs';
export class InvalidArgumentError extends BaseError {
constructor(msg = 'invalid argument passed', ...nested: Array<Error>) {
super(msg, ...nested);
}
}

View File

@ -1,7 +0,0 @@
import { BaseError } from 'noicejs';
export class NotFoundError extends BaseError {
constructor(msg = 'value not found', ...nested: Array<Error>) {
super(msg, ...nested);
}
}

View File

@ -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;
/**

View File

@ -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;

View File

@ -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) {

View File

@ -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,

View File

@ -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', {

View File

@ -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',

View File

@ -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<void>;
type AsyncMochaSuite = (this: Mocha.Suite) => Promise<void>;
/* eslint-disable-next-line @typescript-eslint/ban-types */
function isNil<T>(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<number, TrackedResource>;
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<void> | 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<unknown>((res, rej) => {
cb.call(this).then((value: unknown) => {
res(value);
}, (err: Error) => {
rej(err);
});
});
});
}

View File

@ -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);
});
});

View File

@ -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);

View File

@ -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);
});
});

View File

@ -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"