1
0
Fork 0

feat: copy template from other projects

This commit is contained in:
ssube 2019-09-17 07:45:15 -05:00
commit 7766659391
Signed by: ssube
GPG Key ID: 3EED7B957D362AF1
28 changed files with 3363 additions and 0 deletions

23
.codeclimate.yml Normal file
View File

@ -0,0 +1,23 @@
version: "2"
exclude_patterns:
- src/migration/
- test/old/
- vendor/
plugins:
duplication:
enabled: true
config:
languages:
typescript:
mass_threshold: 120
fixme:
enabled: true
markdownlint:
enabled: true
shellcheck:
enabled: true
tslint:
enabled: true
config: config/tslint.cc.json

10
.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
.nyc_output/
node_modules/
out/
temp/
# types
*.d.ts
*.swp
yarn-error.log

132
.gitlab-ci.yml Executable file
View File

@ -0,0 +1,132 @@
stages:
- status-pre
- build
- image
- publish
- status-post
# template jobs
.branch-deploy: &branch-deploy
only:
- master
.build-curl: &build-curl
image: apextoaster/base:1.2
tags:
- platform:k8s
- runner:shared
.build-climate: &build-climate
image: apextoaster/code-climate:0.6
tags:
- platform:k8s
- runner:shared
allow_failure: false
variables:
CI_BRANCH: "${CI_COMMIT_REF_NAME}"
GIT_BRANCH: "${CI_COMMIT_REF_NAME}"
GIT_COMMIT_SHA: "${CI_COMMIT_SHA}"
.build-codecov: &build-codecov
image: apextoaster/codecov:3.1
tags:
- platform:k8s
- runner:shared
allow_failure: false
.build-node: &build-node
image: apextoaster/node:10.1
tags:
- platform:k8s
- runner:shared
allow_failure: false
before_script:
- echo "${NPM_SECRET}" | base64 -d > ${HOME}/.npmrc
# build jobs
build-node:
<<: [*build-node]
stage: build
variables:
GIT_SUBMODULE_STRATEGY: recursive
script:
- make build test
artifacts:
expire_in: 1 week
paths:
- out/coverage
- out/docs
- out/main.js
- out/noicejs.d.ts
- out/version.json
cache:
key: "${CI_COMMIT_REF_SLUG}"
policy: pull-push
paths:
- node_modules
# publish jobs
publish-npm:
<<: [*build-node]
stage: publish
only:
- tags
dependencies:
- build-node
script:
- npm publish
# commit status
climate-pending:
<<: [*build-climate]
stage: status-pre
script:
- cc-test-reporter before-build
climate-failure:
<<: [*build-climate]
stage: status-post
when: on_failure
script:
- cc-test-reporter after-build --debug --exit-code 1
climate-success:
<<: [*build-climate]
stage: status-post
dependencies:
- build-node
script:
- make upload-climate
codecov-success:
<<: [*build-codecov]
stage: status-post
when: on_success
dependencies:
- build-node
script:
- make upload-codecov
github-pending:
<<: [*build-curl]
stage: status-pre
script:
- ./scripts/github-status.sh pending
github-failure:
<<: [*build-curl]
stage: status-post
when: on_failure
script:
- ./scripts/github-status.sh failure
github-success:
<<: [*build-curl]
stage: status-post
when: on_success
script:
- ./scripts/github-status.sh success

32
.npmignore Normal file
View File

@ -0,0 +1,32 @@
.awcache/
.github/
.nyc_output/
config/
deploy/
node_modules/
out/cache/
out/coverage/
out/coverage-*
out/docs/
out/typings/
out/*.db
out/*.html
out/*.json
out/test-*
scripts/
src/
temp/
test/
vendor/
.codeclimate.yml
.gitlab-ci.yml
.gitmodules
.mdlrc
Dockerfile
licensed.yml
Makefile
tsconfig.json
yarn-*

21
LICENSE.md Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016-2018 Sean Sube
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

153
Makefile Executable file
View File

@ -0,0 +1,153 @@
# Git
export GIT_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
export GIT_COMMIT ?= $(shell git rev-parse HEAD)
export GIT_REMOTES ?= $(shell git remote -v | awk '{ print $1; }' | sort | uniq)
export GIT_OPTIONS ?=
# CI
export CI_COMMIT_REF_SLUG ?= $(GIT_BRANCH)
export CI_COMMIT_SHA ?= $(GIT_COMMIT)
export CI_ENVIRONMENT_SLUG ?= local
export CI_JOB_ID ?= 0
export CI_RUNNER_DESCRIPTION ?= $(shell hostname)
export CI_RUNNER_ID ?= $(shell hostname)
export CI_RUNNER_VERSION ?= 0.0.0
# Debug
export DEBUG_BIND ?= 127.0.0.1
export DEBUG_PORT ?= 9229
# Paths
# resolve the makefile's path and directory, from https://stackoverflow.com/a/18137056
export MAKE_PATH ?= $(abspath $(lastword $(MAKEFILE_LIST)))
export ROOT_PATH ?= $(dir $(MAKE_PATH))
export CONFIG_PATH ?= $(ROOT_PATH)/config
export DOCS_PATH ?= $(ROOT_PATH)/docs
export SCRIPT_PATH ?= $(ROOT_PATH)/scripts
export SOURCE_PATH ?= $(ROOT_PATH)/src
export TARGET_PATH ?= $(ROOT_PATH)/out
export TARGET_LOG ?= $(TARGET_PATH)/apex-reference.log
export TARGET_MAIN ?= $(TARGET_PATH)/main-bundle.js
export TEST_PATH ?= $(ROOT_PATH)/test
export VENDOR_PATH ?= $(ROOT_PATH)/vendor
# Node options
NODE_BIN := $(ROOT_PATH)/node_modules/.bin
NODE_CMD ?= $(shell env node)
NODE_DEBUG ?= --inspect-brk=$(DEBUG_BIND):$(DEBUG_PORT) --nolazy
NODE_INFO := $(shell node -v)
# Tool options
BUNDLE_OPTS ?= --config "$(CONFIG_PATH)/webpack.js" --display-optimization-bailout --display-error-details
COVER_CHECK ?= --check-coverage --branches 90 --functions 100 --lines 95 --statements 95 # increase this every so often
COVER_OPTS ?= --reporter=lcov --reporter=text-summary --reporter=html --report-dir="$(TARGET_PATH)/coverage" --exclude-after-remap
DOCS_OPTS ?= --exclude "test.+" --tsconfig "$(CONFIG_PATH)/tsconfig.json" --out "$(TARGET_PATH)/docs"
MOCHA_MULTI ?= --reporter mocha-multi --reporter-options json="$(TARGET_PATH)/mocha.json",spec
MOCHA_OPTS ?= --check-leaks --colors --sort --ui bdd
RELEASE_OPTS ?= --commit-all
# Versions
export NODE_VERSION := $(shell node -v)
export RUNNER_VERSION := $(CI_RUNNER_VERSION)
all: build test ## builds, bundles, and tests the application
@echo Success!
clean: ## clean up everything added by the default target
clean: clean-deps clean-target
clean-deps: ## clean up the node_modules directory
rm -rf node_modules
clean-target: ## clean up the target directory
rm -rf $(TARGET_PATH)
configure: ## create the target directory and other files not in git
mkdir -p $(TARGET_PATH)
node_modules: yarn-install
# from https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
help: ## print this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort \
| sed 's/^.*\/\(.*\)/\1/' \
| awk 'BEGIN {FS = ":[^:]*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
todo:
@echo "Remaining tasks:"
@echo ""
@grep -i "todo" -r docs/ src/ test/ || true
@echo ""
@echo "Pending tests:"
@echo ""
@grep "[[:space:]]xit" -r test/ || true
@echo "Casts to any:"
@echo ""
@grep "as any" -r src/ test/ || true
@echo ""
# Build targets
build: ## builds, bundles, and tests the application
build: build-bundle build-docs
build-bundle: node_modules
$(NODE_BIN)/rollup --config $(CONFIG_PATH)/rollup.js
build-docs: ## generate html docs
$(NODE_BIN)/api-extractor run --config $(CONFIG_PATH)/api-extractor.json --local -v
$(NODE_BIN)/api-documenter markdown -i $(TARGET_PATH)/api -o $(DOCS_PATH)/api
test: ## run mocha unit tests
test: test-cover
test-check: ## run mocha unit tests with coverage reports
$(NODE_BIN)/nyc $(COVER_OPTS) $(NODE_BIN)/mocha $(MOCHA_OPTS) $(TARGET_PATH)/test.js
test-cover: ## run mocha unit tests with coverage reports
test-cover: test-check
sed -i $(TARGET_PATH)/coverage/lcov.info \
-e '/external ".*"$$/,/end_of_record/d' \
-e '/ sync$$/,/end_of_record/d' \
-e '/test sync/,/end_of_record/d' \
-e '/node_modules/,/end_of_record/d' \
-e '/bootstrap$$/,/end_of_record/d' \
-e '/universalModuleDefinition/,/end_of_record/d'
sed -n '/^SF/,$$p' -i $(TARGET_PATH)/coverage/lcov.info
sed '1s;^;TN:\n;' -i $(TARGET_PATH)/coverage/lcov.info
test-watch:
$(NODE_BIN)/nyc $(COVER_OPTS) $(NODE_BIN)/mocha $(MOCHA_OPTS) --watch $(TARGET_PATH)/test-bundle.js
yarn-install: ## install dependencies from package and lock file
yarn
yarn-update: ## check yarn for outdated packages
yarn upgrade-interactive --latest
# release targets
git-push: ## push to both gitlab and github (this assumes you have both remotes set up)
git push $(GIT_OPTIONS) github $(GIT_BRANCH)
git push $(GIT_OPTIONS) gitlab $(GIT_BRANCH)
# from https://gist.github.com/amitchhajer/4461043#gistcomment-2349917
git-stats: ## print git contributor line counts (approx, for fun)
git ls-files | while read f; do git blame -w -M -C -C --line-porcelain "$$f" |\
grep -I '^author '; done | sort -f | uniq -ic | sort -n
license-check: ## check license status
licensed cache
licensed status
release: ## create a release
$(NODE_BIN)/standard-version --sign $(RELEASE_OPTS)
GIT_OPTIONS=--tags $(MAKE) git-push
release-dry: ## test creating a release
$(NODE_BIN)/standard-version --sign $(RELEASE_OPTS) --dry-run
upload-climate:
cc-test-reporter format-coverage -t lcov -o $(TARGET_PATH)/coverage/codeclimate.json -p $(ROOT_PATH) $(TARGET_PATH)/coverage/lcov.info
cc-test-reporter upload-coverage --debug -i $(TARGET_PATH)/coverage/codeclimate.json -r "$(shell echo "${CODECLIMATE_SECRET}" | base64 -d)"
upload-codecov:
codecov --disable=gcov --file=$(TARGET_PATH)/coverage/lcov.info --token=$(shell echo "${CODECOV_SECRET}" | base64 -d)

0
README.md Normal file
View File

43
config/api-extractor.json Normal file
View File

@ -0,0 +1,43 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"projectFolder": "..",
"mainEntryPointFilePath": "<projectFolder>/out/src/index.d.ts",
"apiReport": {
"enabled": true,
"reportFolder": "<projectFolder>/out/",
"reportTempFolder": "<projectFolder>/out/tmp/"
},
"docModel": {
"enabled": true,
"apiJsonFilePath": "<projectFolder>/out/api/<unscopedPackageName>.api.json"
},
"dtsRollup": {
"enabled": true,
"untrimmedFilePath": "<projectFolder>/out/noicejs.d.ts",
"betaTrimmedFilePath": "<projectFolder>/out/noicejs-beta.d.ts",
"publicTrimmedFilePath": "<projectFolder>/out/noicejs-public.d.ts"
},
"tsdocMetadata": {
},
"messages": {
"compilerMessageReporting": {
"default": {
"logLevel": "warning"
}
},
"extractorMessageReporting": {
"default": {
"logLevel": "warning"
}
},
"tsdocMessageReporting": {
"default": {
"logLevel": "warning"
}
}
}
}

4
config/mocha.json Executable file
View File

@ -0,0 +1,4 @@
{
"reporter": ["json"],
"ui": ["bdd"]
}

5
config/rollup-named.json Normal file
View File

@ -0,0 +1,5 @@
{
"node_modules/chai/index.js": [
"expect"
]
}

85
config/rollup.js Normal file
View File

@ -0,0 +1,85 @@
import commonjs from 'rollup-plugin-commonjs';
import json from 'rollup-plugin-json';
import multiEntry from 'rollup-plugin-multi-entry';
import replace from 'rollup-plugin-replace';
import resolve from 'rollup-plugin-node-resolve';
import tslint from 'rollup-plugin-tslint';
import typescript from 'rollup-plugin-typescript2';
const namedExports = require('./rollup-named.json');
const metadata = require('../package.json');
const shebang = '#! /usr/bin/env node\n\n';
const bundle = {
external: [
'async_hooks',
'chai',
'sinon',
],
input: [
'src/index.ts',
'test/harness.ts',
'test/**/Test*.ts',
],
manualChunks(id) {
if (id.includes('/test/')) {
return 'test'
}
if (id.includes('/node_modules/')) {
return 'vendor';
}
if (id.includes('/src/index')) {
return 'index';
}
if (id.includes('/src/')) {
return 'main';
}
},
output: {
dir: 'out/',
chunkFileNames: '[name].js',
entryFileNames: 'entry-[name].js',
format: 'cjs',
sourcemap: true,
banner: () => {
return shebang;
},
},
plugins: [
multiEntry(),
json(),
replace({
delimiters: ['{{ ', ' }}'],
values: {
APP_NAME: metadata.name,
APP_VERSION: metadata.version,
BUILD_JOB: process.env['CI_JOB_ID'],
BUILD_RUNNER: process.env['CI_RUNNER_DESCRIPTION'],
GIT_BRANCH: process.env['CI_COMMIT_REF_SLUG'],
GIT_COMMIT: process.env['CI_COMMIT_SHA'],
NODE_VERSION: process.env['NODE_VERSION'],
},
}),
resolve({
preferBuiltins: true,
}),
commonjs({
namedExports,
}),
tslint({
configuration: './config/tslint.json',
throwOnError: true,
}),
typescript({
cacheRoot: 'out/cache/rts2',
rollupCommonJSResolveHack: true,
}),
],
};
export default [
bundle,
];

43
config/tsconfig.json Executable file
View File

@ -0,0 +1,43 @@
{
"compileOnSave": false,
"compilerOptions": {
"allowJs": false,
"allowSyntheticDefaultImports": true,
"declaration": true,
"declarationMap": true,
"experimentalDecorators": true,
"importHelpers": true,
"jsx": "react",
"lib": [
"dom",
"es2017"
],
"module": "es6",
"moduleResolution": "node",
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"outDir": "../out",
"sourceMap": true,
"strict": true,
"strictNullChecks": true,
"target": "es2017",
"types": [
"chai-as-promised",
"mocha",
"node",
"sinon-chai"
],
"typeRoots": [
"../node_modules/@types",
"../node_modules"
]
},
"exclude": [
"../node_modules"
],
"include": [
"../src/**/*",
"../test/**/*"
]
}

159
config/tslint.cc.json Executable file
View File

@ -0,0 +1,159 @@
{
"extends": ["tslint:latest"],
"rules": {
"adjacent-overload-signatures": true,
"array-type": [
true,
"generic"
],
"arrow-parens": true,
"arrow-return-shorthand": true,
"await-promise": true,
"ban": [true, {
"message": "use lodash isString",
"name": ["util", "isString"]
}, {
"message": "use lodash isNil",
"name": ["util", "isNullOrUndefined"]
}],
"ban-comma-operator": true,
"ban-types": true,
"class-name": true,
"curly": true,
"cyclomatic-complexity": [
true,
12
],
"deprecation": true,
"eofline": true,
"increment-decrement": true,
"indent": [true, "spaces", 2],
"interface-name": [
true,
"never-prefix"
],
"max-classes-per-file": [
false,
1
],
"max-file-line-count": 250,
"max-line-length": [
true,
180
],
"member-access": true,
"member-ordering": [
true,
{
"order": [
"public-static-method",
"public-static-field",
"public-instance-field",
"protected-instance-field",
"public-constructor",
"public-instance-method",
"protected-instance-method"
]
}
],
"new-parens": true,
"no-angle-bracket-type-assertion": true,
"no-any": true,
"no-arg": true,
"no-bitwise": false,
"no-boolean-literal-compare": true,
"no-conditional-assignment": true,
"no-console": true,
"no-construct": true,
"no-dead-store": false,
"no-debugger": true,
"no-default-export": true,
"no-default-import": true,
"no-duplicate-super": true,
"no-duplicate-variable": true,
"no-empty-interface": true,
"no-floating-promises": true,
"no-for-in-array": true,
"no-implicit-dependencies": false,
"no-inferred-empty-object-type": true,
"no-inferrable-types": [
true,
"ignore-params",
"ignore-properties"
],
"no-internal-module": true,
"no-invalid-this": true,
"no-irregular-whitespace": true,
"no-magic-numbers": [
true,
-1, 0,
1, 2, 3, 4, 5, 6, 7, 8, 9,
10, 20, 30, 40, 50, 60, 70, 80, 90,
100
],
"no-misused-new": true,
"no-namespace": true,
"no-non-null-assertion": true,
"no-null-keyword": true,
"no-parameter-properties": true,
"no-parameter-reassignment": true,
"no-return-await": true,
"no-shadowed-variable": true,
"no-sparse-arrays": true,
"no-string-literal": true,
"no-string-throw": true,
"no-submodule-imports": false,
"no-unbound-method": true,
"no-unused-expression": true,
"no-var-keyword": true,
"no-void-expression": true,
"prefer-conditional-expression": false,
"prefer-const": true,
"prefer-for-of": true,
"prefer-readonly": true,
"prefer-switch": true,
"quotemark": [
true,
"single",
"jsx-double",
"avoid-escape"
],
"radix": true,
"restrict-plus-operands": true,
"strict-boolean-expressions": true,
"strict-type-predicates": true,
"switch-default": true,
"trailing-comma": [
true,
{
"esSpecCompliant": true,
"multiline": {
"arrays": "always",
"functions": "never",
"object": "always"
},
"singleline": "never"
}
],
"triple-equals": true,
"unnecessary-constructor": true,
"use-default-type-parameter": true,
"use-isnan": true,
"variable-name": [
true,
"ban-keywords",
"check-format"
]
},
"jsRules": {
"curly": true,
"trailing-comma": [
true,
{
"multiline": "never",
"singleline": "never"
}
]
},
"rulesDirectory": []
}

12
config/tslint.json Executable file
View File

@ -0,0 +1,12 @@
{
"extends": ["./tslint.cc.json", "tslint-clean-code", "tslint-sonarts"],
"rulesDirectory": ["../node_modules/tslint-microsoft-contrib"],
"rules": {
"mocha-unneeded-done": true,
"no-banned-terms": true,
"no-delete-expression": true,
"no-for-in": true,
"no-relative-imports": false,
"no-small-switch": false
}
}

12
docs/api/index.md Normal file
View File

@ -0,0 +1,12 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md)
## API Reference
## Packages
| Package | Description |
| --- | --- |
| [template](./template.md) | |

6
docs/api/template.md Normal file
View File

@ -0,0 +1,6 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [template](./template.md)
## template package

56
package.json Normal file
View File

@ -0,0 +1,56 @@
{
"name": "template",
"version": "0.1.0",
"description": "rollup/typescript project template repo",
"main": "out/index.js",
"directories": {
"test": "test"
},
"scripts": {
"test": "make test"
},
"publishConfig": {
"registry": "https://registry.npmjs.org"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ssube/rollup-template.git"
},
"author": "ssube",
"license": "MIT",
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "0.1.3",
"@microsoft/api-documenter": "7.4.3",
"@microsoft/api-extractor": "7.4.2",
"@types/chai": "4.2.3",
"@types/chai-as-promised": "^7.1.2",
"@types/mocha": "5.2.7",
"@types/sinon-chai": "3.2.3",
"@types/source-map-support": "0.5.0",
"chai": "4.2.0",
"chai-as-promised": "7.1.1",
"mocha": "6.2.0",
"nyc": "14.1.1",
"rollup": "1.21.4",
"rollup-plugin-commonjs": "10.1.0",
"rollup-plugin-json": "4.0.0",
"rollup-plugin-multi-entry": "2.1.0",
"rollup-plugin-node-resolve": "5.2.0",
"rollup-plugin-replace": "2.2.0",
"rollup-plugin-tslint": "0.2.2",
"rollup-plugin-typescript2": "0.24.2",
"sinon": "7.4.2",
"sinon-chai": "3.3.0",
"source-map-support": "0.5.13",
"tslint": "5.20.0",
"tslint-clean-code": "0.2.10",
"tslint-consistent-codestyle": "1.15.1",
"tslint-etc": "1.7.0",
"tslint-microsoft-contrib": "6.2.0",
"tslint-sonarts": "1.9.0",
"typescript": "3.6.3"
},
"nyc": {
"extends": "@istanbuljs/nyc-config-typescript"
}
}

8
renovate.json Normal file
View File

@ -0,0 +1,8 @@
{
"extends": [
"config:base",
":semanticCommitTypeAll(update)",
":semanticCommitScopeDisabled"
],
"branchPrefix": "update/"
}

4
src/app.ts Normal file
View File

@ -0,0 +1,4 @@
export async function createApp(): Promise<void> {
// tslint:disable-next-line:no-console
console.log('Hello World!');
}

3
src/entity/README.md Normal file
View File

@ -0,0 +1,3 @@
# Entity
Database entities.

20
src/index.ts Normal file
View File

@ -0,0 +1,20 @@
import { createApp } from './app';
/**
* This is the main entry-point to the program and the only file not included in the main bundle.
*
* Keep the main method minimal, since this file cannot be tested.
*/
const STATUS_SUCCESS = 0;
const STATUS_ERROR = 1;
async function main(argv: Array<string>): Promise<number> {
await createApp();
return STATUS_SUCCESS;
}
main(process.argv).then((status) => process.exit(status)).catch((err: Error) => {
/* tslint:disable-next-line:no-console */
console.error('uncaught error during main:', err);
process.exit(STATUS_ERROR);
});

3
src/migration/README.md Normal file
View File

@ -0,0 +1,3 @@
# Migration
Database schema migrations for the entities.

3
src/module/README.md Normal file
View File

@ -0,0 +1,3 @@
# Module
Dependency injection modules.

15
test/TestApp.ts Normal file
View File

@ -0,0 +1,15 @@
import { expect } from 'chai';
import { spy } from 'sinon';
import { describeLeaks, itLeaks } from './helpers/async';
import { createApp } from '../src/app';
describeLeaks('app', async () => {
itLeaks('should log a message', async () => {
const logSpy = spy(console.log);
await createApp();
expect(logSpy).to.have.callCount(1);
});
});

15
test/harness.ts Normal file
View File

@ -0,0 +1,15 @@
import { use } from 'chai';
import chaiAsPromised from 'chai-as-promised';
import sinonChai from 'sinon-chai';
/**
* This will break the whole test run if any test leaks an unhandled rejection.
*/
process.on('unhandledRejection', (reason, promise) => {
// tslint:disable-next-line:no-console
console.error('unhandled error during tests', reason);
process.exit(1);
});
use(chaiAsPromised);
use(sinonChai);

154
test/helpers/async.ts Normal file
View File

@ -0,0 +1,154 @@
import { AsyncHook, createHook } from 'async_hooks';
// this will pull Mocha internals out of the stacks
// tslint:disable-next-line: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>;
export interface TrackedResource {
source: string;
triggerAsyncId: number;
type: string;
}
function debugMode() {
return Reflect.has(process.env, 'DEBUG');
}
function isNil<T>(val: T | null | undefined): val is null | undefined {
return val === null || val === undefined;
}
/**
* 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();
}
public dump() {
/* tslint:disable:no-console */
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');
}
});
/* tslint:enable:no-console */
}
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 {
// tslint: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')) {
// tslint: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);
});
});
});
}

3
tsconfig.json Executable file
View File

@ -0,0 +1,3 @@
{
"extends": "./config/tsconfig.json"
}

2339
yarn.lock Normal file

File diff suppressed because it is too large Load Diff