1
0
Fork 0

basic structure

This commit is contained in:
Sean Sube 2022-12-31 22:13:14 -06:00
commit 853f03f439
27 changed files with 3605 additions and 0 deletions

39
.api-extractor.json Normal file
View File

@ -0,0 +1,39 @@
{
"$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>/docs/",
"reportTempFolder": "<projectFolder>/out/tmp/"
},
"docModel": {
"enabled": true,
"apiJsonFilePath": "<projectFolder>/out/api/<unscopedPackageName>.api.json"
},
"dtsRollup": {
"enabled": true,
"untrimmedFilePath": "<projectFolder>/out/index.d.ts",
"betaTrimmedFilePath": "<projectFolder>/out/index-beta.d.ts",
"publicTrimmedFilePath": "<projectFolder>/out/index-public.d.ts"
},
"tsdocMetadata": {
},
"messages": {
"compilerMessageReporting": {
"default": {
"logLevel": "warning"
}
},
"extractorMessageReporting": {
"default": {
"logLevel": "warning"
}
},
"tsdocMessageReporting": {
"default": {
"logLevel": "warning"
}
}
}
}

296
.eslintrc.json Normal file
View File

@ -0,0 +1,296 @@
{
"env": {
"es6": true
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "tsconfig.json",
"sourceType": "module"
},
"plugins": [
"eslint-plugin-chai",
"eslint-plugin-chai-expect",
"eslint-plugin-chai-expect-keywords",
"eslint-plugin-import",
"eslint-plugin-mocha",
"eslint-plugin-no-null",
"eslint-plugin-sonarjs",
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/adjacent-overload-signatures": "error",
"@typescript-eslint/array-type": [
"error",
{
"default": "generic"
}
],
"@typescript-eslint/await-thenable": "error",
"@typescript-eslint/ban-types": [
"error",
{
"types": {
"null": "Use 'undefined' instead of 'null'"
}
}
],
"@typescript-eslint/consistent-type-assertions": "error",
"@typescript-eslint/consistent-type-definitions": "error",
"@typescript-eslint/explicit-member-accessibility": [
"error",
{
"accessibility": "explicit",
"overrides": {
"constructors": "no-public"
}
}
],
"@typescript-eslint/indent": [
"error",
2,
{
"ObjectExpression": "first",
"FunctionDeclaration": {
"parameters": "first"
},
"FunctionExpression": {
"parameters": "first"
},
"SwitchCase": 1
}
],
"@typescript-eslint/member-delimiter-style": [
"error",
{
"multiline": {
"delimiter": "semi",
"requireLast": true
},
"singleline": {
"delimiter": "semi",
"requireLast": false
}
}
],
"@typescript-eslint/member-ordering": [
"error",
{
"default": [
"public-static-method",
"public-static-field",
"public-instance-field",
"protected-instance-field",
"public-constructor",
"public-instance-method",
"protected-instance-method"
]
}
],
"@typescript-eslint/no-empty-function": "error",
"@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-for-in-array": "error",
"@typescript-eslint/no-inferrable-types": "error",
"@typescript-eslint/no-misused-new": "error",
"@typescript-eslint/no-namespace": "error",
"@typescript-eslint/no-non-null-assertion": "error",
"no-param-reassign": "error",
"@typescript-eslint/no-parameter-properties": "error",
"@typescript-eslint/no-this-alias": "error",
"@typescript-eslint/no-unnecessary-type-arguments": "error",
"@typescript-eslint/no-use-before-declare": "off",
"@typescript-eslint/no-var-requires": "error",
"@typescript-eslint/prefer-for-of": "error",
"@typescript-eslint/prefer-function-type": "error",
"@typescript-eslint/prefer-namespace-keyword": "error",
"@typescript-eslint/quotes": [
"error",
"single",
{
"avoidEscape": true
}
],
"@typescript-eslint/restrict-plus-operands": "error",
"@typescript-eslint/semi": [
"error",
"always"
],
"space-in-parens": [
"error",
"never"
],
"@typescript-eslint/strict-boolean-expressions": "error",
"@typescript-eslint/triple-slash-reference": "error",
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/unbound-method": "error",
"@typescript-eslint/unified-signatures": "error",
"arrow-body-style": "error",
"arrow-parens": [
"error",
"always"
],
"camelcase": "error",
"complexity": [
"error",
{
"max": 12
}
],
"constructor-super": "error",
"curly": "error",
"default-case": "error",
"dot-notation": "error",
"eol-last": "error",
"eqeqeq": [
"error",
"always"
],
"guard-for-in": "error",
"id-blacklist": [
"error",
"any",
"String",
"Boolean",
"Undefined"
],
"id-match": "error",
"import/no-default-export": "error",
"import/no-deprecated": "error",
"import/no-extraneous-dependencies": "off",
"import/no-internal-modules": "off",
"import/order": [
"error",
{
"groups": [
[
"builtin",
"external"
],
[
"index",
"parent",
"sibling",
"unknown"
]
]
}
],
"max-classes-per-file": [
"off",
1
],
"max-len": [
"error",
{
"code": 180
}
],
"max-lines": [
"error",
500
],
"new-parens": "error",
"no-bitwise": "off",
"no-caller": "error",
"no-cond-assign": "error",
"no-console": "error",
"no-debugger": "error",
"no-duplicate-case": "error",
"no-duplicate-imports": "error",
"no-empty": "error",
"no-eval": "error",
"no-extra-bind": "error",
"no-fallthrough": "off",
"no-invalid-this": "error",
"no-irregular-whitespace": "error",
"@typescript-eslint/no-magic-numbers": [
"error",
{
"ignore": [
0,
1,
10
],
"ignoreEnums": true
}
],
"no-multiple-empty-lines": "error",
"no-new-func": "error",
"no-new-wrappers": "error",
"no-null/no-null": "error",
"no-plusplus": [
"error",
{
"allowForLoopAfterthoughts": true
}
],
"no-redeclare": "off",
"no-restricted-syntax": [
"error",
"ForInStatement"
],
"no-return-await": "error",
"no-sequences": "error",
"no-shadow": "off",
"@typescript-eslint/no-redeclare": ["error"],
"@typescript-eslint/no-shadow": ["error"],
"no-sparse-arrays": "error",
"no-template-curly-in-string": "error",
"no-throw-literal": "error",
"no-trailing-spaces": "error",
"no-undef-init": "error",
"no-underscore-dangle": "error",
"no-unsafe-finally": "error",
"no-unused-expressions": "error",
"no-unused-labels": "error",
"no-useless-constructor": "error",
"no-var": "error",
"no-void": "error",
"max-params": [
"error",
4
],
"object-shorthand": "error",
"one-var": [
"error",
"never"
],
"prefer-const": "error",
"prefer-object-spread": "error",
"@typescript-eslint/prefer-readonly": "error",
"quote-props": [
"error",
"consistent-as-needed"
],
"radix": "error",
"space-before-function-paren": [
"error",
{
"anonymous": "never",
"asyncArrow": "always",
"named": "never"
}
],
"spaced-comment": "error",
"use-isnan": "error",
"valid-typeof": "off",
"sonarjs/max-switch-cases": "error",
"sonarjs/cognitive-complexity": "error",
"sonarjs/no-all-duplicated-branches": "error",
"sonarjs/no-collapsible-if": "error",
"sonarjs/no-collection-size-mischeck": "error",
"sonarjs/no-duplicate-string": "error",
"sonarjs/no-duplicated-branches": "error",
"sonarjs/no-element-overwrite": "error",
"sonarjs/no-identical-conditions": "error",
"sonarjs/no-identical-expressions": "error",
"sonarjs/no-identical-functions": "error",
"sonarjs/no-inverted-boolean-check": "error",
"sonarjs/no-redundant-boolean": "error",
"sonarjs/no-redundant-jump": "error",
"sonarjs/no-same-line-conditional": "error",
"sonarjs/no-useless-catch": "error",
"sonarjs/prefer-immediate-return": "error"
}
}

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules/
out/
bot.db

85
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,85 @@
stages:
- build
- package
.build-base:
image: docker.artifacts.apextoaster.com/apextoaster/base:1.5
tags:
- platform:k8s
- runner:shared
.build-node:
extends:
- .build-base
image: docker.artifacts.apextoaster.com/library/node:18
artifacts:
expire_in: 1 day
paths:
- out/
reports:
coverage_report:
coverage_format: cobertura
path: out/coverage/cobertura-coverage.xml
junit: out/test-results.xml
cache:
key:
files:
- yarn.lock
paths:
- node_modules/
policy: pull-push
.build-dind:
extends:
- .build-base
image: docker.artifacts.apextoaster.com/apextoaster/docker:20.10
services:
- docker.artifacts.apextoaster.com/apextoaster/docker-dind:20.10
tags:
- platform:k8s
- runner:shared
allow_failure: false
before_script:
- mkdir ${HOME}/.docker
- echo "${DOCKER_SECRET}" | base64 -d > ${HOME}/.docker/config.json
script:
- ${CI_PROJECT_DIR}/scripts/image-build.sh --push
after_script:
- rm -rfv ${HOME}/.docker
variables:
DOCKER_CERT_PATH: "/shared/docker/client"
DOCKER_DRIVER: overlay2
DOCKER_HOST: tcp://localhost:2376
DOCKER_NAME: "${CI_PROJECT_PATH}"
DOCKER_TLS_CERTDIR: "/shared/docker"
DOCKER_TLS_VERIFY: 1
IMAGE_FILE: Containerfile
IMAGE_ROOT: "${CI_PROJECT_DIR}"
VERSION_TAG: "${CI_COMMIT_REF_SLUG}"
build-js:
extends:
- .build-node
stage: build
script:
- make ci
package-oci:
extends:
- .build-dind
stage: package
needs:
- build-js
package-npm:
extends:
- .build-node
stage: package
needs:
- build-js
only:
- tags
script:
- npm publish

5
.mocharc.json Normal file
View File

@ -0,0 +1,5 @@
{
"reporter": "@mochajs/multi-reporter",
"ui": ["bdd"]
}

5
.reporters.json Normal file
View File

@ -0,0 +1,5 @@
{
"mocha-junit-reporter": true,
"spec": true
}

15
Containerfile Normal file
View File

@ -0,0 +1,15 @@
FROM docker.artifacts.apextoaster.com/library/node:18
WORKDIR /app
# dependencies first, to invalidate other layers when version changes
COPY package.json /app/package.json
COPY yarn.lock /app/yarn.lock
RUN yarn install --production
# copy build output
COPY out/src/ /app/out/src/
ENTRYPOINT [ "node", "/app/out/src/index.js" ]

65
Makefile Normal file
View File

@ -0,0 +1,65 @@
.PHONY: build ci clean docs docs-local lint package run test
# JS targets
node_modules: deps
ci: deps lint build-shebang test
clean:
rm -rf node_modules/
rm -rf out/
deps:
yarn install
docs:
yarn api-extractor run -c .api-extractor.json
yarn api-documenter markdown -i out/api -o docs/api
docs-local:
yarn api-extractor run -c .api-extractor.json --local
yarn api-documenter markdown -i out/api -o docs/api
build: deps
yarn tsc
build-shebang: build
sed -i '1s;^;#! /usr/bin/env node\n\n;g' $(shell pwd)/out/src/index.js
chmod ug+x out/src/index.js
COVER_OPTS := --all \
--exclude ".eslintrc.js" \
--exclude "docs/**" \
--exclude "out/coverage/**" \
--exclude "vendor/**" \
--reporter=text-summary \
--reporter=lcov \
--reporter=cobertura \
--report-dir=out/coverage
MOCHA_OPTS := --async-only \
--check-leaks \
--forbid-only \
--recursive \
--require source-map-support/register \
--require out/test/setup.js \
--sort
lint: deps
yarn eslint src/ test/ --ext .ts,.tsx
test: build
MOCHA_FILE=out/test-results.xml yarn c8 $(COVER_OPTS) mocha $(MOCHA_OPTS) "out/**/Test*.js"
# image-building targets
image:
podman build -t docker-push.artifacts.apextoaster.com/ssube/conan-discord:main -f Containerfile .
image-local: ci
podman pull docker-push.artifacts.apextoaster.com/ssube/conan-discord:main
$(MAKE) image
podman push docker-push.artifacts.apextoaster.com/ssube/conan-discord:main
# run targets
run: build
node out/src/index.js

52
package.json Normal file
View File

@ -0,0 +1,52 @@
{
"name": "deploy-lock",
"version": "0.1.0",
"description": "Deploy lock tool",
"main": "out/src/index.js",
"author": "Sean Sube <seansube@gmail.com>",
"license": "MIT",
"type": "module",
"dependencies": {
"@apextoaster/js-utils": "^0.5.0",
"aws-sdk": "^2.1286.0",
"bunyan": "^1.8.15",
"noicejs": "^5.0.0-3",
"yargs": "^17.6.2"
},
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@microsoft/api-documenter": "^7.19.26",
"@microsoft/api-extractor": "^7.33.7",
"@mochajs/multi-reporter": "^1.1.0",
"@types/bunyan": "^1.8.8",
"@types/chai-as-promised": "^7.1.5",
"@types/mocha": "^10.0.1",
"@types/sinon-chai": "^3.2.9",
"@types/yargs": "^17.0.17",
"@typescript-eslint/eslint-plugin": "^5.46.1",
"@typescript-eslint/parser": "^5.46.1",
"c8": "^7.12.0",
"chai": "^4.3.7",
"chai-as-promised": "^7.1.1",
"eslint": "^8.29.0",
"eslint-plugin-chai": "^0.0.1",
"eslint-plugin-chai-expect": "^3.0.0",
"eslint-plugin-chai-expect-keywords": "^2.1.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-mocha": "^10.1.0",
"eslint-plugin-no-null": "^1.0.2",
"eslint-plugin-sonarjs": "^0.17.0",
"fast-check": "^3.4.0",
"mocha": "^10.2.0",
"mocha-foam": "^0.1.10",
"mocha-junit-reporter": "^2.2.0",
"sinon": "^15.0.0",
"sinon-chai": "^3.7.0",
"source-map-support": "^0.5.21",
"tslib": "^2.4.1",
"typescript": "^4.9.4"
},
"nyc": {
"extends": "@istanbuljs/nyc-config-typescript"
}
}

23
scripts/image-build.sh Executable file
View File

@ -0,0 +1,23 @@
#! /bin/bash
IMAGE_PUSH="${1:---skip}"
IMAGE_NAME="${IMAGE_DEST}/${CI_PROJECT_PATH}"
IMAGE_TAG="$(echo "${CI_COMMIT_TAG:-${CI_COMMIT_REF_SLUG}}" | sed -r 's/[^-_a-zA-Z0-9\\.]/-/g')"
IMAGE_SHORT="${IMAGE_NAME}:${IMAGE_TAG}"
IMAGE_FULL="${IMAGE_NAME}:${IMAGE_TAG}"
echo "Loading last image: ${IMAGE_FULL}"
docker pull "${IMAGE_FULL}" || echo "Failed to load last image."
echo "Building image: ${IMAGE_FULL}"
docker build -f "${IMAGE_FILE}" -t "${IMAGE_FULL}" . || { echo "Failed to build image!"; exit 1; }
if [[ "${IMAGE_PUSH}" == "--push" ]];
then
echo "Pushing image: ${IMAGE_FULL}"
docker push "${IMAGE_FULL}" || { echo "Failed to push image!"; exit 1; }
fi

143
src/args.ts Normal file
View File

@ -0,0 +1,143 @@
import yargs from 'yargs';
import { CommandName } from './command/index.js';
import { LOCK_TYPES, LockType } from './lock.js';
import { STORAGE_TYPES, StorageType } from './storage/index.js';
/**
* CLI options.
*
* @public
*/
export interface ParsedArgs {
/**
* Remaining arguments.
*/
_: Array<string | number>;
command: CommandName;
/**
* Placeholder to allow better exit logic.
*/
help?: boolean;
type: LockType;
path: string;
author?: string;
duration?: string;
until?: string;
recursive: boolean;
'env-cluster'?: string;
'env-account'?: string;
'env-target'?: string;
'ci-project'?: string;
'ci-ref'?: string;
'ci-commit'?: string;
'ci-pipeline'?: string;
'ci-job'?: string;
storage: StorageType;
table?: string;
// TODO: should this stay?
assume: Array<string>;
}
export const APP_NAME = 'deploy-lock';
export const ENV_NAME = 'DEPLOY_LOCK';
/**
* Parse CLI options and environment variables.
*
* @public
*/
export async function parseArgs(argv: Array<string>): Promise<ParsedArgs> {
let command: CommandName = 'check';
const parser = yargs(argv)
.usage(`Usage: ${APP_NAME} <command> [options]`)
.command('check', 'check if a lock exists')
.command('lock', 'lock a path', () => { /* noop */}, () => {
command = 'lock';
})
.command('unlock', 'unlock a path', () => { /* noop */}, () => {
command = 'unlock';
})
.command('prune', 'remove expired locks', () => { /* noop */}, () => {
command = 'prune';
})
.options({
'assume': {
default: [] as Array<string>,
string: true,
type: 'array',
},
'author': {
type: 'string',
},
'duration': {
type: 'string',
},
'path': {
type: 'string',
require: true,
},
'recursive': {
type: 'boolean',
default: true,
},
'storage': {
type: 'string',
choices: STORAGE_TYPES,
require: true,
// TODO: default, but TS gets mad
},
'table': {
type: 'string',
},
'type': {
type: 'string',
require: true,
choices: LOCK_TYPES,
},
'until': {
type: 'string',
},
'env-cluster': {
type: 'string',
},
'env-account': {
type: 'string',
},
'env-target': {
type: 'string',
},
'ci-project': {
type: 'string',
},
'ci-ref': {
type: 'string',
},
'ci-commit': {
type: 'string',
},
'ci-pipeline': {
type: 'string',
},
'ci-job': {
type: 'string',
},
})
.env(ENV_NAME)
.help()
.exitProcess(false);
const args = await parser.parse(argv);
return {
...args,
command,
};
}

6
src/command/check.ts Normal file
View File

@ -0,0 +1,6 @@
import { CommandContext } from './index.js';
export async function checkCommand(context: CommandContext) {
context.logger.info('check command');
return true;
}

14
src/command/index.ts Normal file
View File

@ -0,0 +1,14 @@
import { Logger } from 'noicejs';
import { ParsedArgs } from '../args.js';
import { Storage } from '../storage/index.js';
export interface CommandContext {
args: ParsedArgs;
logger: Logger;
storage: Storage;
}
export type CommandFunction = (context: CommandContext) => Promise<boolean>;
export type CommandName = 'check' | 'lock' | 'unlock' | 'prune';

25
src/command/lock.ts Normal file
View File

@ -0,0 +1,25 @@
import { doesExist } from '@apextoaster/js-utils';
import { splitPath } from '../utils.js';
import { CommandContext } from './index.js';
export async function lockCommand(context: CommandContext) {
const { args, logger, storage } = context;
logger.info('lock command');
const paths = splitPath(args.path);
for (const path of paths) {
const existing = await storage.get(path);
if (doesExist(existing)) {
logger.info({ existing }, 'lock already exists');
return false;
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const lock = await storage.set(args.path, {} as any);
logger.info({ lock, path: args.path }, 'created new lock');
return true;
}

6
src/command/prune.ts Normal file
View File

@ -0,0 +1,6 @@
import { CommandContext } from './index.js';
export async function pruneCommand(context: CommandContext) {
context.logger.info('prune command');
return true;
}

6
src/command/unlock.ts Normal file
View File

@ -0,0 +1,6 @@
import { CommandContext } from './index.js';
export async function unlockCommand(context: CommandContext) {
context.logger.info('unlock command');
return true;
}

21
src/index.ts Normal file
View File

@ -0,0 +1,21 @@
import process from 'node:process';
import { ExitCode, main } from './main.js';
/**
* A Discord chat bot for Conan Exiles game servers.
*
* @packageDocumentation
*/
const ARGS_START = 2; // trim the first few, yargs does not like them
/* c8 ignore start */
main(process.argv.slice(ARGS_START)).then((code) => {
process.exit(code);
}).catch((err) => {
// eslint-disable-next-line no-console
console.error('uncaught error from main', err);
process.exit(ExitCode.ERROR);
});
/* c8 ignore stop */

8
src/lock.ts Normal file
View File

@ -0,0 +1,8 @@
export type LockType = 'automation' | 'deploy' | 'freeze' | 'incident';
export const LOCK_TYPES: ReadonlyArray<LockType> = ['automation', 'deploy', 'freeze', 'incident'] as const;
export interface LockData {
author: string;
type: LockType;
}

61
src/main.ts Normal file
View File

@ -0,0 +1,61 @@
import { createLogger, DEBUG } from 'bunyan';
import { APP_NAME, parseArgs } from './args.js';
import { checkCommand } from './command/check.js';
import { CommandFunction, CommandName } from './command/index.js';
import { lockCommand } from './command/lock.js';
import { pruneCommand } from './command/prune.js';
import { unlockCommand } from './command/unlock.js';
import { StorageFactory, StorageType } from './storage/index.js';
import { connectMemory } from './storage/memory.js';
/**
* Process exit codes.
*/
export enum ExitCode {
SUCCESS = 0,
ERROR = 1,
}
export const Commands: Record<CommandName, CommandFunction> = {
check: checkCommand,
lock: lockCommand,
unlock: unlockCommand,
prune: pruneCommand,
};
export const Storages: Record<StorageType, StorageFactory> = {
dynamo: connectMemory,
memory: connectMemory,
};
/**
* Connect to some bots and wait for commands or an exit signal.
*/
export async function main(argv: Array<string>): Promise<ExitCode> {
const args = await parseArgs(argv);
if (args.help === true) {
return ExitCode.SUCCESS;
}
const logger = createLogger({
name: APP_NAME,
level: DEBUG,
});
logger.info({ args}, `launching ${APP_NAME}`);
const storageFactory = Storages[args.storage];
const storage = await storageFactory({ args, logger });
const commandFunction = Commands[args.command];
const result = await commandFunction({ args, logger, storage });
if (result) {
return ExitCode.SUCCESS;
} else {
logger.error('command failed');
return ExitCode.ERROR;
}
}

0
src/storage/dynamo.ts Normal file
View File

20
src/storage/index.ts Normal file
View File

@ -0,0 +1,20 @@
import { Logger } from 'noicejs';
import { ParsedArgs } from '../args.js';
import { LockData } from '../lock.js';
export interface StorageContext {
args: ParsedArgs;
logger: Logger;
}
export interface Storage {
get(path: string): Promise<LockData | undefined>;
set(path: string, data: LockData): Promise<LockData>;
}
export type StorageFactory = (context: StorageContext) => Promise<Storage>;
export type StorageType = 'dynamo' | 'memory';
export const STORAGE_TYPES: ReadonlyArray<StorageType> = ['dynamo', 'memory'] as const;

32
src/storage/memory.ts Normal file
View File

@ -0,0 +1,32 @@
import { doesExist } from '@apextoaster/js-utils';
import { LockData } from '../lock.js';
import { Storage, StorageContext } from './index.js';
export async function connectMemory(context: StorageContext): Promise<Storage> {
const storage = new Map<string, LockData>();
for (const lock of Array.from(context.args.assume)) {
const match = /^([-a-z/]+):({.+})$/.exec(lock);
if (doesExist(match)) {
const [_full, path, rawData] = Array.from(match);
const data = JSON.parse(rawData) as LockData;
context.logger.info({ path, data }, 'assuming lock exists');
storage.set(path, data);
} else {
context.logger.warn({ lock }, 'invalid lock in assume');
}
}
return {
async get(path: string) {
context.logger.info({ path }, 'getting data from memory');
return storage.get(path);
},
async set(path: string, data: LockData) {
context.logger.info({ path, data }, 'setting data in memory');
storage.set(path, data);
return data;
},
};
}

11
src/utils.ts Normal file
View File

@ -0,0 +1,11 @@
export function splitPath(path: string): Array<string> {
const segments = path.split('/');
const paths = [];
for (let i = 1; i < segments.length; ++i) {
const next = segments.slice(0, i).join('/');
paths.push(next);
}
return paths;
}

3
test/TestTest.ts Normal file
View File

@ -0,0 +1,3 @@
describe('tests', () => {
it('should test things');
});

21
test/setup.ts Normal file
View File

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

45
tsconfig.json Normal file
View File

@ -0,0 +1,45 @@
{
"compileOnSave": false,
"compilerOptions": {
"allowJs": false,
"allowSyntheticDefaultImports": true,
"declaration": true,
"declarationMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"lib": [
"es2017",
"esnext.asynciterable"
],
"module": "esnext",
"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",
"vendor"
]
},
"exclude": [
"node_modules"
],
"include": [
"src/**/*",
"test/**/*"
]
}

2594
yarn.lock Normal file

File diff suppressed because it is too large Load Diff