basic structure
This commit is contained in:
commit
853f03f439
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
node_modules/
|
||||
out/
|
||||
|
||||
bot.db
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"reporter": "@mochajs/multi-reporter",
|
||||
"ui": ["bdd"]
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"mocha-junit-reporter": true,
|
||||
"spec": true
|
||||
}
|
||||
|
|
@ -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" ]
|
||||
|
|
@ -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
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { CommandContext } from './index.js';
|
||||
|
||||
export async function checkCommand(context: CommandContext) {
|
||||
context.logger.info('check command');
|
||||
return true;
|
||||
}
|
|
@ -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';
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { CommandContext } from './index.js';
|
||||
|
||||
export async function pruneCommand(context: CommandContext) {
|
||||
context.logger.info('prune command');
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { CommandContext } from './index.js';
|
||||
|
||||
export async function unlockCommand(context: CommandContext) {
|
||||
context.logger.info('unlock command');
|
||||
return true;
|
||||
}
|
|
@ -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 */
|
|
@ -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;
|
||||
}
|
|
@ -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,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;
|
|
@ -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;
|
||||
},
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
describe('tests', () => {
|
||||
it('should test things');
|
||||
});
|
|
@ -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();
|
||||
|
|
@ -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/**/*"
|
||||
]
|
||||
}
|
||||
|
Loading…
Reference in New Issue