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